多版本并發控制(MVCC)在分布式系統中的應用

fmms 13年前發布 | 13K 次閱讀 版本并發控制

        問題

        最近項目中遇到了一個分布式系統的并發控制問題。該問題可以抽象為:某分布式系統由一個數據中心D和若干業務處理中心 L1,L2 … Ln 組成;D本質上是一個 key-value 存儲,它對外提供基于 HTTP 協議的 CRUD 操作接口。L的業務邏輯可以抽象為下面 3 個步驟:

  1. read: 根據 keySet {k1, … kn}從D獲取 keyValueSet {k1:v1, … kn:vn}
  2. do: 根據 keyValueSet 進行業務處理,得到需要更新的數據集 keyValueSet’ {k1′:v1′, … km’:vm’} (:讀取的 keySet 和更新的 keySet’可能不同)
  3. update: 把 keyValueSet’更新到D (:D保證在一次調用更新多個 key 的原子性)

        在沒有事務支持的情況下,多個L進行并發處理可能會導致數據一致性問題。比如,考慮 L1 和 L2 的如下執行順序:

  1. L1從D讀取 key:123對應的值 100
  2. L2從D讀取 key:123對應的 100
  3. L1將 key:123更新為 100 + 1
  4. L2將 key:123更新為 100 + 2

        如果 L1 和 L2 串行執行,key 123 對應的值將為 103,但上面并發執行中 L1 的執行效果完全被 L2 所覆蓋,實際 key 123 所對應的值變成了 102。

        解決方案1:基于鎖的事務

        為了讓L的處理具有可串行化特性(Serializability),一種最直接的解決方案就是考慮為D加上基于鎖的簡單事務。讓L在進行業務 處理前先鎖定D,完成以后釋放鎖。另外,為了防止持有鎖的L由于某種原因長時間未提交事務,D還需要具有超時機制,當L嘗試提交一個已超時的事務時會得到 一個錯誤響應。

多版本并發控制(MVCC)在分布式系統中的應用

        本方案的優點是實現簡單,缺點是鎖定了整個數據集,粒度太大;時間上包含了L的整個處理時間,跨度太長。為此,可以考慮把鎖定粒度降低到數據項 級別,按 key 進行鎖定,但這又會帶來其他的問題。由于更新的 keySet’可能是事先不確定的,所以可能無法在開始事務時鎖定所有的 key;如果分階段來鎖定需要的 key,又可能出現死鎖(Deadlock)問題。另外,按 key 鎖定在有鎖爭用的情況下并不能解決鎖定時間太長的問題。所以,按 key 鎖定仍然存在重要的不足之處。

        解決方案2:多版本并發控制

        為了實現可串行化,同時避免鎖機制存在的各種問題,我們可以采用基于多版本并發控制(Multiversion concurrency control,MVCC)思想的無鎖事務機制。人們一般把基于鎖的并發控制機稱成為悲觀機制,而把 MVCC 等機制稱為樂觀機制。這是因為鎖機制是一種預防性的,讀會阻塞寫,寫也會阻塞讀,當鎖定粒度較大,時間較長是并發性能就不會太好;而 MVCC 是一種后驗性的,讀不阻塞寫,寫也不阻塞讀,等到提交的時候才檢驗是否有沖突,由于沒有鎖,所以讀寫不會相互阻塞,從而大大提升了并發性能。目 前,Oracle、PostgreSQL 和 MySQL 都已支持基于 MVCC 的并發機制,但具體實現各有不同。

        MVCC 的一種簡單實現是基于 CAS(Compare-and-swap)思想的有條件更新(Conditional Update)。普通的 update 參數只包含了一個 keyValueSet’,Conditional Update 在此基礎上加上了一組更新條件 conditionSet { … data[keyx]=valuex, … },即只有在D滿足更新條件的情況下才將數據更新為 keyValueSet’;否則,返回錯誤信息。這樣,L就形成了如下圖所示的 Try/Conditional Update/(Try again)的處理模式:

多版本并發控制(MVCC)在分布式系統中的應用

        雖然對單個L來講不能保證每次都成功更新,但從整個系統來看,總是有任務能夠順利進行。這種方案利用 Conditional Update 避免了大粒度和長時間的鎖定,當各個業務之間資源爭用不大的情況下,并發性能很好。不過,由于 Conditional Update 需要更多的參數,如果 condition 中 value 的長度很長,那么每次網絡傳送的數據量就會比較大,從而導致性能下降。特別是當需要更新的 keyValueSet’很小,而 condition 很大時,就顯得非常不經濟。

        為了避免 condition 太大所帶來的性能問題,可以為每條數據項增加一個 int 型的版本號字段,由D維護該版本號,每次數據有更新就增加版本號;L在進行 Conditional Update 時,通過版本號取代具體的值。

多版本并發控制(MVCC)在分布式系統中的應用

        另一個問題是上面的解決方案假設了D是可以支持 Conditional Update 的;那么,如果D是一個不支持 Conditional Update 的第三方的 key-value 存儲怎么辦呢?這時,我們可以在L和D之間增加一個P作為代理,所有的 CRUD 操作都必須經過P,讓P來進行條件檢查,而實際的數據操作放在D。這種方式實現了條件檢查和數據操作的分離,但同時降低了性能,需要在P中增加 cache,提升性能。由于P是D的唯一客戶端;所以,P的 cache 管理是非常簡單的,不必像多客戶端情形擔心緩存的失效。不過,實際上,據我所知 redis 和 Amazon SimpleDB 都已經有了 Conditional Update 的支持。

        總結

        本文介紹了一種基于多版本并發控制(MVCC)思想的 Conditional Update 解決分布式系統并發控制問題的方法。和基于鎖的方法相比,該方法避免了大粒度和長時間的鎖定,當各個業務之間資源爭用不大的情況下,并發性能很好。

        參考

        Wikipedia – Serializability

        Wikipedia – Compare-and-swap

        Wikipedia – Multiversion concurrency control

        Lock-free algorithms: The try/commit/(try again) pattern

        Amazon SimpleDB FAQs – Does Amazon SimpleDB support transactions?

        redis – Transactions

        A Quick Survey of MultiVersion Concurrency Algorithms

        非阻塞算法思想在關系數據庫應用程序開發中的使用

        友情推薦

        本文的圖是陳皓開發的 TextDiagram 工具畫的,歡迎試用!如果您喜歡,請推薦給朋友,謝謝

 本文由用戶 fmms 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!