1. 程式人生 > >MySQL分庫分表——保持資料一致性

MySQL分庫分表——保持資料一致性

MySQL處理大規模業務資料的方案一般都是分庫分表.

最開始一般都選擇垂直拆分.
比如電商網站,可能按照家電,圖書,母嬰等商品分類進行拆分.
這樣做的好處是拆分簡單,並且沒有破壞資料庫事務.

但是隨著業務的增長,比如圖書分類的訂單資料表已經到達了10個T的規模.
就需要考慮做水平拆分了.把邏輯上一個表的資料,分別存放到不同的資料庫伺服器.
水平拆分的好處是
    多個數據庫伺服器分擔CPU,記憶體,網路頻寬的壓力.
    多個數據庫伺服器分擔備份、恢復的壓力.
缺點是
    破壞了原生的資料庫事務.如果使用分散式事務,則會拖累資料庫效能.
    增加了運維管理的負擔.原來管一臺伺服器就行了,現在得管一堆伺服器.

水平拆分的三種主要方式
1.Hash拆分.比如按照 userId mod 64.將資料分佈在64個伺服器上
2.範圍拆分.比如每臺伺服器計劃存放一個億的資料,先將資料寫入伺服器A.一旦伺服器A寫滿,則將資料寫入伺服器B,以此類推.
這種方式的好處是擴充套件方便.資料在各個伺服器上分佈均勻.
3.路由表.自定義分佈方式.
    

呂海波老師SACC2015的內容.
用事務補償的方式,實現最終一致性.

下圖是水平拆分之後的結構. 假設使用者A向B轉賬100元.



由於水平拆分破壞了原有的事務.一個轉賬的業務,可能遇到如下的幾個情況.


第一種情況,應用寫佇列超時導致重發了訊息.那麼結果是A本來向B轉賬100元.結果卻轉賬了200元..
第二種情況,應用將訊息成功寫入佇列,但是佇列伺服器掛了.結果是A向B轉賬失敗.
第三種情況,中間層(佇列的消費者)將訊息取出,修改A的賬戶餘額,但是使用者A的庫掛了,導致事務失敗.結果是A向B轉賬失敗.
第四種情況,中間層已經成功修改了使用者A的賬戶餘額,但是在修改B使用者餘額的時候,使用者B的資料庫掛了。結果是使用者A的錢扣了,但是使用者B的錢沒有增加.
第五種情況.中間層從佇列拿到了訊息,但是還未及處理,中間層本身掛了..

最終一致性.
1.應用先將本次事務的業務日誌寫入業務日誌的資料庫,暫不提交
然後,向佇列傳送兩個訊息.一個訊息是使用者A -100元,另一個訊息是使用者B +100元.
確保兩個訊息都成功入隊,則提交業務日誌的事務,獲取全域性事務ID(tran_id).一旦有任何異常,回滾事務.

提交了事務,應用則可以直接返回.提示使用者交易完成.


2.中間層獲取訊息.先連線使用者A的資料庫.
查詢業務日誌表(tran_log),如果沒有該全域性事務ID,則不予處理.(確認有這個全域性事務,才處理)
查詢訊息日誌表(msg_log),如果存在記錄,則不予處理.(防止訊息超時重發)
然後,開始事務.
先update使用者A,減100元.
再寫訊息日誌表,記錄本次處理
最後提交事務.


3.中間層連線使用者B的資料庫,做相同的操作.

每隔5分鐘,檢查tran_log和msg_log.如果有不一致的情況,則進行事務補償.

呂老師說到這裡的時候,我覺得tran_log應該在如下的位置,然後作為各個底層庫的Master.
用MySQL非同步複製,將tran_log複製到使用者A、B所在的庫.

但是當面問呂老師,他說他們不是這麼弄的.因為這個tran_log的資料量也是非常巨大.
他們把tran_log放在了底層庫,但是我實在想不出來這樣怎麼弄.
可惜當時的環境也不容許我再刨根問底了.
不過留著自己琢磨,也挺有意思.

如果按照我的這個方式設計,後期問題會比較多,一個是tran_log資料量巨大.另外,tran_log所在的資料庫容易產生瓶頸.