Skip to content

XA、JTA与2PC:资源登记、原子提交和崩溃恢复

一、学完本页要真正会什么

XA、JTA、2PC经常同时出现,但它们不在同一层:2PC是原子提交协议思想,XA规定事务管理器与资源管理器怎样交互,JTA是Java应用使用事务管理器的API,Narayana、Atomikos或应用服务器事务管理器才是具体协调实现。

学完本页,你应该能够:

  1. 区分2PC、XA、JTA、TM、RM、XAResource和Xid。
  2. 解释应用怎样把两个XA资源enlist到同一全局事务。
  3. 画出start、end、prepare、commit/rollback完整流程。
  4. 解释Prepare返回XA_OKXA_RDONLY的差异。
  5. 说明一阶段提交优化为什么只适合特定条件。
  6. 解释Prepared、in-doubt、恢复扫描和事务日志。
  7. 区分正常回滚、启发式提交/回滚和混合结果。
  8. 说明连接池为什么必须正确处理XA连接和事务关联。
  9. 解释JTA事务传播、超时和Spring代理只负责什么。
  10. 按全局Xid、分支Xid、TM日志和数据库XA状态排查生产故障。

二、六个概念必须分开

概念所在层次作用
2PC协议思想先Prepare收集投票,再统一Commit或Rollback
XA资源接口规范定义TM如何控制数据库等RM的分支事务
JTAJava APIJava应用获取事务、提交、回滚、挂起、恢复
Transaction Manager协调实现维护全局事务、资源列表、日志、决议和恢复
Resource Manager资源系统数据库、支持XA的消息系统等真正保存状态
XAResource驱动/适配对象暴露start、end、prepare、commit、rollback、recover等操作

关系:

mermaid
flowchart TD
    A["业务代码与Spring事务边界"] --> B["JTA API"]
    B --> C["事务管理器TM"]
    C --> D["XAResource A"]
    C --> E["XAResource B"]
    D --> F["数据库A的资源管理器"]
    E --> G["数据库B的资源管理器"]
    C --> H["持久化协调日志和恢复状态"]

只使用@Transactional不会让普通数据源自动变成XA资源;只创建JtaTransactionManager也不会自动配置可恢复的底层TM和XA连接池。

三、Xid如何标识全局事务和分支

XA规范中的Xid概念上包含:

  • formatId:标识Xid格式。
  • globalTransactionId:同一全局事务共享的全局部分。
  • branchQualifier:区分该全局事务下不同资源分支。
text
全局事务 G9001
├─ 数据库A分支:G9001 + branch-A
└─ 数据库B分支:G9001 + branch-B

事务管理器必须稳定生成并持久化Xid映射。应用业务订单号不是XA Xid,但生产排查应该把订单号、traceId和全局Xid关联起来。

四、资源怎样加入同一个JTA事务

抽象过程:

mermaid
flowchart TD
    A["应用开始JTA事务"] --> B["TM创建全局事务上下文"]
    B --> C["业务第一次取得XA数据源A连接"]
    C --> D["连接池/集成层将XAResource A enlist"]
    D --> E["TM对A调用start分支"]
    E --> F["业务执行数据库A SQL"]
    F --> G["业务取得XA数据源B连接并enlist B"]
    G --> H["TM对B调用start并执行SQL"]
    H --> I["业务方法结束,请求全局提交"]

enlistResource的本质是把资源分支加入当前全局事务。连接池需要确保:

  • 返回的是能提供XAResource的XA连接。
  • 当前连接与JTA事务正确关联。
  • 事务结束前连接不会被错误地当普通连接复用。
  • 发生挂起/恢复时关联状态正确切换。
  • 进程重启后TM仍能找到同一资源并执行recover。

五、XA分支生命周期

XAResource常见操作语义:

操作含义
start(xid, flags)把后续资源操作关联到该分支
end(xid, flags)结束当前资源关联,不等于提交
prepare(xid)资源持久化可提交状态并投票
commit(xid, onePhase)提交分支
rollback(xid)回滚分支
recover(flags)扫描资源中Prepared但未完成的分支
forget(xid)在规范允许的启发式结果处理后忘记记录

end只是告诉资源“当前执行线程不再继续在该分支上工作”,不能把它理解成事务成功。

六、两阶段提交完整正常流程

mermaid
flowchart TD
    A["业务请求TM提交"] --> B["TM结束各XA分支"]
    B --> C["阶段一:向资源A发送Prepare"]
    C --> D["资源A刷恢复信息并返回投票"]
    D --> E["向资源B发送Prepare"]
    E --> F["资源B刷恢复信息并返回投票"]
    F --> G{"所有需要提交的资源是否成功Prepare"}
    G -- "是" --> H["TM持久化全局Commit决议"]
    H --> I["阶段二:向各资源发送Commit"]
    I --> J["Commit失败的分支进入恢复重试"]
    G -- "否" --> K["TM持久化Rollback决议"]
    K --> L["通知已参与资源Rollback"]

正确顺序中,TM应在向参与者宣布最终决议前把决议持久化到恢复日志。否则向A发送Commit后宕机,重启时没有证据判断原决议。

七、Prepare到底承诺什么

资源返回成功Prepare,通常意味着:

  • 已将分支更新和恢复信息写入持久化日志。
  • 即使数据库或应用重启,也能通过Xid找到该分支。
  • 已保留完成Commit或Rollback所需的锁/事务上下文。
  • 不会自行把Prepared分支当普通事务回滚。

常见投票:

结果含义二阶段
XA_OK分支有需要提交的修改且已Prepared等待Commit或Rollback
XA_RDONLY分支没有产生需要提交的修改TM可不再发送二阶段Commit
XA异常无法Prepare或协议错误全局通常进入Rollback

只读优化减少一次二阶段调用,但不能由应用凭“我觉得只读”伪造,必须由资源按实际分支状态判断。

八、一阶段提交优化是什么

若全局事务最终只有一个真正参与的XA资源,TM可能对它执行commit(xid, true),跳过Prepare,减少一轮日志和网络交互。这叫one-phase commit optimization。

前提是事务管理器能确认协议和资源条件允许。多个独立更新资源不能为了性能强行一阶段提交,否则第一个提交后第二个失败时失去原子性。

“最后一个资源优化”或Last Resource Commit Optimization允许一个非XA资源与XA资源组合,但存在更复杂的崩溃窗口和启发式结果,支持方式因TM而异。关键资金场景不能把它当成与纯XA等价,必须阅读具体TM文档并做故障注入。

九、事务管理器日志保存什么

可恢复TM至少要保存足以重建的状态:

  • 全局Xid和事务状态。
  • 参与的资源唯一名称。
  • 每个分支Xid和Prepare结果。
  • 最终Commit/Rollback决议。
  • 二阶段完成情况。
  • 超时、重试和启发式结果。

资源唯一名称必须稳定。应用重启后若同一个数据库XA资源换了完全不同的恢复标识,TM可能无法把日志中的分支与新实例中的XAResource对应起来。

协调日志存储也要高可用和持久化。把日志放临时容器文件系统,Pod重建后丢失,会破坏Prepared事务恢复。

十、什么是in-doubt事务

参与者已Prepared,却不知道全局最终决议,这个分支处于in-doubt。

mermaid
flowchart TD
    A["资源A和B都Prepared"] --> B["TM写Commit决议"]
    B --> C["A收到Commit并完成"]
    B --> D["TM在通知B前宕机"]
    D --> E["B仍保留Prepared分支和锁"]
    E --> F["TM重启读取日志并recover资源B"]
    F --> G["找到B的Xid并重发Commit"]

in-doubt不是“数据库坏了”,而是资源为了原子性等待最终决议。它解释了XA为什么可能阻塞:分区期间参与者宁可暂时不可用,也不能自行做出与其他参与者冲突的决定。

十一、recover扫描和崩溃恢复

TM重启后抽象恢复过程:

  1. 读取协调日志,找到未完成全局事务和最终决议。
  2. 为每个稳定资源重新获得XAResource。
  3. 调用recover扫描资源中的Prepared Xid。
  4. 对齐TM日志和RM扫描结果。
  5. 已有Commit决议的分支重发Commit。
  6. 已有Rollback决议的分支重发Rollback。
  7. 记录完成状态并清理可安全删除的协调日志。
  8. 对“资源有Xid但TM无日志”等孤儿情况告警并按TM策略处理。

Recover调用有扫描开始/继续/结束等flags语义,具体封装由事务管理器负责。业务代码通常不直接调用,但运维必须知道恢复扫描需要数据库权限、网络、稳定资源名和正确驱动。

十二、Heuristic Outcome是什么

启发式结果表示资源没有完全遵循协调器的全局决议,而是因超时、管理员命令或资源内部策略自行决定。

结果概念含义
Heuristic Commit全局要求回滚,但某资源自行提交
Heuristic Rollback全局要求提交,但某资源自行回滚
Heuristic Mixed部分提交、部分回滚
Heuristic Hazard无法确定一个或多个分支最终结果

这类结果已经超出自动“全有或全无”,必须:

  • 保留Xid、决议、资源响应和业务流水。
  • 停止无证据的自动覆盖。
  • 对账确定各资源事实。
  • 执行业务补偿或人工会计调整。
  • 完成审计后再按TM规范forget启发式记录。

forget不是删除错误的快捷键,它用于协调器已正确处理启发式结果后的协议清理。

十三、JTA超时不等于所有数据库瞬间回滚

事务超时通常让TM将事务标记为只能回滚或进入超时处理。之后仍需结束分支并通知资源Rollback。若资源暂时不可达,回滚还要恢复重试。

业务线程收到超时异常时:

  • 某些分支可能尚未执行。
  • 某些分支可能已执行但未Prepare。
  • 某些分支可能已Prepared。
  • TM可能已持久化Rollback决议但尚未通知完。

因此要记录全局Xid并查询TM与资源状态,不能只凭HTTP超时判断所有库已回滚。

十四、Spring与JTA的真实关系

Spring的JtaTransactionManager主要把Spring事务抽象适配到底层JTA能力。完整系统还需要:

  • 一个实际可恢复的JTA事务管理器。
  • 每个资源对应的XADataSource/XAResource。
  • 能正确enlist和回收XA连接的连接池/集成。
  • 稳定资源名称和恢复配置。
  • 持久化事务日志。

业务代码可以仍使用:

java
@Transactional(rollbackFor = Exception.class)
public void transfer(Long payerId, Long merchantId, BigDecimal amount) {
    payerRepository.decrease(payerId, amount);
    merchantRepository.increase(merchantId, amount);
}

但这段代码是否跨库原子,不取决于注解文字,而取决于两个Repository实际使用的资源是否加入同一个JTA全局事务。若其中一个仍是普通本地DataSource,它可能独立提交。

不要用下面这种不完整示例误导生产:

java
// 仅new空适配器并不会自动创建可恢复TM、XA数据源和事务日志
return new JtaTransactionManager();

正确配置因Spring Boot代际、TM产品、连接池和数据库而异,应使用对应官方Starter/集成文档并验证依赖矩阵。

十五、挂起、恢复和事务上下文

JTA支持事务挂起和恢复。Spring传播行为如REQUIRES_NEW可能:

  1. 挂起外层JTA事务和其资源关联。
  2. 创建新的全局事务。
  3. 内层事务完成后恢复外层事务。

风险:外层事务的连接和资源可能仍被占用,内层又需要新的连接。高并发下容易耗尽连接池。事务上下文通常与执行线程关联,直接切换到未受管理的线程不会自动加入原全局事务。

十六、同一资源管理器和分支合并

XAResource.isSameRM用于判断两个XAResource是否属于同一个底层资源管理器。TM可能据此优化分支关联或避免不必要的独立Prepare。

错误的驱动/包装实现可能让TM误判资源身份。生产必须使用经过兼容验证的驱动和连接池,不要自行包装XAResource而忽略isSameRM、recover和异常码语义。

十七、XA异常码为什么重要

XAException中的错误类别帮助TM判断:

  • 可以重试的资源错误。
  • 分支不存在或协议顺序错误。
  • 资源已经回滚。
  • 启发式提交/回滚/混合。
  • RM故障或通信问题。

应用日志不能只打印“XA failed”。至少记录全局Xid、分支Xid、资源名、阶段、XA errorCode、数据库错误和TM状态,才能判断是否安全重试。

十八、商业场景:跨库记账

假设付款账户库和商户账户库必须原子记账:

mermaid
flowchart TD
    A["开始JTA事务并生成Xid"] --> B["付款库条件扣减并写流水"]
    B --> C["商户库增加余额并写流水"]
    C --> D["TM对两个资源Prepare"]
    D --> E{"所有Prepare成功"}
    E -- "是" --> F["持久化Commit决议并提交两个分支"]
    E -- "否" --> G["持久化Rollback决议并回滚"]
    F --> H["对账按业务流水验证双方"]

即使使用XA,仍需要业务流水唯一键:客户端超时后可能重复提交同一转账;XA只保证一次全局事务内部的原子性,不自动识别两次不同Xid其实是同一业务意图。

十九、适合与不适合的场景

场景结论原因
同库多表不需要XA本地事务更简单可靠
两个XA数据库短事务原子记账可评估业务不允许中间结果,资源受控
数据库与支持XA的MQ谨慎评估两边驱动和恢复必须真实支持
秒杀热点库存通常不适合Prepare持锁和热点冲突
人工审批、物流长流程不适合不能长时间保留Prepared资源
外部支付与短信无法直接纯XA外部资源通常不暴露XAResource
通知、积分、ES同步优先消息最终一致无需阻塞主交易资源

二十、容量和性能成本怎样评估

压测至少观察:

  • 全局事务TPS。
  • Prepare、Commit、Rollback的P95/P99。
  • 数据库行锁等待和死锁。
  • Prepared/in-doubt数量和最长年龄。
  • TM日志写延迟。
  • XA连接池使用率和等待时间。
  • 参与资源数量与失败率。
  • 故障恢复时长。

参与者越多,全局成功率和尾延迟通常越受最慢资源影响。不要只测正常提交,还要断开TM、重启数据库、丢弃Commit响应并验证恢复。

二十一、生产排查Runbook

21.1 @Transactional没有跨库回滚

  1. 确认实际选中的PlatformTransactionManager是否为JTA适配器。
  2. 确认底层TM是否真正启动并持久化日志。
  3. 确认两个DataSource都是XADataSource或正确XA包装。
  4. 确认两个资源都被enlist到同一全局Xid。
  5. 检查是否手工获取普通连接绕过JTA。
  6. 检查Spring代理、自调用、异常回滚规则和跨线程。

21.2 数据库存在Prepared/in-doubt事务

mermaid
flowchart TD
    A["从数据库列出Prepared Xid"] --> B["从TM日志查同一Xid和最终决议"]
    B --> C{"TM是否有可靠Commit/Rollback决议"}
    C -- "有" --> D["恢复TM资源连接并按决议重放"]
    C -- "没有" --> E["保留现场,核对所有参与者和业务流水"]
    E --> F["按TM/数据库官方流程人工决策并审计"]

不能看到锁等待就直接在数据库中Commit Prepared。另一个资源可能已回滚,草率提交会制造资金不一致。

21.3 TM重启后无法恢复

检查:

  • 协调日志是否在持久卷/可靠数据库中。
  • 资源唯一名称是否变化。
  • XA驱动和认证是否可用。
  • recover权限和网络。
  • Xid序列化是否兼容升级前版本。
  • 数据库中Prepared分支与TM日志是否能对应。

21.4 提交很慢

拆分测量:业务SQL、Prepare、TM日志、Commit、连接池等待、锁等待和网络。只增加全局超时可能让锁保留更久。先缩短事务、移出远程慢调用、减少参与者并解决热点。

21.5 出现启发式结果

冻结自动“重试到成功”的脚本,导出TM日志、资源分支事实和业务流水,逐资源确认结果,执行受审计的业务补偿;处理完成后再按TM规范清理启发式记录。

二十二、JDK 8 Demo:持久化决议为什么不能改变

java
import java.util.LinkedHashMap;
import java.util.Map;

public class TwoPhaseDecisionDemo {
    enum Decision { NONE, COMMIT, ROLLBACK }

    static final class Coordinator {
        private Decision durableDecision = Decision.NONE;
        private final Map<String, Boolean> completed =
                new LinkedHashMap<String, Boolean>();

        void decideCommit(String... branches) {
            if (durableDecision != Decision.NONE) {
                throw new IllegalStateException("decision already durable");
            }
            durableDecision = Decision.COMMIT;
            for (String branch : branches) {
                completed.put(branch, Boolean.FALSE);
            }
        }

        void acknowledge(String branch) {
            if (durableDecision != Decision.COMMIT) {
                throw new IllegalStateException("no commit decision");
            }
            completed.put(branch, Boolean.TRUE);
        }

        void recover() {
            for (Map.Entry<String, Boolean> entry : completed.entrySet()) {
                if (!entry.getValue().booleanValue()) {
                    System.out.println("replay COMMIT to " + entry.getKey());
                    entry.setValue(Boolean.TRUE);
                }
            }
        }
    }

    public static void main(String[] args) {
        Coordinator coordinator = new Coordinator();
        coordinator.decideCommit("db-a", "db-b");
        coordinator.acknowledge("db-a");
        coordinator.recover();
        System.out.println("decision=" + coordinator.durableDecision);
    }
}

输出:

text
replay COMMIT to db-b
decision=COMMIT

真实TM必须把决议和分支完成状态写入可靠存储;Demo内存字段只解释恢复规则。

二十三、常见错误与后果

错误后果正确方向
只new JtaTransactionManager没有真实TM、XA资源和恢复日志使用产品集成并验证enlist/recover
一个XA库加一个普通库普通库无法参与Prepare全部资源受协议管理或改用补偿方案
XA事务内调用慢HTTPPrepared和连接锁长期占用保持纯资源短事务,外部调用拆出
TM日志放容器临时盘Pod重建后无法恢复决议持久化可靠日志和稳定资源名
业务超时就当所有库回滚可能已有Prepared或Commit决议按Xid查询TM和RM事实
手工提交Prepared解除锁可能违背全局决议对齐TM日志后按官方恢复流程
XA替代业务幂等两个不同Xid仍可重复转账业务流水唯一键和结果查询
不做故障注入正常测试无法证明可恢复测TM/DB重启、网络丢包和响应丢失

二十四、面试标准回答

24.1 XA、JTA和2PC有什么关系

2PC是先Prepare再统一Commit/Rollback的原子提交协议;XA定义事务管理器与数据库等资源管理器交互的接口,包括start、end、prepare、commit、rollback和recover;JTA是Java应用使用事务管理器的标准API。具体TM负责生成Xid、登记XAResource、持久化决议和崩溃恢复。

24.2 Prepare后为什么会阻塞

参与者返回Prepared意味着已经持久化可恢复状态并保留完成二阶段所需资源,不能自行改变结果。若协调器暂时不可达,它处于in-doubt,相关锁或事务上下文可能继续保留,直到TM按持久化决议恢复。

24.3 XA为什么不能替代幂等

XA保证一个全局Xid内部多个资源原子提交,但客户端超时后若用新Xid重新发起相同业务,两个全局事务都可能成功。仍要用订单号、支付流水等稳定业务键和唯一约束识别同一业务意图。

24.4 启发式结果是什么

资源在没有遵循全局决议的情况下自行提交或回滚,可能形成Heuristic Commit、Rollback、Mixed或Hazard。它意味着自动原子性已破坏,必须保留事务日志、逐资源对账并执行业务补偿,不能简单forget或删除记录。

二十五、关联知识点

本章小结

XA的价值不只是两阶段流程图,而是标准化资源分支、持久化投票、全局决议和崩溃恢复。JTA负责Java调用边界,具体TM和XA资源决定系统能否真正恢复。理解in-doubt、recover和启发式结果,才算理解XA生产语义。