CRDT——解决最终一致问题的利器
概述
跨數(shù)據(jù)中心的數(shù)據(jù)同步是企業(yè)提升容災(zāi)能力的必備手段,對于社交、視頻直播、電商以及游戲等訪問規(guī)模大、業(yè)務(wù)分布廣的行業(yè),跨區(qū)域全球部署也愈發(fā)重要。
然而面對大型分布式系統(tǒng), 不免要討論CAP理論,在跨區(qū)域多活的場景下如何取舍?顯然P(網(wǎng)絡(luò)分區(qū))是首要考慮因素。其次,跨區(qū)域部署就是為了提高可用性,而且對于常見的一致性協(xié)議,不管是2PC、Paxos還是raft,在此場景下都要做跨區(qū)域同步更新,不僅會降低用戶體驗(yàn),在網(wǎng)絡(luò)分區(qū)的時(shí)候還會影響可用性,因此C必定被排在最后。那是不是C無法被滿足了呢?事實(shí)并非如此,退而求其次,最終一致也是一種選擇。CRDT(Conflict-Free Replicated Data Type)1是各種基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)最終一致算法的理論總結(jié),能根據(jù)一定的規(guī)則自動合并,解決沖突,達(dá)到強(qiáng)最終一致的效果。2012年CAP理論提出者Eric Brewer撰文回顧C(jī)AP[3]時(shí)也提到,C和A并不是完全互斥,建議大家使用CRDT來保障一致性。自從被大神打了廣告,各種分布式系統(tǒng)和應(yīng)用均開始嘗試CRDT,redislabs[4]和riak[5]已經(jīng)實(shí)現(xiàn)多種數(shù)據(jù)結(jié)構(gòu),微軟的CosmosDB[6]也在azure上使用CRDT作為多活一致性的解決方案。
阿里云redis現(xiàn)配套推出了全球多活產(chǎn)品[7],助力企業(yè)在云上部署跨區(qū)域服務(wù),并且依據(jù)CRDT確保在全球多活的場景下,所有redis實(shí)例中數(shù)據(jù)最終一致。本篇文章我們會對CRDT進(jìn)行簡要介紹,下一篇文章將說明我們是如何實(shí)現(xiàn)CRDT的。
CRDT簡介
先簡單統(tǒng)一一下概念和名詞:
- object: 可以理解為“副本”
- operation: 操作接口,由客戶端調(diào)用,分為兩種,讀操作query和寫操作update
- query: 查詢操作,僅查詢本地副本
- update: 更新操作,先嘗試進(jìn)行本地副本更新,若更新成功則將本地更新同步至遠(yuǎn)端副本
- merge: update在遠(yuǎn)端副本的合并操作
一個數(shù)據(jù)結(jié)構(gòu)符合CRDT的條件是update操作和merge操作需滿足交換律、結(jié)合律和冪等律,具體證明見[1],在此不做贅述。如果update操作本身滿足以上三律,merge操作僅需要對update操作進(jìn)行回放即可,這種形式稱為op-based?CRDT,最簡單的例子是集合求并集。
如果update操作無法滿足條件,則可以考慮同步副本數(shù)據(jù),同時(shí)附帶額外元信息,通過元信息讓update和merge操作具備以上三律,這種形式稱為state-based CRDT。讓元信息滿足條件的方式是讓其更新保持__單調(diào)__,這個關(guān)系一般被稱為__偏序關(guān)系__。舉一個簡單例子,每次update操作都帶上時(shí)間戳,在merge時(shí)對本地副本時(shí)間戳及同步副本時(shí)間戳進(jìn)行比對,取更新的結(jié)果,這樣總能保證結(jié)果最新并且最終一致,這種方式稱為Last Write Wins:
有兩點(diǎn)值得注意的地方:
- update操作無法滿足三律,如果能將元信息附加在操作或者增量上,會是一個相對state-based方案更優(yōu)化的選擇
- 如果同步過程能確保exactly once的語義,冪等律條件是可以被放寬掉,比如說加法本身滿足交換律結(jié)合律但不冪等,如果能保證加法操作只回放一次,其結(jié)果還是最終一致的。
有了以上的理論基礎(chǔ)后,我們可以看看各種數(shù)據(jù)結(jié)構(gòu)如何設(shè)計(jì),才能滿足CRDT,達(dá)到最終一致。
CRDTs一覽
以下展示一些典型的CRDT數(shù)據(jù)結(jié)構(gòu)的例子,每一種數(shù)據(jù)類型都會給出示意圖,必要時(shí)給出偽代碼說明,證明略過,有興趣可參見[2]。
Counter
counter是最簡單的例子,為了說明state-based和op-based的差異,在此分別給出兩種形式的描述。
Op-based counter
counter的op-based形式支持兩種寫操作:increment和decrement,由于加法天然滿足交換律和結(jié)合律,所以非常容易實(shí)現(xiàn),直接轉(zhuǎn)發(fā)操作即可:
但要注意的是加法不冪等,所以同步過程中需要保證不丟不重。
G-Counter (Grow-only Counter)
counter的state-based形式并非那么的顯而易見,為了簡化問題,我們先從一個只有increment的counter開始看起。
由于同步的是全量,如果每個副本單獨(dú)進(jìn)行累加,在進(jìn)行merge的時(shí)候無法知道每個副本具體累加了多少,更不能簡單的取一個max作為最終結(jié)果,比如A做一次INCR 1同時(shí)B做一次INCR 2,副本全量同步之后,A和B都取max以2做為結(jié)果并最終一致,但正確的結(jié)果應(yīng)該是3。
所以一種可行的方式是在每個副本上都使用一個數(shù)組保留其它所有副本的值,update時(shí)只操作當(dāng)前副本在數(shù)組中對應(yīng)項(xiàng)即可,merge時(shí)對數(shù)組每一項(xiàng)求max進(jìn)行合并,query時(shí)返回?cái)?shù)組的和,即為counter的當(dāng)前結(jié)果。
易見update和merge均能保證單調(diào)的遞增,所以G-Counter是state-based CRDT。
PN-Counter
帶有decrement的state-based CRDT也并非像G-Counter那樣顯而易見,帶有減法之后,不能滿足update時(shí)單調(diào)的偏序關(guān)系。 所以正確的方式是構(gòu)造兩個G-Counter,一個存放increment的累加值,一個存放decrement的累加值。
Register
register本質(zhì)是一個string,僅支持一種寫操作assign。并發(fā)assign是不存在交換律的,所以需要考慮附加上偏序關(guān)系。
Last-Writer-Wins Register (LWW Register)
一種簡單的做法是后assign的覆蓋先assign的(last write wins),方式是每次修改都附帶時(shí)間戳,update時(shí)通過時(shí)間戳生成偏序關(guān)系,merge時(shí)只取較大時(shí)間戳附帶的結(jié)果。示意圖前文已經(jīng)給出。
Set
Set一共有兩種寫操作,add和remove,多節(jié)點(diǎn)并發(fā)進(jìn)行add和remove操作是無法滿足交換律的, 會產(chǎn)生沖突:
所以必須附加一些額外信息,可以從一個只做添加的set開始看起。
Grow-Only Set (G-Set)
set的add操作本質(zhì)上是求并,天然滿足交換律、結(jié)合律和冪等律, 滿足Op-based CRDT:
交換律: X U Y = Y U X
結(jié)合律: (X U Y) U Z = X U (Y U Z)
2P-Set
考慮刪除操作,思路和PN-Counter一致,使用兩個G-Set, set A只負(fù)責(zé)添加,對于從set A中remove的元素不做實(shí)際刪除,只是復(fù)制到set R中,如下:
query時(shí)如果元素在set A且不在set R中,則表示該元素存在。
query lookup(e): bool b let b = (e in A && e not in R)由于只同步操作,且兩個set只添加不減少,易證其為op-based CRDT。但2P-Set十分不實(shí)用,一方面已經(jīng)被刪除的元素不能再次被添加,一方面刪除的元素還會保留在原set中,占用大量空間。
LWW-element-Set
為了解決刪除元素不能再次添加的問題,可以考慮給2P-Set中A和R的每個元素加一個更新時(shí)間戳,其它操作保持不變,只要在查詢的時(shí)候做如下處理:
query lookup(e): bool blet b = (t1 < t2): (e, t1) in A && (e, t2) not in R一個更優(yōu)化的實(shí)現(xiàn)是不要R集合,而A集合中每一個元素除了維護(hù)一個更新時(shí)間戳之外,還有一個刪除標(biāo)志位。
Observed-Remove Set (OR-Set)
還有一種想法不太相同的設(shè)計(jì),核心思想是每次add(e)的時(shí)候都為元素e加一個唯一的tag,remove(e)將當(dāng)前節(jié)點(diǎn)上的所有e和對應(yīng)的tag都刪除,這樣在remove(e)同時(shí)其它節(jié)點(diǎn)又有并發(fā)add(e)的情況下e是能夠最終保證添加成功,此種語義稱為add wins。如圖,A上做remove e時(shí)僅有A一個tag,所以在C收到A同步過來的remove時(shí),只刪除tag A,tag B保留e在C上仍然存在,最終ABC三個節(jié)點(diǎn)是一致的,都有e及tag B。
雖然在remove時(shí)看似存在并不能保證交換律的刪除操作出現(xiàn),但刪除的元素是全局唯一的,所以并不破壞語義,故仍然是為CRDT。
ORSet相對來說是一種比較實(shí)用的結(jié)構(gòu),但實(shí)現(xiàn)上仍然有幾個問題要解決:
- 重復(fù)add和remove的場景下會產(chǎn)生大量的tag,空間需要優(yōu)化
- 在考慮空間優(yōu)化的前提下如何生成全局唯一的tag
- 需要考慮如何進(jìn)行垃圾回收
學(xué)術(shù)界有多篇論文都是在探討對此種算法的優(yōu)化。但OR-Set在實(shí)踐中最嚴(yán)重的問題是一旦同步通道出現(xiàn)延遲或者中斷,很可能出現(xiàn)用戶認(rèn)為早已刪除掉的字段在同步恢復(fù)之后再次出現(xiàn)。從工程實(shí)踐角度講,更優(yōu)化的方案是使用時(shí)間戳作為unique tag,好處是易保證唯一性,同時(shí)自帶單調(diào)遞增屬性,重復(fù)刪除添加時(shí)不會生成大量tag。
附錄
總結(jié)
以上是生活随笔為你收集整理的CRDT——解决最终一致问题的利器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 复肝能胶囊_功效作用注意事项用药禁忌用法
- 下一篇: 阿里 Goldeneye 四个环节落地智