大規模業務數據的方案一般都是分庫分表,而且一些場景會同時跨多個庫發生業務。在 "分布式事務概述"一文中,我們講到事務消息的MQ補償方案是目前公認的較為理想的分布式事務解決方案,實施成本也較高,今天我們即講述這種補償方案的最終一致性落地細節。
一、消息補償流程
回顧之前我們提到,消息中間件在分布式系統中的主要作用:異步通訊、解耦、並發緩沖。基於MQ實現分布式事務一致性是一種異步確保型的實現方案,將同步阻塞的事務變成異步的,避免對數據庫事務的爭用。大致流程如下:
基於消息補償的分布式事務方案往往用在高並發場景下,將一個分布式事務拆成一個消息事務(A系統的本地操作+發消息)+B系統的本地操作,其中B系統的操作由消息驅動,只要消息事務成功,那么A操作一定成功,消息也一定發出來了,這時候B會收到消息去執行本地操作,如果本地操作失敗,消息會重投,直到B操作成功,這樣就變相地實現了A與B的分布式事務。
二、事務補償應用
2.1 假設業務場景
"假設用戶a從自己的余額中向用戶c余額中轉100元錢。用戶a余額存放在A庫中,用戶c余額存在C庫中"
由於分庫的存在,破壞了事務的原子性,如果沒有分布式事務,轉賬過程可能會出現如下的問題:
第1種情況,應用寫隊列超時導致重發了消息.那么結果是a本來向c轉賬100元.結果卻轉賬了200元..
第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.1 . 從上述流程來看,將本地事務和發消息放在了一個分布式事務里,需要保證要么本地操作成功並且對外發消息成功,要么兩者都失敗。實際中,支持這個原子操作的消息中間件並不多(rabbitMq、kafkaMq等都不支持),阿里開源的RocketMQ支持這一特性。
3.2. 隨着業務增多,transaction_log、message_log表會比較大,這2張表也需要考慮分庫。否則瓶頸會特別大。
3.3. 其他一些性能問題,如消費端檢查message_log時,怎樣防止消費重復消息等。
---------------------
作者:TY1972
來源:CSDN
原文:https://blog.csdn.net/meiliangdeng1990/article/details/81902301?utm_source=copy
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!