### MQ與DB一致性原理(兩方事務)
Apache RocketMQ在4.3.0版中已經支持分布式事務消息,這里RocketMQ采用了2PC的思想來實現了提交事務消息,同時增加一個補償邏輯來處理二階段超時或者失敗的消息

RocketMQ事務消息流程概要
上圖說明了事務消息的大致方案,其中分為兩個流程:正常事務消息的發送及提交、事務消息的補償流程。
1.事務消息發送及提交:
(1) 發送消息(half消息)。
(2) 服務端響應消息寫入結果。
(3) 根據發送結果執行本地事務(如果寫入失敗,此時half消息對業務不可見,本地邏輯不執行)。
(4) 根據本地事務狀態執行Commit或者Rollback(Commit操作生成消息索引,消息對消費者可見)
2.補償流程:
(1) 對沒有Commit/Rollback的事務消息(pending狀態的消息),從服務端發起一次“回查”
(2) Producer收到回查消息,檢查回查消息對應的本地事務的狀態
(3) 根據本地事務狀態,重新Commit或者Rollback
補償階段用于解決消息Commit或者Rollback發生超時或者失敗的情況,MQ內部提供一個名為“事務狀態服務”的服務,此服務會檢查事務消息的狀態,如果發現消息未COMMIT,則通過Producer啟動時注冊的TransactionCheckListener來回調業務系統,業務系統在checkLocalTransactionState方法中檢查DB事務狀態,如果成功,則回復COMMIT_MESSAGE,否則回復ROLLBACK_MESSAGE
> 以上SEND_OK、COMMIT_MESSAGE、ROLLBACK_MESSAGE均是client jar提供的狀態,在MQ服務器內部是一個數字
TransactionCheckListener 是在消息的commit或者rollback消息丟失的情況下才會回調(上圖中灰色部分)。這種消息丟失只存在于斷網或者rocketmq集群掛了的情況下。當rocketmq集群掛了,如果采用異步刷盤,存在1s內數據丟失風險,異步刷盤場景下保障事務沒有意義。所以如果要核心業務用Rocketmq解決分布式事務問題,建議選擇同步刷盤模式
1. 事務消息在一階段對用戶不可見
在RocketMQ事務消息的主要流程中,一階段的消息如何對用戶不可見。其中,事務消息相對普通消息最大的特點就是一階段發送的消息對用戶是不可見的。那么,如何做到寫入消息但是對用戶不可見呢?RocketMQ事務消息的做法是:如果消息是half消息,將備份原消息的主題與消息消費隊列,然后改變主題為RMQ\_SYS\_TRANS\_HALF\_TOPIC。由于消費組未訂閱該主題,故消費端無法消費half類型的消息,然后RocketMQ會開啟一個定時任務,從Topic為RMQ\_SYS\_TRANS\_HALF\_TOPIC中拉取消息進行消費,根據生產者組獲取一個服務提供者發送回查事務狀態請求,根據事務狀態來決定是提交或回滾消息。
在RocketMQ中,消息在服務端的存儲結構如下,每條消息都會有對應的索引信息,Consumer通過ConsumeQueue這個二級索引來讀取消息實體內容,其流程如下:

RocketMQ的具體實現策略是:寫入的如果事務消息,對消息的Topic和Queue等屬性進行替換,同時將原來的Topic和Queue信息存儲到消息的屬性中,正因為消息主題被替換,故消息并不會轉發到該原主題的消息消費隊列,消費者無法感知消息的存在,不會消費。其實改變消息主題是RocketMQ的常用“套路”,回想一下延時消息的實現機制
2.Commit和Rollback操作以及Op消息的引入
在完成一階段寫入一條對用戶不可見的消息后,二階段如果是Commit操作,則需要讓消息對用戶可見;如果是Rollback則需要撤銷一階段的消息。先說Rollback的情況。對于Rollback,本身一階段的消息對用戶是不可見的,其實不需要真正撤銷消息(實際上RocketMQ也無法去真正的刪除一條消息,因為是順序寫文件的)。但是區別于這條消息沒有確定狀態(Pending狀態,事務懸而未決),需要一個操作來標識這條消息的最終狀態。RocketMQ事務消息方案中引入了Op消息的概念,用Op消息標識事務消息已經確定的狀態(Commit或者Rollback)。如果一條事務消息沒有對應的Op消息,說明這個事務的狀態還無法確定(可能是二階段失敗了)。引入Op消息后,事務消息無論是Commit或者Rollback都會記錄一個Op操作。Commit相對于Rollback只是在寫入Op消息前創建Half消息的索引
3.Op消息的存儲和對應關系
RocketMQ將Op消息寫入到全局一個特定的Topic中通過源碼中的方法—TransactionalMessageUtil.buildOpTopic();這個Topic是一個內部的Topic(像Half消息的Topic一樣),不會被用戶消費。Op消息的內容為對應的Half消息的存儲的Offset,這樣通過Op消息能索引到Half消息進行后續的回查操作

4.Half消息的索引構建
在執行二階段Commit操作時,需要構建出Half消息的索引。一階段的Half消息由于是寫到一個特殊的Topic,所以二階段構建索引時需要讀取出Half消息,并將Topic和Queue替換成真正的目標的Topic和Queue,之后通過一次普通消息的寫入操作來生成一條對用戶可見的消息。所以RocketMQ事務消息二階段其實是利用了一階段存儲的消息的內容,在二階段時恢復出一條完整的普通消息,然后走一遍消息寫入流程
5.如何處理二階段失敗的消息?
如果在RocketMQ事務消息的二階段過程中失敗了,例如在做Commit操作時,出現網絡問題導致Commit失敗,那么需要通過一定的策略使這條消息最終被Commit。RocketMQ采用了一種補償機制,稱為“回查”。Broker端對未確定狀態的消息發起回查,將消息發送到對應的Producer端(同一個Group的Producer),由Producer根據消息來檢查本地事務的狀態,進而執行Commit或者Rollback。Broker端通過對比Half消息和Op消息進行事務消息的回查并且推進CheckPoint(記錄那些事務消息的狀態是確定的)。
值得注意的是,rocketmq并不會無休止的的信息事務狀態回查,默認回查15次,如果15次回查還是無法得知事務狀態,rocketmq默認回滾該消息
- 概述
- CAP理論
- BASE理論
- ACID
- 分布式系統相關技術
- 主流數據庫連接池
- 基礎
- 系統單點
- 負載均衡
- HTTP重定向負載均衡
- DNS域名解析負載均衡
- 反向代理負載均衡
- IP負載均衡
- 數據鏈路層負載均衡
- 負載均衡算法
- 輪詢法(Round Robin)
- 加權輪詢(Weight Round Robin)
- 隨機算法(Random)
- 源地址Hash算法
- 加權隨機法(Weight Random)
- 最小連接數法(Least Connections)
- 接入層負載均衡
- 軟件架構
- 性能
- 性能測試指標
- 響應時間
- 并發數
- 吞吐量
- 性能計數器
- 性能測試方法
- 性能測試報告
- 性能優化
- Web前端性能優化
- 應用服務器性能優化
- 可用性
- 服務降級
- 伸縮性
- 擴展性
- 事件驅動架構
- 安全性
- 信息加密技術
- 分布式系統概述
- 自動化
- 分布式唯一ID
- 冪等設計
- 分布式鎖
- 腦裂
- 一致性原理
- Paxos
- Zab
- Raft
- 分布式遠程服務調用
- RMI
- Spring RMI
- WebService
- SOA服務架構
- 微服務架構
- 微服務的九大特性
- 服務注冊和發現
- 解決方案及組件
- 分布式網關
- 注冊中心
- Zookeeper
- ZNode
- Watch接口
- 持久節點-配置中心實現原理
- 臨時節點-注冊中心
- Zookeeper選舉
- Zookeeper角色
- ZooKeeper工作原理
- 選主流程
- 同步流程
- Leader工作流程
- Follower工作流程
- 常見限流算法
- 計數器算法
- 漏桶算法
- 令牌桶算法
- 滑動窗口
- 計數器&滑動窗口
- 斷路器
- 大流量高并發高可用
- 高可用
- 高并發/大流量
- 分布式緩存系統
- 基本概念
- 緩存命中率
- 緩存最大元素
- 緩存回收策略
- 回收算法
- 緩存穿透與緩存雪崩
- CDN緩存
- 緩存分類
- memcached
- 客戶端路由原理
- 內存管理機制
- Redis
- Redis數據模型
- redisObject/Redis type/Redis encoding
- 命令的類型檢查和多態
- skiplist跳躍表
- 為什么使用跳躍表
- redis-內存管理機制
- Redis淘汰策略
- Redis持久化策略
- Redis并發競爭
- redis主從復制
- Redis集群實現方案
- Redis Cluster
- redis事務
- Redis-Sentinel
- Redis適用場景
- Redis客戶端
- redis rehash原理
- dict數據結構
- 觸發rehash的條件
- 漸進式rehash
- 漸進式rehash過程
- Redis多線程版本
- 緩存實際應用
- 堆緩存-Guava Cache
- 主要參數
- Caffeine
- Spring注解緩存
- 分布式存儲
- Database
- AUTOCOMMIT
- 臟讀&幻讀&不可重復讀
- 子查詢
- 連接
- 內連接
- 自連接
- 自然連接
- 外連接
- 組合查詢
- 隔離級別
- 數據庫范式
- 索引實現機制
- 數據庫拆分
- 表分區
- 分庫
- 分表
- MySQL
- MySQL基礎架構
- 鎖分類
- 排它鎖&獨占鎖
- 共享鎖
- 間隙鎖
- 表級鎖
- 存儲引擎
- 磁盤IO
- 磁盤結構圖
- 磁盤數據讀寫原理
- MySQL索引原理
- B+樹索引
- 局部性原理
- 索引數據結構
- 聯合索引
- 最左前綴匹配原則
- 建索引的幾大原則
- 數據文件和索引文件
- 執行計劃explain
- 常見問題
- 數據頁
- MYSQL單表存儲量計算
- 回表
- 索引覆蓋
- 索引下推
- 頁分裂和頁合并
- InnoDB
- innodb索引
- Innodb引擎的底層實現
- MyISAM
- MyISAM引擎的底層實現
- MVCC
- Next-Key Locks
- MySQL索引類型
- MYSQL復制
- 主從復制
- 讀寫分離
- MySQL Dual-Master
- 分庫分表實現方案
- MySQL事務實現原理
- MYSQL調優
- 性能優化
- HBase
- 不停機分庫分表遷移
- RDBMS&NoSQL
- 分布式事務
- 協議或事務模型
- X/Open XA協議
- 分布式事務編程接口規范JTA
- TCC模型
- 解決方案
- 兩階段提交2PC
- 三階段提交3PC
- Seata
- 分布式事務Seata產品模塊
- AT模式
- TCC模式
- Saga模式
- XA模式
- 基于消息中間件的最終一致性事務方案
- 消息隊列
- AMQP
- JMS
- ActiveMQ
- RabbitMQ
- RocketMQ
- RocketMQ基本概念
- 主要特性
- 分區順序消息
- 全局順序消息
- 消息可靠性
- 定時消息
- 消息重試
- 死信隊列
- 分布式事務消息
- RocketMQ架構
- Producer
- Consumer
- NameServer
- Broker
- RocketMQ設計
- 消息存儲
- 頁緩存與內存映射
- 消息刷盤
- 通信機制
- console控制臺
- RocketMQ部署架構
- Kafka
- Pulsar
- MQ消息重復消費與丟失
- 主流消息隊列比較
- 分布式調度系統
- 分布式搜索
- 分布式計算
- 架構案例
- 秒殺業務
- 秒殺整體架構
- 常見的監控系統
- 小米手機搶購秒殺方案
- 架構師領導藝術
- 架構師箴言
- 技術leader核心職責
- WEB服務器
- Servlet
- Servlet實現
- Servlet生命周期
- Servlet容器工作模式
- Servlet工作原理
- servlet線程安全
- CGI&FastCGI
- CGI
- FastCGI
- FastCGI與CGI特點
- CGI與Servlet比較
- HTTP Server
- Nginx
- Apache
- Nginx與Apache比較
- Application Server
- Tomcat
- Tomcat總體架構
- Connector
- 連接器核心功能
- ProtocolHandler
- EndPoint
- Processor
- Adapter
- Container
- 請求定位Servlet的過程
- Lifecycle生命周期
- Tomcat模塊設計
- Tomcat實例
- Tomcat運行原理
- spring & servlet
- Tomcat啟動流程
- Tomcat支持的I/O模型
- Tomcat應用層協議
- Tomcat類加載機制
- Tomcat類加載器
- Tomcat類加載器層次
- Apache+Tomcat
- 序列化
- XML&JSON
- JSON
- JAVA原生序列化
- hessian
- 常見中間件
- Canal
- Databus
- ELK日志套件
- 數據庫連接池
- spring狀態機
- 常見解決方案
- 二維碼掃碼登錄原理
- 前沿技術
- Saas服務
- 服務網格(Service Mesh)
- 云原生
- 常見面試問題
- Redis持久化的幾種方式
- Redis的緩存失效策略
- 附錄
- 二將軍問題
- 常見問題定位步驟
- 如何快速熟悉新系統
- 制定技術方案套路
- NUMA陷阱