前面我們講了分散式事務的2PC、3PC , TCC 的原理。這些事務其實都在盡力的模擬資料庫的事務,我們可以簡單的認為他們是一個同步行的事務。特別是 2PC,3PC 他們完全利用資料庫的事務能力,在一階段開始事務後不進提交會嚴重影響應用程式的併發效能。TCC 一階段雖然不會阻塞資料庫,但是它同樣是在盡力追求同時成功同時失敗的一致性要求。但是在很多時候,我們的應用程式的核心業務為了追求更高的效能、更高的可用性,可以允許在一段時間內的資料不一致性,只需要在最終時刻資料是一致就可以了。基於以上場景我們可以採用基於可靠訊息服務的最終一致性分散式事務處理方案。

總體架構



可靠訊息最終一致性的架構上分3個部分:

  1. 主動方:主動發起事務的一方,一般是指分散式事務中最先開始執行的那個服務,也是核心業務服務
  2. 可靠訊息服務:可靠訊息服務由關係型資料庫、訊息佇列MQ等元件組成,利用關係型資料庫的強一致性特性來持久化訊息的狀態,利用MQ來保證訊息的可靠投遞及消費
  3. 被動方:被動方訂閱MQ的訊息,當收到MQ的訊息後執行對應的業務

    以上是比較粗狂的結構圖,下面我們來詳細分析一下這個事務的執行過程。

流程



該方案總體流程上可分為以下步驟:

  1. 主動方在真正的業務開始前先向可靠訊息服務傳送一個“待確認”的訊息
  2. 可靠訊息服務收到待確認訊息後持久化訊息到資料庫
  3. 如果以上操作成功則主動方開始真正的業務,如果失敗則直接放棄執行業務
  4. 如果業務執行成功則傳送“確認”訊息給可靠訊息服務,如果執行失敗則傳送“取消”給可靠訊息服務。
  5. 如果可靠訊息服務收到“確認”訊息則更新資料庫裡的訊息記錄的狀態為“待發送”,如果收到的訊息為“取消”則更新訊息狀態為“已取消”
  6. 如果上一步更新的資料庫為“待發送”,那麼會開始往MQ投遞訊息,並且更改資料庫裡的訊息記錄的狀態為“已傳送”
  7. 上一步往MQ投遞訊息成功後,MQ會給被動方推送訊息。
  8. 被動方收到訊息後開始處理業務
  9. 如果業務處理成功,則被動方對MQ進行ACK回覆,則這條訊息會從MQ內移除掉
  10. 如果業務處理成功,則傳送“已完成”訊息給可靠訊息服務
  11. 可靠訊息服務收到“已完成”訊息後更新資料庫訊息記錄未“已完成”

異常處理

以上我們描述的是一套在理想情況下執行的邏輯。但是分散式系統由於網路的存在,網路的不可靠性會導致我們訊息的傳遞沒辦法100%成功。我們的可靠訊息服務跟主動方、被動方之間的互動也是分散式的,這就需要我們在流程上有很多補償的機制。以下我們來討論一些異常情況:

  1. 如果步驟1傳送“待確認”訊息失敗,主動方業務不會執行,直接放棄事務,不會有影響
  2. 如果步驟1傳送“待確認”訊息成功,並且可靠訊息已經更新“待確認”成功,但是由於網路問題,比如超時,主動方得到的結果是失敗,主動方會放棄執行事務,標記為“已取消”。這個時候就會出現可靠訊息服務跟主動方的狀態出現不一致的情況。

    為解決這個問題,我需要主動方提供一個事務狀態查詢介面,可靠訊息服務這邊則啟動一個定時任務,定時去查這些長時間處於待確認的事務,然後通過主動方的介面確認這些事務是已執行,還是已取消。
  3. 如果步驟4,主動方傳送“確認”訊息失敗,可靠訊息服務會通過定時任務通過主動方的查詢介面去確認狀態。
  4. 步驟6,投遞訊息給MQ跟更新狀態為“已傳送”,是這個流程中至關重要的一步。理想的流程是整個2個操作同時成功同時失敗,保持強一致性。網上很多文章都會說“為了保證傳送MQ訊息跟更新訊息狀態同時成功同時失敗,需要把這個2個步驟寫同一個本地事務中”。這是完全不靠譜的,資料庫跟MQ本就是2個獨立的服務,如果通過一個本地的事務就能保證一致性,那麼我們現在討論的分散式事務毫無意義,直接寫在一個本地事務裡不就完了麼。

    寫在本地事務內只能儘可能的保證資料庫更新跟MQ投遞訊息同時成功,但是並不能保證100%一致。

    以下我們來分2種情況分析失敗的情況:
事務開始
1. send to mq
2. database update
事務結束

先發送 MQ 訊息,可能 MQ 訊息傳送成功,但是database由於某些原因更新失敗了。資料庫可以回滾,但是通常的MQ沒有回滾能力。

事務開始
1. database update
2. send to mq
事務結束

先更新資料庫,再發送 MQ 訊息,同樣會有問題。就算1,2都執行成功了,但是事務是需要提交的,資料庫有可能在提交階段失敗,資料庫是可以回滾,但是 MQ 的訊息已經發出去了,它並沒有回滾的能力。

為了解決這個問題,我們同樣需要補償機制。在可靠訊息服務一側開啟定時任務,定時去查詢那些長期處於“待發送\已傳送”的事務,再次對 MQ 進行投遞訊息。這個機制有可能造成被動方重複收到 MQ 的訊息,這就需要被動方處理業務的時候要進行冪等處理。

總結

通過以上我們詳細介紹了可靠訊息最終一致性事務解決方案的總體結構跟執行的流程,以及對異常情況的一些補償方法,總體流程上還是比較清晰簡單的。但是可靠訊息最終一致性方案在使用上也是具有比較強的侷限性,因為它的非同步特性跟有可能出現的高延時性不適合處理一些敏感業務。比如它適合處理消費新增積分場景,但是不合適處理積分兌換禮品的場景。因為如果積分扣減延遲了,那麼使用者就可能兌換超出本身積分多的多的禮品。所以我們選擇分散式事務的時候還需根據場景來進行選擇。

好了講了這麼多分散式事務的原理,下一期我們使用 .NET 真正的實現一個分散式事務,敬請期待。

.Net Core with 微服務 - 什麼是微服務

.Net Core with 微服務 - 架構圖

.Net Core with 微服務 - Ocelot 閘道器

.Net Core with 微服務 - Consul 註冊中心

.Net Core with 微服務 - Seq 日誌聚合

.Net Core with 微服務 - Elastic APM

.Net Core with 微服務 - Consul 配置中心

.Net Core with 微服務 - Polly 熔斷降級

.Net Core with 微服務 - 分散式事務 - 2PC、3PC

.Net Core with 微服務 - 分散式事務 - TCC

關注我的公眾號一起玩轉技術