微服务如何解决分布式事务
在系統(tǒng)變的復(fù)雜后,分布式、微服務(wù)等架構(gòu)技術(shù),就要考慮到應(yīng)用在系統(tǒng)中了。尤其數(shù)據(jù)量大了后,就需要對數(shù)據(jù)庫進行拆分。
如:注冊的用戶數(shù)據(jù),量大了后,就需要考慮分庫分表
一旦數(shù)據(jù)庫進行了分拆,那就出現(xiàn)很多頭疼的問題,其中之一就是事務(wù)問題。那我們就來看看問題是怎么出現(xiàn)的?
進行數(shù)據(jù)拆分后,就類似上面的架構(gòu)。
上圖中我們就拿用戶的數(shù)據(jù)進行舉例,用戶量一旦幾千萬時,就需要進行分庫分表;上圖就分了3個庫,每個庫都保證了高可用。
這樣的架構(gòu)設(shè)計,會遇到事務(wù)問題,我們來看看具體的業(yè)務(wù)場景:用戶A轉(zhuǎn)賬100元給用戶B,這個業(yè)務(wù)比較簡單,我們來分析一下里面具體的步驟:
1、用戶A的賬戶先扣除100元
2、再把用戶B的賬戶加100元
邏輯很簡單,上偽代碼
代碼也是比較清晰的,感覺沒有什么問題,那我們來分析一下問題在哪?
問題
我們看到在轉(zhuǎn)賬業(yè)務(wù)中,有兩步,一個是操作用戶A扣錢,一個是操作用戶B加錢
如果在同一個數(shù)據(jù)庫中進行,可以保證這兩步操作,要么同時成功,要么同時不成功。這樣就保證了轉(zhuǎn)賬的數(shù)據(jù)一致性。
但是如果用戶A的數(shù)據(jù)在集群A中,用戶B在集群B中呢?因為他們不在同一個事務(wù)中;如用戶A扣款成功,但用戶B加錢失敗了;那就坑了,數(shù)據(jù)不完整了。
類似這種問題在微服務(wù)架構(gòu)會更多,因為各個服務(wù)都是獨立的模塊,都是遠程調(diào)用,都沒法在同一個事務(wù)中,都會遇到事務(wù)問題。
那怎么解決?網(wǎng)上有一些方案,如:兩階段提交,TCC等,還有常用就是最終一致性方案。就給大家介紹一下如何利用消息中間件去解決。那我們就把方案調(diào)整一下,加入消息中間件,看看如何優(yōu)化。
消息中間件方案
上圖就是利用消息中間件的方式,把扣款業(yè)務(wù)和加錢業(yè)務(wù)異步化,扣款成功后,發(fā)送“扣款成功消息”到消息中間件;加錢業(yè)務(wù)訂閱“扣款成功消息”,再對用戶B加錢
系統(tǒng)怎么知道給用戶B加錢呢?是消息體里面包含了源賬戶和目標賬戶ID,以及錢數(shù)
這個時候也許小伙伴們會問,應(yīng)該也有問題吧:場景一:先扣款后發(fā)消息
先扣款再發(fā)送消息,萬一發(fā)送消息失敗了,那用戶B就沒法加錢
那把順序調(diào)整一下場景二:先發(fā)消息,后扣款
扣款成功消息發(fā)送成功,但用戶A扣款失敗,可加錢業(yè)務(wù)訂閱到了消息,用戶B加了錢
大家應(yīng)該發(fā)現(xiàn)了問題所在,也就是沒法保證扣款和發(fā)送消息,同時成功,或同時失敗;導(dǎo)致數(shù)據(jù)不一致。
RocketMq事務(wù)方案
因為上面的問題,RocketMq消息中間件把消息分為兩個階段:Prepared階段和確認階段Prepared階段(預(yù)備階段)
該階段主要發(fā)一個消息到rocketmq,但該消息只儲存在commitlog中,但consumeQueue中不可見,也就是消費端(訂閱端)無法看到此消息。
commit/rollback階段(確認階段)
該階段主要是把prepared消息保存到consumeQueue中,即讓消費端可以看到此消息,也就是可以消費此消息。
我們用圖來說明下:
整個流程:
1、在扣款之前,先發(fā)送預(yù)備消息2、發(fā)送預(yù)備消息成功后,執(zhí)行本地扣款事務(wù)3、扣款成功后,再發(fā)送確認消息4、消息端(加錢業(yè)務(wù))可以看到確認消息,消費此消息,進行加錢
確認消息說明
注意:上面的確認消息可以為commit消息,可以被訂閱者消費;也可以是Rollback消息,即執(zhí)行本地扣款事務(wù)失敗后,提交rollback消息,即刪除那個預(yù)備消息,訂閱者無法消費
我們來分析一下異常場景:
異常1:如果發(fā)送預(yù)備消息失敗,下面的流程不會走下去;這個是正常的異常2:如果發(fā)送預(yù)備消息成功,但執(zhí)行本地事務(wù)失敗;這個也沒有問題,因為此預(yù)備消息不會被消費端訂閱到,消費端不會執(zhí)行業(yè)務(wù)。異常3:如果發(fā)送預(yù)備消息成功,執(zhí)行本地事務(wù)成功,但發(fā)送確認消息失敗;這個就有問題了,因為用戶A扣款成功了,但加錢業(yè)務(wù)沒有訂閱到確認消息,無法加錢。這里出現(xiàn)了數(shù)據(jù)不一致。
那RocketMq是怎么解決的呢?
RocketMq回查
RocketMq如何解決上面的問題,核心思路就是【狀態(tài)回查】,也就是RocketMq會定時遍歷commitlog中的預(yù)備消息。
因為預(yù)備消息最終肯定會變?yōu)閏ommit消息或Rollback消息,所以遍歷預(yù)備消息去回查本地業(yè)務(wù)的執(zhí)行狀態(tài),如果發(fā)現(xiàn)本地業(yè)務(wù)沒有執(zhí)行成功就rollBack,如果執(zhí)行成功就發(fā)送commit消息。
上面的異常3,發(fā)送預(yù)備消息成功,本地扣款事務(wù)成功,但發(fā)送確認消息失敗;因為RocketMq會進行回查預(yù)備消息,在回查后發(fā)現(xiàn)業(yè)務(wù)已經(jīng)扣款成功了,就補發(fā)“發(fā)送commit確認消息”;這樣加錢業(yè)務(wù)就可以訂閱此消息了。
這個思路其實把異常2也解決了,因為本地事務(wù)沒有執(zhí)行成功,RocketMQ回查業(yè)務(wù),發(fā)現(xiàn)沒有執(zhí)行成功,就會發(fā)送RollBack確認消息,把消息進行刪除。
回查判斷業(yè)務(wù)是否成功
小伙伴們在回查業(yè)務(wù)中,如何判斷本地事務(wù)是否執(zhí)行成功?
如果本地事務(wù)執(zhí)行了很多張表,那是不是我們要把那些表都要進行判斷是否執(zhí)行成功呢?這樣是不是太麻煩了,而且和業(yè)務(wù)很耦合。
有沒有更好的方式呢?就是設(shè)計一張Transaction表,將業(yè)務(wù)表和Transaction綁定在同一個本地事務(wù)中,如果扣款本地事務(wù)成功時,Transaction中應(yīng)當已經(jīng)記錄該TransactionId的狀態(tài)為「已完成」。當RocketMq回查時,只需要檢查對應(yīng)的TransactionId的狀態(tài)是否是「已完成」就好,而不用關(guān)心具體的業(yè)務(wù)數(shù)據(jù)。
總結(jié)
以上是生活随笔為你收集整理的微服务如何解决分布式事务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么是分布式事务以及有哪些解决方案?
- 下一篇: 分布式一致性算法