TM、RM、TC 三种角色分工是什么?各存什么状态?TM 崩溃、TC 崩溃、RM 二阶段失败时谁来收尾?这篇用我手写的三个独立进程(HTTP + MySQL)把这些分工和故障路径逐个跑出来。它不是 Seata,但结构一致。
14 ms [TC] TM → TC begin xid-1792248455324005000(超时 60000 ms)
28 ms [TC] RM(acct_db) → TC register 分支 1 到 xid-1792248455324005000
43 ms [TC] RM → TC lock acct_db.account:1 给 xid-1792248455324005000(等了 7 ms)
50 ms [TC] RM → TC report 分支 1 = PHASE1_DONE
51 ms [RM:acct_db] TM → RM(acct_db) 业务调用:account#1 {'id': 1, 'balance': 1000} → {'id': 1, 'balance': 900},本地已提交,分支 1 报告 PHASE1_DONE
65 ms [TC] RM(stock_db) → TC register 分支 2 到 xid-1792248455324005000
80 ms [TC] RM → TC lock stock_db.stock:1 给 xid-1792248455324005000(等了 7 ms)
87 ms [TC] RM → TC report 分支 2 = PHASE1_DONE
88 ms [RM:stock_db] TM → RM(stock_db) 业务调用:stock#1 {'id': 1, 'qty': 50} → {'id': 1, 'qty': 47},本地已提交,分支 2 报告 PHASE1_DONE
95 ms [TC] TM → TC commit xid-1792248455324005000(状态 → COMMITTING,二阶段由 TC 的后台线程去做)
124 ms [RM:acct_db] RM(acct_db) branch_commit:删除 undo_log(xid-1792248455324005000)
124 ms [TC] TC → RM(acct_db) branch_commit 分支 1 成功(第 1 次)
132 ms [RM:stock_db] RM(stock_db) branch_commit:删除 undo_log(xid-1792248455324005000)
133 ms [TC] TC → RM(stock_db) branch_commit 分支 2 成功(第 1 次)
140 ms [TC] TC 全局事务 xid-1792248455324005000 → COMMITTED(全局锁已释放)
TM 的 commit 调用返回后 78 ms 全局事务才真正 COMMITTED(二阶段是 TC 在后台做的);账户 900、库存 47;undo_log 行数 0 + 0;tc_db:global_tx 1 行、branch 2 行、global_lock 0 行TM 决定开始、提交或回滚;RM 做本地分支、注册、报告,并响应二阶段回调;TC 保存全局事务与分支状态、全局锁,驱动二阶段。
两个分支做完后:账户 900、库存 47,全局事务状态 BEGIN
8 ms [TC] TM → TC begin xid-1792248455601517000(超时 1500 ms)
16 ms [TC] RM(acct_db) → TC register 分支 3 到 xid-1792248455601517000
39 ms [TC] RM → TC report 分支 3 = PHASE1_DONE
39 ms [RM:acct_db] TM → RM(acct_db) 业务调用:account#1 {'id': 1, 'balance': 1000} → {'id': 1, 'balance': 900},本地已提交,分支 3 报告 PHASE1_DONE
47 ms [TC] RM(stock_db) → TC register 分支 4 到 xid-1792248455601517000
69 ms [TC] RM → TC report 分支 4 = PHASE1_DONE
70 ms [RM:stock_db] TM → RM(stock_db) 业务调用:stock#1 {'id': 1, 'qty': 50} → {'id': 1, 'qty': 47},本地已提交,分支 4 报告 PHASE1_DONE
1577 ms [TC] TC 超时扫描:xid-1792248455601517000 一直没有收到 TM 的决定 → ROLLING_BACK
1595 ms [RM:acct_db] RM(acct_db) branch_rollback:按 undo_log 恢复 [('account', '1')],删除 undo_log
1595 ms [TC] TC → RM(acct_db) branch_rollback 分支 3 成功(第 1 次)
1606 ms [RM:stock_db] RM(stock_db) branch_rollback:按 undo_log 恢复 [('stock', '1')],删除 undo_log
1607 ms [TC] TC → RM(stock_db) branch_rollback 分支 4 成功(第 1 次)
1607 ms [TC] TC 全局事务 xid-1792248455601517000 → ROLLED_BACK(全局锁已释放)
开始后 1628 ms 左右 TC 把它回滚完(等待了 1536 ms 才变成 ROLLED_BACK);账户 1000、库存 50,global_lock 行数 0没人决定的全局事务,由 TC 超时扫描器回滚,RM 按 undo_log 恢复。
[commit] TC 被 kill -9 时全局事务状态是 COMMITTING(二阶段没做完);此刻 undo_log 行数 1 + 1,global_lock 行数 2,账户 900、库存 47
140 ms [TC] TC 启动恢复:库里有 1 个没做完二阶段的全局事务 [('xid-1792248457309379000', 'COMMITTING')],继续做
420 ms [TC] TC → RM(acct_db) branch_commit 分支 5 成功(第 4 次)
429 ms [TC] TC → RM(stock_db) branch_commit 分支 6 成功(第 4 次)
437 ms [TC] TC 全局事务 xid-1792248457309379000 → COMMITTED(全局锁已释放)
[commit] TC 重启后 219 ms 把二阶段做完:状态 COMMITTED,账户 900、库存 47,undo_log 行数 0 + 0,global_lock 行数 0
[rollback] TC 被 kill -9 时全局事务状态是 ROLLING_BACK(二阶段没做完);此刻 undo_log 行数 1 + 1,global_lock 行数 2,账户 900、库存 47
135 ms [TC] TC 启动恢复:库里有 1 个没做完二阶段的全局事务 [('xid-1792248458275693000', 'ROLLING_BACK')],继续做
162 ms [TC] TC → RM(acct_db) branch_rollback 分支 7 成功(第 3 次)
170 ms [TC] TC → RM(stock_db) branch_rollback 分支 8 成功(第 3 次)
177 ms [TC] TC 全局事务 xid-1792248458275693000 → ROLLED_BACK(全局锁已释放)
[rollback] TC 重启后 10 ms 把二阶段做完:状态 ROLLED_BACK,账户 1000、库存 50,undo_log 行数 0 + 0,global_lock 行数 0TC 的状态在库里,重启后扫描 COMMITTING / ROLLING_BACK 继续二阶段,分支的重试次数也保留。
80 ms [TC] TC → RM(acct_db) branch_rollback 分支 9 失败(第 1 次):HTTP Error 500: Internal Server Error;100 ms 后重试
87 ms [RM:stock_db] RM(stock_db) branch_rollback:按 undo_log 恢复 [('stock', '1')],删除 undo_log
88 ms [TC] TC → RM(stock_db) branch_rollback 分支 10 成功(第 1 次)
186 ms [TC] TC → RM(acct_db) branch_rollback 分支 9 失败(第 2 次):HTTP Error 500: Internal Server Error;200 ms 后重试
395 ms [TC] TC → RM(acct_db) branch_rollback 分支 9 失败(第 3 次):HTTP Error 500: Internal Server Error;400 ms 后重试
815 ms [RM:acct_db] RM(acct_db) branch_rollback:按 undo_log 恢复 [('account', '1')],删除 undo_log
815 ms [TC] TC → RM(acct_db) branch_rollback 分支 9 成功(第 4 次)
816 ms [TC] TC 全局事务 xid-1792248459039689000 → ROLLED_BACK(全局锁已释放)
账户 1000、库存 50;最终状态 ROLLED_BACK
账户 RM 一直失败 4 秒:全局事务状态 ROLLING_BACK,TC 重试了 6 次(间隔 100 → 200 → 400 → 800 → 1600 ms 封顶),账户 900(分支已经提交的 -100 还没恢复),global_lock 仍有 2 行
另一个全局事务想改同一个账户:等了 1034 ms 后失败(LockConflict: acct_db.account:1 的全局锁被 xid-179224845995621800)—— 没做完二阶段的事务一直占着全局锁
把故障去掉后,TC 的下一次重试成功:最终状态 ROLLED_BACK,账户 1000TC 带退避重试;一直失败则事务停在二阶段,全局锁一直占着,别的事务等锁失败。
TM 线程数 | 全局事务数 | TM 看到的单个事务耗时(中位数,begin 到 commit 返回) | 每秒完成的全局事务(到 commit 返回) | 全部二阶段做完的总用时 -------+-------+-------------------------------------+------------------------+------------ 1 | 100 | 71.4 ms | 14 | 7.34 s 4 | 200 | 90.9 ms | 42 | 4.78 s 8 | 200 | 147.0 ms | 53 | 3.84 s 单线程跑 50 个全局事务(每个 2 个分支)时,各进程收到的 HTTP 调用次数(平均每个全局事务):TC/begin 1.0, TC/commit 1.0, TC/lock 2.0, TC/register 2.0, TC/report 2.0, 库存 RM/branch_commit 1.0, 库存 RM/do 1.0, 账户 RM/branch_commit 1.0, 账户 RM/do 1.0 合计每个全局事务 12.0 次 HTTP 调用(本地事务是 0 次);其中 TM 直接发起的是 begin、两次业务调用、commit 共 4 次,其余是 RM 与 TC 之间互相调用 做完之后各角色库里留下的行数:TC 的 global_tx 50 行(状态 COMMITTED 的历史记录)、branch 100 行,global_lock 0 行(锁已释放);RM 的 undo_log 0 + 0 行(二阶段清理掉了);TM 不存任何状态
每个全局事务多 12 次跨进程调用,TC 里留下历史行;耗时数字只代表这个原型。