分佈式系統數據一致性的解決方案 數據庫
隨着互聯網的發展,計算機系統規模變得愈來愈大,常規的將全部業務單元集中部署在一個或者若干個大型機上的集中式架構,已經愈來愈不能知足當今大型互聯網系統的快速發展。分佈式服務架構以及微服務架構已經愈來愈受到業界的青睞。而在目前的應用系統中,無論是集中式的部署架構仍是分佈式的系統架構,數據的一致性是每一個應用系統都須要面臨的問題。 編程
在傳統的集中式部署的系統中,應用通常不存在橫跨多數據庫的狀況,藉助關係型數據庫自帶的事務管理機制,關係型數據庫具備ACID特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability),保證數據一致性是比較容易的。而在分佈式系統中,一個普通的業務功能,內部可能須要調用部署在多個服務器上的服務,並操做多個數據庫或分片來實現,狀況每每會複雜不少。保證數據一致性也變得更加艱難,再經過傳統的關係型數據庫的事務機制等單一的技術手段和解決方案,已經沒法應對和知足這些複雜的場景了。固然這些問題單純依靠特定的開源框架和組件並不能解決,更多的仍是須要根據本身的業務場景,選擇合適的解決方案。本文筆者將給你們介紹幾種業界比較經常使用的解決數據一致性的實現方案,但願能夠起到一個拋磚引玉的做用。 服務器
一.CAP理論和BASE理論 網絡
一、CAP理論架構
談及分佈式系統,就不能不提到CAP理論,CAP理論由Berkeley的計算機教授Eric Brewer在2000年提出,其核心思想是任何基於網絡的數據共享系統最多隻能知足數據一致性(Consistency)、可用性(Availability)和網絡分區容忍(Partition Tolerance)三個特性中的兩個,三個特性的定義以下: 併發
在分佈式系統中,同時知足"CAP定律"中的"一致性"、"可用性"和"分區容錯性"三者是不可能的。在互聯網應用的絕大多數的場景,都須要犧牲強一致性來換取系統的高可用性,系統每每只須要保證"最終一致性",只要這個最終時間是在用戶能夠接受的範圍內便可。 框架
二、BASE理論 分佈式
BASE是Basically Available(基本可用)、Soft state(軟狀態)和Eventually consistent(最終一致性)三個短語的縮寫。BASE理論是對CAP中一致性和可用性權衡的結果, 是基於CAP定理逐步演化而來的。BASE理論的核心思想是:即便沒法作到強一致性,但每一個應用均可以根據自身業務特色,採用適當的方式來使系統達到最終一致性。接下來看一下BASE中的三要素: 微服務
BASE理論面向的是大型高可用可擴展的分佈式系統,和傳統的事物ACID特性是相反的,它徹底不一樣於ACID的強一致性模型,而是經過犧牲強一致性來得到可用性,並容許數據在一段時間內是不一致的,但最終達到一致狀態。但同時,在實際的分佈式場景中,不一樣業務單元和組件對數據一致性的要求是不一樣的,所以在具體的分佈式系統架構設計過程當中,ACID特性和BASE理論每每又會結合在一塊兒。 高併發
根據CAP理論和BASE理論,在分佈式系統中,咱們沒法找到一種可以知足分佈式系統全部系統屬性的一致性解決方案,若是不想犧牲一致性,咱們只能放棄可用性,這顯然不能接受。所以,爲了保證數據的一致性同時又不影響系統運行的性能,許多分佈式系統採用弱一致性來提升性能,一些不一樣的一致性模型也相繼被提出:
2、數據一致性的幾種解決方案
一、分佈式事務
要想理解分佈式事務,咱們須要先介紹一下兩階段提交協議。
兩階段提交協議(Two-phase Commit,2PC)常常被用來實現分佈式事務。通常分爲協調器和若干事務執行者兩種角色。這裏的事務執行者就是具體的數據庫,抽象點能夠說是能夠控制給數據庫的程序。 協調器能夠和事務執行器在一臺機器上。
在分佈式系統中,每一個節點雖然能夠知曉本身的操做的成功或者失敗,卻沒法知道其餘節點的操做的成功或失敗。當一個事務跨越多個節點時,爲了保持事務的ACID特性,須要引入一個做爲協調者的組件來統一掌控全部節點(稱做參與者)。
兩階段提交協議在主流開發語言平臺,數據庫產品中都有普遍應用和實現:
一、在Java平臺下,WebLogic、Webshare等主流商用的應用服務器提供了JTA的實現和支持。
二、在Windows .NET平臺中,則能夠藉助ado.net中的TransactionScop API來編程實現,還必須配置和藉助Windows操做系統中的MSDTC服務。
總結:這種實現方式實現比較簡單,比較適合傳統的單體應用,在同一個方法中存在跨數據庫操做的狀況。可是由於兩階段的提交會建立屢次節點的網絡通訊,通訊時間變長後,事務的時間也相對變長,鎖定的資源時間也變長,形成資源等待時間也變長,這會帶來嚴重的性能問題,所以大部分高併發服務每每都避免使用二階段提交協議,因此後來業界又引入了三階段提交協議來解決該類問題。
二、非事務型消息隊列+本地消息表
此方案關鍵是要有個本地消息表,基本思路就是:
經過上圖能夠看出,消費方會面臨一個問題就是,當消費方完成本地事務處理,給生產方發送CONFIRM消息失敗時,生產方因爲本地消息表的消息狀態沒有更新,會進行重試,那麼這時候就存在了消息重複投遞的問題,這時候消費方收到重複投遞過來的消息後,要保證消費者調用業務的服務接口的冪等性,即:若是重複消費,也不能所以影響業務結果,同一消息屢次被執行會獲得相同的結果。
總結:這種方式的根本原理就是:將分佈式事務轉換爲多個本地事務,而後依靠重試等方式達到最終一致性。這種方式比較常見。若是MQ自身和業務都具備高可用性,理論上是能夠知足大部分的業務場景的。可是因爲可能存在的長時間處於中間狀態,不建議交易類業務直接使用。
三、事務型消息隊列
事務型消息其實是一個很理想的想法,目前市面上大部分MQ都不支持事務消息,其中包括目前比較火的kafka。阿里的RocketMQ是能夠支持事務型消息的MQ,根據網傳的資料,大概瞭解到RocketMQ的事務消息至關於在普通MQ的基礎上,提供了2PC的提交接口。把非事務型消息隊列中的消息狀態和重發等用中間件形式封裝了。
舉個例子,Bob向Smith轉帳,那咱們究竟是先發送消息,仍是先執行扣款操做?
好像均可能會出問題。若是先發消息,扣款操做失敗,那麼Smith的帳戶裏面會多出一筆錢。反過來,若是先執行扣款操做,後發送消息,那有可能扣款成功了可是消息沒發出去,Smith收不到錢。除了上面介紹的經過異常捕獲和回滾的方式外,還有沒有其餘的思路呢?
下面以阿里巴巴的RocketMQ中間件爲例,分析下其設計和實現思路。
RocketMQ第一階段發送Prepared消息時,會拿到消息的地址,第二階段執行本地事物,第三階段經過第一階段拿到的地址去訪問消息,並修改狀態。細心的讀者可能又發現問題了,若是確認消息發送失敗了怎麼辦?RocketMQ會按期掃描消息集羣中的事物消息,這時候發現了Prepared消息,它會向消息發送者確認,Bob的錢究竟是減了仍是沒減呢?若是減了是回滾仍是繼續發送確認消息呢?RocketMQ會根據發送端設置的策略來決定是回滾仍是繼續發送確認消息。這樣就保證了消息發送與本地事務同時成功或同時失敗。以下圖:
總結:目前各大知名的電商平臺和互聯網公司,幾乎都是採用相似的設計思路來實現"最終一致性"的。這種方式適合的業務場景普遍,並且比較可靠。不過這種方式技術實現的難度比較大。目前主流的開源MQ(ActiveMQ、RabbitMQ、Kafka)均未實現對事務消息的支持,因此需二次開發。
後記: 上述所介紹的幾種方案,筆者也只是大體總結了其基本設計思路,這幾種方案只是衆多解決數據一致性的方案中比較經典的幾種。在現在愈來愈複雜的分佈式系統架構下,數據的一致性,並非說簡單的引入某個中間件可以解決的,最終一致性並無一個放之四海而皆準的成功實踐。更多的仍是根據業務場景、業務特性以及業務不一樣的發展階段,選擇合適的方式來靈活應對。