大規模業務數據的方案通常都是分庫分表,並且一些場景會同時跨多個庫發生業務。在 "分佈式事務概述"一文中,咱們講到事務消息的MQ補償方案是目前公認的較爲理想的分佈式事務解決方案,實施成本也較高,今天咱們即講述這種補償方案的最終一致性落地細節。數據庫
1、消息補償流程服務器
回顧以前咱們提到,消息中間件在分佈式系統中的主要做用:異步通信、解耦、併發緩衝。基於MQ實現分佈式事務一致性是一種異步確保型的實現方案,將同步阻塞的事務變成異步的,避免對數據庫事務的爭用。大體流程以下:併發
基於消息補償的分佈式事務方案每每用在高併發場景下,將一個分佈式事務拆成一個消息事務(A系統的本地操做+發消息)+B系統的本地操做,其中B系統的操做由消息驅動,只要消息事務成功,那麼A操做必定成功,消息也必定發出來了,這時候B會收到消息去執行本地操做,若是本地操做失敗,消息會重投,直到B操做成功,這樣就變相地實現了A與B的分佈式事務。異步
2、事務補償應用分佈式
2.1 假設業務場景高併發
"假設用戶a從本身的餘額中向用戶c餘額中轉100元錢。用戶a餘額存放在A庫中,用戶c餘額存在C庫中"工具
因爲分庫的存在,破壞了事務的原子性,若是沒有分佈式事務,轉帳過程可能會出現以下的問題:性能
第1種狀況,應用寫隊列超時致使重發了消息.那麼結果是a原本向c轉帳100元.結果卻轉帳了200元...net
第2種狀況,應用將消息成功寫入隊列,可是隊列服務器掛了.結果是a向c轉帳失敗.日誌
第3種狀況,中間層(隊列的消費者)將消息取出,修改a的帳戶餘額,可是用戶a的庫掛了,致使事務失敗.結果是a向c轉帳失敗.
第4種狀況,中間層已經成功修改了用戶a的帳戶餘額,可是在修改c用戶餘額的時候,用戶c的數據庫掛了。結果是用戶a的錢扣了,可是用戶c的錢沒有增長.
第5種狀況.中間層從隊列拿到了消息,可是還未及處理,中間層自己掛了..
2.2 基於隊列事務的最終一致性解決方法
須要的前置工具或表:
1. 分佈式ID生成器
2. transaction_log(tran_id,a,c,money),事務業務日誌表
3. message_log(tran_id,account,money),消息日誌表
結合"消息補償流程"中的流程圖,整體過程以下:
1. 應用經過ID生成器生成事務id,將本次事務日誌寫入事務業務日誌表,暫不提交。
2. 向隊列發送兩個消息.一個消息是用戶a -100元,另外一消息是用戶c +100元,兩個消息須要帶上第一步獲得的tran_id。確保兩個消息都成功入隊列,則提交業務日誌的事務。一旦有任何異常,回滾事務。提交了事務,應用則能夠直接返回.提示用戶交易完成。
3. 中間層獲取消息。先鏈接用戶a的數據庫.查詢transaction_log表,若是沒有該全局事務ID,則不予處理.(確認有這個全局事務,才處理),查詢message_log表,若是存在記錄,則不予處理.(防止消息超時重發)。開始消費端本地事務.update用戶a餘額,減100元.再寫message_log表,記錄本次處理,最後本地提交事務。
4. 中間層鏈接用戶c的數據庫,作相同的操做。
5. 一個定時任務,每隔5分鐘,檢查transaction_log和message_log中是否存在不一致的tran_id.若是有不一致的狀況,則進行事務補償或人工處理。
6. 完成完成轉帳。
3、潛在問題
3.1 . 從上述流程來看,將本地事務和發消息放在了一個分佈式事務裏,須要保證要麼本地操做成功而且對外發消息成功,要麼二者都失敗。實際中,支持這個原子操做的消息中間件並很少(rabbitMq、kafkaMq等都不支持),阿里開源的RocketMQ支持這一特性。
3.2. 隨着業務增多,transaction_log、message_log表會比較大,這2張表也須要考慮分庫。不然瓶頸會特別大。