在前面三篇文章中,介紹了關於分布式系統中數據一致性的問題,這一篇主要介紹CAP定理以及自己對CAP定理的了解。
CAP定理是2000年,由 Eric Brewer 提出來的
Brewer認為在分布式的環境下設計和部署系統時,有3個核心的需求,以一種特殊的關系存在。這里的分布式系統說的是在物理上分布的系統,比如我們常見的web系統。
這3個核心的需求是:Consistency,Availability和Partition Tolerance,賦予了該理論另外一個名字 - CAP。
Consistency:一致性,這個和數據庫ACID的一致性類似,但這里關注的所有數據節點上的數據一致性和正確性,而數據庫的ACID關注的是在在一個事務內,對數據的一些約束。
Availability:可用性,關注的在某個結點的數據是否可用,可以認為某一個節點的系統是否可用,通信故障除外。
Partition Tolerance:分區容忍性,是否可以對數據進行分區。這是考慮到性能和可伸縮性。
為什么不能完全保證這個三點了,個人覺得主要是因為一旦進行分區了,就說明了必須節點之間必須進行通信,涉及到通信,就無法確保在有限的時間內完成指定的行文,如果要求兩個操作之間要完整的進行,因為涉及到通信,肯定存在某一個時刻只完成一部分的業務操作,在通信完成的這一段時間內,數據就是不一致性的。如果要求保證一致性,那么就必須在通信完成這一段時間內保護數據,使得任何訪問這些數據的操作不可用。
如果想保證一致性和可用性,那么數據就不能夠分區。一個簡單的理解就是所有的數據就必須存放在一個數據庫里面,不能進行數據庫拆分。這個對於大數據量,高並發的互聯網應用來說,是不可接受的。
我們可以拿一個簡單的例子來說明:假設一個購物系統,賣家A和賣家B做了一筆交易100元,交易成功了,買家把錢給賣家。
這里面存在兩張表的數據:Trade表Account表 ,涉及到三條數據Trade(100),Account A ,Account B
假設 trade表和account表在一個數據庫,那么只需要使用數據庫的事務,就可以保證一致性,同時不會影響可用性。但是隨着交易量越來越大,我們可以考慮按照業務分庫,把交易庫和account庫單獨分開,這樣就涉及到trade庫和account庫進行通信,也就是存在了分區,那么我們就不可能同時保證可用性和一致性。
我們假設初始狀態
trade(buyer,seller,tradeNo,status) = trade(A,B,20121001,I)
account(accountNo,balance) = account(A,300)
account(accountNo,balance) = account(B,10)
在理想情況下,我們期望的狀態是
trade(buyer,seller,tradeNo,status) = trade(A,B,20121001,S)
account(accountNo,balance) = account(A,200)
account(accountNo,balance) = account(B,110)
但是考慮到一些異常情況
假設在trade(20121001,S)更新完成之前,帳戶A進行扣款之后,帳戶A進行了另外一筆300款錢的交易,把錢消費了,那么就存在一個狀態
trade(buyer,seller,tradeNo,status) = trade(A,B,20121001,S)
account(accountNo,balance) = account(A,0)
account(accountNo,balance) = account(B,10)
產生了數據不一致的狀態
由於這個涉及到資金上的問題,對資金要求比較高,我們必須保證一致性,那么怎么辦,只能在進行trade(A,B,20121001)交易的時候,對於任何A的后續交易請求trade(A,X,X),必須等到A完成之后,才能夠進行處理,也就是說在進行trade(A,B,20121001)的時候,Account(A)的數據是不可用的。
任何架構師在設計分布式的系統的時候,都必須在這三者之間進行取舍。首先就是是否選擇分區,由於在一個數據分區內,根據數據庫的ACID特性,是可以保證一致性的,不會存在可用性和一致性的問題,唯一需要考慮的就是性能問題。對於可用性和一致性,大多數應用就必須保證可用性,畢竟是互聯網應用,犧牲了可用性,相當於間接的影響了用戶體驗,而唯一可以考慮就是一致性了。
犧牲一致性
對於犧牲一致性的情況最多的就是緩存和數據庫的數據同步問題,我們把緩存看做一個數據分區節點,數據庫看作另外一個節點,這兩個節點之間的數據在任何時刻都無法保證一致性的。在web2.0這樣的業務,開心網來舉例子,訪問一個用戶的信息的時候,可以先訪問緩存的數據,但是如果用戶修改了自己的一些信息,首先修改的是數據庫,然后在通知緩存進行更新,這段期間內就會導致的數據不一致,用戶可能訪問的是一個過期的緩存,而不是最新的數據。但是由於這些業務對一致性的要求比較高,不會帶來太大的影響。
異常錯誤檢測和補償
還有一種犧牲一致性的方法就是通過一種錯誤補償機制來進行,可以拿上面購物的例子來說,假設我們把業務邏輯順序調整一下,先扣買家錢,然后更新交易狀態,在把錢打給賣家
我們假設初始狀態
account(accountNo,balance) = account(A,300)
account(accountNo,balance) = account(B,10)
trade(buyer,seller,tradeNo,status) = trade(A,B,20121001,I)
那么有可能出現
account(accountNo,balance) = account(A,200)
trade(buyer,seller,tradeNo,status) = trade(A,B,20121001,S)
account(accountNo,balance) = account(B,10)
那么就出現了A扣款成功,交易狀態也成功了,但是錢沒有打給B,這個時候可以通過一個時候的異常恢復機制,把錢打給B,最終的情況保證了一致性,在一定時間內數據可能是不一致的,但是不會影響太大。
兩階段提交協議
當然,還有一種方式就是我另外一篇文章里面《X/Open DTP-分布式事務模型》里面說的,但是再第一階段和第二階段之間,數據也可不能是一致性的,也可能出現同樣的情況導致異常。而且DTP的分布式事務模型 限制太多,例如必須有實現其功能的相關的容器支持,並且資源管理器也必須實現了XA規范。限制比較多。
國外有的架構師有兩種方案去解決CAP的限制,但是也是比較適合特定的業務,而沒有通用的解決方案,
探知分區->分區內操作->事后補償
就是上面介紹的異常檢測恢復機制,這種機制其實還是有限制,
首先對於分區檢測操作,不同的業務涉及到的分區操作可能不一樣
分區內操作限制:不同的業務對應的約束不一致
事后補償:由於業務約束不一樣,補償方式也不一樣。
所以這只能作為一種思想,不能做一個通用的解決方案
還有就是ebay使用的BASE 思想((basically available, soft state, eventually consistent),這個比較有意思,有時間在寫吧。