咱們流連於事物的表象,知足淺嘗輒止的片刻歡愉,卻幾乎從不久留。咱們在人生的道路上爭先恐後,卻吝於用片刻思考目標和方向。
至今沒有接觸過MySQL
多主的狀況,即存在多個MySQL
實例同時負責讀寫請求(拋棄只讀庫)。思考後認爲:沒有這麼實現的技術難點在於:數據的一致性得不到保證。此外,還會涉及:html
MySQL
採用自增主鍵索引的話,多主之間的數據同步簡直是災難。那麼支持分佈式的其餘數據庫又是怎麼搞定這個問題的呢?好比Cassandra
,多個節點之間能夠同時處理讀寫請求,那麼它是如何處理節點間數據同步以保證一致性的呢?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; ## 成功
最後,數據庫從某一個時間點開始,Master
和Salve
的數據會變得不一致了固然不可能,MySQL
在數據同步上作了很是硬的約束。包括Slave_IO_Running
、Slave_SQL_Running
以及Seconds_Behind_Master
等。數據庫
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 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
在讀取result1
到result1=result1+1
的過程當中,沒法保證result1
不被別的goroutine
所修改。併發
從MySQL
解決問題的思路來考慮:加鎖。咱們要對讀取result1
到result1=result1+1
的過程進行加鎖,保證這個過程是同步的。異步
在國內第三方支付(微信/支付寶)場景中,用戶是否支付了某個商品,是經過服務端接受第三方異步回調通知的方式,來做爲判斷依據的。而回調通知存在相應的重試策略,並且都要求冪等處理。
假設下面一個場景,咱們建立了以user_id
爲惟一索引的表(user_pay
)用於統計該用戶支付成功的次數,以及用戶支付明細表(user_pay_detail
),二者是一對多的對應關係。服務端每次收到第三方的支付回調,都在user_pay_detail
追加一條新記錄,同時相應的調整user_pay
的信息。
若是在回調過程當中,存在這樣一個場景:在03-02
號收到了支付回調通知,對數據進行了調整。而在03-15
號的時候卻又收到了02-01
的回調通知(該通知已經在02-01
處理過了)。如何保證user_pay
中的數據不會被多加一次?
固然,解決辦法很是簡單。其中一個解決辦法即是:在user_pay
中記錄上一次回調通知的時間戳,以此做爲這行記錄的版本號,後續也只有大於該版本號的通知纔會被處理。
CAP
瞭解一下分佈式的環境下的CAP
定理,這裏主要強調一下:Consistency
。在分佈式系統中,存在多節點同時對外提供讀寫服務,數據存儲多份副本的狀況。那麼,這些節點在同步數據的過程當中,可能會由於網絡或者機器的緣由致使數據同步失敗,從而形成各個節點數據不一致的狀況發生。
Last-write-wins
Last-write-wins
表示在對一條記錄應用多個修改的時候,最後的改動會覆蓋掉以前的操做,返回給客戶端的記錄都以最後一次的改動爲準。
這也是分佈式系統解決衝突的一個策略。基於timestamp
的版本控制系統,好比HBase
。每次操做都會給記錄附加一個timestamp
的版本號。這樣一來,當某些數據發生衝突時,咱們就能夠簡單的認爲最新的記錄是準確的。
但實際上,基於Last-write-wins
的策略並不必定是正確的。好比多個節點對同一條記錄進行修改。首先,節點服務上的時間鐘不是嚴格相等的;其次,客戶端發出的請求時間,跟到達節點服務的時間也是沒有任何聯繫的。
vector clock
先說一下須要用到向量時鐘的場景。咱們在寫數據時候,常常但願數據不要存儲在單點。如db1,db2均可以同時提供寫服務,而且都存有全量數據。而client不論是寫哪個db都不用擔憂數據寫亂問題。可是現實場景中每每會碰到並行同時修改。致使db1和db2數據不一致。因而乎就有人想出一些解決策略。向量時鐘算是其中一種。簡單易懂。可是並無完全解決衝突問題,現實分佈式存儲補充了不少額外技巧。
文章vector clock 向量時鐘算法解釋的實在是太完美了,這裏就不冗餘解釋了。下圖是一個分佈式服務的示例,各個節點均可以提供讀寫服務。
Cassandra
的思路KV
類型的分佈式數據庫在存儲對象時,存儲的是對象序列化的結果。舉個例子:
jbellis
的對象,初始值爲{'email': 'jbellis@example.com', 'phone': '555-5555'}
,咱們認爲這個初始值爲V0
jbellis
的郵件地址,這時候值記做V1
,{'email': 'jbellis@illustration.com', 'phone': '555-5555'}
。但由於網絡或其餘問題,在同步數據到其餘節點的時候失敗了,致使該修改僅僅被成功寫到了其中一個節點上jbellis
中的電話信息。但咱們讀取到的jbellis
是V0
,因此,修改後的V3
爲{'email': 'jbellis@example.com', 'phone': '444-4444'}
從Last-write-wins
的角度考慮,咱們採納了V2
的值,而丟棄了V1
。簡單直接,但不必定正確;
從vector clock
的角度來看,當同步V2
到其餘節點時,就會發生數據衝突,由於當前節點的版本爲[V0, V2]
,而其餘節點的版本是[V0, V1]
,這時候就須要依靠具體的衝突解決策略。
而Cassandra
在存儲數據結構上作了處理,將對象中email
和phone
單獨存儲,並給每一個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.
實事求是,具體問題具體分析。請記住,對你而言,上面這些方法可能都不合適。
參考文章: