從零開(kāi)始開(kāi)發(fā)一個(gè)單機(jī)存儲(chǔ)引擎

從零開(kāi)始開(kāi)發(fā)一個(gè)單機(jī)存儲(chǔ)引擎

1.VDL Logstore概述

如何設(shè)計(jì)存儲(chǔ)引擎麻敌,使得讀寫接口的性能足夠高,如何保證在機(jī)器宕機(jī)時(shí)掂摔,存儲(chǔ)引擎能夠?qū)⒁汛鎯?chǔ)的數(shù)據(jù)恢復(fù)到一個(gè)一致性狀態(tài)术羔。如何測(cè)試存儲(chǔ)引擎的正確性?本文將著重介紹一下VDL系統(tǒng)的日志存儲(chǔ)引擎--Logstore的架構(gòu)設(shè)計(jì)與核心流程實(shí)現(xiàn)乙漓,及為了保證Logstore的正確性级历,我們做了哪些工作;為了進(jìn)一步提高Logstore的讀寫性能叭披,我們又做了哪些工作寥殖。希望通過(guò)這篇文章,給大家介紹一下設(shè)計(jì)和開(kāi)發(fā)一個(gè)存儲(chǔ)引擎的『前世今生』涩蜘。

1.1 Logstore提供的功能

VDL中有兩種日志形態(tài)嚼贡,一種是raft日志(以下稱為raft log),由raft算法產(chǎn)生和使用皱坛,另一種是用戶形態(tài)的Log(以下稱為user log)编曼,由用戶產(chǎn)生和使用。Logstore作為VDL日志存儲(chǔ)引擎剩辟,同時(shí)存儲(chǔ)著VDL的raft log 和user log掐场。Logstore在設(shè)計(jì)中,將兩種Log形態(tài)組合成一個(gè)Log Entry贩猎。只是通過(guò)不同的頭部信息來(lái)區(qū)分熊户。Logstore需要同時(shí)提供兩種不同形態(tài)的Log操作接口,主要有以下幾類:

  • 讀取吭服,根據(jù)索引信息嚷堡,讀取對(duì)應(yīng)的Log。
  • 寫入艇棕,將用戶產(chǎn)生的Log蝌戒,封裝成相應(yīng)的user Log和Raft Log寫入到Logstore中。
  • 刪除沼琉,刪除用戶不再使用的Log北苟,以文件為粒度,從最開(kāi)始位置往后刪除打瘪。
  • 轉(zhuǎn)換友鼻,由Raft Log獲取對(duì)應(yīng)的user Log傻昙。
  • 截?cái)啵財(cái)嘁徊糠諰og彩扔,主要是為了支持raft lib中刪除未達(dá)成一致的Log的功能妆档。

2.Logstore的架構(gòu)設(shè)計(jì)

2.1系統(tǒng)架構(gòu)

Logstore由數(shù)據(jù)文件和索引文件組成,同時(shí)Logstore還會(huì)在內(nèi)存中緩存最新的一段Log Entry虫碉,用于Raft lib能夠快速地從內(nèi)存中讀取到最近Raft log贾惦,同時(shí)用戶也能夠快速讀取到最新存儲(chǔ)到Logstore中的user log。Logstore的組成如下圖所示:


image.png
  • segment: 用于存儲(chǔ)log的文件蔗衡,大小固定(默認(rèn)是512MB)纤虽。Segment文件從前到后代表著log的順序,Logstore通過(guò)追加的方式不斷將Log Entry寫入到segment中绞惦。Logstore只追加Log Entry到最后的Segment文件中,對(duì)于整個(gè)Logstore只有最后一個(gè)segment可讀可寫洋措,其他Segment文件只讀济蝉。由于Segment文件大小固定,我們采用mmap函數(shù)方式對(duì)segment文件進(jìn)行讀寫菠发。
  • index: 用于存儲(chǔ)對(duì)應(yīng)的segment中的log entry的元信息王滤,例如:log entry在segment文件中的偏移,raft log index等滓鸠。每個(gè)索引項(xiàng)大小固定雁乡。用于加速查找raft log和user log。
  • MemCache: 緩存最后一段log entry數(shù)據(jù)糜俗,保證VDL能夠從內(nèi)存中讀取最新的一段log entry數(shù)據(jù)踱稍。

segment由一條一條的raft log entry組成,raft log的data部分存放的是user log悠抹。每個(gè)segment文件對(duì)應(yīng)一個(gè)index文件珠月,index file由index entry組成,index 文件中的索引項(xiàng)紀(jì)錄了對(duì)應(yīng)raft log的位置和大小等信息楔敌。示意圖如下所示:


image.png

3. Logstore的核心流程實(shí)現(xiàn)

3.1 讀數(shù)據(jù)流程

Logstore讀數(shù)據(jù)分為兩種情況:

Read in MemCache,MemCache的元數(shù)據(jù)記錄了緩存的Log范圍信息啤挎,當(dāng)讀取范圍剛好落在MemCache內(nèi)時(shí),則Logstore直接從MemCache中讀取Log并返回卵凑。
Read in Segment,當(dāng)上層讀取的Log范圍未完全落在MemCache中時(shí)庆聘,則會(huì)從segment文件中讀取。Logstore記錄了每個(gè)segment的Log范圍元數(shù)據(jù)信息勺卢,先通過(guò)segment范圍元數(shù)據(jù)信息伙判,定位到讀取的開(kāi)始segment,然后在通過(guò)索引來(lái)定位具體的文件偏移值漫。例如澳腹,讀取raft index 為10010-10019這段范圍的raft log,segment范圍如下圖所示:


image.png

根據(jù)segment的Log范圍元數(shù)據(jù)信息织盼,我們可以知道此次讀取范圍開(kāi)始位置和結(jié)束位置都在segment_2中,由于Raft log entry的長(zhǎng)度是不固定的酱塔,如何定位讀取開(kāi)始位置和結(jié)束位置的文件偏移呢沥邻?這時(shí)候就需要用到索引項(xiàng),在Logstore中每個(gè)Log entry對(duì)應(yīng)的索引項(xiàng)大小是固定的羊娃,索引項(xiàng)紀(jì)錄了該raft log entry在segment文件內(nèi)的文件偏移唐全。segment_2對(duì)應(yīng)的index文件第一個(gè)索引項(xiàng)紀(jì)錄的是raft index為10001的raft log entry索引項(xiàng),所以需要在index文件中超找raft log index范圍是:10010-10019蕊玷,就非常簡(jiǎn)單了邮利。直接讀取index 文件的第10到第19范圍的索引項(xiàng),然后根據(jù)索引項(xiàng)內(nèi)的文件偏移到segment上讀取raft log垃帅。大概的流程如下圖所示:

image.png

3.2 寫數(shù)據(jù)流程

raft算法要求寫入的raft log必須強(qiáng)制落盤后延届,才能返回成功。通過(guò)將log entry批量異步寫入segment文件贸诚,并調(diào)用sync_file_range函數(shù)強(qiáng)制刷盤方庭。為了提升寫入segment性能,segment文件創(chuàng)建時(shí)就預(yù)分配了512MB的磁盤空間酱固,這種預(yù)分配文件空間的方式有助于提升寫性能械念。將索引信息寫入index文件是異步寫完后就返回。同步寫segment运悲,異步寫index的方式降低了raft log寫耗時(shí)龄减,但又不影響raft算法的正確性。因?yàn)閞aft算法是以segment中的數(shù)據(jù)作為參考標(biāo)準(zhǔn)的班眯。

Logstore寫入流程如下圖所示:

image.png

3.3 數(shù)據(jù)恢復(fù)流程

Logstore必須要考慮到在VDL系統(tǒng)異常退出時(shí)希停,存儲(chǔ)的數(shù)據(jù)有可能出現(xiàn)不一致。例如在Logstore寫數(shù)據(jù)過(guò)程中鳖敷,機(jī)器突然宕機(jī)脖苏。這時(shí)候就有可能只寫入了部分?jǐn)?shù)據(jù),在設(shè)計(jì)Logstore時(shí)就必須考慮到如何支持?jǐn)?shù)據(jù)恢復(fù)操作定踱,保證寫入Logstore的數(shù)據(jù)的一致性棍潘。

在Logstore中,只有最后一個(gè)segment文件可能出現(xiàn)數(shù)據(jù)不一致的可能崖媚。因?yàn)長(zhǎng)ogstore在寫滿一個(gè)segment文件后亦歉,會(huì)創(chuàng)建一個(gè)新的segment文件。在創(chuàng)建新的segment文件之前畅哑,Logstore通過(guò)sync系統(tǒng)調(diào)用讓最后的segment對(duì)應(yīng)的index文件內(nèi)容強(qiáng)制刷盤肴楷,并且最后一個(gè)segment文件寫入本身就是同步寫。通過(guò)這種機(jī)制保證了只有最后一個(gè)segment寫入的數(shù)據(jù)存在部分寫的可能荠呐。而在這之前的segment文件和index文件內(nèi)容都是完整的赛蔫。

有了上面的保證砂客,數(shù)據(jù)恢復(fù)我們只需要考慮最后一個(gè)segment及其index文件中的數(shù)據(jù)是否完整。Logstore通過(guò)一個(gè)標(biāo)識(shí)文件來(lái)標(biāo)識(shí)系統(tǒng)是否正常退出呵恢,如果文件存在且里面的標(biāo)記為正常退出鞠值,Logstore就走正常啟動(dòng)流程,否則渗钉,轉(zhuǎn)入數(shù)據(jù)恢復(fù)流程彤恶,Logstore數(shù)據(jù)恢復(fù)流程,主要操作如下圖所示:


image.png

4.Logstore的測(cè)試

為保證Logstore的正確性鳄橘,我們對(duì)Logstore對(duì)外提供的接口函數(shù)及內(nèi)部調(diào)用的核心函數(shù)都做了單元測(cè)試声离,通過(guò)gitlab+jenkins持續(xù)集成的方式,保證每次提交都會(huì)觸發(fā)腳本將所有的單元測(cè)試重新運(yùn)行一次瘫怜,如果新增代碼或改動(dòng)代碼术徊,導(dǎo)致單元測(cè)試失敗,我們可以立刻發(fā)現(xiàn)宝磨。通過(guò)這種持續(xù)集成的方式弧关,我們可以保證每次代碼提交的質(zhì)量。

僅僅有單元測(cè)試還是不夠的唤锉,因?yàn)槲覀儫o(wú)法預(yù)測(cè)Logstore某個(gè)接口函數(shù)異常,對(duì)整個(gè)VDL系統(tǒng)造成什么影響别瞭。所以窿祥,我們還對(duì)Logstore進(jìn)行了異常測(cè)試,通過(guò)一個(gè)自研工具FIU蝙寨,對(duì)Logstore中特定的函數(shù)注入各種異常條件晒衩,測(cè)試Logstore的在異常情況下,對(duì)系統(tǒng)的影響墙歪。我們?cè)贚ogstore相關(guān)代碼中插入固定的異常代碼听系,然后通過(guò)FIU來(lái)觸發(fā)相應(yīng)的異常點(diǎn)。這樣就可以讓Logstore走入指定的異常邏輯代碼虹菲。異常注入測(cè)試主要分為兩類:

  • 增加讀或?qū)懷舆t靠胜,Logstore向上層提供讀寫raft log和user log等操作。例如毕源,讀取raft log增加3s的延遲浪漠、寫入user log增加1s-3s的隨機(jī)延遲。我們測(cè)試在這類異常場(chǎng)景下霎褐,對(duì)上層VDL會(huì)造成什么影響址愿,結(jié)果是否跟我們的預(yù)期一致。
  • 部分寫問(wèn)題冻璃,機(jī)器突然宕機(jī)响谓,有可能導(dǎo)致Logstore部分寫操作损合。也就是segment有可能只寫入了部分?jǐn)?shù)據(jù),或者index文件只寫入了部分?jǐn)?shù)據(jù)娘纷。同樣嫁审,我們也是在寫入segment文件邏輯和index文件邏輯中增加異常點(diǎn),利用FIU觸發(fā)指定的異常邏輯失驶。這樣就可以測(cè)試到在Logstore出現(xiàn)部分寫時(shí)土居,Logstore的數(shù)據(jù)恢復(fù)流程是否能夠正常工作,是否符合預(yù)期嬉探。

有了這類異常測(cè)試擦耀,我們可以提前去模擬線上有可能出現(xiàn)的異常場(chǎng)景,并修復(fù)可能存在的未知缺陷涩堤。保證VDL上線后更加穩(wěn)定眷蜓、可靠。并且添加異常各類異常測(cè)試用例是一個(gè)持續(xù)的過(guò)程胎围,伴隨著VDL系統(tǒng)開(kāi)發(fā)和演進(jìn)的全過(guò)程吁系。

5.Logstore的性能優(yōu)化

為保證Logstore具有高性能的讀寫,在設(shè)計(jì)階段就考慮到了白魂。比如通過(guò)文件空間預(yù)分配來(lái)提升寫性能汽纤,通過(guò)mmap方式讀日志數(shù)據(jù),提升讀性能福荸。在代碼開(kāi)發(fā)完成后蕴坪,結(jié)合go pprof和火焰圖來(lái)定位Logstore的性能開(kāi)銷較大的系統(tǒng)調(diào)用或代碼段,并做相應(yīng)優(yōu)化敬锐。性能優(yōu)化方面的工作背传,比較有意義的幾點(diǎn),可以分享一下:

  • 批量寫數(shù)據(jù)台夺,不管是寫segment還是寫index文件径玖,都是將數(shù)據(jù)先組合在一個(gè)內(nèi)存空間中,然后批量寫入到磁盤颤介。減少IO調(diào)用帶來(lái)的開(kāi)銷梳星。
  • index文件異步刷盤,在前面的設(shè)計(jì)中买窟,我們談到在segment rolling操作中丰泊,需要將index文件同步刷盤后,再創(chuàng)建新的segment文件始绍。通過(guò)持續(xù)觀察發(fā)現(xiàn)瞳购,每次index文件刷盤都要消耗4ms-8ms的時(shí)間。寫入操作如果需要segment rolling時(shí)亏推,這次的寫入延遲額外會(huì)增加4ms-8ms学赛。Logstore的寫入就會(huì)出現(xiàn)抖動(dòng)年堆。經(jīng)過(guò)分析,我們可以發(fā)現(xiàn)index文件同步刷盤所做的操作就是將index文件對(duì)應(yīng)的內(nèi)存臟頁(yè)更新到磁盤盏浇。如果我們能夠減少segment rolling操作時(shí)index文件對(duì)應(yīng)的內(nèi)存臟頁(yè)數(shù)量变丧。就可以縮短index刷盤的耗時(shí)。我們采用的方式是每次寫index文件時(shí)绢掰,再調(diào)用sync_file_range操作異步將index文件數(shù)據(jù)刷盤痒蓬,這樣就可以分?jǐn)傋詈笠淮嗡⒈P的壓力。經(jīng)過(guò)優(yōu)化后的index文件刷盤操作耗時(shí)縮短到200us-300us滴劲。使得整個(gè)Lostore的寫入耗時(shí)更加平滑攻晒。
    在核心函數(shù)調(diào)用中Logstore記錄相關(guān)metric信息,在Logstore上線后班挖,通過(guò)日志收集系統(tǒng)鲁捏,收集metric信息到influxdb,然后通過(guò)grafana展示出來(lái)萧芙。有了grafana的直觀展示给梅,我們可以監(jiān)控到耗時(shí)比較長(zhǎng)的系統(tǒng)調(diào)用,并做針對(duì)性地優(yōu)化双揪。目前關(guān)鍵的讀取和寫入操作都達(dá)到了預(yù)期的性能目標(biāo)动羽。

6.總結(jié)

本文介紹了Logstore在設(shè)計(jì)、開(kāi)發(fā)渔期、測(cè)試和性能優(yōu)化等方面曹质,我們所做的工作。希望能夠給讀者在設(shè)計(jì)和開(kāi)發(fā)分布式存儲(chǔ)系統(tǒng)時(shí)擎场,提供一定的參考思路。在后續(xù)演進(jìn)中几莽,我們希望結(jié)合業(yè)務(wù)場(chǎng)景迅办,對(duì)數(shù)據(jù)做冷熱分離,進(jìn)一步降低生產(chǎn)系統(tǒng)的成本章蚣。到時(shí)候有新的心得體會(huì)站欺,我們繼續(xù)給大家分享。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末纤垂,一起剝皮案震驚了整個(gè)濱河市矾策,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌峭沦,老刑警劉巖贾虽,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異吼鱼,居然都是意外死亡蓬豁,警方通過(guò)查閱死者的電腦和手機(jī)绰咽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)地粪,“玉大人取募,你說(shuō)我怎么就攤上這事◇〖迹” “怎么了玩敏?”我有些...
    開(kāi)封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)质礼。 經(jīng)常有香客問(wèn)我旺聚,道長(zhǎng),這世上最難降的妖魔是什么几苍? 我笑而不...
    開(kāi)封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任翻屈,我火速辦了婚禮,結(jié)果婚禮上妻坝,老公的妹妹穿的比我還像新娘伸眶。我一直安慰自己,他們只是感情好刽宪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布厘贼。 她就那樣靜靜地躺著,像睡著了一般圣拄。 火紅的嫁衣襯著肌膚如雪嘴秸。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天庇谆,我揣著相機(jī)與錄音岳掐,去河邊找鬼。 笑死饭耳,一個(gè)胖子當(dāng)著我的面吹牛串述,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寞肖,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼纲酗,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了新蟆?” 一聲冷哼從身側(cè)響起觅赊,我...
    開(kāi)封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎琼稻,沒(méi)想到半個(gè)月后吮螺,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年规脸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坯约。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡莫鸭,死狀恐怖闹丐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情被因,我是刑警寧澤卿拴,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站梨与,受9級(jí)特大地震影響堕花,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜粥鞋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一缘挽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呻粹,春花似錦壕曼、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至筹燕,卻和暖如春轧飞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背撒踪。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工过咬, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人制妄。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓援奢,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親忍捡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容