數據一致性(二)

咱們流連於事物的表象,知足淺嘗輒止的片刻歡愉,卻幾乎從不久留。咱們在人生的道路上爭先恐後,卻吝於用片刻思考目標和方向。

概述

至今沒有接觸過MySQL多主的狀況,即存在多個MySQL實例同時負責讀寫請求(拋棄只讀庫)。思考後認爲:沒有這麼實現的技術難點在於:數據的一致性得不到保證。此外,還會涉及:html

  1. MySQL採用自增主鍵索引的話,多主之間的數據同步簡直是災難。
  2. 內部鎖機制的優點大打折扣,跨主庫間的鎖應該也是災難級別的吧。

那麼支持分佈式的其餘數據庫又是怎麼搞定這個問題的呢?好比Cassandra,多個節點之間能夠同時處理讀寫請求,那麼它是如何處理節點間數據同步以保證一致性的呢?mysql

MySQL數據的一致性

We think this is an unacceptable burden to place ondevelopers and that consistency problems should be solved at the database level

細細想一想,MySQL自身實現的數據一致性也是至關複雜的。以Innodb舉例,若是經過普通索引執行查詢,首先獲取到的僅僅是主鍵索引,後面還須要經過主鍵索引來獲取完整的記錄。查詢如此,更新亦如此。算法

Master-Slave模式

一般狀況下,MySQL部署都是一主多從。Master做爲更新DB的入口,而Slave的數據經過binlog來進行同步。因此大膽想想,有沒有可能出現一種狀況(假設id=1記錄原始的name值爲neojos):sql

## 第一次同步數據
update s-1 set name="neojos-1" where id = 1;    ## 失敗
update s-2 set name="neojos-1" where id = 1;    ## 成功
update s-3 set name="neojos-1" where id = 1;    ## 成功

## 第二次同步數據
update s-1 set name="neojos-2" where id = 1;    ## 成功
update s-2 set name="neojos-2" where id = 1;    ## 失敗
update s-3 set name="neojos-2" where id = 1;    ## 成功

最後,數據庫從某一個時間點開始,MasterSalve的數據會變得不一致了固然不可能,MySQL在數據同步上作了很是硬的約束。包括Slave_IO_RunningSlave_SQL_Running以及Seconds_Behind_Master等。數據庫

mysql

併發下的數據一致性

MySQL併發下的數據一致性是經過鎖來保證的。併發的請求,誰先拿到X,誰就有修改的權限。鎖相似扮演了一個操做版本號的做用。微信

X IX S IS
X Conflict Conflict Conflict Conflict
IX Conflict Compatible Conflict Compatible
S Conflict Conflict Compatible Compatible
IS Conflict Compatible Compatible Compatible

理解衝突

以數據讀取和寫入爲切入點,引伸出兩個工做中可能可能遇到的衝突問題,並經過加鎖以及設置版本號來避免衝突的發生。網絡

Go的併發問題

下面是一個簡單的Go Test代碼問題:求1-100的累加和。咱們經過Goroutine和最普通的兩個方式分別計算。同時,在代碼的末尾對兩種方式的計算結果進行了比較並打印輸出。數據結構

// 輸出結果每次都是變化的。其中一次:5499 != 5050
func TestSum1To100(t *testing.T) {

    result1 := 0
    result2 := 0
    
  // 併發的進行計算
    var wg sync.WaitGroup
    for i := 1; i <= 100; i++ {
        wg.Add(1)
        go func(m int) {
            defer wg.Done()
            result1 += m
        }(i)
    }

  // 正常的For循環
    for i := 1; i <= 100; i++ {
        result2 += i
    }

    wg.Wait()
    if result1 != result2 {
        t.Fatalf(" %d != %d", result1, result2)
    }
}

併發狀況下,每一個goroutine在讀取result1result1=result1+1的過程當中,沒法保證result1不被別的goroutine所修改。併發

MySQL解決問題的思路來考慮:加鎖。咱們要對讀取result1result1=result1+1的過程進行加鎖,保證這個過程是同步的。異步

一對多狀況

在國內第三方支付(微信/支付寶)場景中,用戶是否支付了某個商品,是經過服務端接受第三方異步回調通知的方式,來做爲判斷依據的。而回調通知存在相應的重試策略,並且都要求冪等處理。

假設下面一個場景,咱們建立了以user_id爲惟一索引的表(user_pay)用於統計該用戶支付成功的次數,以及用戶支付明細表(user_pay_detail),二者是一對多的對應關係。服務端每次收到第三方的支付回調,都在user_pay_detail追加一條新記錄,同時相應的調整user_pay的信息。

table

若是在回調過程當中,存在這樣一個場景:在03-02號收到了支付回調通知,對數據進行了調整。而在03-15號的時候卻又收到了02-01的回調通知(該通知已經在02-01處理過了)。如何保證user_pay中的數據不會被多加一次?

固然,解決辦法很是簡單。其中一個解決辦法即是:在user_pay中記錄上一次回調通知的時間戳,以此做爲這行記錄的版本號,後續也只有大於該版本號的通知纔會被處理。

CAP

瞭解一下分佈式的環境下的CAP定理,這裏主要強調一下:Consistency。在分佈式系統中,存在多節點同時對外提供讀寫服務,數據存儲多份副本的狀況。那麼,這些節點在同步數據的過程當中,可能會由於網絡或者機器的緣由致使數據同步失敗,從而形成各個節點數據不一致的狀況發生。

cap

Last-write-wins

Last-write-wins表示在對一條記錄應用多個修改的時候,最後的改動會覆蓋掉以前的操做,返回給客戶端的記錄都以最後一次的改動爲準。

這也是分佈式系統解決衝突的一個策略。基於timestamp的版本控制系統,好比HBase。每次操做都會給記錄附加一個timestamp的版本號。這樣一來,當某些數據發生衝突時,咱們就能夠簡單的認爲最新的記錄是準確的。

但實際上,基於Last-write-wins的策略並不必定是正確的。好比多個節點對同一條記錄進行修改。首先,節點服務上的時間鐘不是嚴格相等的;其次,客戶端發出的請求時間,跟到達節點服務的時間也是沒有任何聯繫的。

vector clock

先說一下須要用到向量時鐘的場景。咱們在寫數據時候,常常但願數據不要存儲在單點。如db1,db2均可以同時提供寫服務,而且都存有全量數據。而client不論是寫哪個db都不用擔憂數據寫亂問題。可是現實場景中每每會碰到並行同時修改。致使db1和db2數據不一致。因而乎就有人想出一些解決策略。向量時鐘算是其中一種。簡單易懂。可是並無完全解決衝突問題,現實分佈式存儲補充了不少額外技巧。

文章vector clock 向量時鐘算法解釋的實在是太完美了,這裏就不冗餘解釋了。下圖是一個分佈式服務的示例,各個節點均可以提供讀寫服務。

peer-to-perr

Cassandra的思路

KV類型的分佈式數據庫在存儲對象時,存儲的是對象序列化的結果。舉個例子:

  1. 有一個jbellis的對象,初始值爲{'email': 'jbellis@example.com', 'phone': '555-5555'},咱們認爲這個初始值爲V0
  2. 以後修改了jbellis的郵件地址,這時候值記做V1{'email': 'jbellis@illustration.com', 'phone': '555-5555'}。但由於網絡或其餘問題,在同步數據到其餘節點的時候失敗了,致使該修改僅僅被成功寫到了其中一個節點上
  3. 接着,咱們更新jbellis中的電話信息。但咱們讀取到的jbellisV0,因此,修改後的V3{'email': 'jbellis@example.com', 'phone': '444-4444'}

Last-write-wins的角度考慮,咱們採納了V2的值,而丟棄了V1。簡單直接,但不必定正確;

vector clock的角度來看,當同步V2到其餘節點時,就會發生數據衝突,由於當前節點的版本爲[V0, V2],而其餘節點的版本是[V0, V1],這時候就須要依靠具體的衝突解決策略。

Cassandra在存儲數據結構上作了處理,將對象中emailphone單獨存儲,並給每一個column都指定一個獨立的timestamp做爲版本號。這樣,當衝突發生時,就能夠簡單運用Last-write-wins策略了。

A column is the basic data structure of Cassandra with three values, namely key or column name, value, and a timestamp. Given below is the structure of a column.

cassandra-data-mode

總結

實事求是,具體問題具體分析。請記住,對你而言,上面這些方法可能都不合適。

參考文章:

  1. vector clock 向量時鐘算法
  2. Why Cassandra doesn’t need vector clocks
  3. Cassandra - Data Model
相關文章
相關標籤/搜索