分布式事务解决方案全景从 2PC 到 Saga每种方案的适用场景与落地要点分布式事务是后端架构最难的问题之一。很多团队的方案就是不管他出了问题手动补这在业务量小的时候还行一旦上了规模就会成为定时炸弹。本文系统梳理分布式事务的六大解决方案配合真实代码示例帮你根据业务场景选对方案不走弯路。一、分布式事务的根本难题为什么本地事务解决不了分布式场景单库事务本地事务 BEGIN UPDATE order SET statusPAID WHERE id1001; -- 订单库 UPDATE inventory SET stockstock-1 WHERE id501; -- 同一个库 COMMIT -- ACID 保障要么全成功要么全回滚 跨库/跨服务场景分布式事务困境 订单服务BEGIN → UPDATE order → COMMIT ✅ 库存服务BEGIN → UPDATE inventory → COMMIT ✅ or ❌ 支付服务BEGIN → INSERT payment → COMMIT ✅ or ❌ 问题三个服务分别提交网络/机器故障导致部分成功、部分失败 → 数据不一致CAP 理论的本质约束在网络分区P不可避免的情况下C强一致性和 A高可用二选一强一致性方案CP2PC、TCC → 性能开销大可用性受损最终一致性方案APSaga、可靠消息 → 性能好暂时不一致可接受二、六大分布式事务方案全景方案1XA/2PC两阶段提交Phase 1准备阶段 协调者 → 参与者1prepare? → 参与者1ready ✅ 协调者 → 参与者2prepare? → 参与者2ready ✅ 协调者 → 参与者3prepare? → 参与者3ready ✅ Phase 2提交阶段 协调者 → 参与者1commit! 协调者 → 参与者2commit! 协调者 → 参与者3commit!Java 实现Atomikos XAConfigurationpublicclassXADataSourceConfig{BeanPrimarypublicDataSourceorderXADataSource(){AtomikosDataSourceBeandsnewAtomikosDataSourceBean();ds.setUniqueResourceName(orderDataSource);ds.setXaDataSourceClassName(com.mysql.cj.jdbc.MysqlXADataSource);PropertiespnewProperties();p.setProperty(URL,jdbc:mysql://order-db:3306/order);p.setProperty(user,root);p.setProperty(password,${ORDER_DB_PASS});ds.setXaProperties(p);ds.setPoolSize(20);returnds;}// 库存数据源同理}// 业务代码Spring 自动管理跨库事务Transactional// Atomikos JTA 事务管理器自动处理跨库提交publicvoidcreateOrder(OrderDTOdto){orderRepository.save(dto.toOrder());// 写订单库inventoryRepository.deduct(dto.itemId());// 写库存库同一事务}适用场景同一机房内的异构数据库跨库操作不适合跨服务调用性能差协调者故障导致全局阻塞方案2TCCTry-Confirm-CancelTCC 将一次业务操作拆成三个阶段Try 预留资源冻结库存、冻结余额 Confirm确认提交实际扣减库存、实际扣款 Cancel 取消回滚释放冻结库存、退还冻结余额实现示例Seata TCC// 库存服务TCC 接口LocalTCCpublicinterfaceInventoryTccAction{TwoPhaseBusinessAction(namedeductInventory,commitMethodconfirm,rollbackMethodcancel)booleantryDeduct(BusinessActionContextParameter(paramNameitemId)LongitemId,BusinessActionContextParameter(paramNameqty)Integerqty,BusinessActionContextcontext);booleanconfirm(BusinessActionContextcontext);booleancancel(BusinessActionContextcontext);}ComponentpublicclassInventoryTccActionImplimplementsInventoryTccAction{OverrideTransactionalpublicbooleantryDeduct(LongitemId,Integerqty,BusinessActionContextcontext){// Try 阶段冻结库存不实际扣减InventoryinvinventoryRepository.findById(itemId);if(inv.getAvailable()qty){returnfalse;// 库存不足Try 失败}// 冻结库存inv.setFrozen(inv.getFrozen()qty);inv.setAvailable(inv.getAvailable()-qty);inventoryRepository.save(inv);returntrue;}OverrideTransactionalpublicbooleanconfirm(BusinessActionContextcontext){// Confirm 阶段将冻结库存转为实际扣减LongitemIdLong.valueOf(context.getActionContext(itemId).toString());IntegerqtyInteger.valueOf(context.getActionContext(qty).toString());InventoryinvinventoryRepository.findById(itemId);inv.setFrozen(inv.getFrozen()-qty);// 解冻// 库存已在 Try 阶段预扣Confirm 只需解冻inventoryRepository.save(inv);returntrue;}OverrideTransactionalpublicbooleancancel(BusinessActionContextcontext){// Cancel 阶段释放冻结库存LongitemIdLong.valueOf(context.getActionContext(itemId).toString());IntegerqtyInteger.valueOf(context.getActionContext(qty).toString());InventoryinvinventoryRepository.findById(itemId);inv.setFrozen(inv.getFrozen()-qty);inv.setAvailable(inv.getAvailable()qty);// 退还inventoryRepository.save(inv);returntrue;}}TCC 三个必须处理的坑// 坑1空回滚Try 未执行直接收到 Cancel// 解决Cancel 前检查是否有 Try 记录无记录则直接返回成功publicbooleancancel(BusinessActionContextcontext){Stringxidcontext.getXid();if(!tryRecordRepository.exists(xid)){log.warn(空回滚Try 记录不存在xid: {},xid);returntrue;// 幂等返回}// 正常回滚逻辑...}// 坑2幂等Confirm/Cancel 可能被重复调用// 解决记录事务状态重复调用直接返回publicbooleanconfirm(BusinessActionContextcontext){Stringxidcontext.getXid();TccRecordrecordtccRecordRepository.findByXid(xid);if(record!nullrecord.getStatus()CONFIRMED){returntrue;// 已提交幂等返回}// 执行 Confirm 逻辑...}// 坑3悬挂Cancel 先于 Try 执行// 解决收到 Cancel 后记录后续 Try 到来时拒绝publicbooleantryDeduct(LongitemId,Integerqty,BusinessActionContextcontext){Stringxidcontext.getXid();if(cancelRecordRepository.exists(xid)){log.warn(悬挂Cancel 已执行拒绝 Tryxid: {},xid);returnfalse;}// 正常 Try 逻辑...}方案3Saga 模式长事务首选Saga 将长事务拆分成多个本地事务的序列每个步骤有对应的补偿操作正向流程 T1创建订单→ T2冻结库存→ T3发起支付→ T4扣减库存→ T5发货通知 补偿流程T3 失败时 C2释放库存← C1取消订单Seata Saga State Machine 配置JSON{Name:orderFulfillmentStateMachine,Comment:订单履约 Saga,StartState:CreateOrder,Version:0.0.1,States:{CreateOrder:{Type:ServiceTask,ServiceName:orderService,ServiceMethod:createOrder,CompensateState:CompensateCreateOrder,Next:FreezeInventory,Output:{orderId:$.#root}},FreezeInventory:{Type:ServiceTask,ServiceName:inventoryService,ServiceMethod:freezeInventory,CompensateState:CompensateFreezeInventory,Input:[{orderId:$.orderId}],Next:MakePayment,Catch:[{Exceptions:[com.example.InsufficientInventoryException],Next:CompensateCreateOrder}]},MakePayment:{Type:ServiceTask,ServiceName:paymentService,ServiceMethod:makePayment,CompensateState:CompensateMakePayment,Next:Succeed},CompensateCreateOrder:{Type:ServiceTask,ServiceName:orderService,ServiceMethod:cancelOrder,Next:Fail},CompensateFreezeInventory:{Type:ServiceTask,ServiceName:inventoryService,ServiceMethod:releaseInventory,Next:CompensateCreateOrder}}}方案4可靠消息最终一致性// 下单时将消息与本地事务绑定发送Transactional Outbox 模式TransactionalpublicvoidcreateOrder(OrderDTOdto){// 1. 写订单OrderorderorderRepository.save(dto.toOrder());// 2. 写消息发件箱与订单同一个事务OutboxMessagemsgOutboxMessage.builder().aggregateId(order.getId().toString()).aggregateType(Order).eventType(OrderCreated).payload(JSON.toJSONString(newOrderCreatedEvent(order))).status(PENDING).createdAt(LocalDateTime.now()).build();outboxRepository.save(msg);// 本地事务提交订单 消息同时成功 or 同时失败}// 消息中继器独立轮询发件箱发送到 MQScheduled(fixedDelay100)publicvoidrelayMessages(){ListOutboxMessagependingoutboxRepository.findPending(100);for(OutboxMessagemsg:pending){try{kafkaProducer.send(newProducerRecord(domain-events,msg.getAggregateId(),msg.getPayload()));msg.setStatus(SENT);outboxRepository.save(msg);}catch(Exceptione){log.error(消息发送失败将重试,e);}}}// 库存服务消费幂等处理KafkaListener(topicsdomain-events)publicvoidhandleOrderCreated(Stringpayload){OrderCreatedEventeventJSON.parseObject(payload,OrderCreatedEvent.class);// 幂等检查if(processedEventRepository.exists(event.getEventId())){log.info(重复消息忽略eventId: {},event.getEventId());return;}// 处理业务逻辑inventoryService.deductInventory(event.getItemId(),event.getQty());// 标记已处理processedEventRepository.save(event.getEventId());}三、方案选型矩阵方案一致性性能开发复杂度适用场景XA/2PC强一致★★低同机房异构数据库低并发TCC最终一致极短暂不一致★★★★高金融/支付不允许中间状态暴露Saga最终一致★★★★中长流程业务订单履约、退款流程可靠消息最终一致★★★★★低跨服务异步解耦允许短暂不一致最大努力通知最终一致★★★★★最低非核心跨系统通知短信/积分AT 模式Seata最终一致★★★最低快速落地业务代码改动最少四、Seata AT 模式最快落地方案# application.yml - Seata 配置seata:enabled:trueapplication-id:order-servicetx-service-group:order-tx-groupregistry:type:nacosnacos:server-addr:nacos:8848namespace:seataconfig:type:nacos// 使用 GlobalTransactional 开启分布式事务// AT 模式Seata 自动拦截 SQL生成 undo_log无需手写补偿逻辑GlobalTransactional(timeoutMills30000,namecreate-order-tx)publicvoidcreateOrder(OrderDTOdto){// 调用订单服务本地事务orderService.createOrder(dto);// 调用库存服务RPCSeata 自动协调inventoryService.deductStock(dto.getItemId(),dto.getQty());// 调用支付服务RPCpaymentService.createPayment(dto.getUserId(),dto.getAmount());// 任何一步失败Seata 自动回滚所有参与方}五、痛点与避坑指南坑1Saga 补偿逻辑漏写每个正向步骤必须有对应的补偿步骤且补偿操作必须是幂等的。建议用状态机工具Seata Saga强制管理不要手写。坑2TCC 忘记处理空回滚和幂等这是 TCC 最常见的 BUG见上文代码示例三个反模式必须全部处理。坑3Seata AT 模式在高并发下锁竞争AT 模式对热点数据会有行锁竞争。高并发场景秒杀、抢购建议改用 TCC Redis 热点数据处理。坑4可靠消息丢失导致数据不一致必须用 Transactional Outbox 模式消息与本地数据同事务写入不能先写 DB 再发 MQ中间可能崩溃。六、全文总结分布式事务方案选型核心决策树是否强一致性需求 ├── 是 → 同机房 → XA/2PC跨机房 → TCC └── 否最终一致可接受→ 有长业务流程需要补偿→ 是 → Saga 纯异步解耦场景→ 是 → 可靠消息 快速落地改造最少→ Seata AT 模式七、行业技术展望Seata 2.x 版本支持更多数据库驱动AT 模式性能优化显著基于 AI 的异常事务检测自动识别长时间未完成的 Saga 并触发告警XA over 云数据库阿里云 PolarDB、腾讯云 TDSQL 已原生支持跨实例 XA 事务参考文献Seata 官方文档 - https://seata.apache.org/zh-cn/docs/overview/what-is-seataMartin Fowler - Saga Pattern - https://microservices.io/patterns/data/saga.htmlTransactional Outbox Pattern - https://microservices.io/patterns/data/transactional-outbox.html阿里云 Seata 最佳实践 - https://help.aliyun.com/zh/mse/use-cases/seata-best-practicesChris Richardson - Microservices Patterns - https://microservices.io/Microsoft Azure 分布式事务指南 - https://learn.microsoft.com/zh-cn/azure/architecture/patterns/《分布式系统概念与设计》第5版George Coulouris 著腾讯云 DTF分布式事务框架文档 - https://cloud.tencent.com/document/product/1291
分布式事务解决方案全景:从 2PC 到 Saga,每种方案的适用场景与落地要点
分布式事务解决方案全景从 2PC 到 Saga每种方案的适用场景与落地要点分布式事务是后端架构最难的问题之一。很多团队的方案就是不管他出了问题手动补这在业务量小的时候还行一旦上了规模就会成为定时炸弹。本文系统梳理分布式事务的六大解决方案配合真实代码示例帮你根据业务场景选对方案不走弯路。一、分布式事务的根本难题为什么本地事务解决不了分布式场景单库事务本地事务 BEGIN UPDATE order SET statusPAID WHERE id1001; -- 订单库 UPDATE inventory SET stockstock-1 WHERE id501; -- 同一个库 COMMIT -- ACID 保障要么全成功要么全回滚 跨库/跨服务场景分布式事务困境 订单服务BEGIN → UPDATE order → COMMIT ✅ 库存服务BEGIN → UPDATE inventory → COMMIT ✅ or ❌ 支付服务BEGIN → INSERT payment → COMMIT ✅ or ❌ 问题三个服务分别提交网络/机器故障导致部分成功、部分失败 → 数据不一致CAP 理论的本质约束在网络分区P不可避免的情况下C强一致性和 A高可用二选一强一致性方案CP2PC、TCC → 性能开销大可用性受损最终一致性方案APSaga、可靠消息 → 性能好暂时不一致可接受二、六大分布式事务方案全景方案1XA/2PC两阶段提交Phase 1准备阶段 协调者 → 参与者1prepare? → 参与者1ready ✅ 协调者 → 参与者2prepare? → 参与者2ready ✅ 协调者 → 参与者3prepare? → 参与者3ready ✅ Phase 2提交阶段 协调者 → 参与者1commit! 协调者 → 参与者2commit! 协调者 → 参与者3commit!Java 实现Atomikos XAConfigurationpublicclassXADataSourceConfig{BeanPrimarypublicDataSourceorderXADataSource(){AtomikosDataSourceBeandsnewAtomikosDataSourceBean();ds.setUniqueResourceName(orderDataSource);ds.setXaDataSourceClassName(com.mysql.cj.jdbc.MysqlXADataSource);PropertiespnewProperties();p.setProperty(URL,jdbc:mysql://order-db:3306/order);p.setProperty(user,root);p.setProperty(password,${ORDER_DB_PASS});ds.setXaProperties(p);ds.setPoolSize(20);returnds;}// 库存数据源同理}// 业务代码Spring 自动管理跨库事务Transactional// Atomikos JTA 事务管理器自动处理跨库提交publicvoidcreateOrder(OrderDTOdto){orderRepository.save(dto.toOrder());// 写订单库inventoryRepository.deduct(dto.itemId());// 写库存库同一事务}适用场景同一机房内的异构数据库跨库操作不适合跨服务调用性能差协调者故障导致全局阻塞方案2TCCTry-Confirm-CancelTCC 将一次业务操作拆成三个阶段Try 预留资源冻结库存、冻结余额 Confirm确认提交实际扣减库存、实际扣款 Cancel 取消回滚释放冻结库存、退还冻结余额实现示例Seata TCC// 库存服务TCC 接口LocalTCCpublicinterfaceInventoryTccAction{TwoPhaseBusinessAction(namedeductInventory,commitMethodconfirm,rollbackMethodcancel)booleantryDeduct(BusinessActionContextParameter(paramNameitemId)LongitemId,BusinessActionContextParameter(paramNameqty)Integerqty,BusinessActionContextcontext);booleanconfirm(BusinessActionContextcontext);booleancancel(BusinessActionContextcontext);}ComponentpublicclassInventoryTccActionImplimplementsInventoryTccAction{OverrideTransactionalpublicbooleantryDeduct(LongitemId,Integerqty,BusinessActionContextcontext){// Try 阶段冻结库存不实际扣减InventoryinvinventoryRepository.findById(itemId);if(inv.getAvailable()qty){returnfalse;// 库存不足Try 失败}// 冻结库存inv.setFrozen(inv.getFrozen()qty);inv.setAvailable(inv.getAvailable()-qty);inventoryRepository.save(inv);returntrue;}OverrideTransactionalpublicbooleanconfirm(BusinessActionContextcontext){// Confirm 阶段将冻结库存转为实际扣减LongitemIdLong.valueOf(context.getActionContext(itemId).toString());IntegerqtyInteger.valueOf(context.getActionContext(qty).toString());InventoryinvinventoryRepository.findById(itemId);inv.setFrozen(inv.getFrozen()-qty);// 解冻// 库存已在 Try 阶段预扣Confirm 只需解冻inventoryRepository.save(inv);returntrue;}OverrideTransactionalpublicbooleancancel(BusinessActionContextcontext){// Cancel 阶段释放冻结库存LongitemIdLong.valueOf(context.getActionContext(itemId).toString());IntegerqtyInteger.valueOf(context.getActionContext(qty).toString());InventoryinvinventoryRepository.findById(itemId);inv.setFrozen(inv.getFrozen()-qty);inv.setAvailable(inv.getAvailable()qty);// 退还inventoryRepository.save(inv);returntrue;}}TCC 三个必须处理的坑// 坑1空回滚Try 未执行直接收到 Cancel// 解决Cancel 前检查是否有 Try 记录无记录则直接返回成功publicbooleancancel(BusinessActionContextcontext){Stringxidcontext.getXid();if(!tryRecordRepository.exists(xid)){log.warn(空回滚Try 记录不存在xid: {},xid);returntrue;// 幂等返回}// 正常回滚逻辑...}// 坑2幂等Confirm/Cancel 可能被重复调用// 解决记录事务状态重复调用直接返回publicbooleanconfirm(BusinessActionContextcontext){Stringxidcontext.getXid();TccRecordrecordtccRecordRepository.findByXid(xid);if(record!nullrecord.getStatus()CONFIRMED){returntrue;// 已提交幂等返回}// 执行 Confirm 逻辑...}// 坑3悬挂Cancel 先于 Try 执行// 解决收到 Cancel 后记录后续 Try 到来时拒绝publicbooleantryDeduct(LongitemId,Integerqty,BusinessActionContextcontext){Stringxidcontext.getXid();if(cancelRecordRepository.exists(xid)){log.warn(悬挂Cancel 已执行拒绝 Tryxid: {},xid);returnfalse;}// 正常 Try 逻辑...}方案3Saga 模式长事务首选Saga 将长事务拆分成多个本地事务的序列每个步骤有对应的补偿操作正向流程 T1创建订单→ T2冻结库存→ T3发起支付→ T4扣减库存→ T5发货通知 补偿流程T3 失败时 C2释放库存← C1取消订单Seata Saga State Machine 配置JSON{Name:orderFulfillmentStateMachine,Comment:订单履约 Saga,StartState:CreateOrder,Version:0.0.1,States:{CreateOrder:{Type:ServiceTask,ServiceName:orderService,ServiceMethod:createOrder,CompensateState:CompensateCreateOrder,Next:FreezeInventory,Output:{orderId:$.#root}},FreezeInventory:{Type:ServiceTask,ServiceName:inventoryService,ServiceMethod:freezeInventory,CompensateState:CompensateFreezeInventory,Input:[{orderId:$.orderId}],Next:MakePayment,Catch:[{Exceptions:[com.example.InsufficientInventoryException],Next:CompensateCreateOrder}]},MakePayment:{Type:ServiceTask,ServiceName:paymentService,ServiceMethod:makePayment,CompensateState:CompensateMakePayment,Next:Succeed},CompensateCreateOrder:{Type:ServiceTask,ServiceName:orderService,ServiceMethod:cancelOrder,Next:Fail},CompensateFreezeInventory:{Type:ServiceTask,ServiceName:inventoryService,ServiceMethod:releaseInventory,Next:CompensateCreateOrder}}}方案4可靠消息最终一致性// 下单时将消息与本地事务绑定发送Transactional Outbox 模式TransactionalpublicvoidcreateOrder(OrderDTOdto){// 1. 写订单OrderorderorderRepository.save(dto.toOrder());// 2. 写消息发件箱与订单同一个事务OutboxMessagemsgOutboxMessage.builder().aggregateId(order.getId().toString()).aggregateType(Order).eventType(OrderCreated).payload(JSON.toJSONString(newOrderCreatedEvent(order))).status(PENDING).createdAt(LocalDateTime.now()).build();outboxRepository.save(msg);// 本地事务提交订单 消息同时成功 or 同时失败}// 消息中继器独立轮询发件箱发送到 MQScheduled(fixedDelay100)publicvoidrelayMessages(){ListOutboxMessagependingoutboxRepository.findPending(100);for(OutboxMessagemsg:pending){try{kafkaProducer.send(newProducerRecord(domain-events,msg.getAggregateId(),msg.getPayload()));msg.setStatus(SENT);outboxRepository.save(msg);}catch(Exceptione){log.error(消息发送失败将重试,e);}}}// 库存服务消费幂等处理KafkaListener(topicsdomain-events)publicvoidhandleOrderCreated(Stringpayload){OrderCreatedEventeventJSON.parseObject(payload,OrderCreatedEvent.class);// 幂等检查if(processedEventRepository.exists(event.getEventId())){log.info(重复消息忽略eventId: {},event.getEventId());return;}// 处理业务逻辑inventoryService.deductInventory(event.getItemId(),event.getQty());// 标记已处理processedEventRepository.save(event.getEventId());}三、方案选型矩阵方案一致性性能开发复杂度适用场景XA/2PC强一致★★低同机房异构数据库低并发TCC最终一致极短暂不一致★★★★高金融/支付不允许中间状态暴露Saga最终一致★★★★中长流程业务订单履约、退款流程可靠消息最终一致★★★★★低跨服务异步解耦允许短暂不一致最大努力通知最终一致★★★★★最低非核心跨系统通知短信/积分AT 模式Seata最终一致★★★最低快速落地业务代码改动最少四、Seata AT 模式最快落地方案# application.yml - Seata 配置seata:enabled:trueapplication-id:order-servicetx-service-group:order-tx-groupregistry:type:nacosnacos:server-addr:nacos:8848namespace:seataconfig:type:nacos// 使用 GlobalTransactional 开启分布式事务// AT 模式Seata 自动拦截 SQL生成 undo_log无需手写补偿逻辑GlobalTransactional(timeoutMills30000,namecreate-order-tx)publicvoidcreateOrder(OrderDTOdto){// 调用订单服务本地事务orderService.createOrder(dto);// 调用库存服务RPCSeata 自动协调inventoryService.deductStock(dto.getItemId(),dto.getQty());// 调用支付服务RPCpaymentService.createPayment(dto.getUserId(),dto.getAmount());// 任何一步失败Seata 自动回滚所有参与方}五、痛点与避坑指南坑1Saga 补偿逻辑漏写每个正向步骤必须有对应的补偿步骤且补偿操作必须是幂等的。建议用状态机工具Seata Saga强制管理不要手写。坑2TCC 忘记处理空回滚和幂等这是 TCC 最常见的 BUG见上文代码示例三个反模式必须全部处理。坑3Seata AT 模式在高并发下锁竞争AT 模式对热点数据会有行锁竞争。高并发场景秒杀、抢购建议改用 TCC Redis 热点数据处理。坑4可靠消息丢失导致数据不一致必须用 Transactional Outbox 模式消息与本地数据同事务写入不能先写 DB 再发 MQ中间可能崩溃。六、全文总结分布式事务方案选型核心决策树是否强一致性需求 ├── 是 → 同机房 → XA/2PC跨机房 → TCC └── 否最终一致可接受→ 有长业务流程需要补偿→ 是 → Saga 纯异步解耦场景→ 是 → 可靠消息 快速落地改造最少→ Seata AT 模式七、行业技术展望Seata 2.x 版本支持更多数据库驱动AT 模式性能优化显著基于 AI 的异常事务检测自动识别长时间未完成的 Saga 并触发告警XA over 云数据库阿里云 PolarDB、腾讯云 TDSQL 已原生支持跨实例 XA 事务参考文献Seata 官方文档 - https://seata.apache.org/zh-cn/docs/overview/what-is-seataMartin Fowler - Saga Pattern - https://microservices.io/patterns/data/saga.htmlTransactional Outbox Pattern - https://microservices.io/patterns/data/transactional-outbox.html阿里云 Seata 最佳实践 - https://help.aliyun.com/zh/mse/use-cases/seata-best-practicesChris Richardson - Microservices Patterns - https://microservices.io/Microsoft Azure 分布式事务指南 - https://learn.microsoft.com/zh-cn/azure/architecture/patterns/《分布式系统概念与设计》第5版George Coulouris 著腾讯云 DTF分布式事务框架文档 - https://cloud.tencent.com/document/product/1291