Etcd存儲的實現(xiàn)

概覽

下圖中展示了etcd如何處理一個客戶端請求的涉及到的模塊和流程。圖中淡紫色的矩形表示etcd胧砰,它包括如下幾個模塊:

  • etcd server:對外接收客戶端的請求鳍鸵,對應(yīng)etcd代碼中的etcdserver目錄,其中還有一個raft.go的模塊與etcd-raft庫進行通信朴则。etcdserver中與存儲相關(guān)的模塊是applierV3权纤,這里封裝了V3版本的數(shù)據(jù)存儲钓简,WAL(write ahead log)乌妒,用于寫數(shù)據(jù)日志,etcd啟動時會根據(jù)這部分內(nèi)容進行恢復(fù)外邓。
  • etcd raft:etcd的raft庫撤蚊,前面的文章已經(jīng)具體分析過這部分代碼。除了與本節(jié)點的etcd server通信之外损话,還與集群中的其他etcd server進行交互做一致性數(shù)據(jù)同步的工作(在圖中集群中其他etcd服務(wù)用橙色的橢圓表示)侦啸。

在上圖中,一個請求與一個etcd集群交互的主要流程分為兩大部分:

  1. 寫數(shù)據(jù)到某個etcd server中丧枪。
  2. 該etcd server與集群中的其他etcd節(jié)點進行交互光涂,當(dāng)確保數(shù)據(jù)已經(jīng)被存儲之后應(yīng)答客戶端。

請求流程劃分為了以下的子步驟:

  • 1.1:etcd server收到客戶端請求拧烦。
  • 1.2:etcd server將請求發(fā)送給本模塊中的raft.go忘闻,這里負責(zé)與etcd raft模塊進行通信。
  • 1.3:raft.go將數(shù)據(jù)封裝成raft日志的形式提交給raft模塊恋博。
  • 1.4:raft模塊會首先保存到raftLog的unstable存儲部分齐佳。
  • 1.5:raft模塊通過raft協(xié)議與集群中其他etcd節(jié)點進行交互。

注意在以上流程中债沮,假設(shè)這里寫入數(shù)據(jù)的etcd是leader節(jié)點炼吴,因為在raft協(xié)議中,如果提交數(shù)據(jù)到非leader節(jié)點的話需要路由到etcd leader節(jié)點去疫衩。

而應(yīng)答步驟如下:

  • 2.1:集群中其他節(jié)點向leader節(jié)點應(yīng)答接收這條日志數(shù)據(jù)硅蹦。
  • 2.2:當(dāng)超過集群半數(shù)以上節(jié)點應(yīng)答接收這條日志數(shù)據(jù)時,etcd raft通過Ready結(jié)構(gòu)體通知etcd server中的raft該日志數(shù)據(jù)已經(jīng)commit。
  • 2.3:raft.go收到Ready數(shù)據(jù)將首先將這條日志寫入到WAL模塊中童芹。
  • 2.4:通知最上層的etcd server該日志已經(jīng)commit命爬。
  • 2.5:etcd server調(diào)用applierV3模塊將日志寫入持久化存儲中。
  • 2.6:etcd server應(yīng)答客戶端該數(shù)據(jù)寫入成功辐脖。
  • 2.7:最后etcd server調(diào)用etcd raft饲宛,修改其raftLog模塊的數(shù)據(jù),將這條日志寫入到raftLog的storage中嗜价。

從上面的流程可以看到

  • etcd raft模塊在應(yīng)答某條日志數(shù)據(jù)已經(jīng)commit之后艇抠,是首先寫入到WAL模塊中的,因為這個模塊只是添加一條日志久锥,所以速度會很快家淤,即使在后面applierV3寫入失敗,重啟的時候也可以根據(jù)WAL模塊中的日志數(shù)據(jù)進行恢復(fù)瑟由。
  • etcd raft中的raftLog絮重,按照前面文章的分析,其中的數(shù)據(jù)是保存到內(nèi)存中的歹苦,重啟即失效青伤,上層應(yīng)用真實的數(shù)據(jù)是持久化保存到WAL和applierV3中的。

以下就來分析etcd server與這部分相關(guān)的幾個模塊殴瘦。

etcd server與raft的交互

EtcdServer結(jié)構(gòu)體狠角,負責(zé)對外與客戶端進行通信。內(nèi)部有一個raftNode結(jié)構(gòu)的成員蚪腋,負責(zé)與etcd的raft庫進行交互丰歌。

etcd V3版本的API,通過GRPC協(xié)議與客戶端進行交互屉凯,其相關(guān)代碼在etcdserver/v3_server.go中立帖。以一次Put請求為例,最后將會調(diào)用的代碼在函數(shù)EtcdServer::processInternalRaftRequestOnce中悠砚,代碼的主要流程分析如下晓勇。

  1. 拿到當(dāng)前raft中的apply和commit索引,如果commit索引比apply索引超出太多哩簿,說明當(dāng)前有很多數(shù)據(jù)都沒有apply宵蕉,返回ErrTooManyRequests錯誤。
  2. 調(diào)用s.reqIDGen.Next()函數(shù)生成一個針對當(dāng)前請求的ID节榜,注意這個ID并不是一個隨機數(shù)而是一個嚴格遞增的整數(shù)羡玛。同時將請求序列化為byte數(shù)據(jù),這會做為raft的數(shù)據(jù)進行存儲宗苍。
  3. 根據(jù)第2步中的ID稼稿,調(diào)用Wait.Register函數(shù)進行注冊薄榛,這會返回一個用于通知結(jié)果的channel,后續(xù)就通過監(jiān)聽該channel來確定是否成功儲存了提交的值让歼。
  4. 調(diào)用Raft.Process函數(shù)提交數(shù)據(jù)敞恋,這里傳入的參數(shù)除了前面序列化的數(shù)據(jù)之外,還有使用超時時間創(chuàng)建的Context谋右。
  5. 監(jiān)聽前面的Channel以及Context對象: a. 如果context.Done返回硬猫,說明數(shù)據(jù)提交超時,使用s.parseProposeCtxErr函數(shù)返回具體的錯誤改执。 b. 如果channel返回啸蜜,說明已經(jīng)提交成功。

從以上的流程可以看出辈挂,在調(diào)用Raft.Process函數(shù)向Raft庫提交數(shù)據(jù)之后衬横,等待被喚醒的Channel才是正常提交數(shù)據(jù)成功的路徑。

在EtcdServer.run函數(shù)中终蒂,最終會進入一個死循環(huán)中蜂林,等待raftNode.apply返回的channel被喚醒,而raftNode繼承了raft.Node的實現(xiàn)拇泣,從前面分析etcd raft的流程中可以明白噪叙,EtcdServer就是在向raft庫提交了數(shù)據(jù)之后,做為其上層消費Ready數(shù)據(jù)的應(yīng)用層挫酿。

自此构眯,整體的流程大體已經(jīng)清晰:

  1. EtcdServer對外通過GRPC協(xié)議接收客戶端請求愕难,對內(nèi)有一個raftNode類型的成員早龟,該類型繼承了raft.Node的實現(xiàn)。
  2. 客戶端通過EtcdServer提交的數(shù)據(jù)修改都會通過raftNode來提交猫缭,而EtcdServer本身通過監(jiān)聽channel與raft庫進行通信葱弟,由Ready結(jié)構(gòu)體來通過EtcdServer哪些數(shù)據(jù)已經(jīng)提交成功。
  3. 由于每個請求都會一個對應(yīng)的ID猜丹,ID綁定了Channel芝加,所以提交成功的請求通過ID找到對應(yīng)的Channel來喚醒提交流程,最后通知客戶端提交數(shù)據(jù)成功射窒。

WAL

以上介紹了EtcdServer的大體流程藏杖,接下來看WAL的實現(xiàn)。

前面已經(jīng)分析過了脉顿,etcd raft提交數(shù)據(jù)成功之后蝌麸,將通知上面的應(yīng)用層(在這里就是EtcdServer),然后再進行持久化數(shù)據(jù)存儲艾疟。而數(shù)據(jù)的持久化可能會花費一些時間来吩,因此在應(yīng)答應(yīng)用層之前敢辩,EtcdServer中的raftNode會首先將這些數(shù)據(jù)寫入WAL日志中。這樣即使在做持久化的時候數(shù)據(jù)丟失了弟疆,啟動恢復(fù)的時候也可以根據(jù)WAL的日志進行數(shù)據(jù)恢復(fù)戚长。

etcdserver模塊中,給raftNode用于寫WAL日志的工作怠苔,交給了接口Storage來完成同廉,而這個接口由storage來具體實現(xiàn):

type storage struct {

    *wal.WAL

    *snap.Snapshotter

}

可以看到,這個結(jié)構(gòu)體組合了WAL和snap.Snapshotter結(jié)構(gòu)柑司,Snapshotter負責(zé)的是存儲快照數(shù)據(jù)恤溶。

WAL日志文件中,每條日志記錄有以下的類型:

  1. Type:日志記錄類型帜羊,下面詳細解釋都有哪些類型咒程。
  2. Crc:這一條日志記錄的校驗數(shù)據(jù)。
  3. Data:真正的數(shù)據(jù)讼育,根據(jù)類型不同存儲的數(shù)據(jù)也不同帐姻。

日志記錄又有如下的類型:

  1. metadataType:存儲的是元數(shù)據(jù)(metadata),每個WAL文件開頭都有這類型的一條記錄數(shù)據(jù)奶段。
  2. entryType:保存的是raft的數(shù)據(jù)饥瓷,也就是客戶端提交上來并且已經(jīng)commit的數(shù)據(jù)。
  3. stateType:保存的是當(dāng)前集群的狀態(tài)信息痹籍,即前面提到的HardState呢铆。
  4. crcType:校驗數(shù)據(jù)。
  5. snapshotType:快照數(shù)據(jù)蹲缠。

etcd使用兩個目錄分別存放WAL文件以及快照文件棺克。其中,WAL文件的文件名格式是“16位的WAL文件編號-該WAL第一條entry數(shù)據(jù)的index號.wal”线定,這樣就能從WAL文件名知道該WAL文件中保存的entry數(shù)據(jù)至少大于什么索引號娜谊。而快照文件名的格式則是“16位的快照數(shù)據(jù)最后一條日志記錄任期號-16位的快照數(shù)據(jù)最后一條記錄的索引號.snap”。

Etcd會管理WAL目錄中的所有WAL文件斤讥,但是在生成快照文件之后纱皆,在快照數(shù)據(jù)之前的WAL文件將被清除掉,保證磁盤不會一直增長芭商。

比如當(dāng)前etcd中有三個WAL文件派草,可以從這些文件的文件名知道其中存放數(shù)據(jù)的索引范圍。

在生成快照文件之后铛楣,此時就只剩一個WAL文件和一個快照文件了:

那么近迁,又是在什么情況下生成快照文件呢?Etcdserver在主循環(huán)中通過監(jiān)聽channel獲知當(dāng)前raft協(xié)議返回的Ready數(shù)據(jù)蛉艾,此時會做判斷如果當(dāng)前保存的快照數(shù)據(jù)索引距離上一次已經(jīng)超過一個閾值(EtcdServer.snapCount)钳踊,此時就從raft的存儲中生成一份當(dāng)前的快照數(shù)據(jù)衷敌,寫入快照文件成功之后,就可以將這之前的WAL文件釋放了拓瞪。

以上流程和對應(yīng)的具體函數(shù)見下面的流程圖缴罗。

backend store的實現(xiàn)

revision概念

Etcd存儲數(shù)據(jù)時,并不是像其他的KV存儲那樣祭埂,存放數(shù)據(jù)的鍵做為key面氓,而是以數(shù)據(jù)的revision做為key,鍵值做為數(shù)據(jù)來存放蛆橡。如何理解revision這個概念舌界,以下面的例子來說明。

比如通過批量接口兩次更新兩對鍵值泰演,第一次寫入數(shù)據(jù)時呻拌,寫入<key1,value1>和<key2,value2>,在Etcd這邊的存儲看來睦焕,存放的數(shù)據(jù)就是這樣的:

  revision={1,0}, key=key1, value=value1
  revision={1,1}, key=key2, value=value2

而在第二次更新寫入數(shù)據(jù)<key1,update1>和<key2,update2>后藐握,存儲中又記錄(注意不是覆蓋前面的數(shù)據(jù))了以下數(shù)據(jù):

  revision={2,0}, key=key1, value=update1
  revision={2,1}, key=key2, value=update2

其中revision有兩部分組成,第一部分成為main revision垃喊,每次事務(wù)遞增1猾普;第二部分稱為sub revision,一個事務(wù)內(nèi)的一次操作遞增1本谜。 兩者結(jié)合初家,就能保證每次key唯一而且是遞增的。

但是乌助,就客戶端看來溜在,每次操作的時候是根據(jù)Key來進行操作的,所以這里就需要一個Key映射到當(dāng)前revision的操作了眷茁,為了做到這個映射關(guān)系炕泳,Etcd引入了一個內(nèi)存中的Btree索引,整個操作過程如下面的流程所示上祈。

查詢時,先通過內(nèi)存中的btree索引來查詢該key對應(yīng)的keyIndex結(jié)構(gòu)體浙芙,然后再根據(jù)這個結(jié)構(gòu)體才能去boltdb中查詢真實的數(shù)據(jù)返回登刺。

所以,下面先展開討論這個keyIndex結(jié)構(gòu)體和btree索引嗡呼。

keyIndex結(jié)構(gòu)

keyIndex結(jié)構(gòu)體有以下成員:

  • key:存儲數(shù)據(jù)真實的鍵纸俭。
  • modified:最后一次修改該鍵對應(yīng)的revision。
  • generations:generation數(shù)組南窗。

如何理解generation結(jié)構(gòu)呢揍很,可以認為每個generation對應(yīng)一個數(shù)據(jù)從創(chuàng)建到刪除的過程郎楼。每次刪除key的操作箍镜,都會導(dǎo)致一個generation最后添加一個tombstone記錄谈截,然后創(chuàng)建一個新的空generation記錄添加到generations數(shù)組中何暇。

generation結(jié)構(gòu)體存放以下數(shù)據(jù):

  • ver:當(dāng)前generation中存放了多少次修改描扯,其實就是revs數(shù)組的大小-1(因為需要去掉tombstone)祸轮。
  • created:創(chuàng)建該generation時的revision八匠。
  • revs:存放該generation中存放的revision數(shù)組摘盆。

以下圖來說明keyIndex結(jié)構(gòu)體:

如上圖所示坛梁,存放的鍵為test的keyIndex結(jié)構(gòu)聋庵。

它的generations數(shù)組有兩條記錄膘融,其中g(shù)enerations[0]在revision 1.0時創(chuàng)建,當(dāng)revision2.1的時候進行tombstone操作祭玉,因此該generation的created是1.0氧映;對應(yīng)的generations[1]在revision3.3時創(chuàng)建,緊跟著就做了tombstone操作脱货。

所以該keyIndex.modifiled成員存放的是3.3屯耸,因為這是這條數(shù)據(jù)最后一次被修改的revision。

一個已經(jīng)被tombstone的generation是可以被刪除的蹭劈,如果整個generations數(shù)組都已經(jīng)被刪除空了疗绣,那么整個keyIndex記錄也可以被刪除了。

如上圖所示铺韧,keyIndex.compact(n)函數(shù)可以對keyIndex數(shù)據(jù)進行壓縮操作多矮,將刪除滿足main revision < n的數(shù)據(jù)。

  • compact(2):找到了generations[0]的1.0 revision的數(shù)據(jù)進行了刪除哈打。
  • compact(3):找到了generations[0]的2.1 revision的數(shù)據(jù)進行了刪除塔逃,此時由于generations[0]已經(jīng)沒有數(shù)據(jù)了,所以這一整個generation被刪除料仗,原先的generations[1]變成了generations[0]湾盗。
  • compact(4):找到了generations[0]的3.3 revision的數(shù)據(jù)進行了刪除。由于所有的generation數(shù)據(jù)都被刪除了立轧,此時這個keyIndex數(shù)據(jù)可以刪除了格粪。

treeIndex結(jié)構(gòu)

Etcd中使用treeIndex來在內(nèi)存中存放keyIndex數(shù)據(jù)信息,這樣就可以快速的根據(jù)輸入的key定位到對應(yīng)的keyIndex氛改。

treeIndex使用開源的github.com/google/btree來在內(nèi)存中存儲btree索引信息帐萎,因為用的是外部庫,所以不打算就這部分做解釋胜卤。而如果很清楚了前面keyIndex結(jié)構(gòu)疆导,其實這部分很好理解。

所有的操作都以key做為參數(shù)進行操作葛躏,treeIndex使用btree根據(jù)key查找到對應(yīng)的keyIndex澈段,再進行相關(guān)的操作悠菜,最后重新寫入到btree中。

store

前面講到了WAL數(shù)據(jù)的存儲败富、內(nèi)存索引數(shù)據(jù)的存儲悔醋,這部分討論持久化存儲數(shù)據(jù)的模塊。

etcd V3版本中囤耳,使用BoltDB來持久化存儲數(shù)據(jù)(etcd V2版本的實現(xiàn)不做討論)篙顺。所以這里先簡單解釋一下BoltDB中的相關(guān)概念。

BoltDB相關(guān)概念

BoltDB中涉及到的幾個數(shù)據(jù)結(jié)構(gòu)充择,分別為DB德玫、Bucket、Tx椎麦、Cursor宰僧、Tx等。

其中:

  • DB:表示數(shù)據(jù)庫观挎,類比于Mysql琴儿。
  • Bucket:數(shù)據(jù)庫中的鍵值集合,類比于Mysql中的一張數(shù)據(jù)表嘁捷。
  • 鍵值對:BoltDB中實際存儲的數(shù)據(jù)造成,類比于Mysql中的一行數(shù)據(jù)。
  • Cursor:迭代器雄嚣,用于按順序遍歷Bucket中的鍵值對晒屎。
  • Tx:表示數(shù)據(jù)庫操作中的一次只讀或者讀寫事務(wù)。

Backend與BackendTx接口

Backend和BackendTx內(nèi)部的實現(xiàn)缓升,封裝了BoltDB鼓鲁,太簡單就不做分析了。

Lessor接口

etcd中沒有提供針對數(shù)據(jù)設(shè)置過期時間的操作港谊,通過租約(Lease)來實現(xiàn)數(shù)據(jù)過期的效果骇吭。而Lessor接口就提供了管理租約的相關(guān)接口。

比如歧寺,使用etcdctl命令可以創(chuàng)建一個lease:

etcdctl lease grant 10 lease 694d67ed2bfbea03 granted with TTL(10s)

這樣就創(chuàng)建了一個ID為694d67ed2bfbea03的Lease燥狰,此時可以將鍵值與這個lease進行綁定:

etcdctl put --lease=694d67ed2bfbea03 a b

當(dāng)時間還沒超過過期時間10S時,能通過etcd拿到這對鍵值的數(shù)據(jù)成福。如果超時了就獲取不到數(shù)據(jù)了碾局。

從上面的命令可以看出,一個Lease可以與多個鍵值對應(yīng)奴艾,由這個Lease通過管理與其綁定的鍵值數(shù)據(jù)的生命周期。

etcd中内斯,將Lease ID存放在名為“l(fā)ease”的Bucket中蕴潦,注意在這里只存放Lease相關(guān)的數(shù)據(jù)像啼,其鍵值為:<Lease ID,序列化后的Lease數(shù)據(jù)包括TTL潭苞、ID>忽冻,之所以不存放與Lease綁定的鍵值,是因為這些鍵值已經(jīng)存放到另外的Bucket里了此疹,寫入數(shù)據(jù)的時候也會將這些鍵值綁定的Lease ID寫入僧诚,這樣在恢復(fù)數(shù)據(jù)的時候就可以將鍵值與Lease ID綁定的關(guān)系寫入內(nèi)存中。

即:Lease這邊需要持久化的數(shù)據(jù)只有Lease ID與TTL值蝗碎,而鍵值對這邊會持久化所綁定的Lease ID湖笨,這樣在啟動恢復(fù)的時候可以將兩者對應(yīng)的關(guān)系恢復(fù)到內(nèi)存中。

Lease

明白了以上關(guān)系再來理解Lessor的實現(xiàn)就很簡單了蹦骑。

lessor中主要包括以下的成員:

  • leaseMap map[LeaseID]*Lease:存儲LeaseID與Lease實例之間的對應(yīng)關(guān)系慈省。
  • itemMap map[LeaseItem]LeaseID:leaseItem實際存放的是鍵值,所以這個map管理的就是鍵值與Lease ID之間的對應(yīng)關(guān)系眠菇。
  • b backend.Backend:持久化存儲边败,每個Lease的持久化數(shù)據(jù)會寫入名為“l(fā)ease”的Bucket中。
  • minLeaseTTL int64:最小過期時間捎废,設(shè)置給每個lease的過期時間不得小于這個數(shù)據(jù)笑窜。
  • expiredC chan []*Lease:通過這個channel通知外部有哪些Lease過期了。

其他的就很簡單了:

  1. lessor啟動之后會運行一個goroutine協(xié)程登疗,在這個協(xié)程里定期查詢哪些Lease超時排截,超時的Lease將通過expiredC channel通知外部。
  2. 而針對Lease的CRUD操作谜叹,都需要進行加鎖才能操作匾寝。

KV接口

有了以上的準備,可以開始分析數(shù)據(jù)存儲相關(guān)的內(nèi)容了荷腊。在etcd V3中艳悔,所有涉及到數(shù)據(jù)的存儲,都會通過KV接口女仰。

store結(jié)構(gòu)體實現(xiàn)了KV接口猜年,其中最重要的就是封裝了前面提到的幾個數(shù)據(jù)結(jié)構(gòu):

  • b backend.Backend:用于將持久化數(shù)據(jù)寫入BoltDB中。
  • kvindex index:保存key索引疾忍。
  • changes []mvccpb.KeyValue:保存每次寫操作之后進行了修改的數(shù)據(jù)乔外,用于通知watch了這些數(shù)據(jù)變更的客戶端。

在store結(jié)構(gòu)體初始化時一罩,根據(jù)傳入的backend.Backend杨幼,初始化backend.BatchTx結(jié)構(gòu),后面的任何涉及到事務(wù)的操作,都可以通過這個backend.BatchTx來進行差购。

其實有了前面的準備四瘫,理解store結(jié)構(gòu)做的事情已經(jīng)不難,以一次Put操作為例欲逃,其流程主要如下圖所示:

applierV3

EtcdServer內(nèi)部實現(xiàn)中找蜜,實際使用的是applierV3接口來進行持久化數(shù)據(jù)的操作。

這個接口有以下幾個實現(xiàn)稳析,但是其中applierV3backend的實現(xiàn)是最重要的洗做,其內(nèi)部使用了前面提到的KV接口來進行數(shù)據(jù)的處理。

另外彰居,applierV3接口還有其他幾個實現(xiàn)诚纸,這里分別列舉一下。

  • applierV3backend:基礎(chǔ)的applierV3接口實現(xiàn)裕菠,其他幾個實現(xiàn)都在此實現(xiàn)上做功能擴展咬清。內(nèi)部調(diào)用EtcdServer中的KV接口進行持久化數(shù)據(jù)讀寫操作。
  • applierV3Capped:磁盤空間不足的情況下奴潘,EtcdServer中的applierV3切換到這個實現(xiàn)里面來旧烧,這個實現(xiàn)的任何寫入操作都會失敗,這樣保證底層存儲的數(shù)據(jù)量不再增加画髓。
  • authApplierV3:在applierV3backend的基礎(chǔ)上擴展出權(quán)限控制的功能掘剪。
  • quotaApplierV3:在applierV3backend的基礎(chǔ)上加上了限流功能,即底層的存儲到了上限的話奈虾,會觸發(fā)限流操作夺谁。

綜述

下圖將上面涉及到的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)串聯(lián)在一起,看看EtcdServer在收到Raft庫通過Ready channel通知的可以持久化數(shù)據(jù)之后肉微,都做了什么操作匾鸥。

  1. raft庫通過Ready Channel通知上層的raftNode哪些數(shù)據(jù)可以進行持久化。
  2. raftNode啟動之后也是會啟動一個Goroutine來一直監(jiān)聽這個Ready Channel碉纳,以便收到可以持久化數(shù)據(jù)的通知勿负。
  3. raftNode在收到Ready數(shù)據(jù)之后,將首先寫入WAL日志中劳曹。這里的WAL日志由storage結(jié)構(gòu)體來管理奴愉,分為兩大部分:WAL日志以及WAL快照文件數(shù)據(jù)Snapshotter,后者用來避免WAL文件一直增大铁孵。
  4. raftNode在寫WAL數(shù)據(jù)完成之后锭硼,通過apply Channel通知EtcdServer。
  5. EtcdServer啟動之后也是啟動一個Goroutine來監(jiān)聽這個channel蜕劝,以便收到可以持久化數(shù)據(jù)的通知檀头。
  6. EtcdServer通過調(diào)用applierV3接口來持久化數(shù)據(jù)轰异。applierV3backend結(jié)構(gòu)體實現(xiàn)applierV3接口, applierV3backend結(jié)構(gòu)體實現(xiàn)applierV3接口,內(nèi)部通過調(diào)用KV接口進行持久化操作鳖擒。而在實現(xiàn)KV接口的store結(jié)構(gòu)體中溉浙,treeIndex負責(zé)在內(nèi)存中維護數(shù)據(jù)鍵值與revision的對應(yīng)關(guān)系即keyIndex數(shù)據(jù)烫止,Backend接口負責(zé)持久化數(shù)據(jù)蒋荚,最后持久化的數(shù)據(jù)將落盤到BoltDB中。

原文鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末馆蠕,一起剝皮案震驚了整個濱河市期升,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌互躬,老刑警劉巖播赁,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吼渡,居然都是意外死亡容为,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門寺酪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坎背,“玉大人,你說我怎么就攤上這事寄雀〉寐耍” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵盒犹,是天一觀的道長懂更。 經(jīng)常有香客問我,道長急膀,這世上最難降的妖魔是什么沮协? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮卓嫂,結(jié)果婚禮上慷暂,老公的妹妹穿的比我還像新娘。我一直安慰自己命黔,他們只是感情好呜呐,可當(dāng)我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悍募,像睡著了一般蘑辑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坠宴,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天洋魂,我揣著相機與錄音,去河邊找鬼。 笑死副砍,一個胖子當(dāng)著我的面吹牛衔肢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播豁翎,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼角骤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了心剥?” 一聲冷哼從身側(cè)響起邦尊,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎优烧,沒想到半個月后蝉揍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡畦娄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年又沾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熙卡。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡杖刷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出再膳,到底是詐尸還是另有隱情挺勿,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布喂柒,位于F島的核電站不瓶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏灾杰。R本人自食惡果不足惜蚊丐,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望艳吠。 院中可真熱鬧麦备,春花似錦、人聲如沸昭娩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽栏渺。三九已至呛梆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間磕诊,已是汗流浹背填物。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工纹腌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人滞磺。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓升薯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親击困。 傳聞我的和親對象是個殘疾皇子涎劈,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,974評論 2 355