談談資料一致性

咖啡拿鐵發表於2019-02-25

什麼是資料一致性

資料一致性這個單詞在平常開發中,或者各種文章中都能經常看見,我們常常聽見什麼東西資料不一致了,造成了一定的損失,趕快修復一下。但是很多同學對一致性具體代表什麼意思,他有什麼作用依然不是很瞭解,今天我們就來聊聊一致性。

一般來說資料一致性我們可以分成三類,時間點一致性,事務一致性,應用一致性。

時間點一致性(Point in time Consistency)

時間點一致性我覺得也可以叫做副本一致性,時間點一致性的定義為:

如果所有相關的資料元件在任意時刻都是一致的,那麼可以稱作為時間點一致性。

這個定義如果你瞭解過CAP理論的話,那麼你應該不會太陌生。(如果不熟悉的同學可以看我這篇文章分散式事務)

在CAP中的C的定義為對某個指定的客戶端來說,讀操作能返回最新的寫操作。我們可以發現時間點並沒有規定一致的需要保證是最新的,所以可能有同學會提出疑問時間點一致性的範圍比CAP中的一致性範圍要大一點。其實細想一下如果我們某個資料元件更新了資料,如果為了滿足時間點一致性,那麼我們所有相關的資料元件的資料都是一致的,所以其他的資料都會變為最新的,那麼其實就和CAP是一樣的,都需要滿足如果在某個節點更新了資料,那麼在其他節點如果都能讀取到這個最新的資料。

當然CAP和時間點一致性並不是完全的一致:時間點一致性的定義中要求所有資料元件的資料在任意時刻都是完全一致的,但是一般來說資訊傳播的速度最大是光速,其實並不能達到任意時刻一致,總有一定的時間不一致,對於我們CAP中的一致性來說只要達到讀取到最新資料即可,達到這種情況並不需要嚴格的任意時間一致。

這裡我們還需要注意的是這個並不總是用於分散式系統中的,在我們單個機器中如果有多核處理器,我們再任意時刻訪問不同處理器對同一變數資料都需要是一致的也可以同樣適用。

事務一致性

一致性不僅僅可以表示資料的同時變更或相同性,還可以用來表示約束,而我們的事務一致性就是其中的一種。事務一致性就是我們平時所說的ACID中的C,其定義如下:

事務的一致性指的是在一個事務執行之前和執行之後資料庫都必須處於一致性狀態。如果事務成功地完成,那麼系統中所有變化將正確地應用,系統處於有效狀態。如果在事務中出現錯誤,那麼系統中的所有變化將自動地回滾,系統返回到原始狀態。

事務一致性只能存在在事務開始前的和事務完成之後,在事務過程中資料有可能不一致。舉個例子:比如A轉100元給B,A扣減100,B加上100,在事務開始前和事務完成之後都能保證他們的帳是對上的,那麼這就是事務一致性。但是在事務過程中有可能會出現A扣減了100元,B沒有加上100元的情況,這就是不一致。

這裡一般的初學者都會把CAP和ACID中的C都會誤解成一樣的含義,其實他們其中一個表示的資料的相同,而另一個是用來表示某種約束。

應用一致性

應用一致性可以看做是約束一致性中的一種。上面的事務一致性代表的是單一資料來源,如果資料來源是多個,比如資料來源有多個資料庫,檔案系統,快取等。那麼就需要我們應用一致性,這裡也看做是分散式事務一致性。

在應用程式中涉及多個不同的單機事務,只有在所有的單機事務完成之前和完成之後,資料是完全一致的。比如給使用者傳送券和積分,券服務和積分服務是兩個服務,他們各自有自己單機事務,這兩個單機單機事務開始前和完成後都能保證使用者的帳是對應上的。但是在這兩個單機事務執行過程有可能會出現只送了券,沒有送積分的情況,有可能狀態不正確。

這三種一致性可以簡單的看做兩類,一個是資料副本一致,另一個是資料約束一致。接下來我更多的會介紹資料副本的一致的型別,而資料約束的一致,可以參考我之前寫過的分散式事務的那篇文章。

一致性的模型

再寫這篇文章之前,我一直以為一致性就那麼幾個常聽說的,強一致,弱一致,最終一致。再查詢了一些文獻資料之後發現一致性的型別真的是非常的多,這裡我挑選一些比較重要的

如果有人問你你知道哪些一致性模型呢?很多人馬上答出,強一致,最終一致。其實一致性的模型遠遠不止這麼點,在《Operational Characterization of Weak Memory Consistency Models》這篇論文當中描述了15種弱記憶體一致模型,而在維基百科對記憶體模型的描述還有更多。

很多一致性的模型最開始是用來描述記憶體是否一致的,也就是最開始並不是運用於分散式系統當中的。如果我們的機器是單核的話,那麼他的記憶體一定是強一致的。如果我們的機器是多核的話,那麼由於處理器並不是直接訪問的記憶體而是訪問的處理器獨享的快取,那麼就有可能會出現不一致。再分散式中我們的每個節點其實就可以看成一個獨立的處理器,而我們最初運用於記憶體一致性模型,也可以運用於我們分散式系統當中。下面我會從強到弱講講一些常見的一致性模型。

線性一致性

線性一致性又叫做原子一致性,強一致性。線性一致性可以看做只有一個單核處理器,或者可以看做只有一個資料副本,並且所有操作都是原子的。在可線性化的分散式系統中,如果某個節點更新了資料,那麼在其他節點如果都能讀取到這個最新的資料。可以看見線性一致性和我們的CAP中的C是一致的。

舉個非線性一致性的例子,比如有個秒殺活動,你和你的朋友同時去搶購一樣東西,有可能他那裡的庫存已經沒了,但是在你手機上顯示還有幾件,這個就違反了線性一致性,哪怕過了一會你的手機也顯示庫存沒有,也依然是違反了。

線性一致性有什麼作用呢?在《DDIA》這本書中描述了下面3個作用:

  • 加鎖與主節點選舉:主從複製系統需要確保只有一個主節點,否則會產生腦裂。選舉新的主節點一般是使用鎖:每個啟動的節點都需要獲得鎖。而這個鎖就需要滿足可線性化,讓所有的節點都同時同意哪個節點有鎖。我們的ZooKeeper就可以用來提供分散式鎖功能,那麼我們就可以說ZooKeeper是滿足線性一致性的嗎?這個只能說說對了一部分,後面再順序一致性的時候會對ZK是什麼一致性再次說明。
  • 約束與唯一性保證:比如同一個檔案目錄下不允許有兩個相同的檔名,資料庫主鍵不能重複,這些都需要線性化。其實這些本質和加鎖類似,比如相同的檔名,那其實就是對這個檔名去做一個加鎖操作,然後去儲存,後儲存的自然會出錯。
  • 跨通道的時間依賴:之前的那個搶購的那個例子為什麼會被違反呢?原因是因為我們通過朋友告知這個通道,讓我們提前知道了這個貨物已經賣完。同樣的如果我們計算機中出現了多個通道。舉個例子,在使用者交易的場景下,使用者使用了50元,那麼會在其餘額中扣減50元,這個時候把這個事件作為一個訊息佇列給傳送出去,然後簡訊服務會查詢使用者的餘額然後進行傳送簡訊,如果餘額資料庫的從庫這個時候還沒有更新資料,那麼這個簡訊就有可能會取到使用者舊的餘額。這裡出現不一致的原因就是因為多了一個通道,就和我們上面朋友告知我們賣完的通道一樣。解決這個辦法可以控制某一個通道,比如說將這個使用者的餘額作為引數給傳進去,或者只讀主庫。秒殺的那個例子中,你可以不要自己的手機,去用朋友的手機。

談談資料一致性

順序一致性

順序一致性弱於嚴格一致性。對變數的寫操作不一定要在瞬間看到,但是,不同處理器對變數的寫操作必須在所有處理器上以相同的順序看到,這裡處理器再分散式系統中可以換成不同的節點。

這裡我們又再回到Zookeeper到底是什麼一致性?有很多面試題都會問到Zookeeper是CP還是AP呢?很多人都會回答到Zookeeper是CP,其實這個回答並不是很嚴謹的,我們從線性一致性中知道CAP中的一致性指的是線性一致性,那我們就可以說Zookeeper是線性一致性的嗎?答案是否定的。當我們寫入一個值的時候,會交由Leader去處理,Zab協議只需要保證半數從節點成功即可,那麼就會有節點的資料是老的資料,這樣客戶端就有可能讀出的資料並非是最新的從而破壞了線性一致性。

Zookeeper其實實現的是順序一致性,在ZK中利用zxid(ZooKeeper Transaction Id),實現了整體順序一致性,當然也可以認為Zookeeper的的寫是線性一致性,讀是順序一致性。從節點通過zxid順序的接收leader的廣播,所以ZK不能保證所有的資訊馬上看到,但是最終都會看到。當然Zookeeper其實可以實現線性化,在ZK中有一個sync()命令,只要我們每次讀的時候都去呼叫sync()強制同步資料,那麼我們都能保證其是最新的。

順序一致性是由Lamport(Paxos演算法的作者)提出的,最開始只用來定義多處理記憶體的一致性,在Lamport的《How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs》中其定義了什麼是順序一致性:

the result of any execution is the same as if the operations of all the processors were executed in some sequential order, and the operations of each individual processor appear in this sequence in the order specified by its program.

這句話的大致意思是多處理器的執行效果和單個處理器的執行效果是一樣的,每個獨立的處理器的操作都會按照指定的順序出現在操作佇列。這個最開始是用於併發程式設計的,但是讓多處理器的執行變得和單處理器的確是沒啥作用,後來就用於分散式系統當中。在ZK中所有的寫操作都會交給Leader節點去做,並且所有操作的更新都會根據zxid的順序進行更新,這裡就是上面所說的指定的順序,這個佇列就是按照zxid的順序。

因果一致性

因果一致性是弱於順序一致性的一致性模型,順序一致性要求所有的操作的順序都必須按照某個單個處理器(節點)的順序,而因果一致性只需要滿足有因果關係的操作是順序一致性即可。

怎麼理解因果關係呢?簡單來說如果有人問你一個問題,那麼你給出答案,這兩個就是因果關係,但如果你給出答案再問題之前,那麼這個就違反了因果關係。 舉個簡單的例子如果節點1更新了資料A,節點2讀取資料A,並更新資料B,這裡的資料B有可能是根據資料A計算出來的,所有具備因果關係,但是如果節點3看到的是先更新的B,再更新的A那麼就破壞了因果一致性。

談談資料一致性

處理器一致性

處理器一致性是更加弱的一致性模型,他只需要保證處理器看到某個處理器或者多個不同處理對相同位置的寫入都是一致的。不需要考慮因果關係,而是對同一個記憶體或者同一個資料更新需要看到一致的順序。

FIFO一致性

FIFO一致性是比處理器一致性還更加弱的一種,它不需要保證對相同位置的寫入是一致的。 是指在一個處理器上完成的所有寫操作,將會被以它實際發生的順序通知給所有其它的處理器;但是在不同處理器上完成的寫操作也許會被其它處理器以不同於實際執行的順序所看到。這個在分散式系統中反映了網路中不同節點的延遲可能是不相同的。為了說明其和處理器一致性不同有如下例子:

談談資料一致性
上面這個圖中,可以發現是違反了處理器一致性的,為什麼呢因為寫入順序是w(x)1,w(x)2而,p4應該是先R(x)1再R(x)2。但是這個符合FIFO一致性,FIFO只需要把自己的發生順序通知給其他的處理器或者節點,不需要保證同一個值寫入順序是一致的。

最終一致性

其實除了強一致以外,其他的一致性都可以看作為最終一致性,只是根據一致性不同模型的不同要求又衍生出了很多具體一致性模型。當然最簡單的最終一致性,是不需要關注中間變化的順序,只需要保證在某個時間點一致即可。只是這個某個時間點需要根據不同的系統,不同業務再去衡量。再最終一致性完成之前,有可能返回任何的值,不會對這些值做任何順序保證。

BASE理論中的E就是最終一致。

一致性模型有什麼用

上面介紹了這麼多一致性模型,我們瞭解到越強的一致性他的約束條件就越多,如果我們實現的話成本那麼也就會越大。可以看見ZK如果想實現完全的線性一致性,那麼他就需要隨時都呼叫sync()去進行同步資料。

再我們真實的場景中我們資料庫的主從複製模型(通過binlog複製也是順序一致性),從庫的很大作用就是為了緩解主庫的讀壓力,如果我們想盲目的達到線性化一致性,那麼就必須去訪問主庫,這樣我們的從庫的意義就微乎其微了。

所以根據不同的系統的模型,不同的業務要求,我們對於一致性的要求是不同的,所以我們瞭解這些一致性的模型是有很多必要的。

總結

這篇文章主要是介紹了什麼是一致性,包括很多一致性模型,這裡少講了兩個一致性事務一致性和應用一致性,有興趣的可以閱讀分散式事務。還有一個值得一提的是,談到一致性其實就離不開共識,因為當資料副本有多個的時候,到底選擇誰,如何選擇才是正確的,這個有興趣的同學可以自行查閱一些資料,比如Raft,Paxos和Zab等。

最後也再給大家幾個問題:

  1. ZK到底是一致性模型?
  2. Mysql主從是什麼一致性模型?
  3. Mysql主主是什麼一致性模型?
  4. 你常用的一致性模型是什麼?

最後這篇文章被我收錄於JGrowing-CaseStudy篇,一個全面,優秀,由社群一起共建的Java學習路線,如果您想參與開源專案的維護,可以一起共建,github地址為:github.com/javagrowing… 麻煩給個小星星喲。

歡迎留言和我討論,當然最好是關注我的公眾號和我一起探討哦,如果大家覺得這篇文章對你有幫助,你的關注和轉發是對我最大的支援,O(∩_∩)O:

談談資料一致性

參考文件:

  • 如何理解Zookeeper的順序一致性:blog.csdn.net/cadem/artic…

  • How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs,Lamprot

  • Operational Characterization of Weak Memory Consistency Models:mp.weixin.qq.com/s/gg4q_53ei…

  • Designing Data-Intensive Applications, Martin Kleppman

相關文章