為什么單體不用考慮一致性而分布式需要?
??目錄
1 背景
1.1 數(shù)據(jù)存儲(chǔ)讀取
2 數(shù)據(jù)存儲(chǔ)一致性
2.1 主從架構(gòu)
2.2 主主架構(gòu)
2.3 無(wú)主架構(gòu)
3 總結(jié)
網(wǎng)上談?wù)摂?shù)據(jù)一致性的文章不少,大多從算法的角度切入,本文作者選擇了從服務(wù)架構(gòu)的角度切入,詳細(xì)拆解了主從架構(gòu)、主主架構(gòu)、無(wú)主架構(gòu)三種架構(gòu)模式下,數(shù)據(jù)一致性的難點(diǎn)與解決方案。
01
隨著軟件規(guī)模的不斷擴(kuò)大,服務(wù)也不得不從單體應(yīng)用走向分布式部署,通過(guò)分布式應(yīng)用的部署使我們極大程度地提高了系統(tǒng)的并發(fā)量,突破了 CPU 計(jì)算的瓶頸,也突破了磁盤存儲(chǔ)的限制,看似我們使用了分布式技術(shù)就能夠解決所有問(wèn)題。但是任何技術(shù)都不是銀彈。
解決問(wèn)題的同時(shí)也帶來(lái)相應(yīng)的“副作用”——數(shù)據(jù)一致性問(wèn)題。在分布式場(chǎng)景下,數(shù)據(jù)的處理存儲(chǔ)讀取由原先的單節(jié)點(diǎn)拓展成為了多節(jié)點(diǎn),由于服務(wù)對(duì)外表現(xiàn)需要一致,意味著需要多節(jié)點(diǎn)協(xié)同數(shù)據(jù)保持一致,但是達(dá)到數(shù)據(jù)一致并不是一件簡(jiǎn)單的事情,后面其實(shí)需要做很多的努力。
下面我們來(lái)看下分布式場(chǎng)景下是如何影響數(shù)據(jù)一致性的。
數(shù)據(jù)庫(kù)是我們?nèi)粘P枰蚪坏赖淖畛R姷闹虚g件,承載著數(shù)據(jù)存儲(chǔ)的任務(wù)。但是在分布式場(chǎng)景下,這確實(shí)會(huì)帶來(lái)不小的挑戰(zhàn)。如圖所示,當(dāng)小明給自己的女朋友小紅情人節(jié)發(fā)了520的紅包,但是此時(shí)女朋友卻沒(méi)有收到感到非常生氣,小明也很委屈,明明剛發(fā)的紅包去哪里呢?
其實(shí)是因?yàn)樾∶靼l(fā)紅包之后寫入的數(shù)據(jù)節(jié)點(diǎn)和小紅讀取的數(shù)據(jù)節(jié)點(diǎn)不一致,并且小明發(fā)送的紅包的數(shù)據(jù)節(jié)點(diǎn)并沒(méi)有及時(shí)同步到小紅讀取的數(shù)據(jù)節(jié)點(diǎn)。
下面我們從數(shù)據(jù)存儲(chǔ)讀取的角度分析可能帶來(lái)的問(wèn)題以及解決方法。
02
在討論這個(gè)問(wèn)題我們先來(lái)想下,分布式環(huán)境下導(dǎo)致存儲(chǔ)不穩(wěn)定的本質(zhì)原因其實(shí)是“無(wú)法讀到正確的寫”。那么為了解決“讀正確的寫”這個(gè)問(wèn)題,就很有必要討論下分布式環(huán)境下的存儲(chǔ)節(jié)點(diǎn)之間是如何相互工作的,因?yàn)橹挥姓莆樟苏_的數(shù)據(jù)流向,才能更好地剖析問(wèn)題。
我們?cè)賮?lái)思考一個(gè)問(wèn)題,為什么單體應(yīng)用不存在這個(gè)問(wèn)題?這是因?yàn)閱误w情況下數(shù)據(jù)的存儲(chǔ)和讀取的節(jié)點(diǎn)是唯一的,但是在分布式的場(chǎng)景下,情況就不一樣了,多個(gè)節(jié)點(diǎn)之間就涉及到數(shù)據(jù)同步問(wèn)題,而數(shù)據(jù)同步問(wèn)題又和服務(wù)的部署架構(gòu)有關(guān)系,因此我們從服務(wù)架構(gòu)入手來(lái)分類討論。

主從架構(gòu)是數(shù)據(jù)庫(kù)常見的部署架構(gòu),即主節(jié)點(diǎn)負(fù)責(zé)寫入數(shù)據(jù),從節(jié)點(diǎn)來(lái)分擔(dān)讀流量,在這種架構(gòu)模式下,主從間的數(shù)據(jù)同步存在三種方式:
同步復(fù)制;
半同步復(fù)制;
異步復(fù)制。
同步復(fù)制指的是數(shù)據(jù)在主節(jié)點(diǎn)寫入成功之后,還需要確保各個(gè)從節(jié)點(diǎn)同步數(shù)據(jù)成功之后才向上游返回成功 ack。一般用于對(duì)數(shù)據(jù)可靠性要求較高的場(chǎng)景,如金融級(jí)的數(shù)據(jù)庫(kù) tdsql,生產(chǎn)環(huán)境一般使用一主兩備強(qiáng)同步的方式,這種同步復(fù)制的方式存在相應(yīng)的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):主從間的數(shù)據(jù)同步強(qiáng)一致,數(shù)據(jù)可靠性高。
缺點(diǎn):數(shù)據(jù)同步環(huán)境對(duì)網(wǎng)絡(luò)的延時(shí)要求較高,并且從節(jié)點(diǎn)越多,寫入的效率會(huì)越低,受網(wǎng)絡(luò)波動(dòng)影響越大,相比于半同步和異步復(fù)制來(lái)說(shuō),數(shù)據(jù)復(fù)制效率較低。
因此在這種同步模式下,數(shù)據(jù)寫入和讀取是對(duì)等的,因?yàn)橐坏┗貜?fù)了成功的 ack 之后,代表主從間的數(shù)據(jù)保持一致,數(shù)據(jù)穩(wěn)定性高。
半同步復(fù)制指的是數(shù)據(jù)在主節(jié)點(diǎn)寫入成功之后,從節(jié)點(diǎn)同步只要同步成功一個(gè)即返回成功 ack。一般用于對(duì)數(shù)據(jù)寫入效率要求較高的場(chǎng)景,據(jù)作者了解,shopee 的 db 同步主要采用這種半同步復(fù)制的方式,這種同步復(fù)制方式存在相應(yīng)的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):主從同步效率高,對(duì)網(wǎng)絡(luò)延時(shí)較不敏感。
缺點(diǎn):在數(shù)據(jù)同步過(guò)程中,由于只要一個(gè)節(jié)點(diǎn)同步成功即算寫入成功,如果此時(shí)其他數(shù)據(jù)節(jié)點(diǎn)異步復(fù)制失敗,恰好此時(shí)路由到該節(jié)點(diǎn),可能出現(xiàn)幻寫(即剛寫入的數(shù)據(jù)再讀取的時(shí)候發(fā)生丟失),數(shù)據(jù)可靠性較低。
因此在此同步模式下,數(shù)據(jù)的一致性不高,只能通過(guò)未同步節(jié)點(diǎn)異步追趕主節(jié)點(diǎn)日志來(lái)達(dá)到最終數(shù)據(jù)一致的目的,此時(shí)如果對(duì)于數(shù)據(jù)一致性要求較高的場(chǎng)景,可以通過(guò)“主寫主讀”的方式來(lái)實(shí)現(xiàn)。半同步復(fù)制通過(guò)犧牲了一部分?jǐn)?shù)據(jù)穩(wěn)定性來(lái)?yè)Q取同步寫入的高效,是比較好的折中方式。
異步復(fù)制指的是數(shù)據(jù)在主節(jié)點(diǎn)寫入成功后立即返回,從節(jié)點(diǎn)異步復(fù)制主節(jié)點(diǎn)的數(shù)據(jù)。一般用于對(duì)數(shù)據(jù)寫入效率要求較高的場(chǎng)景,如此時(shí) db 做災(zāi)備以及異地多活場(chǎng)景下,可能涉及到跨區(qū)的數(shù)據(jù)復(fù)制,可以采用這種方式進(jìn)行同步:
優(yōu)點(diǎn):主從同步效率很高,從節(jié)點(diǎn)的網(wǎng)絡(luò)延時(shí)對(duì)主節(jié)點(diǎn)的寫入完全沒(méi)有影響。
缺點(diǎn):從節(jié)點(diǎn)無(wú)法和主節(jié)點(diǎn)保持完全同步,通過(guò)異步同步的方式很有可能出現(xiàn)主從間數(shù)據(jù)不一致的場(chǎng)景。
因此在這種同步模式下,數(shù)據(jù)的可靠性較低,如果對(duì)于數(shù)據(jù)一致性要求較高的場(chǎng)景,同樣可以通過(guò)“主寫主讀”的方式來(lái)實(shí)現(xiàn)。
通過(guò)上述的討論可得主從模式下數(shù)據(jù)的可靠性主要是通過(guò)主從間的數(shù)據(jù)復(fù)制機(jī)制來(lái)保證的,同時(shí),主節(jié)點(diǎn)存在單點(diǎn)問(wèn)題,承載了寫入流量。那么為了保證數(shù)據(jù)的一致性,我們應(yīng)該考慮如何處理節(jié)點(diǎn)失效?
這個(gè)問(wèn)題我們可以從兩方面考慮:
從節(jié)點(diǎn)失效:追趕主節(jié)點(diǎn)。這里我覺(jué)得在全同步模式下可以借鑒 kafka 的 ISR 機(jī)制,去維護(hù)一個(gè)網(wǎng)絡(luò)效率高的同步節(jié)點(diǎn)隊(duì)列,當(dāng)延時(shí)較大時(shí)主動(dòng)剔除該隊(duì)列并進(jìn)行數(shù)據(jù)追趕,等到追趕完成之后再加入到該隊(duì)列中。
主節(jié)點(diǎn)失效:重新選主。重新選主的話也需要注意以下幾個(gè)問(wèn)題。選舉的時(shí)候可能會(huì)導(dǎo)致選舉的節(jié)點(diǎn)有數(shù)據(jù)缺失問(wèn)題;還有可能存在腦裂問(wèn)題,參考 Redis 解決腦裂的方式,通過(guò)參數(shù)配置 min-slaves-to-write(最小從服務(wù)器數(shù)) 和 min-slaves-max-lag(從連接的最大延遲時(shí)間)。
min-slaves-to-write 是指主庫(kù)最少得有 N 個(gè)健康的從庫(kù)存活才能執(zhí)行寫命令。這個(gè)配置雖然不能保證 N 個(gè)從庫(kù)都一定能接收到主庫(kù)的寫操作,但是能避免當(dāng)沒(méi)有足夠健康的從庫(kù)時(shí),主庫(kù)無(wú)法正常寫入,以此來(lái)避免數(shù)據(jù)的丟失 ,如果設(shè)置為 0 則表示關(guān)閉該功能。
min-slaves-max-lag :是指從庫(kù)和主庫(kù)進(jìn)行數(shù)據(jù)復(fù)制時(shí)的 ACK 消息延遲的最大時(shí)間;可以確保從庫(kù)在指定的時(shí)間內(nèi),如果 ACK 時(shí)間沒(méi)在規(guī)定時(shí)間內(nèi),則拒絕寫入。

主主架構(gòu)解決了主從架構(gòu)下單點(diǎn)寫的問(wèn)題,可以由多個(gè)節(jié)點(diǎn)承載著寫和讀的流量,并且完成從節(jié)點(diǎn)的數(shù)據(jù)同步動(dòng)作,常見的應(yīng)用場(chǎng)景主要見于:
多數(shù)據(jù)中心。為了容忍整個(gè)數(shù)據(jù)中心級(jí)別故障或者更接近用戶,可以把數(shù)據(jù)庫(kù)的副本橫跨多個(gè)數(shù)據(jù)中心。
離線客戶端操作。如手機(jī)或其他電子設(shè)備的筆記軟件設(shè)備,能夠在多端寫入以及在多端進(jìn)行數(shù)據(jù)同步。
協(xié)同編輯。如在線文檔操作,多用戶同時(shí)編輯文檔,當(dāng)用戶編輯更改,會(huì)立即在其他用戶端數(shù)據(jù)界面生效。
在這種架構(gòu)下數(shù)據(jù)脫離了單節(jié)點(diǎn)寫入的控制,但是帶來(lái)的副作用就是如何協(xié)同多主間的數(shù)據(jù)同步問(wèn)題,數(shù)據(jù)的寫入得到了保障,但是數(shù)據(jù)間的同步以及沖突卻成了另外一個(gè)我們需要解決的棘手的問(wèn)題。
這里我們可以解決思路可以從這幾方面入手
處理寫沖突。在發(fā)生同時(shí)寫入某行的數(shù)據(jù)沖突情況下能夠有機(jī)制及時(shí)發(fā)現(xiàn),并且阻塞其他的寫請(qǐng)求,直到該寫入完成再進(jìn)行后續(xù)節(jié)點(diǎn)的寫入,但是這么做會(huì)極大降低并發(fā)效率,如果是多設(shè)備的數(shù)據(jù)同步場(chǎng)景下,沖突概率不大,因?yàn)槔碚撋嫌脩舢?dāng)前時(shí)間只會(huì)針對(duì)某個(gè)設(shè)備下進(jìn)行編輯;但是對(duì)于協(xié)同編輯的場(chǎng)景下,這種沖突的概率就很大,因此寫入的效率就會(huì)大打折扣。
避免沖突。解決沖突最好的方式就是避免沖突,這種思想在很多產(chǎn)品上都可以得以體現(xiàn),如 MySQL的 mvcc 模式,在事務(wù)中讀取的是 mvcc 的快照,隔離了不同事務(wù)之間對(duì)于動(dòng)態(tài)更改數(shù)據(jù)的訪問(wèn),轉(zhuǎn)而請(qǐng)求快照,避免了沖突場(chǎng)景下的數(shù)據(jù)讀取,只在數(shù)據(jù)提交的時(shí)候?qū)Ω男械臎_突以及一致性約束進(jìn)行檢測(cè)。同樣,在多數(shù)據(jù)中心的場(chǎng)景下,我們也可以將用戶路由到最近的數(shù)據(jù)中心進(jìn)行寫入,以此來(lái)降低數(shù)據(jù)沖突的概率。
一致性狀態(tài)收斂。理想情況下,我們還是希望數(shù)據(jù)能夠?qū)ν獗3忠恢拢敲醋钪苯拥姆绞骄褪墙鉀Q沖突,即在用戶提交數(shù)據(jù)時(shí)由服務(wù)端檢測(cè)各節(jié)點(diǎn)之間的數(shù)據(jù)差異,主動(dòng)或者被動(dòng)進(jìn)行數(shù)據(jù)合并,可以使用以下幾種方式:
給寫入分配時(shí)間戳,并且以后寫入的數(shù)據(jù)為準(zhǔn)。
為每個(gè)同步節(jié)點(diǎn)分配唯一 ID,指定規(guī)則如序號(hào)高的副本始終優(yōu)先于序號(hào)低副本的寫入。
按照預(yù)定義的合并規(guī)則自動(dòng)將數(shù)據(jù)進(jìn)行合并。
將合并控制權(quán)交給用戶,讓用戶自行決定數(shù)據(jù)合并。
由上述分析可得,主主架構(gòu)下雖然能夠解決單節(jié)點(diǎn)寫入的問(wèn)題,但是問(wèn)題矛盾轉(zhuǎn)移為主主間數(shù)據(jù)沖突解決的問(wèn)題,這個(gè)問(wèn)題雖然也有相應(yīng)的解決方案,但是不同的解決方案只能針對(duì)部分場(chǎng)景進(jìn)行解決,因此開發(fā)者在使用這種架構(gòu)的數(shù)據(jù)存儲(chǔ)方式的時(shí)候,應(yīng)當(dāng)根據(jù)自身的業(yè)務(wù)選擇不同的策略解決。
無(wú)主結(jié)構(gòu)則更為激進(jìn),相比于主從和主主架構(gòu),放棄主節(jié)點(diǎn),允許任何副本節(jié)點(diǎn)承載寫和讀的流量,寫入的時(shí)候只要保證寫入“大多數(shù)成功”即認(rèn)為寫入成功,那么這時(shí)候假設(shè)其中的一個(gè)節(jié)點(diǎn)寫入失敗,接著讀取時(shí)又恰巧讀取到了該節(jié)點(diǎn)的數(shù)據(jù),是不是就會(huì)發(fā)生數(shù)據(jù)缺失問(wèn)題?為了解決這個(gè)問(wèn)題,就需要我們讀取數(shù)據(jù)的時(shí)候不僅從一個(gè)節(jié)點(diǎn)進(jìn)行讀取,而是從多個(gè)節(jié)點(diǎn)進(jìn)行,并且寫入的數(shù)據(jù)中附帶版本號(hào),只要我們能夠保證絕大部分節(jié)點(diǎn)可信任,就可以獲取到正確的數(shù)據(jù)。
所以在無(wú)主架構(gòu)下,主要矛盾轉(zhuǎn)化為如何保證寫入和讀取正確的問(wèn)題。
即通過(guò)寫入一定冗余節(jié)點(diǎn)的數(shù)據(jù)來(lái)保證數(shù)據(jù)的可靠性。即如果有 n 個(gè)副本,寫入需要 w 個(gè)節(jié)點(diǎn)確認(rèn),讀取必須至少查詢 r 個(gè)節(jié)點(diǎn),則只要滿足 w+r>n 的條件,那么讀取的節(jié)點(diǎn)中一定會(huì)包含最新值。這個(gè)結(jié)論舉個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明,如圖所示,一共五個(gè)節(jié)點(diǎn),w 和 r 都為3,這樣就可以保證讀取的時(shí)候無(wú)論是讀取哪幾個(gè)節(jié)點(diǎn),都至少有一個(gè)節(jié)點(diǎn)能讀到寫入成功的對(duì)應(yīng)的節(jié)點(diǎn)。也就是只要我們能保證讀和寫的節(jié)點(diǎn)之間存在交叉即可。
看似這個(gè)方案好像完美解決了數(shù)據(jù)一致性問(wèn)題,但是我們仔細(xì)思考下,還是存在一定的問(wèn)題。
當(dāng)此時(shí)數(shù)據(jù)節(jié)點(diǎn)出現(xiàn)宕機(jī)的情況下,即 n<r 的情況,服務(wù)就不可用了,根據(jù) cap 理論,只能夠?qū)崿F(xiàn) cp,犧牲可用性來(lái)滿足一致性。
即使在 w+r>n 的場(chǎng)景下,也可能存在數(shù)據(jù)返回異常的情況。
寫操作同時(shí)發(fā)生,無(wú)法確定先后順序,因?yàn)榭赡艽嬖跁r(shí)鐘偏移的情況。
寫和讀操作同時(shí)發(fā)生,寫操作可能僅在一部分副本上完成,此時(shí),讀取時(shí)返回舊值和新值不確定。
寫操作在節(jié)點(diǎn)中部分成功,部分失敗,但是由于已成功的數(shù)據(jù)無(wú)法回滾,可能引入臟數(shù)據(jù)。
當(dāng)然,我們也可以采用寬松的 quorum 的方式,即在寫入的節(jié)點(diǎn)不滿足w的情況下,增加 n 意外的臨時(shí)節(jié)點(diǎn),再在網(wǎng)絡(luò)回復(fù)的時(shí)候把臨時(shí)節(jié)點(diǎn)的數(shù)據(jù)同步到主節(jié)點(diǎn)上,但是這樣就犧牲了數(shù)據(jù)一致性來(lái)滿足可用性。所以收到 CAP 理論的限制,無(wú)論怎么樣都無(wú)法同時(shí)滿足,只能根據(jù)具體的實(shí)際情況來(lái)做取舍。
由上述分析可得,在無(wú)主的情況下,數(shù)據(jù)的寫入雖然不受到單節(jié)點(diǎn)的限制,但是在極端場(chǎng)景下同樣無(wú)法保障數(shù)據(jù)的可靠性,需要能夠容忍數(shù)據(jù)不一致的場(chǎng)景。
03
我們?yōu)榱颂骄糠植际江h(huán)境下的數(shù)據(jù)一致性,從數(shù)據(jù)架構(gòu)入手,分別探究了
主從架構(gòu)。
主主架構(gòu)。
無(wú)主架構(gòu)。
這三種架構(gòu)下數(shù)據(jù)是否能夠保持寫入和讀取的一致,綜上可知,每個(gè)方法都有對(duì)應(yīng)的優(yōu)缺點(diǎn),在實(shí)際的開發(fā)過(guò)程中,我們應(yīng)當(dāng)要能夠?qū)嶋H的業(yè)務(wù)情況,有所取舍地選擇合適的架構(gòu),并且在實(shí)現(xiàn)的過(guò)程中注意這些可能出現(xiàn)的數(shù)據(jù)坑點(diǎn),這樣才能做到有的放矢,寫出更加魯棒的軟件。


????歡迎加入騰訊云開發(fā)者社群,享前沿資訊、大咖干貨,找興趣搭子,交同城好友,更有鵝廠招聘機(jī)會(huì)、限量周邊好禮等你來(lái)~

(長(zhǎng)按圖片立即掃碼)



