[RSCH] 6 分鐘閱讀OraCore 編輯部

CRDT 讓副本不用鎖也能同步

CRDT 讓分散式應用能同時接受多方寫入,之後再自動收斂成一致結果,適合離線優先、協作編輯與多區部署。

分享 LinkedIn
CRDT 讓副本不用鎖也能同步

CRDT 讓分散式應用能同時接受多方寫入,之後再自動收斂成一致結果。

說真的,這東西很適合分散式系統。你不用每次寫入都先問伺服器。副本先收資料,之後再合併就好。

這篇講的是 CRDT。它是 Conflict-free Replicated Data Type 的縮寫。概念在 2011 年正式整理出來。作者是 Marc Shapiro、Nuno Preguiça、Carlos Baquero 和 Marek Zawirski。

實務上,RedisRiak,還有 Azure Cosmos DB 都碰過這套路。講白了,它不是論文玩具,是真的進過生產環境。

FactValueWhy it matters
Formal definition2011CRDT 變成正式研究主題
Core propertyEventual convergence副本可短暫不同,最後仍一致
Two main familiesState-based and operation-based團隊可在簡單合併與省流量間選擇
Example systemsRedis, Riak, Cosmos DB它已進入實際資料庫與服務

CRDT 為什麼重要

訂閱 AI 趨勢週報

每週精選模型發布、工具應用與深度分析,直送信箱。不定期,不騷擾。

不會寄垃圾信,隨時可取消。

分散式軟體最煩的,就是大家同時改同一份資料。兩個使用者一起編輯,兩台伺服器同時收寫入,結果就會撞車。傳統作法是加鎖、做協調、跑共識。能解,但延遲會上來,服務也更脆。

CRDT 讓副本不用鎖也能同步

CRDT 的想法比較直白。先讓每個副本本地接受更新。之後再用固定規則合併。只要資料型別設計對了,副本就算先分歧,最後也會收斂到同一份結果。

這對離線優先 app 很有用。像協作編輯、聊天、筆記同步、手機離線回寫,都很吃這套。你不想因為網路慢,就卡住每一次輸入。

  • 每個副本都能先本地寫入。
  • 不需要每次都做跨節點協調。
  • 合併規則寫在資料型別裡。
  • 副本最後會收斂成一致狀態。

兩大類型,兩種取捨

CRDT 大致分成兩種。第一種是 state-based CRDT,也叫 CvRDT。第二種是 operation-based CRDT,也叫 CmRDT。兩者都追求 strong eventual consistency,但資料傳輸方式不同。

state-based CRDT 會把整個狀態送出去。接收端再用 merge function 合併。這個 merge 必須滿足 commutative、associative、idempotent。翻成白話就是,順序亂掉、重複送一次,都不能把結果弄壞。

operation-based CRDT 則是送操作,不送整包狀態。這樣通常比較省頻寬。代價是傳輸層要更可靠。操作要避免重複,還要維持 causal order。網路層沒做好,資料就會亂。

“The merge function should compute the join for any pair of replica states, and should form a semilattice with the initial state as the neutral element.”

這句話很硬,但意思很單純。合併函式要能把兩個副本狀態接起來,而且不怕晚到、重送、亂序。這就是 CRDT 的核心約束。沒有這條,整套設計就會翻車。

  • state-based CRDT 比較好設計。
  • 它常搭配 gossip 傳播。
  • operation-based CRDT 比較省流量。
  • 它更吃訊息系統品質。
  • 兩者都要保證最後收斂。

幾個經典例子最好懂

先看 G-Counter。它是只增不減的計數器。每個節點只改自己的槽位。合併時取每個槽位的最大值。最後再把整個陣列加總。這樣就不怕兩邊同時加 1。

CRDT 讓副本不用鎖也能同步

再看 PN-Counter。它把增和減拆成兩個 G-Counter。對外看起來可以加減,內部其實還是單向遞增。這種設計很 CRDT。外表像一般資料結構,底層卻很克制。

集合類型也很經典。G-Set 只能新增。2P-Set 加上 tombstone,刪除後就不能再回來。LWW-Element-Set 用時間戳決勝負。OR-Set 則更細膩,能保留更多並行意圖。

  • G-Counter:分散式累加。
  • PN-Counter:可加可減,但內部仍單向成長。
  • G-Set:只允許新增。
  • 2P-Set:刪除有墓碑。
  • LWW-Element-Set:靠 timestamp 決定勝負。
  • OR-Set:更適合多人協作。

跟傳統一致性方案比,差在哪

先講結論。CRDT 不是拿來取代所有一致性方案。它比較像一把專用工具。適合的地方很好用,不適合的地方就會很卡。你如果硬拿去做複雜交易,通常只會把自己搞累。

跟鎖、兩階段提交、共識協定比,CRDT 最大的好處是少協調。少協調代表延遲低,跨區也比較不怕。代價是資料型別要先設計好,不能隨便亂塞任意邏輯。

從系統角度看,CRDT 很適合 counters、flags、sets、協作註記。這些資料有個共通點。它們常常只需要「最後能對上」,不一定要每一步都同步到完全一致。

  • CRDT 適合離線優先與多區部署。
  • 鎖和共識適合強一致交易。
  • CRDT 對資料模型有要求。
  • 不是每種狀態都能輕鬆改成 CRDT。
  • 設計好時,延遲和阻塞會少很多。

產業脈絡其實很現實

CRDT 最早是為了協作編輯和行動裝置。這很合理。手機常常斷線。多人又會同時改同一份文件。你不可能每打一個字,就等遠端確認。那體驗會爛到爆。

後來,聊天系統、媒體分發、遊戲狀態、地理分散資料庫也開始碰它。SoundCloud 就是常被拿來提的例子之一。它提醒大家,CRDT 不只是學術名詞。它真的能處理分散式寫入的髒活。

如果你在做多區服務,CRDT 會出現在幾個地方。像是計數、通知已讀、線上狀態、協作標記。這些欄位看起來小,卻常常是系統最容易吵架的地方。把它們改成可收斂資料型別,通常比硬上全域鎖更實際。

你也可以先看另一篇 eventual consistency vs. strong consistency。先搞懂一致性光譜,再回來看 CRDT,會比較有感。

講白了,CRDT 解的是一個很工程的問題。不是要你相信某種理論。是要你少踩同步地雷。

下一步怎麼看待 CRDT

CRDT 很強,但不是萬能。它最適合的是單調成長、可合併、可收斂的狀態。像任意刪除、複雜交易、嚴格排序,這些就沒那麼順。

如果你是開發者,我會建議先挑一個功能來看。像是計數器、草稿同步、協作註解,或線上狀態。問自己一句話:這個狀態能不能改成 monotonic?如果可以,你就有機會少掉很多 coordination 成本。

我自己的判斷很直接。CRDT 不是拿來炫技的。它是拿來救分散式系統的手感。當你的產品開始跨區、離線、多人同寫時,這套東西就會變得很值錢。下一步最實際的做法,就是把一個功能畫成狀態圖,看看能不能先從 CRDT 化開始。