分散式事務處理方案,微服事務處理方案

Java_老男孩發表於2019-05-04

微服事務處理方案(分散式事務處理方案)

1. 什麼是事務

由一組操作構成的可靠、 獨立的工作單元。 事務具有以下特點:

•Atomicity(原子性)

•Consistency(一致性)

•Isolation(隔離性)

•Durability(永續性)

2.事務的一致性

單體應用可以在資料庫的事物管理器中獲得強一致性,這種本地事物可靠簡單。 而在微服或者SOA的場景下,我們的本地事物就不作用了。對於分散式系統 Google 提出 CAP定理 , 分散式的事物只能同時擁有以下三項中的兩個:

•Consistency(一致性): 所有 使用者看到一致的資料。

•Availability(可用性): 總能找 到一個可用的資料複本。

•Tolerance to Network Partition(分割槽容忍性): 即使 在系統被分割槽的情況下,仍然滿足上述兩點。

分散式系統的事物無法做到強一致性,只能做到最終一致性。

3.常見分散式事物的處理方案

  • 2pc 兩段提交方案
  • 3pc 三段提交方案,是兩段提交方案的進化。
  • TCC 模式 try ,confirm ,cancel。
  • 可靠訊息機制 ,可靠訊息分為 非事物訊息 和 事物訊息。

事物訊息,簡單的說就是訊息投遞成功,你本地的資料庫事物肯定提交成功,訊息投遞失敗你本地事物也肯定提交失敗。相當於你投遞訊息和運算元據庫是繫結在一起的,兩者是在同一個事物中。

非事物訊息,簡單的說就是訊息投遞成功,本地資料庫事物不一定執行成功。

而現有的開源的MQ框架 大多數是不支援 事物訊息的,也沒有和本地事物進行配合。 RocketMQ 好像實現了這個事物訊息功能,有興趣的同學可以去看看。

可靠訊息機制保證資料一致性的解決方案(非事物訊息)

先來看個案例: 假設我有一個 文章微服 負責釋出文章,查詢文章列表等,還有一個 使用者微服 保持使用者的一些基本資訊,如:使用者發文章的篇數等。

現在發文章在 文章微服,而使用者的發文篇數在 使用者微服。這種情況下我們怎麼保證 發文的計數 不會多也不會少,保證其的一致性的呢?

大家先來看一段程式碼: 假設這是 文章微服 發文程式碼:

@Transactional
    public void saveArticle(Article article){
        try{
            //儲存文章
            articleDao.saveArticle(article);
            MqEvent saveArticleEvent = new MqEvent();
            //訊息ID 
            saveArticleEvent.setMsgId(UUIDUtil.mongoObjectId());
            //傳送儲存文章 事件個使用者服務
            sender.sendAddArticleMqEvent(saveArticleEvent);
        }catch (Exception e){
            //拋異常 讓事務回滾
            throw new RuntimeException();
        }
    }
}
複製程式碼

看起來很正常呀!沒有什麼問題! 我們來列舉一下可能會出現的情況;

  1. 儲存文章正常,MQ傳送也正常,好萬事大吉,資料都正常。

  2. 儲存文章正常,MQ傳送失敗,拋異常。這時資料回滾,雖然出了點問題 但是資料正常。這也沒問題。

  3. 文章儲存失敗,這時肯定拋異常,MQ傳送走不到,這時也是正常的。

  4. 文章儲存成功,MQ傳送成功,這時會不會有問題出現呢? 剛剛我們說了 我們的MQ是非事物性的,恰巧這個時候 MQ 回執異常導致 MQ拋異常了。本地事物回滾,但是MQ雖然拋異常,訊息卻發成功了。這時候 會導致 使用者發文計數多了一篇,資料不一致了導致。

雖然這種做法看起來沒有什麼問題,但其實是有問題的。

為了解決這個問題,我們需要 增加一個本地事件表,專門存放 MQ事件 ,有定時器輪訓去傳送MQ訊息,傳送成功後做標記,或者刪除。很多MQ都會有 訊息回執的。當成功投遞到訊息佇列是 就會得到回執。

於是我們的程式碼應該改成這樣:

  @Transactional
    public void saveArticle(Article article) {
        articleDao.saveArticle(article);
        MqEvent saveArticleEvent = new MqEvent();
        //訊息ID
        saveArticleEvent.setMsgId(UUIDUtil.mongoObjectId());
        // 儲存事件到事件表
        mqEventDao.addMQEvent(saveArticleEvent);
    }
複製程式碼

這樣在本地事物的強一致性下可以保證,發文的同事插入發文事件。

說說 使用者微服那邊的處理 , 使用者微服也應該有一個這樣的事件表,儲存接收的事件,通過輪訓等方式處理這些事件,只有成功的接收了事件,事件才能從MQ佇列裡面消失。MQ在消費訊息的時候如果遇到異常會重新將消失重新發回到佇列中,很多MQ具有這個特性。

細心的同學可能會想到我同一個事件訊息投遞了兩次怎麼辦?這就涉及冪等性的設計了,看到上面的訊息ID沒? 這就是為冪等性設計的 唯一ID;當使用者微服收到兩個訊息ID是一樣的時候,丟棄掉一個。

總結

我們的微服系統 只要涉及 增 刪 改 的操作都應該通過可靠事件進行操作,而不能直接通過 REST 介面,去操作,也不能直接的傳送事件去操作。對於消防端,要做好冪等性的處理。可靠事件處理微服的事物算是比較靠譜的做法,2pc,3pc ,Tcc 在微服事物處理這一塊,基本上起不了什麼作用。 本人的理解大概是這樣,歡迎大家提出疑問。

相關文章