多Leader備份(Multi-Leader Replication)
這章當(dāng)目前位置我們都在討論單Leader的備份架構(gòu),這個(gè)方案用的很普遍,但也有些問題吊趾。因?yàn)橹挥幸粋€(gè)Leader,所有寫請求都要通過通過這個(gè)Leader,一旦客戶端和Leader連接斷了吧寺,就不能寫了。
一個(gè)最自然的解決方法是多個(gè)節(jié)點(diǎn)都可以接受寫請求酒觅,備份方式是一樣的撮执,每個(gè)節(jié)點(diǎn)處理一個(gè)寫請求后把更新發(fā)送給其他節(jié)點(diǎn)。我們管他叫多Leader配置舷丹。(multi-leader configuration)抒钱。每個(gè)節(jié)點(diǎn)都是Leader,有都是其他Leader的Follower颜凯。
多Leader備份的應(yīng)用場景
如果你只有單個(gè)數(shù)據(jù)中心谋币,那就不要用了,因?yàn)槭找孢h(yuǎn)比付出的代價(jià)小症概。但是在某些場景下這么搞是有意義的蕾额。
多數(shù)據(jù)中心
如果你的服務(wù)是多地部署的,那Leader只能在某一地的機(jī)房彼城,這樣所有的寫請求都要發(fā)到這個(gè)機(jī)房去诅蝶。跨機(jī)房跨地域的請求會很慢募壕。在多Leader的配置下调炬,你可以每個(gè)機(jī)房設(shè)置一個(gè)Leader,在機(jī)房內(nèi)部舱馅,還是運(yùn)用前面講到的Leader和Follower的機(jī)制做備份缰泡。機(jī)房之間,2個(gè)Leader之間彼此把自己的更新發(fā)送給對方代嗤。方法如Figure 5-6
那么我們來對比下在多數(shù)據(jù)中心場景下棘钞,單Leader和多Leader的優(yōu)劣
- 性能
在單Leader下缠借,每個(gè)寫請求都必須翻山越嶺發(fā)到某一個(gè)數(shù)據(jù)中心的Leader去,這個(gè)會有很大的耗時(shí)宜猜,而且這違背了最初設(shè)立多個(gè)數(shù)據(jù)中心的目的泼返。在多Leader下,每次寫請求可以發(fā)送給本地的Leader宝恶,然后在異步的同步給其他的數(shù)據(jù)中心符隙。從用戶側(cè)看來,性能更好垫毙。 - 數(shù)據(jù)中心容錯(cuò)性
單Leader配置下霹疫,一旦Leader所在的數(shù)據(jù)中心掛了,失敗重啟(failover)機(jī)制會在另一個(gè)數(shù)據(jù)中心再選一個(gè)Leader综芥。在多Leader場景下丽蝎,其他的數(shù)據(jù)中心可以繼續(xù)工作,等到異常的數(shù)據(jù)中心恢復(fù)后膀藐,再讓這個(gè)中心備份重新同步 - 網(wǎng)絡(luò)異常容錯(cuò)性
跨數(shù)據(jù)中心的網(wǎng)絡(luò)往往走公網(wǎng)屠阻,這就要比內(nèi)部網(wǎng)絡(luò)可靠性差很多了。單Leader 情況下额各,網(wǎng)絡(luò)問題十分重要国觉,因?yàn)橐坏┚W(wǎng)絡(luò)不好,這個(gè)系統(tǒng)就跪了虾啦。但是多Leader情況下對網(wǎng)絡(luò)的容錯(cuò)性就好很多麻诀。因?yàn)榭蛻舳丝梢韵劝l(fā)個(gè)本地的Leader然后異步的在Leader之間同步。
有些數(shù)據(jù)庫默認(rèn)支持多Leader傲醉,但是往往都是外部實(shí)現(xiàn)的蝇闭。例如MySQL的Tungsten Replicator, PostgreSQL 的BDR硬毕,Oracle的GoldenGate呻引。
雖說多Leader的配置很多數(shù)據(jù)庫都作為一個(gè)額外特征,但是他和很多其他的數(shù)據(jù)庫特征在一起可能會有很奇怪的問題吐咳。比如自增鍵逻悠, 觸發(fā)器,完整性約束可能都是個(gè)問題韭脊。所以一般來說多Leader很多時(shí)候都是個(gè)危險(xiǎn)的東西蹂风,能不用就不用。
離線操作
如果你的應(yīng)用需要即使斷網(wǎng)也能正常工作乾蓬,那多Leader就很好用了。比如你有一個(gè)日歷的app慎恒, 無論身處何地任内,網(wǎng)絡(luò)如何撵渡,都要能夠正常查看你的會議(讀請求),加入新的會議(寫請求)死嗦。這就要求當(dāng)你做任何更新的時(shí)候趋距,在設(shè)備下次連上網(wǎng)絡(luò)的時(shí)候,需要把這些更新發(fā)送給服務(wù)器和其他的設(shè)備越除。
這種情況下节腐,每個(gè)設(shè)備有一個(gè)自己的本地?cái)?shù)據(jù)庫作為Leader,同時(shí)還有一個(gè)異步的多Leader同步流程把你的數(shù)據(jù)正確備份到所有設(shè)備去摘盆。備份節(jié)點(diǎn)的落后可能有幾個(gè)小時(shí)甚至幾天翼雀。
從架構(gòu)角度講,這跟多個(gè)數(shù)據(jù)中心間進(jìn)行多Leader備份沒什么區(qū)別孩擂。每個(gè)設(shè)備是一個(gè)數(shù)據(jù)中心狼渊,網(wǎng)絡(luò)連接非常不可靠。在日歷同步的豐富歷史中类垦,多Leader備份機(jī)制是表現(xiàn)不錯(cuò)的一個(gè)狈邑。
協(xié)同編輯
實(shí)時(shí)協(xié)同編輯應(yīng)用允許多個(gè)人同時(shí)編輯一個(gè)文檔。比如Etherpad和Google Docs蚤认。我們一般不把這件事情當(dāng)做一個(gè)數(shù)據(jù)庫同步的問題米苹,但是當(dāng)出現(xiàn)離線編輯的時(shí)候,就跟上面的問題比較像了砰琢。當(dāng)一個(gè)用戶修改文檔時(shí)蘸嘶,首先把更新發(fā)送給本地的備份,然后異步的把更新發(fā)送給server和其他用戶氯析。
如果你要保證永遠(yuǎn)沒有編輯沖突亏较,那應(yīng)用就需要在一個(gè)用戶編輯前加鎖。當(dāng)一個(gè)用戶想要修改文檔時(shí)掩缓,他必須等前一個(gè)人提交他們的修改并且釋放鎖雪情。這種協(xié)同模型就跟單Leader備份加事務(wù)的方式很像了。但是為了更快你辣,你可能希望每次更新更新的內(nèi)容很小也不要鎖巡通。但這就會帶來多Leader備份的各種問題,比如沖突解決舍哄。
應(yīng)對寫入沖突
多Leader架構(gòu)最大的問題就是寫入沖突宴凉,所以我們需要一個(gè)重提解決機(jī)制。舉個(gè)例子表悬,假設(shè)一個(gè)網(wǎng)頁被兩個(gè)人同時(shí)編輯弥锄,用戶1把標(biāo)題從A改成B,用戶2把標(biāo)題從A改成C,兩個(gè)用戶的成功把自己的更新提交到了本地的Leader籽暇。但是當(dāng)更新異步的在節(jié)點(diǎn)中同步時(shí)温治,這就有沖突了。這個(gè)問題在單Leader的數(shù)據(jù)庫中是不存在的戒悠。
同步方案 vs 異步?jīng)_突檢測
在單Leader時(shí)熬荆,在第一個(gè)提成功提交之前,第二個(gè)請求會一直阻塞住绸狐,或者直接失敗了卤恳。但是在多Leader時(shí),兩個(gè)寫入都會成功寒矿,這種沖突會在未來的某個(gè)時(shí)間點(diǎn)異步備份時(shí)被發(fā)現(xiàn)突琳。這個(gè)時(shí)候讓用戶來解決沖突已經(jīng)太晚了。從道理上來說劫窒,你可以在線的檢測沖突本今,也就是說只有在成功把更新數(shù)據(jù)發(fā)送到所有其他的節(jié)點(diǎn)后才告訴用戶更新成功了。但是這就是去了多Leader的優(yōu)勢主巍,你沒有辦法再允許每個(gè)備份節(jié)點(diǎn)獨(dú)立的接受寫請求了冠息。如果要同步的沖突檢測,還不如就搞一個(gè)單Leader備份框架呢孕索。
避免沖突
最簡單的處理沖突的方法就是避免沖突逛艰。如果應(yīng)用能夠保證對一條數(shù)據(jù)的所有寫都發(fā)給同一個(gè)Leader,就不會有沖突了搞旭。因?yàn)榇蟛糠侄郘eader備份的實(shí)現(xiàn)對于沖突的處理能力都很弱散怖,避免沖突往往是一個(gè)推薦的方法。
舉個(gè)例子肄渗,如果一個(gè)用戶修改他自己的數(shù)據(jù)镇眷,那你可以將特定用戶的請求永遠(yuǎn)發(fā)給相同的數(shù)據(jù)中心,用這個(gè)數(shù)據(jù)中心的Leader進(jìn)行讀寫翎嫡。不同的用戶有不同的數(shù)據(jù)中心(比如基于他們的地理位置選最近的數(shù)據(jù)中心)欠动,從單個(gè)用戶的角度來看,這就變成了一個(gè)單Leader的架構(gòu)惑申。
當(dāng)時(shí)有時(shí)候你可能會需要修改用戶對應(yīng)的數(shù)據(jù)中心具伍,可能因?yàn)槟骋粋€(gè)數(shù)據(jù)中掛了,也可能是因?yàn)橛脩舻乩砦恢米兞巳ν眨阋阉频疆?dāng)前離他最近的數(shù)據(jù)中心人芽。這種情況下,又會有沖突出來绩脆,因?yàn)樵谀扯螘r(shí)間內(nèi)萤厅,同一個(gè)用戶會寫兩個(gè)Leader橄抹。
一致性收斂
單Leader的架構(gòu)下,寫請求是有順序的祈坠,多個(gè)更新同時(shí)發(fā)生時(shí)害碾,最后一個(gè)寫確定了這個(gè)字段的最終值。但是多Leader下赦拘,寫入就沒有一個(gè)確定順序了。在Figure 5-7中芬沉,leader 1的標(biāo)題先改成B躺同,后又變成C,Leader 2卻是先是C丸逸,后是B蹋艺。這兩個(gè)順序沒有誰比誰更對的問題。
如果每個(gè)備份節(jié)點(diǎn)只根據(jù)他收到請求的順序更新自己的數(shù)據(jù)黄刚,那最終就會使一個(gè)不一致狀態(tài)捎谨。leader 1的最終結(jié)果是C, leader 2的最終結(jié)果是B,這是不能接受的憔维。我們要求所有備份最終的結(jié)果必須是一樣的涛救,這也就要求數(shù)據(jù)庫必須用一種收斂的方法來處理沖突。這就有很多方法了
- 給每個(gè)寫一個(gè)唯一id(時(shí)間戳业扒,隨機(jī)數(shù)检吆,UUID,key/value的hash值)程储,選id最大的寫請求作為贏家蹭沛,把其他的都丟掉。如果用時(shí)間戳作為id章鲤,就加作最后寫生效(last write wins, LWW).LWW 我們這章最后還會講摊灭。
- 給每個(gè)備份節(jié)點(diǎn)一個(gè)唯一id,沖突時(shí)優(yōu)先選用id大的節(jié)點(diǎn)的數(shù)據(jù),但這會造成數(shù)據(jù)丟失败徊。
- 合并值帚呼, 比如把他們按照字母序合并,F(xiàn)igure 5-7的結(jié)果就會變成B/C
- 把沖突記成一種特殊的數(shù)據(jù)格式然后交由應(yīng)用后期處理集嵌。
自定義沖突解決邏輯
其實(shí)最合適解決沖突的方法依賴于應(yīng)用萝挤,多Leader備份架構(gòu)很多都允許你自定義沖突解決代碼。這段代碼可能是在寫入或者讀取時(shí)執(zhí)行根欧。
- 寫入執(zhí)行
當(dāng)數(shù)據(jù)庫系統(tǒng)在備份日志中檢測到?jīng)_突怜珍,他會調(diào)用沖突處理器。這種處理器一般無法提示用戶凤粗,因?yàn)樗窃诤笈_進(jìn)程中執(zhí)行的酥泛,所以必須速度很快今豆。Bucardo就是這么搞的。 - 讀取執(zhí)行
當(dāng)發(fā)現(xiàn)沖突時(shí)柔袁,所有沖突的現(xiàn)場都被保留下來呆躲。當(dāng)這條數(shù)據(jù)下次被讀取的時(shí)候,這條數(shù)據(jù)的多個(gè)版本都會返回給應(yīng)用捶索。應(yīng)用把這個(gè)沖突數(shù)據(jù)提示給用戶插掂,然后由用戶手動(dòng)解決沖突,再把最終結(jié)果寫到數(shù)據(jù)庫中腥例。CouchDB 就是這么搞的辅甥。
有一點(diǎn)注意,沖突解決往往是針對一條數(shù)據(jù)燎竖,而不是一個(gè)事務(wù)璃弄。所以雖然你的一個(gè)事務(wù)可能一次性包含多個(gè)寫請求,但是他們還是在沖突解決中分開處理的构回,也就說可能會不一致夏块。
什么是沖突?
有時(shí)候沖突其實(shí)很顯然纤掸,就好像Figure 5-7一樣脐供,兩個(gè)人同時(shí)修改同一條數(shù)據(jù)的同一個(gè)字段,毫無疑問這是沖突茁肠。
但是其他的沖突就很難檢測了患民。比如想象你有一個(gè)會議室,這個(gè)會議室在任何時(shí)刻都只能有一個(gè)人預(yù)訂垦梆。在這種場景下匹颤,當(dāng)一個(gè)會議室在某個(gè)時(shí)間點(diǎn)有超過1個(gè)人預(yù)訂時(shí),這就是一個(gè)沖突了托猩。(但是他和Figure 5-7的區(qū)別是他不再是同一個(gè)字段的問題印蓖,而是一個(gè)時(shí)間段的問題,就好像2個(gè)人一個(gè)人預(yù)約7-8京腥,一個(gè)人預(yù)約9-10就不沖突赦肃,但是一個(gè)人預(yù)約7-10,一個(gè)人預(yù)約9-11就沖突了公浪。)即使應(yīng)用層在預(yù)訂之前檢查會議室在這個(gè)時(shí)間段是否可用他宛,也不一定能避免沖突,因?yàn)檎埱罂赡馨l(fā)給多個(gè)Leader導(dǎo)致沖突欠气。
多Leader備份拓?fù)鋱D
備份拓?fù)洌?em>replication topology)是指更新在節(jié)點(diǎn)間傳播的路徑厅各,如果你像Figure 5-7一樣只有2個(gè)節(jié)點(diǎn),那就很簡單了预柒。但是如果你有多個(gè)節(jié)點(diǎn)队塘,就可能有多鐘拓?fù)潢P(guān)系袁梗,例如Figure 5-8
最常見的拓?fù)潢P(guān)系是全相連,如(c)憔古。但是其他的拓?fù)浣Y(jié)構(gòu)也有人用遮怜,比如MySQL默認(rèn)是有環(huán)裝拓?fù)洌?a), 每個(gè)節(jié)點(diǎn)從上個(gè)節(jié)點(diǎn)接收更新請求鸿市,把收到的更新加上自己的更新發(fā)給下一個(gè)節(jié)點(diǎn)锯梁。(b)中的星型結(jié)構(gòu)也很流行,一個(gè)節(jié)點(diǎn)負(fù)責(zé)總控分發(fā)焰情,其他節(jié)點(diǎn)只跟總控通信涝桅。
在環(huán)型和星型拓?fù)渲小R粋€(gè)寫請求要傳遞多個(gè)節(jié)點(diǎn)才能實(shí)現(xiàn)所有節(jié)點(diǎn)都備份完成烙样。因此一個(gè)節(jié)點(diǎn)要把他收到的內(nèi)容進(jìn)行轉(zhuǎn)發(fā)。為了防止無限循環(huán)蕊肥,每個(gè)節(jié)點(diǎn)都會有一個(gè)唯一的編號谒获,當(dāng)一個(gè)節(jié)點(diǎn)處理完一條備份日志后,會給這個(gè)日志當(dāng)上這個(gè)節(jié)點(diǎn)的id壁却。當(dāng)再次收到這條日志發(fā)現(xiàn)已經(jīng)打上了自己的id后批狱,節(jié)點(diǎn)直接把這條日志丟掉。
環(huán)型和星型的問題是一旦有一個(gè)節(jié)點(diǎn)掛了展东,那整個(gè)系統(tǒng)的消息備份都會受影響赔硫。這就要求系統(tǒng)必須在一個(gè)節(jié)點(diǎn)掛掉后重新配置通信的節(jié)點(diǎn),去掉失效節(jié)點(diǎn)盐肃,但是這個(gè)工作往往依賴于人工介入爪膊。這點(diǎn)就是全連接的拓?fù)浣Y(jié)構(gòu)的優(yōu)勢所在,他有著更好的容錯(cuò)性砸王,因?yàn)橐粋€(gè)節(jié)點(diǎn)掛了推盛,其他節(jié)點(diǎn)還是能從別的通路中獲取到所有的更新消息。
不要以為全連接就沒有問題谦铃,在網(wǎng)絡(luò)環(huán)境中耘成,有些鏈路會比其他的更快,這可能會導(dǎo)致有些更新被莫名其妙的覆蓋了驹闰。就像Figure 5-9一樣瘪菌。客戶端A 通過Leader1插入了一條數(shù)據(jù), 客戶端B通過Leader3更新這條數(shù)據(jù)嘹朗,但是Leader 2收到的日志順序就跟實(shí)際不一樣了师妙,這就是前面講到的邏輯前后順序的問題。這種情況下骡显,簡單的用時(shí)間戳都不能解決問題疆栏,因?yàn)闀r(shí)間戳也不一定能保證時(shí)序的嚴(yán)格一直曾掂,這個(gè)第8章講。
為了解決這個(gè)問題壁顶,用到了一個(gè)叫版本列表(version vectors)的技術(shù)珠洗,這個(gè)也后面再講了。但是殘酷的事實(shí)是沖突解決在很多多Leader備份的系統(tǒng)中實(shí)現(xiàn)的都很差若专。如果你要用一個(gè)而類似這樣的系統(tǒng)许蓖,一定是小心這些問題,仔細(xì)讀文檔调衰。充分測試膊爪,確保他的實(shí)際工作結(jié)果跟你預(yù)想的一樣。