引子
因為一直在跟 Raft 打交道,雖然對 Raft 很熟悉了,但如果你要我去給一個完全不知道什么是 Raft 的人講 Raft,我覺得難度還是非常大的。所以我決定使用我一貫羅里吧嗦敬辣,用比喻和講故事的方式,來嘗試說說 Raft。
如果你跟你孩子一起看過小豬佩奇溉跃,你大概就能知道我為啥用了這么怪的取名汰聋。如果沒看過的,強烈推薦你去看看喊积,這真的是一部很不錯的兒童動畫烹困。
日志和狀態(tài)機
兔小姐準備在泥坑小鎮(zhèn)成立一家銀行(就叫泥坑銀行吧)。對于銀行儲蓄系統(tǒng)的設(shè)計乾吻,兔小姐找來了豬爸爸髓梅。
兔小姐:『豬爸爸,我們要保證绎签,無論怎樣用戶的金錢不能有錯誤枯饿。假如客戶存了 100 塊錢,那么他的賬戶就會多出來 100 塊錢诡必,不會是 101奢方,也不會是 99“质妫』
豬爸爸:『好的蟋字,兔小姐,我覺得我們可以這樣扭勉。如果一個客戶來存錢鹊奖,那么,首先我們可以將交易依次順序記錄下來涂炎,然后兔先生再把客戶實際的錢放到金庫對應的保險柜里面忠聚。當兔先生把錢放好之后,我們就可以告訴客戶這筆交易完成了』
兔小姐:『好主意唱捣,豬爸爸两蟀,不過為什么要先將交易記錄下來呢?直接放到金庫不就可以了嗎震缭?』
豬爸爸:『首先赂毯,如果交易記錄都沒成功,那么我們就不用再麻煩將錢放到金庫了蛀序。其次欢瞪,假設(shè)同時很多人來存錢活烙,兔先生有點處理不過來徐裸,沒準就會弄錯。我們有記錄的話啸盏,兔先生就可以按照記錄一個一個處理重贺,雖然這樣慢一點,但不會出錯。另外气笙,交易記錄永遠不可能被篡改或者被覆蓋次企,譬如如果我們在記錄 N 這個位置記錄下客戶 A 存了 100 塊錢,那么這條記錄 N 后面一定是這筆交易潜圃,而不可能在變成客戶 B 取了 100 塊錢缸棵。』
兔小姐:『嗯谭期,聽起來很有道理堵第,那么我們就這么做吧∷沓觯』
好了踏志,雖然上面的例子有點不切合實際,畢竟如果銀行真這么玩胀瞪,離倒閉也就不遠了针余,但各位還是先認為這套機制能很好的工作吧。在 Raft 里面凄诞,交易記錄圆雁,我們可以叫做日志,而金庫帆谍,則是狀態(tài)機摸柄。對于任何的操作,Raft 會首先將其記錄到日志既忆,然后等這個日志提交之后驱负,我們再將其對應的數(shù)據(jù)寫入到狀態(tài)機里面。每一條日志都有一個唯一的編號患雇,這個編號是嚴格按照加一單調(diào)遞增的跃脊,也就是說,leader 只會追加日志苛吱,而不會覆蓋日志酪术。假設(shè)現(xiàn)在的日志編號是 10,那么下一條日志的編號就一定是 11翠储。如果這條日志被應用到了狀態(tài)機绘雁,那么我們就可以認為這條日志已經(jīng)是被 applied 了。
Quorum
不久之后援所,泥坑銀行儲蓄系統(tǒng)第一個版本就上線了庐舟。一切工作的良好,直到有一天住拭,電閃雷鳴挪略,泥坑銀行停電了历帚。用戶發(fā)現(xiàn)根本沒辦法進行交易了,雖然狗爺爺盡了最大的努力杠娱,但讓整個銀行正常工作也花了不少時間挽牢。銀行正常營業(yè)之后,兔小姐找來了豬爸爸摊求。
兔小姐:『豬爸爸禽拔,現(xiàn)在看起來我們必須要保證,即使在泥坑小鎮(zhèn)的銀行出現(xiàn)了問題室叉,譬如停電這種的奏赘,用戶仍然能夠正常的進行交易√荩』
豬爸爸:『是的磨淌,兔小姐。那要做到這樣凿渊,我們必須在其他地方建立另一個銀行網(wǎng)點梁只,這樣,即使在泥坑小鎮(zhèn)銀行不能對外提供服務(wù)了埃脏,但客戶還是能在其他銀行網(wǎng)點進行交易搪锣。』
兔小姐:『好主意彩掐,豬爸爸构舟,那么我們 就在回音山谷建立一個分部,當泥坑小鎮(zhèn)的銀行出現(xiàn)了故障堵幽,用戶仍然能夠在回音山谷進行交易狗超。』
豬爸爸:『兔小姐朴下,恐怕只是在回音山谷建立一個分部努咐,是不行的∨闺剩』
兔小姐:『為什么呢渗稍?豬爸爸,我有點不明白了团滥「鸵伲』
豬爸爸:『現(xiàn)在假設(shè)我們在泥坑小鎮(zhèn)和回音山谷都部署好系統(tǒng),如果一個客戶到泥坑小鎮(zhèn)存錢灸姊,我們首先要在泥坑小鎮(zhèn)這邊記錄這筆交易拱燃,然后在通知回音山谷那邊也記錄這筆交易,只有我們知道回音山谷記錄交易成功了厨钻,我們才可以進行下一步扼雏,也就是將用戶的錢放到金庫里面,同時告訴回音山谷那邊夯膀,也需要將對應錢放到那邊的金庫诗充,這樣如果泥坑小鎮(zhèn)出現(xiàn)了問題,客戶仍然能到回音山谷那邊取錢诱建『眩』
兔小姐:『這聽起來很復雜,但這樣看起來沒問題俺猿,所以將系統(tǒng)放到兩個地方茎匠,沒問題呀!』
豬爸爸:『不不押袍,兔小姐诵冒。上面我說的是都兩邊都能正常工作的情況,但實際會有很多異常情況谊惭。譬如汽馋,假設(shè)泥坑小鎮(zhèn)這邊的系統(tǒng)能正常工作,但回音山谷的出現(xiàn)了問題圈盔,那么客戶來泥坑小鎮(zhèn)存錢豹芯,因為我們沒辦法在回音山谷記錄這筆交易,所以用戶仍然不能存錢驱敲√福』
兔小姐:『出現(xiàn)這種情況,我們還是先讓客戶能存錢吧众眨,等回音山谷系統(tǒng)好了再把相關(guān)的交易記錄放到那邊去握牧,這樣不行嗎?』
豬爸爸:『當然不可以娩梨,兔小姐我碟。因為我們要保證客戶金錢的絕對安全瞬内。假設(shè)客戶先在泥坑小鎮(zhèn)這邊存了錢秕衙,回音山谷那邊可能因為出現(xiàn)了問題并不知道這筆交易,如果泥坑小鎮(zhèn)這邊的系統(tǒng)出現(xiàn)了問題慷蠕,那么用戶去回音山谷取錢掸冤,就會發(fā)現(xiàn)厘托,他在回音山谷的錢還是之前的總額,這樣問題就大了稿湿。所以铅匹,如果只有兩個地方有系統(tǒng),我們必須要保證這兩個地方的系統(tǒng)都完全能正常工作饺藤,任何一方出現(xiàn)了問題包斑,整個系統(tǒng)就是不可用的流礁。』
兔小姐:『哦罗丰,我大概明白了神帅,那我們怎么辦?』
豬爸爸:『如果我們要容忍一個地方的銀行不能對外提供服務(wù)萌抵,但客戶還是能正常的進行交易找御,我們至少需要在三個地方部署系統(tǒng)∩芴睿』
兔小姐:『哦霎桅,我有點糊涂了,你能仔細解釋下嗎讨永,為什么三個就可以呢滔驶?』
豬爸爸:『好的,兔小姐卿闹。假設(shè)現(xiàn)在我們在三個地方有部署好了系統(tǒng)瓜浸,譬如這三個地方就是泥坑小鎮(zhèn),回音山谷和海盜島吧比原。假設(shè)一個客戶來泥坑小鎮(zhèn)存錢插佛,首先,我們會在泥坑小鎮(zhèn)記錄下這筆交易量窘,然后告訴回音山谷和海盜島也記錄下這次交易雇寇,如果回音山谷或者海盜島有一個回復泥坑小鎮(zhèn)這邊交易已經(jīng)被成功記錄,我們就可以允許客戶在泥坑小鎮(zhèn)將錢存到金庫了蚌铜,然后在告訴回音山谷和海盜島那邊可以存入金庫了锨侯。』
豬爸爸停頓了一下冬殃,喝了一口水囚痴,接著道:『上面我們說到,只要我們知道有兩個地方成功記錄下這次交易审葬,我們就可以繼續(xù)存錢了深滚,即使一個地方出現(xiàn)了問題也不會有問題。譬如涣觉,我們知道泥坑小鎮(zhèn)和回音山谷成功記錄了交易痴荐,但海盜島因為一些問題導致了反饋延遲,但還能正常工作官册。然后泥坑小鎮(zhèn)這邊突然出現(xiàn)了問題生兆,不能對外提供服務(wù)了,但我們還是能正常對外提供服務(wù)膝宁,因為我們知道最新的交易信息已經(jīng)被記錄到了回音山谷鸦难,我們從回音山谷這邊就一定能得到正確的金錢總數(shù)根吁。但是,這時候我們?nèi)匀恢挥袃蓚€地方能正常工作了合蔽,所以如果第二個地方出現(xiàn)了問題击敌,我們?nèi)匀徊荒軐ν馓峁┓?wù)了。所以辈末,如果我們要容忍兩個地方出現(xiàn)問題愚争,但系統(tǒng)仍然能夠?qū)ν馓峁┓?wù)映皆,我們就需要——』
『我們就需要在五個地方部署服務(wù)了挤聘,是吧,豬爸爸捅彻∽槿ィ』兔小姐直接插話道。
『是的步淹,非常正確从隆,兔小姐$择桑』豬爸爸由衷的贊嘆道键闺。
『那么我覺得我們先考慮三個地方吧,容忍一個地方不能工作就可以了澈驼,那就在回音山谷和海盜島那邊也建立分部吧辛燥。』
『好的缝其,兔小姐挎塌,不過其實我有點擔心海盜島那邊。内边。榴都。。漠其。嘴高。』
『就這么決定了和屎,豬爸爸』阳惹,兔小姐沒等豬爸爸說完,就直接做了決定眶俩。
好了莹汤,說了這么多,還是回到現(xiàn)實中來吧颠印。上面的例子纲岭,我們可是假設(shè)了金錢能被復制成多份放到不同的金庫里面的抹竹,但現(xiàn)實銀行可不會這么干。為了要設(shè)計一個高可用的系統(tǒng)止潮,單點問題是必須要解決的窃判,畢竟如果這個點出現(xiàn)了問題,整個系統(tǒng)就沒法服務(wù)了喇闸。為了解決這個問題袄琳,我們需要在多個地方部署系統(tǒng),但這樣就會引入另一個問題燃乍,也就是數(shù)據(jù)一致性的問題唆樊。
CAP
這里我們來簡單說說 CAP,也就是一致性刻蟹,可用性和分區(qū)容忍性逗旁。因為在分布式系統(tǒng)里面,P 一定是避免不了的舆瘪,所以我們無非就是選擇 C 或者選擇 A 的問題片效。通常 A 都是能做到 HA,也就是高可用的英古,所以對于需要完全保證數(shù)據(jù)安全的系統(tǒng)淀衣,我們一定會選擇 C。為了保證 C召调,我們在寫入數(shù)據(jù)的時候膨桥,一定會保證至少 quorum 的節(jié)點都成功被寫入了數(shù)據(jù),才會認為這次寫入是成功的某残。 在 Raft 里面国撵,如果一條日志被 quorum 的節(jié)點成功接收,那么我們就可以認為這條日志已經(jīng)被 committed 了玻墅。
通常介牙,我們說的 C,其實就是線性一致性澳厢,也就是我在某個時間寫入了一個值环础,那么這個時間點之后的任何時間,我們讀到的就是這個最新的值剩拢,而不可能是老的线得。在數(shù)據(jù)寫入 quorum 節(jié)點之后,我們的讀取如果也能夠保證在 quorum 節(jié)點讀取徐伐,那么就一定能讀到最新的值贯钩。這個就是 Amazon 的 Dynamo 做法,但這樣就把線性一致性保證的負擔落到了讀取數(shù)據(jù)的客戶端上面。Raft 采用了另一種簡單的做法角雷,我們后續(xù)繼續(xù)說明祸穷。
小結(jié)
好吧,說了這么多勺三,說了這么多雷滚,其實也就提了幾個 Raft 的概念。這里稍微總結(jié)一下吗坚,Raft 使用的是 Log Replication + State Machine 的方式來處理分布式數(shù)據(jù)的一致性問題祈远,這也是現(xiàn)在的通用做法。對于 Raft 來說商源,Log 的 ID 一定是加一單調(diào)遞增的车份,如果一個 Log 被至少 quorum 個節(jié)點接受,我們就可以認為這條日志被 committed 了炊汹,然后就可以應用這條 Log躬充,當 Log 被應用之后逃顶,改 Log 就是 applied 了讨便。后面,我們將開始討論 Raft 的 Leader 了以政。