使用事件和消息隊列實現分散式事務(轉)

来源:http://www.cnblogs.com/zhangjianbin/archive/2017/01/13/6283819.html
-Advertisement-
Play Games

不同於單一架構應用(Monolith), 分散式環境下, 進行事務操作將變得困難, 因為分散式環境通常會有多個數據源, 只用本地資料庫事務難以保證多個數據源數據的一致性. 這種情況下, 可以使用兩階段或者三階段提交協議來完成分散式事務.但是使用這種方式一般來說性能較差, 因為事務管理器需要在多個數據 ...


不同於單一架構應用(Monolith), 分散式環境下, 進行事務操作將變得困難, 因為分散式環境通常會有多個數據源, 只用本地資料庫事務難以保證多個數據源數據的一致性. 這種情況下, 可以使用兩階段或者三階段提交協議來完成分散式事務.但是使用這種方式一般來說性能較差, 因為事務管理器需要在多個數據源之間進行多次等待. 有一種方法同樣可以解決分散式事務問題, 並且性能較好, 這就是我這篇文章要介紹的使用事件,本地事務以及消息隊列來實現分散式事務.

我們從一個簡單的實例入手. 基本所有互聯網應用都會有用戶註冊的功能. 在這個例子中, 我們對於用戶註冊有兩步操作: 
1. 註冊成功, 保存用戶信息.
2. 需要給用戶發放一張代金券, 目的是鼓勵用戶進行消費.
如果是一個單一架構應用, 實現這個功能非常簡單: 在一個本地事務里, 往用戶表插一條記錄, 並且在代金券表裡插一條記錄, 提交事務就完成了. 但是如果我們的應用是用微服務實現的, 可能用戶和代金券是兩個獨立的服務, 他們有各自的應用和資料庫, 那麼就沒有辦法簡單的使用本地事務來保證操作的原子性了. 現在來看看如何使用事件機制和消息隊列來實現這個需求.(我在這裡使用的消息隊列是kafka, 原理同樣適用於ActiveMQ/RabbitMQ等其他隊列)

我們會為用戶註冊這個操作創建一個事件, 該事件就叫做用戶創建事件(USER_CREATED). 用戶服務成功保存用戶記錄後, 會發送用戶創建事件到消息隊列, 代金券服務會監聽用戶創建事件, 一旦接收到該事件, 代金券服務就會在自己的資料庫中為該用戶創建一張代金券. 好了, 這些步驟看起來都相當的簡單直觀, 但是怎麼保證事務的原子性呢?

考慮下麵這兩個場景:
1. 用戶服務在保存用戶記錄, 還沒來得及向消息隊列發送消息之前就宕機了. 怎麼保證用戶創建事件一定發送到消息隊列了?
2. 代金券服務接收到用戶創建事件, 還沒來得及處理事件就宕機了. 重新啟動之後如何消費之前的用戶創建事件?
這兩個問題的本質是: 如何讓操作資料庫和操作消息隊列這兩個操作成為一個原子操作. 不考慮2PC, 這裡我們可以通過事件表來解決這個問題. 下麵是類圖. 

 

EventPublish是記錄待發佈事件的表. 其中:
      id: 每個事件在創建的時候都會生成一個全局唯一ID, 例如UUID.
      status: 事件狀態, 枚舉類型. 現在只有兩個狀態: 待發佈(NEW), 已發佈(PUBLISHED).
      payload: 事件內容. 這裡我們會將事件內容轉成json存到這個欄位里.
      eventType: 事件類型, 枚舉類型. 每個事件都會有一個類型, 比如我們之前提到的創建用戶USER_CREATED就是一個事件類型.

EventProcess是用來記錄待處理的事件. 欄位與EventPublish基本相同.

我們首先看看事件的發佈過程. 下麵是用戶服務發佈用戶創建事件的順序圖. 

 

1. 用戶服務在接收到用戶請求後開啟事務, 在用戶表創建一條用戶記錄, 並且在EventPublish表創建一條status為NEW的記錄, payload記錄的是事件內容, 提交事務.
2. 用戶服務中的定時器首先開啟事務, 然後查詢EventPublish是否有status為NEW的記錄, 查詢到記錄之後, 拿到payload信息, 將消息發佈到kafka中對應的topic.
發送成功之後, 修改資料庫中EventPublish的status為PUBLISHED, 提交事務.

下麵是代金券服務處理用戶創建事件的順序圖


1. 代金券服務接收到kafka傳來的用戶創建事件(實際上是代金券服務主動拉取的消息, 先忽略消息隊列的實現), 在EventProcess表創建一條status為NEW的記錄, payload記錄的是事件內容, 如果保存成功, 向kafka返回接收成功的消息.
2. 代金券服務中的定時器首先開啟事務, 然後查詢EventProcess是否有status為NEW的記錄, 查詢到記錄之後, 拿到payload信息, 交給事件回調處理器處理, 這裡是直接創建代金券記錄. 處理成功之後修改資料庫中EventProcess的status為PROCESSED, 最後提交事務.

回過頭來看我們之前提出的兩個問題:
1. 用戶服務在保存用戶記錄, 還沒來得及向消息隊列發送消息之前就宕機了. 怎麼保證用戶創建事件一定發送到消息隊列了?
根據事件發佈的順序圖, 我們把創建事件和發佈事件分成了兩步操作. 如果事件創建成功, 但是在發佈的時候宕機了. 啟動之後定時器會重新對之前沒有發佈成功的事件進行發佈. 如果事件在創建的時候就宕機了, 因為事件創建和業務操作在一個資料庫事務里, 所以對應的業務操作也失敗了, 資料庫狀態的一致性得到了保證.
2. 代金券服務接收到用戶創建事件, 還沒來得及處理事件就宕機了. 重新啟動之後如何消費之前的用戶創建事件?
根據事件處理的順序圖, 我們把接收事件和處理事件分成了兩步操作. 如果事件接收成功, 但是在處理的時候宕機了. 啟動之後定時器會重新對之前沒有處理成功的事件進行處理. 如果事件在接收的時候就宕機了, kafka會重新將事件發送給對應服務.

通過這種方式, 我們不用2PC, 也保證了多個數據源之間狀態的最終一致性.
和2PC/3PC這種同步事務處理的方式相比, 這種非同步事務處理方式具有非同步系統通常都有的優點:
1. 事務吞吐量大. 因為不需要等待其他數據源響應.
2. 容錯性好. A服務在發佈事件的時候, B服務甚至可以不線上.
缺點:
1. 編程與調試較複雜.
2. 容易出現較多的中間狀態. 比如上面的例子, 在用戶服務已經保存了用戶併發布了事件, 但是代金券服務還沒來得及處理之前, 用戶如果登錄系統, 會發現自己是沒有代金券的. 這種情況可能在有些業務中是能夠容忍的, 但是有些業務卻不行. 所以開發之前要考慮好.

另外, 上面的流程在實現的過程中還有一些可以改進的地方:
1. 定時器在更新EventPublish狀態為PUBLISHED的時候, 可以一次批量更新多個EventProcess的狀態.
2. 定時器查詢EventProcess並交給事件回調處理器處理的時候, 可以使用線程池非同步處理, 加快EventProcess處理周期.
3. 在保存EventPublish和EventProcess的時候同時保存到Redis, 之後的操作可以對Redis中的數據進行, 但是要小心處理緩存和資料庫可能狀態不一致問題.
4. 針對Kafka, 因為Kafka的特點是可能重發消息, 所以在接收事件並且保存到EventProcess的時候可能報主鍵衝突的錯誤(因為重覆消息id是相同的), 這個時候可以直接丟棄該消息.


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 閱讀目錄 前言 準備 實現 結語 一、前言 最近實在太忙,上周停更了一周。按流程一步一步走到現在,到達了整個下單流程的最後一公裡——結算頁的處理。從整個流程來看,這裡需要用戶填寫的信息是最多的,那麼在後端的設計中如何考慮到業務邊界的劃分,和相互之間的交互複雜度,又是我們需要考慮的地方。總體來說本篇講 ...
  • java 企業網站源碼 前後臺都有 靜態模版引擎, 代碼生成器大大提高開發效率 系統介紹 點擊:獲取地址 : 1.網站後臺採用主流的 SSM 框架 jsp JSTL,網站後臺採用freemaker靜態化模版引擎生成html 2.因為是生成的html,所以訪問速度快,輕便,對伺服器負擔小 3.網站前端 ...
  • 一、概述 狀態模式允許對象在內部狀態改變時改變它的行為,對象看起來好像修改了它自己的類 二、解決問題 它主要用來解決對象在多種狀態轉換時,需要對外輸出不同的行為的問題。狀態和行為是一一對應的,狀態之間可以相互轉換。 三、結構類圖 四、應用實例 現在很多APP都有抽獎活動,我們在這裡就用這個大家熟悉的 ...
  • 1.創建實體類 參考:http://www.cnblogs.com/farb/p/4923137.html 在Core(領域層)項目下新建一個目錄Entities,在此目錄下新建一個Category類,代碼如下: public class Category:Entity { /// <summary ...
  • 什麼是策略模式 策略模式定義了一系列的演算法,並將每一個演算法封裝起來,而且使它們還可以相互替換。策略模式讓演算法獨立於使用它的客戶而獨立變化(摘自百度百科) 關鍵字:演算法封裝,相互替換,獨立變化 演算法封裝表示,每個演算法只提供介面,屏蔽實現的細節。相互替換很好理解,就是有一個共同的父類,當然父類不一定就是 ...
  • 以一個商品分類管理功能來編寫,代碼儘量簡單易懂。從一個實體開始,一直到許可權控制,由淺到深一步步對功能進行完善。 1.打開語言文件 【..\MyCompanyName.AbpZeroTemplate.Core\Localization\AbpZeroTemplate\AbpZeroTemplate-z ...
  • 一、概述 允許你將對象組合成樹形結構來表現“整體/部分”層次結構。組合能讓客戶以一致的方式處理個別對象以及組合對象。 二、解決問題 組合模式解決這樣的問題,當我們的要處理的對象可以生成一顆樹形結構,而我們要對樹上的節點和葉子進行操作時,它能夠提供一致的方式,而不用考慮它是節點還是葉子。 三、結構類圖 ...
  • 看完大話設計模式後,個人總結 命令模式的出發點就是: 在類a中func_a1函數中,寫了調用類b中func_b1 和func_2等幾個方法的幾條語句。然後出現需求,需要對調用類b方法的幾個調用過程 進行排序 或者進行組合,或者撤回等操作, 最簡單的操作 就是重寫func_a中的方法,滿足需求。同樣的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...