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