進(jìn)入到第五章了星瘾,來到了分布式系統(tǒng)之中最核心與復(fù)雜的內(nèi)容:副本與一致性。通常分布式系統(tǒng)會通過網(wǎng)絡(luò)連接的多臺機(jī)器上保存相同數(shù)據(jù)的副本惧辈,所以在本篇之中琳状,我們來展開看看如何去管理和維護(hù)這些副本,以及這個(gè)過程之中會遇到的各種問題盒齿。
1.副本
在數(shù)據(jù)系統(tǒng)之中念逞,我們通常會有這樣幾個(gè)原因來使用副本技術(shù):
- 保持地理位置接近用戶困食,從而減少延遲(如:Cache,CDN技術(shù))
- 提高系統(tǒng)的可用性和魯棒性翎承,即使系統(tǒng)中的某些部分已經(jīng)失效了硕盹,仍然可以對外提供服務(wù)。(如:GFS三副本的設(shè)計(jì))
- 通過擴(kuò)展性來提供讀查詢叨咖,從而增加讀取吞吐量瘩例。(如:ZooKeeper之中的Observer)
首先,如果副本的數(shù)據(jù)不隨時(shí)間變化甸各,那么副本的管理是比較簡單的:只需要將數(shù)據(jù)復(fù)制到每個(gè)節(jié)點(diǎn)一次垛贤,就OK了。副本管理真正的困難在于對副本數(shù)據(jù)的修改趣倾,這會涉及到很多瑣碎的問題聘惦。其次,副本復(fù)制時(shí)要考慮許多權(quán)衡儒恋,使用同步還是異步復(fù)制善绎,以及如何處理失效的副本?接下來我們來一一探討這個(gè)問題碧浊。
2.Leader-Follower機(jī)制
如何保障多個(gè)副本在不同節(jié)點(diǎn)上的一致性一直分布式系統(tǒng)之中的一個(gè)核心問題涂邀。分布式系統(tǒng)在寫入數(shù)據(jù)時(shí),需要由每個(gè)副本進(jìn)行處理箱锐;否則比勉,副本將不再包含相同的數(shù)據(jù)。Leader-Follower是一種常見的機(jī)制驹止,我們來梳理一下它的原理:
- 一個(gè)節(jié)點(diǎn)上的副本被指定為Leader浩聋。當(dāng)客戶端需要向系統(tǒng)寫入數(shù)據(jù)時(shí),必須將寫入請求發(fā)送給Leader臊恋,由Leader首先將新數(shù)據(jù)寫入本地存儲的副本衣洁。
- 管理其他副本的節(jié)點(diǎn)稱為Follower。每當(dāng)Leader將新數(shù)據(jù)寫入本地存儲d的副本時(shí)抖仅,也會將數(shù)據(jù)更改寫入日志之中坊夫。每個(gè)Follower會從Leader那里獲取修改日志,并相應(yīng)地更新數(shù)據(jù)到的本地副本之中撤卢,這樣环凿,所有的在Follower上副本的修改順序會和Leader保持相同的順序。
- 當(dāng)客戶端需要從系統(tǒng)之中讀取數(shù)據(jù)時(shí)放吩,它可以查詢Leader或其他Follower智听。(注:Follower與Leader之中的數(shù)據(jù)存在延遲,無法保證強(qiáng)一致性)寫入請求只能由Leader來響應(yīng),或是由Follower轉(zhuǎn)發(fā)給Leader到推。
許多關(guān)系數(shù)據(jù)庫在同步副本時(shí)使用這樣的機(jī)制考赛,如PostgreSQL,MySQL莉测,Oracle Data Guard 和SQL Server颜骤。同時(shí)許多非關(guān)系型數(shù)據(jù)庫與分布式消息隊(duì)列也采用這樣的機(jī)制,包括MongoDB悔雹,Rethinkd复哆,Kafka,RabbitMQ腌零。
2.1 同步與異步復(fù)制
在副本進(jìn)行主從復(fù)制時(shí)一個(gè)重要細(xì)節(jié)是復(fù)制是同步還是異步發(fā)生的梯找?(在關(guān)系數(shù)據(jù)庫中,這往往是一個(gè)可配置的選項(xiàng)益涧。在其他系統(tǒng)之中锈锤,如Ceph,是系統(tǒng)默認(rèn)的)
由上圖可知闲询,同步復(fù)制有相當(dāng)大的延遲久免,而異步復(fù)制的響應(yīng)相當(dāng)快速。但是異步復(fù)制卻不能保證完成所需要多長時(shí)間扭弧。有些情況下阎姥,F(xiàn)ollower的數(shù)據(jù)可能比Leader上的數(shù)據(jù)落后幾分鐘或更多。如:節(jié)點(diǎn)之間存在網(wǎng)絡(luò)問題或節(jié)點(diǎn)的故障恢復(fù)鸽捻。如果Leader失敗且不可恢復(fù)呼巴,則尚未復(fù)制到Follower的任何寫操作都將丟失。
而同步復(fù)制的優(yōu)點(diǎn)是保證了Follower與Leader之間的副本一致性御蒲,一旦任意一個(gè)Leader失效了衣赶,任何一個(gè)Follower的數(shù)據(jù)都與Leader相同。但是同步復(fù)制一旦出現(xiàn)網(wǎng)絡(luò)或節(jié)點(diǎn)的故障厚满,會導(dǎo)致無法處理寫入府瞄。Leader必須阻止所有寫入并等待Follow上的副本再次可用。如果所有的Follower都是同步復(fù)制碘箍,那么任何一個(gè)節(jié)點(diǎn)的中斷都會導(dǎo)致整個(gè)系統(tǒng)癱瘓遵馆。在實(shí)際運(yùn)用之中,如果在數(shù)據(jù)庫上啟用同步復(fù)制丰榴,通常其中一個(gè)副本是同步復(fù)制的团搞,而另一個(gè)是異步復(fù)制的。如果同步的副本變得不可用或十分緩慢多艇,可以將同步操作切換到另一個(gè)異步副本之中。這樣保證了至少兩個(gè)節(jié)點(diǎn)上有一個(gè)數(shù)據(jù)的最新副本:Leader和一個(gè)同步Follower像吻。這種配置稱之為半同步峻黍。(鏈?zhǔn)綇?fù)制也是類似于半同步的一種復(fù)制機(jī)制复隆,不丟失數(shù)據(jù)但仍能提供良好性能和可用性的復(fù)制方法。)
2.2 添加新的Follower
有時(shí)我們需要添加新的Follower來增加副本的數(shù)量姆涩,或者替換失敗的節(jié)點(diǎn)挽拂。此時(shí)就需要確保新的Follower擁有一個(gè)正確的副本的數(shù)據(jù)。僅僅將數(shù)據(jù)文件從一個(gè)節(jié)點(diǎn)復(fù)制到另一個(gè)節(jié)點(diǎn)通常是不夠的:客戶端不停向系統(tǒng)寫入數(shù)據(jù)骨饿,所以數(shù)據(jù)副本總是處于不斷變化的狀態(tài)亏栈。這里可以簡單地通過鎖定系統(tǒng),使其拒絕客戶端的寫請求來使各個(gè)副本上保持一致宏赘,但這樣會大大降低系統(tǒng)的可用性绒北。所以我們需要一個(gè)不停機(jī)的方式來添加新的Follower:
1.在某個(gè)時(shí)間點(diǎn)對Leader的副本進(jìn)行快照,并且將快照復(fù)制到新加入的Follower節(jié)點(diǎn)察署。
2 .Follower連接到Leader闷游,并向Leader請求快照之后所有的數(shù)據(jù)更改。通常是Leader節(jié)點(diǎn)的日志序列號贴汪。
- 當(dāng)Follower處理完快照之后的數(shù)據(jù)更改之后脐往,它就可以正常處理來自Leader的數(shù)據(jù)更改了。
2.3 節(jié)點(diǎn)故障
在分布式系統(tǒng)之中扳埂,任何節(jié)點(diǎn)都可能出現(xiàn)故障业簿,而能夠在不停機(jī)的情況下重新啟動(dòng)單個(gè)節(jié)點(diǎn)是操作和維護(hù)是十分必要的。盡管每個(gè)節(jié)點(diǎn)故障阳懂,但我們需要讓一個(gè)節(jié)點(diǎn)停機(jī)的影響盡可能小梅尤。
- Follower故障
在Follower的本地磁盤上,都保存著從Leader收到的數(shù)據(jù)更改的日志希太。當(dāng)一個(gè)Follower崩潰并重新啟動(dòng)克饶,或者Leader與Follower之間的網(wǎng)絡(luò)暫時(shí)中斷。Follower可從它的日志找到故障發(fā)生之前處理的最后一個(gè)事務(wù)誊辉,然后連接到Leader并請求在Follower斷開連接的時(shí)候發(fā)生的所有數(shù)據(jù)變化矾湃。(這個(gè)流程和添加新的Follower其實(shí)是同樣的思路)
- Leader故障
在處理Leader的失敗時(shí)顯然會更為棘手:其中一個(gè)Follower需要被提升為新的Leader,客戶端也需要識別并且將后續(xù)的請求發(fā)送給新的Leader堕澄,而其他的Follower則需要開始在新Leader之下工作邀跃。處理Leader故障通常是如下的流程:
1、確認(rèn)Leader失效蛙紫。絕大多數(shù)系統(tǒng)使用超時(shí)機(jī)制:如果一個(gè)節(jié)點(diǎn)不響應(yīng)一段時(shí)間拍屑,例如,30秒坑傅,它被認(rèn)為是失效了僵驰。(如果是中心化的系統(tǒng)可以采用Lease機(jī)制。筆者在碩士生階段對Cassandra數(shù)據(jù)庫有過系統(tǒng)的調(diào)研,在Cassandra中采用了由日本學(xué)者Naohiro Hayashibara提出的《The Phi Accrual Failure Detector》失敗探測算法蒜茴,通過多維度累積量來判斷節(jié)點(diǎn)是否失效星爪,不失為一個(gè)好的解決方案,十分適合類P2P架構(gòu)的分布式系統(tǒng))
2粉私、選取新的Leader顽腾。在中心化架構(gòu)之中,如HDFS诺核,新的Leader可以用中心化節(jié)點(diǎn)指定抄肖。而在非中心化的架構(gòu)之中,則可以通過選舉過程來完成窖杀,分布式系統(tǒng)之中的選舉協(xié)議有很多:2PC漓摩,3PC,Paxos陈瘦,Raft等等幌甘。
3、調(diào)整系統(tǒng)配置以使用新的Leader痊项。如果舊的Leader回歸到集群锅风,它可能仍然認(rèn)為自己是Leader,這時(shí)需要確保舊的Leader成為Follower并承認(rèn)新的Leader鞍泉。
如果是異步復(fù)制的場景皱埠,新的Leader可能舊的Leader之前的完整的寫入信息。最常見的解決方案是丟棄舊Leader之前寫入多于新Leader的信息丟棄咖驮,但是這顯然違反了數(shù)據(jù)系統(tǒng)寫入持久性的要求边器。
在某些故障場景中,可能會出現(xiàn)兩個(gè)節(jié)點(diǎn)都認(rèn)為他們是Leader托修,這種情況被稱為腦裂忘巧。此時(shí)兩個(gè)Leader都會接受寫請求搅裙,數(shù)據(jù)很可能會出現(xiàn)丟失或損壞凰盔。
什么時(shí)候進(jìn)行故障切換也是一個(gè)值得探討的問題:較長的超時(shí)時(shí)間意味著在Leader失效的情況下恢復(fù)時(shí)間更長。然而照瘾,如果時(shí)間太短涩拙,可能會有不必要的故障轉(zhuǎn)移际长。例如,臨時(shí)負(fù)載高峰時(shí)刻可能導(dǎo)致節(jié)點(diǎn)的響應(yīng)時(shí)間增加到超時(shí)兴泥,那么不必要的故障轉(zhuǎn)移會使情況變得更糟工育,而不是更好。為此搓彻,一些運(yùn)營團(tuán)隊(duì)更愿意執(zhí)行手動(dòng)的故障轉(zhuǎn)移如绸,即使系統(tǒng)本身支持自動(dòng)的故障轉(zhuǎn)移嘱朽。
3. 日志的復(fù)制
日志在副本的一致性之中是至關(guān)重要的,所以我們接下來簡要的梳理一下日志復(fù)制可用的方法:
Statement-Based復(fù)制
在最簡單的情況下怔接,Leader將每個(gè)寫請求通過日志的形式發(fā)送給Follower燥翅。每個(gè)Follower解析和執(zhí)行對應(yīng)的操作語句,雖然這聽起來很合理蜕提,但是實(shí)際操作中會存在一些坑:
(1) 非確定性函數(shù),如now()獲得當(dāng)前的日期和時(shí)間或rand()得到一個(gè)隨機(jī)數(shù)靶端,這樣會導(dǎo)致副本之間的不一致谎势。(這里可以轉(zhuǎn)換思維,用一個(gè)確定的修改值杨名,來替換不確定性的函數(shù)調(diào)用)
(2) 如果使用一個(gè)自動(dòng)遞增的列脏榆,或如果他們依賴于數(shù)據(jù)庫中的現(xiàn)有數(shù)據(jù)(例如,更新…在<條件>)台谍,他們必須執(zhí)行完全相同的順序在每個(gè)副本须喂,否則也會產(chǎn)生不一致性。(異步轉(zhuǎn)發(fā)趁蕊,亂序到達(dá)坞生。這個(gè)可以通過操作序列號等強(qiáng)制要求進(jìn)行規(guī)避。)
(3) 有副作用的語句(例如觸發(fā)器掷伙、存儲過程是己、用戶定義函數(shù))可能會導(dǎo)致每個(gè)副本上出現(xiàn)不同的副作用。Write-ahead日志復(fù)制
日志是一個(gè)只包含所有寫入操作的字節(jié)序列任柜。我們可以使用完全相同的日志來在另一個(gè)節(jié)點(diǎn)上構(gòu)建一個(gè)副本卒废。Leader將日志寫入磁盤之后,將它通過網(wǎng)絡(luò)發(fā)送給Follower宙地。當(dāng)Follower處理這個(gè)日志時(shí)摔认,它構(gòu)建了一個(gè)與Leader完全相同的數(shù)據(jù)結(jié)構(gòu)的副本。這種方式的缺點(diǎn)是:日志在非常低的級別上描述數(shù)據(jù)宅粥。這使得數(shù)據(jù)拷貝與存儲引擎緊密耦合参袱。Row-based日志復(fù)制
Row-based與Write-ahead的方法類似,但是它允許復(fù)制日志與存儲引擎內(nèi)部分離粹胯。這種日志稱為邏輯日志蓖柔,邏輯日志通常是描述在一個(gè)行的粒度上記錄寫入操作:
對于插入的行,日志包含所有列的新值风纠。
對于已刪除的行况鸣,日志包含足夠的信息以唯一地標(biāo)識刪除的行。(主鍵)
對于更新的行竹观,日志包含足夠的信息以唯一地標(biāo)識更新的行镐捧,以及所有列的新值潜索。
由于邏輯日志與存儲引擎內(nèi)部分離,因此可以更容易地保持向后兼容懂酱,從而允許Leader與Follower運(yùn)行不同版本的數(shù)據(jù)系統(tǒng)竹习,甚至是不同的存儲引擎。同時(shí)列牺,邏輯日志格式對外部應(yīng)用程序也更容易解析整陌。可以將邏輯日志的內(nèi)容發(fā)送到外部系統(tǒng)(如用于離線分析的數(shù)據(jù)倉庫)瞎领,或者用于構(gòu)建自定義索引和緩存泌辫。
4. 復(fù)制延遲
副本可以增加系統(tǒng)的可伸縮性(處理比單個(gè)機(jī)器處理更多的請求)和降低延遲(將副本放置在離用戶更近的地方)。寫入操作必須通過Leader副本九默,但是只讀查詢可以在任何副本上進(jìn)行震放。 對于一次寫入,多次讀取的應(yīng)用來說驼修,采用讀擴(kuò)展架構(gòu)是十分合理的殿遂。但是由于上文提及的原因,我們通常不會采用同步復(fù)制的方式乙各。這將導(dǎo)致數(shù)據(jù)出現(xiàn)明顯的不一致性:如果您同時(shí)對Leader和Follwer執(zhí)行相同的查詢墨礁,可能會得到不同的結(jié)果,因?yàn)椴⒉皇撬械膶懭雽?shí)時(shí)在Follower上反饋觅丰。這種不一致性僅僅是暫時(shí)狀態(tài)饵溅,所以這種情況被稱為最終一致性。
對于這種情況我們應(yīng)該這么去處理和理解妇萄,我們下回分解~~~(第五章的內(nèi)容炒雞多蜕企,接下來會通過多篇讀書筆記來給大家梳理,講解冠句,下一篇再見~~)