01 背景
在B站的業(yè)務(wù)場(chǎng)景中歇父,存在很多種不同模型的數(shù)據(jù),有些數(shù)據(jù)關(guān)系比較復(fù)雜像:賬號(hào)再愈、稿件信息庶骄。有些數(shù)據(jù)關(guān)系比較簡(jiǎn)單,只需要簡(jiǎn)單的kv模型即可滿(mǎn)足践磅。此外单刁,又存在某些讀寫(xiě)吞吐比較高的業(yè)務(wù)場(chǎng)景,該場(chǎng)景早期的解決方案是通過(guò)MySQL來(lái)進(jìn)行數(shù)據(jù)的持久化存儲(chǔ)府适,同時(shí)通過(guò)redis來(lái)提升訪問(wèn)的速度與吞吐羔飞。但是這種模式帶來(lái)了兩個(gè)問(wèn)題,其一是存儲(chǔ)與緩存一致性的問(wèn)題檐春,該問(wèn)題在B站通過(guò)canal異步更新緩存的方式得以解決逻淌,其二則是開(kāi)發(fā)的復(fù)雜度,對(duì)于這樣一套存儲(chǔ)系統(tǒng)疟暖,每個(gè)業(yè)務(wù)都需要額外維護(hù)一個(gè)任務(wù)腳本來(lái)消費(fèi)canal數(shù)據(jù)進(jìn)行緩存數(shù)據(jù)的更新卡儒。基于這種場(chǎng)景俐巴,業(yè)務(wù)需要的其實(shí)是一個(gè)介于Redis與MySQL之間的提供持久化高性能的kv存儲(chǔ)骨望。此外對(duì)象存儲(chǔ)的元數(shù)據(jù),對(duì)數(shù)據(jù)的一致性欣舵、可靠性與擴(kuò)展性有著很高的要求擎鸠。
基于此背景,我們對(duì)自研KV的定位從一開(kāi)始就是構(gòu)建一個(gè)高可靠缘圈、高可用劣光、高性能、高拓展的系統(tǒng)糟把。對(duì)于存儲(chǔ)系統(tǒng)绢涡,核心是保證數(shù)據(jù)的可靠性,當(dāng)數(shù)據(jù)不可靠時(shí)提供再高的可用性也是沒(méi)用的遣疯⌒劭桑可靠性的一個(gè)核心因素就是數(shù)據(jù)的多副本容災(zāi),通過(guò)raft一致性協(xié)議保證多副本數(shù)據(jù)的一致性。
分布式系統(tǒng)滞项,如何對(duì)數(shù)據(jù)進(jìn)行分片放置狭归,業(yè)界通常有兩種做法夭坪,一是基于hash進(jìn)行分區(qū)文判,二是基于range進(jìn)行分區(qū),兩種方式各有優(yōu)缺點(diǎn)室梅。hash分區(qū)戏仓,可以有效防止熱點(diǎn)問(wèn)題,但是由于key是hash以后放置的亡鼠,無(wú)法保證key的全局有序赏殃。range分區(qū),由于相鄰的數(shù)據(jù)都放在一起间涵,因此可以保證數(shù)據(jù)的有序仁热,但是同時(shí)也可能帶來(lái)寫(xiě)入熱點(diǎn)的問(wèn)題」戳ǎ基于B站的業(yè)務(wù)場(chǎng)景抗蠢,我們同時(shí)支持了range分區(qū)和hash分區(qū),業(yè)務(wù)接入的時(shí)候可以根據(jù)業(yè)務(wù)特性進(jìn)行選擇思劳。大部分場(chǎng)景迅矛,并不需要全局有序,所以默認(rèn)推薦hash分區(qū)的接入方式潜叛,比如觀看記錄秽褒、用戶(hù)動(dòng)態(tài)這些場(chǎng)景,只需要保證同一個(gè)用戶(hù)維度的數(shù)據(jù)有序即可威兜,同一個(gè)用戶(hù)維度的數(shù)據(jù)可以通過(guò)hashtag的方式保證局部有序销斟。
02 架構(gòu)設(shè)計(jì)
2.1 總體架構(gòu)
整個(gè)系統(tǒng)核心分為三個(gè)組件:
Metaserver用戶(hù)集群元信息的管理,包括對(duì)kv節(jié)點(diǎn)的健康監(jiān)測(cè)椒舵、故障轉(zhuǎn)移以及負(fù)載均衡票堵。
Node為kv數(shù)據(jù)存儲(chǔ)節(jié)點(diǎn),用于實(shí)際存儲(chǔ)kv數(shù)據(jù)逮栅,每個(gè)Node上保存數(shù)據(jù)的一個(gè)副本悴势,不同Node之間的分片副本通過(guò)raft保證數(shù)據(jù)的一致性,并選出主節(jié)點(diǎn)對(duì)外提供讀寫(xiě)措伐,業(yè)務(wù)也可以根據(jù)對(duì)數(shù)據(jù)一致性的需求指定是否允許讀從節(jié)點(diǎn)特纤,在對(duì)數(shù)據(jù)一致性要求不高的場(chǎng)景時(shí),通過(guò)設(shè)置允許讀從節(jié)點(diǎn)可以提高可用性以及降低長(zhǎng)尾侥加。
Client模塊為用戶(hù)訪問(wèn)入口捧存,對(duì)外提供了兩種接入方式,一種是通過(guò)proxy模式的方式進(jìn)行接入,另一種是通過(guò)原生的SDK直接訪問(wèn)昔穴,proxy本身也是封裝自c++的原生SDK镰官。SDK從Metaserver獲取表的元數(shù)據(jù)分布信息,根據(jù)元數(shù)據(jù)信息決定將用戶(hù)請(qǐng)求具體發(fā)送到哪個(gè)對(duì)應(yīng)的Node節(jié)點(diǎn)吗货。同時(shí)為了保證高可用泳唠,SDK還實(shí)現(xiàn)了重試機(jī)制以及backoff請(qǐng)求。
2.2 集群拓?fù)?/h5>
集群的拓?fù)浣Y(jié)構(gòu)包含了幾個(gè)概念宙搬,分別是Pool笨腥、Zone、Node勇垛、Table脖母、Shard 與Replica。
- Pool為資源池連通域闲孤,包含多個(gè)可用區(qū)谆级。也可用于業(yè)務(wù)資源隔離域。
- Zone為可用區(qū)讼积,同一個(gè)pool內(nèi)部的zone是網(wǎng)路聯(lián)通并且故障隔離的肥照。通常為一個(gè)機(jī)房或者一個(gè)交換機(jī)
- Node為實(shí)際的物理主機(jī)節(jié)點(diǎn),負(fù)責(zé)具體的數(shù)據(jù)存儲(chǔ)邏輯與數(shù)據(jù)持久化币砂。
- Table對(duì)應(yīng)到具體的業(yè)務(wù)表建峭,類(lèi)似MySQL里的表。
- Shard為邏輯分片决摧,通過(guò)將table分為多個(gè)shard將數(shù)據(jù)打散分布亿蒸。
- Replica為shard的副本,同一個(gè)shard的不同副本不能分布在同一個(gè)zone掌桩,必須保證故障隔離边锁。每一個(gè)replica包含一個(gè)engine,engine存儲(chǔ)全量的業(yè)務(wù)數(shù)據(jù)波岛。engine的實(shí)現(xiàn)包含rocksdb和sparrowdb茅坛。其中sparrowdb是針對(duì)大value寫(xiě)放大的優(yōu)化實(shí)現(xiàn)。
03 核心特征
3.1 分區(qū)分裂
基于不同的業(yè)務(wù)場(chǎng)景则拷,我們同時(shí)支持了range分區(qū)和hash分區(qū)贡蓖。對(duì)于range場(chǎng)景,隨著用戶(hù)數(shù)據(jù)的增長(zhǎng)煌茬,需要對(duì)分區(qū)數(shù)據(jù)進(jìn)行分裂遷移斥铺。對(duì)于hash分區(qū)的場(chǎng)景,使用上通常會(huì)根據(jù)業(yè)務(wù)的數(shù)據(jù)量做幾倍的冗余預(yù)估坛善,然后創(chuàng)建合適的分片數(shù)晾蜘。但是即便是幾倍的冗余預(yù)估邻眷,由于業(yè)務(wù)發(fā)展速度的不可預(yù)測(cè),也很容易出現(xiàn)實(shí)際使用遠(yuǎn)超預(yù)估的場(chǎng)景剔交,從而導(dǎo)致單個(gè)數(shù)據(jù)分片過(guò)大肆饶。
之所以不在一開(kāi)始就創(chuàng)建足夠的分片數(shù)有兩個(gè)原因:其一,由于每一個(gè)replica都包含一個(gè)獨(dú)立的engine岖常,過(guò)多的分片會(huì)導(dǎo)致數(shù)據(jù)文件過(guò)多驯镊,同時(shí)對(duì)于批量寫(xiě)入場(chǎng)景存在一定的寫(xiě)扇出放大。其二腥椒,每一個(gè)shard都是一組raftgroup阿宅,過(guò)多的raft心跳會(huì)對(duì)服務(wù)造成額外的開(kāi)銷(xiāo)候衍,這一點(diǎn)后續(xù)我們會(huì)考慮基于節(jié)點(diǎn)做心跳合并優(yōu)化減少集群心跳數(shù)笼蛛。
為了滿(mǎn)足業(yè)務(wù)的需求場(chǎng)景,我們同時(shí)支持了range和hash兩種模式下的分裂蛉鹿。兩種模式分裂流程類(lèi)似滨砍,下面以hash為例進(jìn)行說(shuō)明。
hash模式下的分裂為直接根據(jù)當(dāng)前分片數(shù)進(jìn)行倍增妖异。分裂的流程主要涉及三個(gè)模塊的交互惋戏。
metaserver
分裂時(shí),metaserver會(huì)根據(jù)當(dāng)前分片數(shù)計(jì)算出目標(biāo)分片數(shù)他膳,并且下發(fā)創(chuàng)建replica指令到對(duì)應(yīng)的Node節(jié)點(diǎn)响逢,同時(shí)更新shard分布信息,唯一不同的是棕孙,處于分裂中的shard狀態(tài)為splitting舔亭。該狀態(tài)用于client流量請(qǐng)求路由識(shí)別。當(dāng)Node完成數(shù)據(jù)分裂以后上報(bào)metaserver蟀俊,metaserver更新shard狀態(tài)為normal從而完成分裂钦铺。
Node
node收到分裂請(qǐng)求以后,會(huì)根據(jù)需要分裂的分片id在原地拉起創(chuàng)建一個(gè)新的分片肢预。然后對(duì)舊分片的數(shù)據(jù)進(jìn)行checkpoint矛洞,同時(shí)記錄舊分片checkpoint對(duì)應(yīng)的logid。新分片創(chuàng)建完成后烫映,會(huì)直接從舊分片的checkpoint進(jìn)行open沼本,然后在異步復(fù)制logid之后的數(shù)據(jù)保證數(shù)據(jù)的一致性。新分片加載完checkpoint后锭沟,原來(lái)的舊分片會(huì)向raftgroup提交一條分裂完成日志抽兆,該日志處理流程與普通raft日志一致。分裂完成后上報(bào)分裂狀態(tài)到metaserver冈钦,同時(shí)舊分片開(kāi)始拒絕不再屬于自己分片的數(shù)據(jù)寫(xiě)入郊丛,client收到分片錯(cuò)誤以后會(huì)請(qǐng)求metaserver更新shard分布李请。
完成分裂以后的兩個(gè)分片擁有的兩倍冗余數(shù)據(jù),這些數(shù)據(jù)會(huì)在engine compaction的時(shí)候根據(jù)compaction_filter過(guò)濾進(jìn)行刪除厉熟。
Client
用戶(hù)請(qǐng)求時(shí)导盅,根據(jù)hash(key) % shard_cnt 獲取目標(biāo)分片。表分裂期間揍瑟,該shard_cnt表示分裂完成后的最終分片數(shù)白翻。以上圖3分片的分裂為例:
hash(key) = 4, 分裂前shard_cnt為3,因此該請(qǐng)求會(huì)被發(fā)送到shard1. 分裂期間绢片,由于shard_cnt變?yōu)?滤馍,因此目標(biāo)分片應(yīng)該是shard4, 但是由于shard4為splitting底循,因此client會(huì)重新計(jì)算分片從而將請(qǐng)求繼續(xù)發(fā)送給shard1. 等到最終分裂完成后巢株,shard4狀態(tài)變更為Normal,請(qǐng)求才會(huì)被發(fā)送到shard4.
分裂期間熙涤,如果Node返回分片信息錯(cuò)誤阁苞,那么client會(huì)請(qǐng)求metaserver更新分片分布信息。
3.2 binlog支持
類(lèi)似于MySQL的binlog祠挫,我們基于raftlog日志實(shí)現(xiàn)了kv的binlog. 業(yè)務(wù)可以根據(jù)binlog進(jìn)行實(shí)時(shí)的事件流訂閱那槽,同時(shí)為了滿(mǎn)足事件流回溯的需求,我們還對(duì)binlog數(shù)據(jù)進(jìn)行冷備等舔。通過(guò)將binlog冷備到對(duì)象存儲(chǔ)骚灸,滿(mǎn)足了部分場(chǎng)景需要回溯較長(zhǎng)事件記錄的需求。
直接復(fù)用raftlog作為用戶(hù)行為的binlog慌植,可以減少binlog產(chǎn)生的額外寫(xiě)放大甚牲,唯一需要處理的是過(guò)濾raft本身的配置變更信息。learner通過(guò)實(shí)時(shí)監(jiān)聽(tīng)不斷拉取分片產(chǎn)生的binlog到本地并解析涤浇。根據(jù)learner配置信息決定將數(shù)據(jù)同步到對(duì)應(yīng)的下游鳖藕。同時(shí)binlog數(shù)據(jù)還會(huì)被異步備份到對(duì)象存儲(chǔ),當(dāng)業(yè)務(wù)需要回溯較長(zhǎng)時(shí)間的事件流的時(shí)候只锭,可以直接指定位置從S3拉取歷史binlog進(jìn)行解析著恩。
3.3 多活
基于上述提到的binlog能力,我們還基于此實(shí)現(xiàn)了kv的多活蜻展。learner模塊會(huì)實(shí)時(shí)將用戶(hù)寫(xiě)入的數(shù)據(jù)同步到跨數(shù)據(jù)中心的其他kv集群喉誊。對(duì)于跨數(shù)據(jù)中心部署的業(yè)務(wù),業(yè)務(wù)可以選擇就近的kv集群進(jìn)行讀取訪問(wèn)纵顾,降低訪問(wèn)延時(shí)伍茄。
kv的多活分為讀多活和寫(xiě)多活。對(duì)于讀多活施逾,機(jī)房A的寫(xiě)入會(huì)被異步復(fù)制到機(jī)房B敷矫,機(jī)房B的服務(wù)可以直接讀取本機(jī)房的數(shù)據(jù)例获,該模式下只有機(jī)房A的kv可以寫(xiě)入。對(duì)于寫(xiě)多活曹仗,kv在機(jī)房A B 都能同時(shí)提供寫(xiě)入并且進(jìn)行雙向同步榨汤,但是為了保證數(shù)據(jù)的一致性,需要業(yè)務(wù)上做數(shù)據(jù)的單元化寫(xiě)入怎茫,保證兩個(gè)機(jī)房不會(huì)同時(shí)修改同一條記錄收壕。通過(guò)將用戶(hù)劃分單元,提供了寫(xiě)多活的能力轨蛤。通過(guò)對(duì)binlog數(shù)據(jù)打標(biāo)蜜宪,解決了雙向同步時(shí)候的數(shù)據(jù)回環(huán)問(wèn)題。
3.4 bulk load
對(duì)于用戶(hù)畫(huà)像和特征引擎等場(chǎng)景祥山,需要將離線(xiàn)生成的大量數(shù)據(jù)快速導(dǎo)入KV存儲(chǔ)系統(tǒng)提供用戶(hù)讀取訪問(wèn)圃验。傳統(tǒng)的寫(xiě)入方式是根據(jù)生成的數(shù)據(jù)記錄一條條寫(xiě)入kv存儲(chǔ),這樣帶來(lái)兩個(gè)問(wèn)題枪蘑。其一损谦,大批量寫(xiě)入會(huì)對(duì)kv造成額外的負(fù)載與寫(xiě)入帶寬放大造成浪費(fèi)岖免。其次岳颇,由于寫(xiě)入量巨大,每次導(dǎo)入需要花費(fèi)較長(zhǎng)的時(shí)間颅湘。為了減少寫(xiě)入放大以及導(dǎo)入提速话侧,我們支持了bulk load的能力。離線(xiàn)平臺(tái)只需要根據(jù)kv的存儲(chǔ)格式離線(xiàn)生成對(duì)應(yīng)的SST文件闯参,然后上傳到對(duì)象存儲(chǔ)服務(wù)瞻鹏。kv直接從對(duì)象存儲(chǔ)拉取SST文件到本地,然后直接加載SST文件即可對(duì)外提供讀服務(wù)鹿寨。bulk load的另外一個(gè)好處是可以直接在生成SST后離線(xiàn)進(jìn)行compaction新博,將compaction的負(fù)載offload到離線(xiàn)的同時(shí)也降低了空間的放大。
3.5 kv存儲(chǔ)分離
由于LSM tree的寫(xiě)入特性脚草,數(shù)據(jù)需要被不斷的compaction到更底層的level赫悄。在compaction時(shí),如果該key還有效馏慨,那么會(huì)被寫(xiě)入到更底層的level里埂淮,如果該key已經(jīng)被刪除,那么會(huì)判斷當(dāng)前l(fā)evel是否是最底層的写隶,一條被刪除的key倔撞,會(huì)被標(biāo)記為刪除,直到被compaction到最底層level的時(shí)候才會(huì)被真正刪除慕趴。compaction的時(shí)候會(huì)帶來(lái)額外的寫(xiě)放大痪蝇,尤其當(dāng)value比較大的時(shí)候鄙陡,會(huì)造成巨大的帶寬浪費(fèi)。為了降低寫(xiě)放大躏啰,我們參考了Bitcask實(shí)現(xiàn)了kv分離的存儲(chǔ)引擎sparrowdb.
sparrowdb 介紹
用戶(hù)寫(xiě)入的時(shí)候柔吼,value通過(guò)append only的方式寫(xiě)入data文件,然后更新索引信息丙唧,索引的value包含實(shí)際數(shù)據(jù)所在的data文件id愈魏,value大小以及position信息,同時(shí)data文件也會(huì)包含索引信息想际。與原始的bitcask實(shí)現(xiàn)不一樣的是培漏,我們將索引信息保存在 rocksdb。
更新寫(xiě)入的時(shí)候胡本,只需要更新對(duì)應(yīng)的索引即可牌柄。compaction的時(shí)候,只需將索引寫(xiě)入底層的level侧甫,而無(wú)需進(jìn)行data的拷貝寫(xiě)入珊佣。對(duì)于已經(jīng)失效的data,通過(guò)后臺(tái)線(xiàn)程進(jìn)行檢查披粟,當(dāng)發(fā)現(xiàn)data文件里的索引與rocksdb保存的索引不一致的時(shí)候咒锻,說(shuō)明該data已經(jīng)被刪除或更新,數(shù)據(jù)可以被回收淘汰守屉。
使用kv存儲(chǔ)分離降低了寫(xiě)放大的問(wèn)題惑艇,但是由于kv分離存儲(chǔ),會(huì)導(dǎo)致讀的時(shí)候多了一次io拇泛,讀請(qǐng)求需要先根據(jù)key讀到索引信息滨巴,再根據(jù)索引信息去對(duì)應(yīng)的文件讀取data數(shù)據(jù)。為了降低讀訪問(wèn)的開(kāi)銷(xiāo)俺叭,我們針對(duì)value比較小的數(shù)據(jù)進(jìn)行了inline恭取,只有當(dāng)value超過(guò)一定閾值的時(shí)候才會(huì)被分離存儲(chǔ)到data文件。通過(guò)inline以及kv分離獲取讀性能與寫(xiě)放大之間的平衡熄守。
3.6 負(fù)載均衡
在分布式系統(tǒng)中蜈垮,負(fù)載均衡是繞不過(guò)去的問(wèn)題。一個(gè)好的負(fù)載均衡策略可以防止機(jī)器資源的空閑浪費(fèi)柠横。同時(shí)通過(guò)負(fù)載均衡窃款,可以防止流量?jī)A斜導(dǎo)致部分節(jié)點(diǎn)負(fù)載過(guò)高從而影響請(qǐng)求質(zhì)量。對(duì)于存儲(chǔ)系統(tǒng)牍氛,負(fù)載均衡不僅涉及到磁盤(pán)的空間晨继,也涉及到機(jī)器的內(nèi)存、cpu搬俊、磁盤(pán)io等紊扬。同時(shí)由于使用raft進(jìn)行主從選主蜒茄,保證主節(jié)點(diǎn)盡可能的打散也是均衡需要考慮的問(wèn)題。
副本均衡
由于設(shè)計(jì)上我們會(huì)盡量保證每個(gè)副本的大小盡量相等餐屎,因此對(duì)于空間的負(fù)載其實(shí)可以等價(jià)為每塊磁盤(pán)的副本數(shù)檀葛。創(chuàng)建副本時(shí),會(huì)從可用的zone中尋找包含副本數(shù)最少的節(jié)點(diǎn)進(jìn)行創(chuàng)建腹缩。同時(shí)考慮到不同業(yè)務(wù)類(lèi)型的副本讀寫(xiě)吞吐可能不一樣導(dǎo)致CPU負(fù)載不一致屿聋,在挑選副本的時(shí)候會(huì)進(jìn)一步檢查當(dāng)前節(jié)點(diǎn)的負(fù)載情況,如果當(dāng)前節(jié)點(diǎn)負(fù)載超過(guò)閾值藏鹊,則跳過(guò)該節(jié)點(diǎn)繼續(xù)選擇其他合適的節(jié)點(diǎn)润讥。目前基于最少副本數(shù)以及負(fù)載校驗(yàn)基本可以做到集群內(nèi)部的節(jié)點(diǎn)負(fù)載均衡。
當(dāng)出現(xiàn)負(fù)載傾斜時(shí)盘寡,則從負(fù)載較高的節(jié)點(diǎn)選擇副本進(jìn)行遷出楚殿,從集群中尋找負(fù)載最低的節(jié)點(diǎn)作為待遷入節(jié)點(diǎn)。當(dāng)出現(xiàn)節(jié)點(diǎn)故障下線(xiàn)以及新機(jī)器資源加入的時(shí)候竿痰,也是基于均值計(jì)算待遷出以及遷入節(jié)點(diǎn)進(jìn)行均衡脆粥。
主從均衡
雖然通過(guò)最少副本數(shù)策略保證了節(jié)點(diǎn)副本數(shù)的均衡,但是由于raft選主的性質(zhì)影涉,可能出現(xiàn)主節(jié)點(diǎn)都集中在部分少數(shù)節(jié)點(diǎn)的情況变隔。由于只有主節(jié)點(diǎn)對(duì)外提供寫(xiě)入,主節(jié)點(diǎn)的傾斜也會(huì)導(dǎo)致負(fù)載的不均衡常潮。為了保證主節(jié)點(diǎn)的均衡弟胀,Node節(jié)點(diǎn)會(huì)定期向metaserver上報(bào)當(dāng)前節(jié)點(diǎn)上副本的主從信息。
主從均衡基于表維度進(jìn)行操作喊式。metaserver會(huì)根據(jù)表在Node的分布信息進(jìn)行副本數(shù)的計(jì)算。主副本的數(shù)量基于最樸素簡(jiǎn)單的數(shù)學(xué)期望進(jìn)行計(jì)算: 主副本期望值 = 節(jié)點(diǎn)副本數(shù) / 分片副本數(shù)萧朝。下面為一個(gè)簡(jiǎn)單的例子:
假設(shè)表a包含10個(gè)shard岔留,每個(gè)shard 3個(gè)replica。在節(jié)點(diǎn)A检柬、B献联、C、D的分布為 10何址、5里逆、6、9. 那么A用爪、B原押、C、D的主副本數(shù)期望值應(yīng)該為 3偎血、1诸衔、2盯漂、3. 如果節(jié)點(diǎn)數(shù)實(shí)際的主副本數(shù)少于期望值,那么被放入待遷入?yún)^(qū)笨农,如果大于期望值就缆,那么被放入待遷出區(qū)。同時(shí)通過(guò)添加誤差值來(lái)避免頻繁的遷入遷出谒亦。只要節(jié)點(diǎn)的實(shí)際主副本數(shù)處于 [x-δx,x+δx] 則表示主副本數(shù)處于穩(wěn)定期間竭宰,x、δx 分別表示期望值和誤差值份招。
需要注意的是羞延,當(dāng)對(duì)raft進(jìn)行主從切換的時(shí)候,從節(jié)點(diǎn)需要追上所有已提交的日志以后才能成功選為主脾还,如果有節(jié)點(diǎn)落后的時(shí)候進(jìn)行主從切換伴箩,那么可能導(dǎo)致由于追數(shù)據(jù)產(chǎn)生的一段時(shí)間無(wú)主的情況。因此在做主從切換的時(shí)候必須要檢查主從的日志復(fù)制狀態(tài)鄙漏,當(dāng)存在慢節(jié)點(diǎn)的時(shí)候禁止進(jìn)行切換嗤谚。
3.7 故障檢測(cè)&修復(fù)
一個(gè)小概率的事件,隨著規(guī)模的變大怔蚌,也會(huì)變成大概率的事件巩步。分布式系統(tǒng)下,隨著集群規(guī)模的變大桦踊,機(jī)器的故障將變得愈發(fā)頻繁椅野。因此如何對(duì)故障進(jìn)行自動(dòng)檢測(cè)容災(zāi)修復(fù)也是分布式系統(tǒng)的核心問(wèn)題。故障的容災(zāi)主要通過(guò)多副本raft來(lái)保證籍胯,那么如何進(jìn)行故障的自動(dòng)發(fā)現(xiàn)與修復(fù)呢竟闪。
健康監(jiān)測(cè)
metaserver會(huì)定期向node節(jié)點(diǎn)發(fā)送心跳檢查node的健康狀態(tài),如果node出現(xiàn)故障不可達(dá)杖狼,那么metaserver會(huì)將node標(biāo)記為故障狀態(tài)并剔除炼蛤,同時(shí)將node上原來(lái)的replica遷移到其他健康的節(jié)點(diǎn)。
為了防止部分node和metaserver之間部分網(wǎng)絡(luò)隔離的情況下node節(jié)點(diǎn)被誤剔除蝶涩,我們添加了心跳轉(zhuǎn)發(fā)的功能理朋。上圖中三個(gè)node節(jié)點(diǎn)對(duì)于客戶(hù)端都是正常的,但是node3由于網(wǎng)絡(luò)隔離與metaserver不可達(dá)了绿聘,如果metaserver此時(shí)直接剔除node3會(huì)造成節(jié)點(diǎn)無(wú)必要的剔除操作嗽上。通過(guò)node2轉(zhuǎn)發(fā)心跳探測(cè)node3的狀態(tài)避免了誤剔除操作。
除了對(duì)節(jié)點(diǎn)的狀態(tài)進(jìn)行檢測(cè)外熄攘,node節(jié)點(diǎn)本身還會(huì)檢查磁盤(pán)信息并進(jìn)行上報(bào)兽愤,當(dāng)出現(xiàn)磁盤(pán)異常時(shí)上報(bào)異常磁盤(pán)信息并進(jìn)行踢盤(pán)。磁盤(pán)的異常主要通過(guò)dmesg日志進(jìn)行采集分析。
故障修復(fù)
當(dāng)出現(xiàn)磁盤(pán)節(jié)點(diǎn)故障時(shí)烹看,需要將原有故障設(shè)備的replica遷移到其他健康節(jié)點(diǎn)国拇,metaserver根據(jù)負(fù)載均衡策略選擇合適的node并創(chuàng)建新replica, 新創(chuàng)建的replica會(huì)被加入原有shard的raft group并從leader復(fù)制快照數(shù)據(jù)惯殊,復(fù)制完快照以后成功加入raft group完成故障replica的修復(fù)酱吝。
故障的修復(fù)主要涉及快照的復(fù)制。每一個(gè)replica會(huì)定期創(chuàng)建快照刪除舊的raftlog土思,快照信息為完整的rocksdb checkpoint务热。通過(guò)快照進(jìn)行修復(fù)時(shí),只需要拷貝checkpoint下的所有文件即可己儒。通過(guò)直接拷貝文件可以大幅減少快照修復(fù)的時(shí)間崎岂。需要注意的是快照拷貝也需要進(jìn)行io限速,防止文件拷貝影響在線(xiàn)io.
04 實(shí)踐經(jīng)驗(yàn)
4.1 rocksdb
過(guò)期數(shù)據(jù)淘汰
在很多業(yè)務(wù)場(chǎng)景中闪湾,業(yè)務(wù)的數(shù)據(jù)只需要存儲(chǔ)一段時(shí)間冲甘,過(guò)期后數(shù)據(jù)即可以自動(dòng)刪除清理,為了支持這個(gè)功能途样,我們通過(guò)在value上添加額外的ttl信息江醇,并在compaction的時(shí)候通過(guò)compaction_filter進(jìn)行過(guò)期數(shù)據(jù)的淘汰。level之間的容量呈指數(shù)增長(zhǎng)何暇,因此rocksdb越底層能容納越多的數(shù)據(jù)陶夜,隨著時(shí)間的推移,很多數(shù)據(jù)都會(huì)被移動(dòng)到底層裆站,但是由于底層的容量比較大条辟,很難觸發(fā)compaction,這就導(dǎo)致很多已經(jīng)過(guò)期的數(shù)據(jù)沒(méi)法被及時(shí)淘汰從而導(dǎo)致了空間放大宏胯。與此同時(shí)羽嫡,大量的過(guò)期數(shù)據(jù)也會(huì)對(duì)scan的性能造成影響。這個(gè)問(wèn)題可以通過(guò)設(shè)置periodic_compaction_seconds 來(lái)解決胳嘲,通過(guò)設(shè)置周期性的compaction來(lái)觸發(fā)過(guò)期數(shù)據(jù)的回收厂僧。
scan慢查詢(xún)
除了上面提到的存在大批過(guò)期數(shù)據(jù)的時(shí)候可能導(dǎo)致的scan慢查詢(xún),如果業(yè)務(wù)存在大批量的刪除了牛,也可能導(dǎo)致scan的時(shí)候出現(xiàn)慢查詢(xún)。因?yàn)閐elete對(duì)于rocksdb本質(zhì)也是一條append操作辰妙,delete寫(xiě)入會(huì)被添加刪除標(biāo)記鹰祸,只有等到該記錄被compaction移動(dòng)到最底層后該標(biāo)記才會(huì)被真正刪除。帶來(lái)的一個(gè)問(wèn)題是如果用戶(hù)scan的數(shù)據(jù)區(qū)間剛好存在大量的delete標(biāo)記密浑,那么iterator需要迭代過(guò)濾這些標(biāo)記直到找到有效數(shù)據(jù)從而導(dǎo)致慢查詢(xún)蛙婴。該問(wèn)題可以通過(guò)添加 CompactOnDeletionCollector 來(lái)解決。當(dāng)memtable flush或者sst compaction的時(shí)候尔破,collector會(huì)統(tǒng)計(jì)當(dāng)前key被刪除的比例街图,通過(guò)設(shè)置合理的 deletion_trigger 浇衬,當(dāng)發(fā)現(xiàn)被delete的key數(shù)量超過(guò)閾值的時(shí)候主動(dòng)觸發(fā)compaction。
delay compaction
通過(guò)設(shè)置 CompactOnDeletionCollector 解決了delete導(dǎo)致的慢查詢(xún)問(wèn)題餐济。但是對(duì)于某些業(yè)務(wù)場(chǎng)景耘擂,卻會(huì)到來(lái)嚴(yán)重的寫(xiě)放大。當(dāng)L0被compaction到L1時(shí)候絮姆,由于閾值超過(guò)deletion_trigger ,會(huì)導(dǎo)致L1被添加到compaction隊(duì)列醉冤,由于業(yè)務(wù)的數(shù)據(jù)特性,L1和L2存在大量重疊的數(shù)據(jù)區(qū)間篙悯,導(dǎo)致每次L1的compaction會(huì)同時(shí)帶上大量的L2文件造成巨大的寫(xiě)放大蚁阳。為了解決這個(gè)問(wèn)題,我們對(duì)這種特性的業(yè)務(wù)數(shù)據(jù)禁用了CompactOnDeletionCollector 鸽照。通過(guò)設(shè)置表級(jí)別參數(shù)來(lái)控制表級(jí)別的compaction策略螺捐。后續(xù)會(huì)考慮優(yōu)化delete trigger的時(shí)機(jī),通過(guò)只在指定層級(jí)觸發(fā)來(lái)避免大量的io放大矮燎。
compaction限速
由于rocksdb的compaction會(huì)造成大量的io讀寫(xiě)定血,如果不對(duì)compaction的io進(jìn)行限速,那么很可能影響到在線(xiàn)的寫(xiě)入漏峰。但是限速具體配置多少比較合適其實(shí)很難確定糠悼,配置大了影響在線(xiàn)業(yè)務(wù),配置小了又會(huì)導(dǎo)致低峰期帶寬浪費(fèi)浅乔【笪梗基于此rocksdb 在5.9以后為 NewGenericRateLimiter 添加了 auto_tuned 參數(shù),可以根據(jù)當(dāng)前負(fù)載自適應(yīng)調(diào)整限速靖苇。需要注意的是席噩,該函數(shù)還有一個(gè)參數(shù) RateLimiter::Mode 用來(lái)限制操作類(lèi)型,默認(rèn)值為 kWritesOnly,通常情況該模式不會(huì)有問(wèn)題贤壁,但是如果業(yè)務(wù)存在大量被刪除的數(shù)據(jù)悼枢,只限制寫(xiě)可能會(huì)導(dǎo)致compaction的時(shí)候造成大量的讀io。
關(guān)閉WAL
由于raft log本身已經(jīng)可以保證數(shù)據(jù)的可靠性脾拆,因此寫(xiě)入rocksdb的時(shí)候可以關(guān)閉wal減少磁盤(pán)io馒索,節(jié)點(diǎn)重啟的時(shí)候根據(jù)rocksdb里保存的last_apply_id從raft log進(jìn)行狀態(tài)機(jī)回放即可。
4.2 Raft
降副本容災(zāi)
對(duì)于三副本的raft group名船,單副本故障并不會(huì)影響服務(wù)的可用性绰上,即使是主節(jié)點(diǎn)故障了剩余的兩個(gè)節(jié)點(diǎn)也會(huì)快速選出主并對(duì)外提供讀寫(xiě)服務(wù)。但是考慮到極端情況渠驼,假設(shè)同時(shí)出現(xiàn)兩個(gè)副本故障呢蜈块? 這時(shí)只剩一個(gè)副本無(wú)法完成選主服務(wù)將完全不可用。根據(jù)墨菲定律,可能發(fā)生的一定會(huì)發(fā)生百揭。服務(wù)的可用性一方面是穩(wěn)定提供服務(wù)的能力爽哎,另一方面是故障時(shí)快速恢復(fù)的能力。那么假設(shè)出現(xiàn)這種故障的時(shí)候我們應(yīng)該如何快速恢復(fù)服務(wù)的可用呢器一。
如果通過(guò)創(chuàng)建新的副本進(jìn)行修復(fù)课锌,新副本需要等到完成快照拷貝以后才能加入raft group進(jìn)行選舉,期間服務(wù)還是不可用的盹舞。那么我們可以通過(guò)強(qiáng)制將分片降為單副本模式产镐,此時(shí)剩余的單個(gè)健康副本可以獨(dú)自完成選主,后續(xù)再通過(guò)變更副本數(shù)的方式進(jìn)行修復(fù)踢步。
RaftLog 聚合提交
對(duì)于寫(xiě)入吞吐非常高的場(chǎng)景癣亚,可以通過(guò)犧牲一定的延時(shí)來(lái)提升寫(xiě)入吞吐,通過(guò)log聚合來(lái)減少請(qǐng)求放大获印。對(duì)于SSD盤(pán)述雾,每一次寫(xiě)入都是4k刷盤(pán),value比較小的時(shí)候會(huì)造成磁盤(pán)帶寬的浪費(fèi)兼丰。我們?cè)O(shè)置了每5ms或者每聚合4k進(jìn)行批量提交玻孟。該參數(shù)可以根據(jù)業(yè)務(wù)場(chǎng)景進(jìn)行動(dòng)態(tài)配置修改。
異步刷盤(pán)
有些對(duì)于數(shù)據(jù)一致性要求不是非常高的場(chǎng)景鳍征,服務(wù)故障的時(shí)候允許部分?jǐn)?shù)據(jù)丟失黍翎。對(duì)于該場(chǎng)景,可以關(guān)閉fsync通過(guò)操作系統(tǒng)進(jìn)行異步刷盤(pán)艳丛。但是如果寫(xiě)入吞吐非常高導(dǎo)致page cache的大小超過(guò)了 vm.diry_ratio ,那么即便不是fsync也會(huì)導(dǎo)致io等待匣掸,該場(chǎng)景往往會(huì)導(dǎo)致io抖動(dòng)。為了避免內(nèi)核pdflush大量刷盤(pán)造成的io抖動(dòng)氮双,我們支持對(duì)raftlog進(jìn)行異步刷盤(pán)碰酝。
05 未來(lái)探討
透明多級(jí)存儲(chǔ),和緩存結(jié)合戴差,自動(dòng)冷熱分離送爸,通過(guò)將冷數(shù)據(jù)自動(dòng)搬遷到kv降低內(nèi)存使用成本。
新硬件場(chǎng)景接入暖释,使用SPDK 進(jìn)行IO提速袭厂,使用PMEM進(jìn)行訪問(wèn)加速。
參考文獻(xiàn)
[1] Bitcask A Log-Structured Hash Table for Fast Key/Value Data
[2] Lethe: A Tunable Delete-Aware LSM Engine