這是PingCAP的首席架構(gòu)師唐劉在 Rust 專場(chǎng) Meetup 英文演講稿的翻譯篇。鑒于譯者水平有限仲翎,錯(cuò)誤之處還請(qǐng)批評(píng)指正浓恶。文末可以點(diǎn)擊閱讀原文查看英文原版。
大家好树肃,今天我想和大家談?wù)勎覀兪侨绾卧?TiKV 中使用 Rust 的劣像。
開始之前,請(qǐng)?jiān)试S我先做下自我介紹癞揉。我叫唐劉,PingCAP 的首席架構(gòu)師姐刁。在我加入 PingCAP 之前芥牌,我在金山和騰訊工作過(guò)。我熱愛開源聂使,而且開發(fā)過(guò)一些項(xiàng)目壁拉,比如 LedisDB 出革、go-mysql 等等杂曲。
首先,我會(huì)解釋下為什么我們選擇 Rust 開發(fā) TiKV滔金,然后再向你們簡(jiǎn)要展示下 TiKV的基本架構(gòu)屎蜓,以及一些關(guān)鍵技術(shù)痘昌。最后,我會(huì)介紹下我們未來(lái)的計(jì)劃炬转。
什么是 TiKV
好的辆苔,現(xiàn)在開始。首先扼劈,什么是 TiKV ? TiKV 是一個(gè)分布式的 Key-Value 數(shù)據(jù)庫(kù)驻啤,有以下幾個(gè)特點(diǎn):
- 異地復(fù)制 : 我們使用 Raft 和 Placement Driver 進(jìn)行異地復(fù)制來(lái)保證數(shù)據(jù)的安全性。
- 水平擴(kuò)展 : 如果發(fā)現(xiàn)快速的數(shù)據(jù)增長(zhǎng)很快就要突破系統(tǒng)容量荐吵,我們可以通過(guò)直接添加節(jié)點(diǎn)的方式來(lái)擴(kuò)展系統(tǒng)容量骑冗。
- 一致性分布式事務(wù):我們使用基于 Google Percolator、優(yōu)化后的兩階段提交協(xié)議來(lái)支持分布式事務(wù)捍靠。你可以使用 “begin” 來(lái)開啟一個(gè)事務(wù)沐旨,然后做些事情,之后再使用 “commit” 或者 “rollback” 來(lái)完成這個(gè)事務(wù)榨婆。
- 分布式計(jì)算的協(xié)處理器 : 就像 HBase 那樣磁携,我們支持協(xié)處理器框架來(lái)讓用戶直接在 TiKV 中做計(jì)算。
- 和 TiDB 融合就像 Spanner 之于 F1 :使用 TiKV 作為 TiDB 的后端存儲(chǔ)引擎良风,我們可以提供最好的分布式關(guān)系型數(shù)據(jù)庫(kù)谊迄。
我們需要一門語(yǔ)言具有...
正如你所見闷供,TiKV 有很多強(qiáng)大的功能。為了開發(fā)這些功能统诺,我們也需要一個(gè)強(qiáng)大的編程語(yǔ)言歪脏。這種編程語(yǔ)言應(yīng)該具備這些特點(diǎn):
- 快速 : 我們非常重視 TiKV 的性能,所以我們需要一個(gè)在運(yùn)行時(shí)運(yùn)行很快的語(yǔ)言粮呢。
- 內(nèi)存安全:對(duì)于一個(gè)準(zhǔn)備運(yùn)行很長(zhǎng)時(shí)間的程序婿失,我們不想遇到任何內(nèi)存問(wèn)題,比如野指針啄寡、內(nèi)存泄漏等豪硅。
- 線程安全:我們必須保證數(shù)據(jù)一直都是一致的,所以任何數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題都必須避免挺物。
- 和 C 高效綁定: 我們嚴(yán)重依賴 RocksDB懒浮,所以我們必須盡可能快地調(diào)用 RocksDB 的 API,不能有一點(diǎn)性能上的下降识藤。
為什么不是 C++?
為了開發(fā)高性能服務(wù)砚著,在大多數(shù)場(chǎng)景下 C++ 可能是最好的選擇,但是我們并沒有選擇它痴昧。我們能夠預(yù)料到會(huì)花費(fèi)大量的時(shí)間在避免內(nèi)存問(wèn)題或者數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題上稽穆。并且
C++ 沒有官方包管理器,這讓維護(hù)和編譯第三方依賴變得異常麻煩和困難赶撰,進(jìn)而導(dǎo)致很長(zhǎng)的研發(fā)周期秧骑。
為什么不是 Go ?
一開始我們考慮的是使用 Go 扣囊,但是接著就放棄了這個(gè)想法。Go 的 GC 能修復(fù)很多內(nèi)存問(wèn)題绒疗,但是有時(shí)仍然會(huì)停止運(yùn)行中的進(jìn)程侵歇。無(wú)論這個(gè)停止的時(shí)間有多短,我們都不能承受的起吓蘑。Go 也沒有解決數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題惕虑。即使我們?cè)跍y(cè)試或運(yùn)行時(shí)使用兩次data -race 進(jìn)行檢測(cè)(注:go 語(yǔ)言可以用 -race 命令行參數(shù)檢測(cè)數(shù)據(jù)競(jìng)爭(zhēng)),也仍然不夠磨镶。
另外溃蔫,盡管我們可以使用 Goroutine 很容易寫出并發(fā)邏輯,我們?nèi)匀徊荒芎鲆曊{(diào)度器的運(yùn)行時(shí)開銷琳猫。幾天前我們遇到一個(gè)問(wèn)題:我們使用多個(gè) Goroutine 共享同一個(gè)
Context伟叛,卻發(fā)現(xiàn)性能很糟糕,所以我們不得不讓一個(gè) Goroutine 使用一個(gè)子
Context脐嫂,然后性能才變好點(diǎn)统刮。
嚴(yán)格來(lái)說(shuō)紊遵,CGO 有很嚴(yán)重的性能開銷,但是我們必須無(wú)延遲地調(diào)用 RocksDB 的
API侥蒙“的ぃ基于以上原因,我們沒有選擇 Go鞭衩,即使 Go 是我們團(tuán)隊(duì)最喜愛的語(yǔ)言学搜。
所以,我們轉(zhuǎn)向了 Rust...
但是 Rust...
Rust 是一門系統(tǒng)編程語(yǔ)言论衍,由 Mozilla 在維護(hù)瑞佩。它是一門非常強(qiáng)大的語(yǔ)言,但是你可以看看下面的曲線圖饲齐,Rust 的學(xué)習(xí)曲線非常陡峭钉凌。
我用過(guò)很多編程語(yǔ)言,像 C++捂人、Go御雕、Python、Lua 等等滥搭,Rust 對(duì)我來(lái)說(shuō)是最難掌握的一門語(yǔ)言酸纲。在 PingCAP,我們會(huì)讓新同事至少花一個(gè)月的時(shí)間來(lái)學(xué)習(xí) Rust瑟匆,先和編譯錯(cuò)誤做斗爭(zhēng)闽坡,然后再提高 Rust 水平。這個(gè)在學(xué)習(xí) Go 的過(guò)程中從來(lái)沒發(fā)生過(guò)愁溜。
此外疾嗅,Rust 的編譯時(shí)間很長(zhǎng),比 C++ 都長(zhǎng)冕象。每次當(dāng)我輸完 cargo build 命令開始構(gòu)建 TiKV 的時(shí)候代承,我就可以做好多個(gè)俯臥撐了。
盡管 Rust 出來(lái)很長(zhǎng)一段時(shí)間了渐扮,但是仍然缺乏很多庫(kù)和工具论悴,有些第三方項(xiàng)目至今還沒有在生產(chǎn)環(huán)境中驗(yàn)證過(guò)。使用 Rust 對(duì)我們來(lái)說(shuō)有很大的風(fēng)險(xiǎn)墓律。最為嚴(yán)重的是膀估,我們很難找到 Rust 程序員,因?yàn)樵谥袊?guó)知道 Rust 的人寥寥無(wú)幾耻讽,我們很缺人手察纯。
然后,為什么還是選擇 Rust ?
盡管 Rust 有以上種種缺點(diǎn)捐寥,但是它的好處深深地吸引我們笤昨。Rust 是內(nèi)存安全的,我們?cè)僖膊槐負(fù)?dān)心內(nèi)存泄漏和野指針問(wèn)題握恳。
Rust 是線程安全的瞒窒,所以也沒有任何數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。所有的安全都是由編譯器保證的乡洼。所以在大多少情況下崇裁,編譯一旦通過(guò),我們確信程序就能安全地運(yùn)行束昵。
Rust 沒有 GC 開銷拔稳,所以我們不會(huì)遇到 “stop the world” 問(wèn)題。通過(guò) FFI 調(diào)用 C 程序是非城鲁快的巴比,所以我們不擔(dān)心調(diào)用 RocksDB API 會(huì)有性能上的降低。最后礁遵,Rust
有個(gè)官方的包管理器 crate 轻绞,我們找到很多庫(kù)可以直接拿來(lái)用。
我們做了一個(gè)艱難而偉大的決定:使用 Rust佣耐!
TiKV 時(shí)間線
這里你可以看到 TiKV 的時(shí)間線政勃。我們?cè)?2016 年1月1號(hào)開始開發(fā) TiKV,然后在2016年4月1號(hào)開源了兼砖,就像 Gmail 在每個(gè)4月1號(hào)愚人節(jié)那樣做的奸远,這并不是一個(gè)玩笑。2016年10月份讽挟,TiKV 第一次被使用在生產(chǎn)環(huán)境懒叛,那時(shí)我們甚至都還沒有發(fā)布
beta 版。2016年11月耽梅,我們發(fā)布了第一個(gè) beta 版芍瑞;接著2016年12月發(fā)布 RC1 版本,今年2月份發(fā)布 RC2 版本褐墅。稍后我們計(jì)劃4月份發(fā)布 RC3 版本,在6月份發(fā)布第一個(gè) GA 版本洪己。
如你所見妥凳,TiKV 的開發(fā)非常快答捕,發(fā)布的版本都很穩(wěn)定逝钥。選擇 Rust 已經(jīng)被證明是一個(gè)正確的決定。感謝 Rust。
TiKV 架構(gòu)
現(xiàn)在讓我們深入探究 TiKV艘款。你可以從 TiKV 的架構(gòu)中看到 TiKV 的層次很清晰持际,很容易理解。
TiKV 在底層使用了 RocksDB哗咆,一種高性能的持久化 Key-Value 存儲(chǔ)蜘欲,來(lái)作為后端的存儲(chǔ)引擎。
接下來(lái)一層是 Raft KV 層晌柬。TiKV 使用 Raft 算法來(lái)實(shí)現(xiàn)異地復(fù)制數(shù)據(jù)姥份。TiKV 被設(shè)計(jì)來(lái)存儲(chǔ)海量數(shù)據(jù),一個(gè) Raft 組遠(yuǎn)不夠年碘。所以我們按范圍切分澈歉,然后把每個(gè)范圍的數(shù)據(jù)作為一個(gè)獨(dú)立的 Raft Group。我們把這種方式命名為 Multi-Raft Groups屿衅。
TiKV 提供了一個(gè)簡(jiǎn)單的 Key-Value API埃难,包括 SET、GET涤久、DELETE 等方法涡尘,我們可以像使用其他任何分布式 Key-Value 存儲(chǔ)系統(tǒng)那樣使用 TiKV 的 API。TiKV 的上層也使用這些 API 來(lái)支持更高級(jí)的功能拴竹。
在 Raft 層之上是 MVCC悟衩。TiKV 中保存的所有 Key 必須包含一個(gè)全局唯一的時(shí)間戳,這個(gè)時(shí)間戳由 Placement Driver 來(lái)分配栓拜。TiKV 使用這個(gè)時(shí)間戳來(lái)支持分布式事務(wù)座泳。
在最上層,是 KV 和協(xié)處理器 API 層幕与,用來(lái)處理客戶端的請(qǐng)求挑势。
Multi-Raft
這是一個(gè) Multi-Raft 的例子。
你可以看到有4個(gè) TiKV 節(jié)點(diǎn)啦鸣。在每一個(gè)節(jié)點(diǎn)存儲(chǔ)中潮饱,我們有多個(gè) Region。Region 是數(shù)據(jù)遷移和 Raft 復(fù)制的基本單元诫给。每一個(gè) Region 會(huì)被復(fù)制到三個(gè)節(jié)點(diǎn)中去香拉。一個(gè) Region 的三個(gè)副本組成了一個(gè) Raft Group。
水平擴(kuò)展</擴(kuò)展>
水平擴(kuò)展(初始狀態(tài))
這是一個(gè)水平擴(kuò)展的例子中狂。首先凫碌,我們有四個(gè)節(jié)點(diǎn),節(jié)點(diǎn)A有3個(gè) Region胃榕,其他節(jié)點(diǎn)都是2個(gè) Region盛险。
當(dāng)然了,節(jié)點(diǎn)A比其他節(jié)點(diǎn)更忙,我們想減輕一下它的壓力苦掘。
水平擴(kuò)展(添加新節(jié)點(diǎn))
我們添加一個(gè)新的節(jié)點(diǎn) E换帜,開始把節(jié)點(diǎn) A 中的 Region 1 轉(zhuǎn)移到節(jié)點(diǎn) E。但是我們發(fā)現(xiàn) Region 1 的 Leader 在節(jié)點(diǎn) A 中鹤啡,所以我們首先把 Leader 從節(jié)點(diǎn) A 轉(zhuǎn)移到節(jié)點(diǎn)B惯驼。
水平擴(kuò)展(均衡)
之后 Region 1 的 Leader 在節(jié)點(diǎn)B中,然后我們?cè)诠?jié)點(diǎn) E 中添加一個(gè) Region 1 的副本揉忘。
然后我們從節(jié)點(diǎn) A 中移除 Region 1 的副本跳座。所有這一切都是被 Placement Driver 自動(dòng)執(zhí)行的。我們唯一要做的就是泣矛,如果發(fā)現(xiàn)系統(tǒng)繁忙就添加節(jié)點(diǎn)疲眷。非常簡(jiǎn)單,不是嗎您朽?
一個(gè)簡(jiǎn)單的寫入流
這是一個(gè)簡(jiǎn)單的寫入流:當(dāng)客戶端發(fā)送一個(gè)寫請(qǐng)求給 TiKV狂丝,TiKV 首先解析協(xié)議,然后把請(qǐng)求分發(fā)給 KV 線程哗总,KV 線程執(zhí)行一些事務(wù)邏輯并把請(qǐng)求發(fā)送給 Raft 線程几颜,在TiKV 復(fù)制 Raft Log 并且應(yīng)用到 RocksDB 之后,整個(gè)寫請(qǐng)求就結(jié)束了讯屈。
關(guān)鍵技術(shù)
現(xiàn)在蛋哭,讓我們關(guān)注一些核心技術(shù)。
關(guān)于網(wǎng)絡(luò)層涮母,我們使用了一個(gè)已經(jīng)被廣泛使用的協(xié)議 Protocol Buffers 來(lái)快速地對(duì)數(shù)據(jù)進(jìn)行序列化和反序列化谆趾。
首先,我們使用** MIO **來(lái)構(gòu)建網(wǎng)絡(luò)框架叛本。盡管 MIO 封裝了底層的網(wǎng)絡(luò)操作沪蓬,但是它仍然是非常基礎(chǔ)的庫(kù)来候,我們必須手動(dòng)地接受和發(fā)送數(shù)據(jù)跷叉,來(lái)解碼或者編碼我們自定義的網(wǎng)絡(luò)協(xié)議。事實(shí)上很不方便营搅。所以從 RC2 版本之后云挟,我們已經(jīng)在用 gPRC 來(lái)重構(gòu)網(wǎng)絡(luò)層了。gRPC 的優(yōu)勢(shì)非常明顯转质。我們?cè)僖膊挥藐P(guān)心如何處理網(wǎng)絡(luò)植锉,只關(guān)系我們自己的邏輯,代碼也看起來(lái)簡(jiǎn)單清晰峭拘。同時(shí),用戶可以很容易地使用其他編程語(yǔ)言來(lái)構(gòu)建 TiKV 的客戶端。我們已經(jīng)在開發(fā) TiKV 的 Java 客戶端鸡挠。
關(guān)于** 異步框架 **辉饱。接收到請(qǐng)求之后,TiKV 把請(qǐng)求分發(fā)給不同的線程來(lái)異步處理拣展。一開始我們使用 MIO 加回調(diào)來(lái)處理異步請(qǐng)求彭沼,但是回調(diào)會(huì)中斷代碼邏輯,很難正確地閱讀和寫好代碼备埃,所以我們使用 tokio-core 和 futures 來(lái)重構(gòu)代碼姓惑,我們認(rèn)為這是一種更為現(xiàn)代化的風(fēng)格,對(duì)于未來(lái)的 Rust 來(lái)說(shuō)按脚。有時(shí)于毙,我們也用線程池來(lái)分發(fā)簡(jiǎn)單的任務(wù),以后會(huì)用 futures-cpupool辅搬。
關(guān)于** 存儲(chǔ) **唯沮,我們使用 rust-rocksdb 來(lái)訪問(wèn) RocksDB。
關(guān)于** 監(jiān)控 **堪遂,我們寫了個(gè) Prometheus 的 Rust 客戶端介蛉,而且這個(gè)客戶端在官方
wiki 上被推薦。關(guān)于性能監(jiān)測(cè)溶褪,我們使用開啟了監(jiān)測(cè)功能的 jemallocator币旧,用 clippy
來(lái)監(jiān)測(cè)我們的代碼。
未來(lái)計(jì)劃
Ok, 以上就是我們已經(jīng)做的和正在做的事情猿妈。我們未來(lái)將會(huì)做的事情有:
- 讓 TiKV 更快吹菱,比如移除 Box。我們?yōu)榱藢懘a容易于游,在 TiKV 中使用了大量的 box
技術(shù)毁葱,事實(shí)上一點(diǎn)也不高效。我們做性能測(cè)試發(fā)現(xiàn)贰剥,動(dòng)態(tài)分發(fā)比靜態(tài)分發(fā)至少慢3倍倾剿,以后我們會(huì)直接使用 Trait。 - 讓 TiKV 更穩(wěn)定蚌成,比如引入 Rust sanitizer前痘。
- 貢獻(xiàn)更多的 Rust 開源模塊,比如 Raft 庫(kù)担忧,open-tracing 等等
- 更加深度地參與其他 Rust 項(xiàng)目芹缔,比如 rust-gRPC
- 在中國(guó)的社交媒體上寫更多關(guān)于 Rust 的文章,組織更多的 Rust 聚會(huì)
- 成為中國(guó)強(qiáng)有力的 Rust 倡導(dǎo)者