<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          學(xué)了微服務(wù)架構(gòu),不會(huì)分布式事務(wù),等于沒學(xué)?

          共 2237字,需瀏覽 5分鐘

           ·

          2021-08-29 16:49

          市面上各種講述微服務(wù)框架的教程(SpringCloud, Dubbo),但這些框架解決的是應(yīng)用層的網(wǎng)絡(luò)通信問題,存儲(chǔ)層的數(shù)據(jù)一致性問題還是需要自己去解決。


          集中式時(shí)代,所有數(shù)據(jù)裝一個(gè)DB里面,通過單機(jī)事務(wù)來保證數(shù)據(jù)一致性。改成微服務(wù)之后,DB不可避免的要拆庫拆表,所以分布式事務(wù)就變成了一個(gè)無法逃避的問題。


          解決分布式事務(wù)的方法很多,2PC、TCC、全局事務(wù)協(xié)調(diào)器。。。這里仔細(xì)討論2個(gè)最常見、最實(shí)用的辦法:最終一致性 + 對(duì)賬。


          從一個(gè)最簡單的例子入手:

          微服務(wù)A要更新自己的DB,同時(shí)調(diào)用微服務(wù)B的一個(gè)接口更新微服務(wù)B的數(shù)據(jù)。


          最終一致性的思路大家基本上都知道,就是微服務(wù)A更新完自己的DB之后,通過消息中間件給微服務(wù)B發(fā)一條消息。但實(shí)現(xiàn)細(xì)節(jié)上卻有很多“坑”:


          方案1:最終一致性的第1種實(shí)現(xiàn)方式

          1) 微服務(wù)A增加一張消息表,微服務(wù)A不再直接給消息中間件發(fā)送消息,而是把消息寫入到這張消息表中,把自身的DB操作和寫入消息表這兩個(gè)操作放在一個(gè)數(shù)據(jù)庫事務(wù)里,保證兩者的原子性。

          2)微服務(wù)A準(zhǔn)備一個(gè)后臺(tái)程序,源源不斷地把消息表中的消息傳送給消息中間件。發(fā)成功了,從DB消息表里面刪除;失敗了,不斷嘗試重傳。因?yàn)榫W(wǎng)絡(luò)的2將軍問題,微服務(wù)A發(fā)送給消息中間件的消息超時(shí)了,微服務(wù)A會(huì)再次發(fā)送該消息,直到消息中間件返回成功。所以,微服務(wù)A發(fā)送給消息中間件的消息可能重復(fù),但不會(huì)丟失,順序也不會(huì)打亂。


          微服務(wù)B對(duì)消息的消費(fèi)要解決下面兩個(gè)問題:

          問題1:丟失消費(fèi)。微服務(wù)B從消息中間件取出消息(此時(shí)還在內(nèi)存里面),如果處理了一半,微服務(wù)B宕機(jī)并再次重啟,此時(shí)這條消息未處理成功,怎么辦?

          答案是通過消息中間件的ACK機(jī)制,凡是沒有發(fā)送ACK的消息,微服務(wù)B重啟之后消息中間件會(huì)再次推送。


          問題2: 重復(fù)消費(fèi)。重復(fù)消費(fèi)有2個(gè)場景:

          場景1: 即使微服務(wù)B把消息處理成功了,但是正要發(fā)送ACK的時(shí)候宕機(jī)了,消息中間件以為這條消息沒有處理成功,微服務(wù)B再次重啟的時(shí)候又會(huì)收到這條消息,微服務(wù)B就會(huì)重復(fù)消費(fèi)這條消息

          場景2: 上面說的,微服務(wù)A重復(fù)發(fā)送,導(dǎo)致微服務(wù)B的重復(fù)消費(fèi)。


          解決重復(fù)消費(fèi)的辦法就是“冪等”。關(guān)于實(shí)現(xiàn)冪等的N種辦法,參見作者另外一篇文章。


          這種方案有一個(gè)缺點(diǎn):系統(tǒng)A需要增加消息表,同時(shí)還需要一個(gè)后臺(tái)任務(wù),不斷掃描此消息表,會(huì)導(dǎo)致消息的處理和業(yè)務(wù)邏輯耦合,額外增加業(yè)務(wù)方的開發(fā)負(fù)擔(dān)。


          為此,業(yè)界有了第2種實(shí)現(xiàn)最終一致性的辦法。


          方案2:最終一致性 - 事務(wù)消息

          以阿里開源的RocketMQ為例,它不是提供一個(gè)單一的“發(fā)送”接口,而是把消息的發(fā)送拆成了兩個(gè)階段,Prepare階段(消息預(yù)發(fā)送)和Confirm階段(確認(rèn)發(fā)送)。具體使用方法如下:

          步驟1:微服務(wù)A調(diào)用Prepare接口,預(yù)發(fā)送消息。此時(shí)消息保存在消息中間件里,但消息中間件不會(huì)把消息給消費(fèi)方消費(fèi),消息只是暫存在那。

          步驟2:微服務(wù)A做業(yè)務(wù)邏輯,更新自己的DB

          步驟3:微服務(wù)A調(diào)用Comfirm接口,確認(rèn)發(fā)送消息。此時(shí)消息中間件才會(huì)把消息給消費(fèi)方進(jìn)行消費(fèi)。


          顯然,這里有兩種異常場景:

          場景1:步驟1成功,步驟2成功,步驟3失敗或超時(shí),怎么處理?

          場景2:步驟1成功,步驟2失敗或超時(shí),步驟3不會(huì)執(zhí)行。怎么處理?

          這就涉及RocketMQ的關(guān)鍵點(diǎn):RocketMQ會(huì)定期掃描所有的預(yù)發(fā)送但還沒有確認(rèn)的消息,回調(diào)發(fā)送方,詢問這條消息是要發(fā)出去,還是取消。發(fā)送方根據(jù)自己的業(yè)務(wù)數(shù)據(jù),知道這條消息是應(yīng)該發(fā)出去(DB更新成功了),還是應(yīng)該取消(DB更新失敗)。

          對(duì)比最終一致性的兩種實(shí)現(xiàn)方案會(huì)發(fā)現(xiàn),RocketMQ最大的改變其實(shí)是把“掃描消息表”這件事不讓業(yè)務(wù)方做,而是讓消息中間件完成。

          至于消息表,其實(shí)還是沒有省掉。因?yàn)橄⒅虚g件要詢問發(fā)送方事務(wù)是否執(zhí)行成功,還需要一個(gè)“變相的本地消息表”,記錄事務(wù)執(zhí)行狀態(tài)和消息發(fā)送狀態(tài)。

          對(duì)于消費(fèi)方微服務(wù)B,實(shí)現(xiàn)方式?jīng)]有變化,需要解決自己上面的丟失消費(fèi) + 重復(fù)消費(fèi)問題。


          最后拋個(gè)問題:Kafka里面也實(shí)現(xiàn)了事務(wù)消息,能否拿來用在這個(gè)場景,有什么缺陷?


          方案3:同步調(diào)用 + 對(duì)賬

          微服務(wù)A更新自己的DB,同步調(diào)用微服務(wù)B的接口,如果2者都成功,皆大歡喜;調(diào)用接口失敗,開啟對(duì)賬。


          (1)即時(shí)對(duì)賬:

          失敗之后,往消息中間件扔一個(gè)異常消息。用一個(gè)后臺(tái)程序消費(fèi)消息,重新調(diào)用微服務(wù)B,直到2個(gè)微服務(wù)數(shù)據(jù)補(bǔ)齊。

          但這個(gè)辦法不能100%保證,給消息中間件發(fā)消息也很能失敗


          事后對(duì)賬:

          用一個(gè)后臺(tái)程序,調(diào)用微服務(wù)A的查詢接口,分批次查詢數(shù)據(jù),再調(diào)用微服務(wù)B的查詢接口。比對(duì),不一致,用A的數(shù)據(jù)補(bǔ)齊B的數(shù)據(jù)。

          這里又分為增量對(duì)賬、全量對(duì)賬。

          增量,可以按數(shù)據(jù)更新時(shí)間,每次取最近的一批數(shù)據(jù)對(duì);

          全量,每次都對(duì)所有的。

          一般策略是:頻繁的增量對(duì),比如幾分鐘一次;偶爾全量對(duì),比如每天凌晨做一次。


          即時(shí)對(duì)賬是保證及時(shí)性,事后對(duì)賬保證不漏,做兜底。2者是配合關(guān)系,不是取代。


          最后做一個(gè)總結(jié):最終一致性方案是放棄了及時(shí)性,保證數(shù)據(jù)最終一致。而同步更新 + 對(duì)賬,99%情況下數(shù)據(jù)是強(qiáng)一致,1%異常情況下,對(duì)賬補(bǔ)齊。


          最后留個(gè)問題:還沒來得及對(duì)賬補(bǔ)齊之前,數(shù)據(jù)被下游其它服務(wù)讀了,導(dǎo)致下游服務(wù)出現(xiàn)業(yè)務(wù)邏輯錯(cuò)誤,怎么解決?


          瀏覽 64
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色亚洲干日本 | 久久精品无码一区二区小草千夏 | 国产色天使 | 黄的全免费| 日韩欧美AAA |