资讯动态

Seata XA与TCC实战:异常处理、故障注入与选型

发布时间:2026/10/27 22:34:51 来源:尧图企业网站定制
分布式事务这个坑做微服务的人基本都绕不开。我第一次在订单链路里引入 Seata 是在一个交易系统的重构项目上当时的诉求很朴素下单要同时写订单库、扣库存库、扣账户余额任何一个环节失败前面写进去的数据都得干净地退回去。AT 模式上手快但遇到库存表有热点行、账户余额涉及复杂的冲正规则时AT 的全局锁和反向 SQL 补齐就开始让人不踏实。后来把链路拆成两套强一致要求高的资金类走 XA业务逻辑复杂、需要精细控制资源占用的走 TCC。这两种模式在 Seata 里都不是加个注解就完事配置、建表、幂等、空回滚、悬挂每一块都有具体的落地细节。这篇内容面向的是已经能跑起来 Seata 单机 Demo、准备把 XA 和 TCC 真正用进业务代码的同学。我会把两种模式的协议差异、工程配置的必改项、TCC 三大经典问题的处理代码、故障注入验证方法以及我自己踩过的那些坑一条条摊开讲。看完之后你应该能做到知道自己的业务该选哪个模式能独立把两种模式跑通并且在 Confirm 重试、Cancel 先到这种异常时序下不出错。1. 先把场景摆清楚XA 和 TCC 的选择题从哪来1.1 一个订单链路的真实约束先假设我们有一个最典型的交易场景用户下单订单服务写一条订单记录库存服务扣减库存账户服务扣减余额。三个服务三个库没有跨库事务但业务上要求它们要么全成、要么全败。AT 模式下Seata 会自动生成 undo_log用反向 SQL 把数据改回去。这条路在 90% 的场景里够用但它有两个隐含前提一是业务表能被反向 SQL 干净还原二是业务对全局锁的等待时间不敏感。库存扣减如果写成了update stock set qty qty - 5 where sku_id 1 and qty 5AT 能处理可如果业务里还夹着扣完库存要同步写一条流水、流水的 id 由库存表自增生成反向 SQL 的适用性就开始变得微妙回滚之后自增序列不会退回去流水号就断了。XA 模式的思路完全不同。它把提交和回滚的权力交给了数据库本身——分支事务在 prepare 阶段就完成了数据写入并持有了行锁TC 说提交才真正 commitTC 说回滚就直接 rollback。业务代码不用关心怎么补偿数据库保证原子性。代价也很直接从 prepare 到 commit 这段时间连接和锁一直被占着。TCC 则是把控制权完全交还给业务。Try 阶段做资源预留冻结库存、冻结金额Confirm 阶段确认扣减Cancel 阶段释放预留。Seata 只负责在全局事务结束的时候根据结果去调用你写好的 commit 或 cancel 方法不碰你的数据。所以 TCC 的一致性上限取决于你代码写得对不对反过来说性能上限也取决于你预留的粒度设计得好不好。这三个模式的差别本质上是一致性由谁保证和资源锁定时间由谁控制两个维度的排列组合。选型的时候不要一上来就问哪个性能好先问自己这段业务的数据能不能被自动反向还原不能就别在 AT 上硬扛直接考虑 TCC。1.2 两种模式在协议层的分水岭把 XA 和 TCC 放在一起对比最容易混淆的是它们看起来都是两阶段。确实都是两阶段但阶段里干的事情完全不同。维度XA 模式TCC 模式第一阶段做什么数据库层面的 prepare数据已写入但未提交持锁业务层面的 Try做资源预留不真正扣减第二阶段做什么数据库 commit 或 rollback调用业务写的 Confirm 或 Cancel谁保证原子性数据库的 XA 协议业务代码的正确性是否需要 undo_log不需要不需要是否需要业务改造几乎零改造Try/Confirm/Cancel 三个方法都要写锁持有时间长从 prepare 到全局 commit短Try 结束即释放数据库锁只留业务资源占用回滚的确定性高数据库直接回滚取决于 Cancel 写得对不对空回滚/悬挂风险不存在必须处理典型适用场景资金、账务、短链路的强一致场景库存、券、额度这类需要预留的资源注意XA 的不需要 undo_log这一点经常被搞混。AT 模式必须建 undo_log 表每个业务库都要建XA 模式依赖数据库自身的 XA 能力和 Seata 的 XA 数据源代理不需要 undo_log。我见过有人把两种模式的建表脚本混着执行跑起来没问题但排查问题时会被误导。这里还要澄清一个常见误解XA 并不等于性能差到不能用。MySQL 的 InnoDB 从很早的版本就支持 XAprepare 阶段的写操作开销和普通事务差别不大真正拖慢的是锁和连接的持有时间。如果你的链路只有两三个分支、每个分支的执行时间在毫秒级XA 的表现完全可接受。真正需要警惕的是长链路、慢 SQL 混在里面的场景——一个分支卡住十秒整条链路的连接池都会被拖垮。2. XA 模式落地把两阶段提交交给数据库2.1 从 TM 到 RM 的完整链路Seata 的 XA 模式里角色分工是清晰的。发起全局事务的服务是 TM它向 TC 注册一个全局事务拿到 XID然后通过 RPC 把这个 XID 传给下游分支。下游服务作为 RM在收到请求后通过数据源代理拿到 XID向 TC 注册分支然后执行本地的 prepare。全局事务结束时TM 根据结果通知 TCTC 再逐个通知 RM 执行 commit 或 rollback。这个链路里最容易出问题的环节是 XID 的传递。Seata 通过拦截器在服务调用时把 XID 塞进请求头Feign、RestTemplate、Dubbo 都有对应的适配。如果你用的是自研的 RPC 框架或者消息队列做异步调用XID 大概率传不过去表现就是下游分支拿不到全局事务上下文于是它自己开了一个本地事务提交了回滚的时候根本回不去。我遇到过一次线上事故就是这么来的下单走同步调用扣积分走的是消息队列结果订单回滚了积分却扣掉了。解决办法是在消息头里手动透传 XID消费端用RootContext.bind(xid)把上下文恢复回来消费完再 unbind。另一个容易忽略的点是数据源代理的生效范围。Seata 的 XA 代理需要把你的 DataSource 包一层DataSourceProxyXA如果你的项目里有多个数据源、或者手动 new 了 JdbcTemplate被包住的只有 Spring 容器里的那个数据源。所以在 XA 模式下任何绕过注入数据源的数据库操作都不会参与全局事务——这类代码在测试环境很难暴露因为回滚路径不常走等到生产上真的回滚了一次才发现数据没退代价就大了。2.2 工程配置的几个必改项XA 模式的配置比 AT 多几个关键项核心是告诉 Seata用 XA 代理。seata: enabled: true application-id: order-service tx-service-group: trade_tx_group enable-auto-data-source-proxy: true >XA RECOVER;这条命令会列出所有处于 prepared 状态的 XA 事务包含 formatID、gtrid、bplength 等信息。gtrid 里通常编码了 Seata 的 XID用它能对应上是哪个全局事务。处理方式有两种等 TC 重试成功自动清理或者是在确认业务已经不需要这个分支之后手动XA ROLLBACK xid。手动回滚是最后手段一定要确认清楚否则可能把本该提交的数据回滚掉。提示把XA RECOVER做成定时巡检任务输出到监控里。我在一个资金系统上就是这么干的每天凌晨跑一次有残留就告警比等业务方反馈数据被锁住了要主动得多。3. TCC 模式落地用三段式代码换回来的控制权3.1 Try/Confirm/Cancel 的职责边界TCC 的核心是把扣减拆成预留和确认。以账户扣款为例Try 阶段不是直接扣余额而是冻结balance不动frozen加上去。Confirm 阶段把frozen减掉、balance减掉完成真正的扣款。Cancel 阶段把frozen减回去余额恢复。这个拆法的好处非常直接Try 只是一个普通的本地事务执行完就提交数据库行锁立刻释放不会像 XA 那样一直持锁到全局事务结束。真正的资源占用变成了业务层面的冻结金额粒度和时长都由你控制。但代价也跟着来了。三个方法都要你自己写而且必须满足几个约束Try 要能判断资源是否充足预留失败要让全局事务回滚Confirm 必须能成功不能有业务校验失败的分支否则全局事务会卡在重试上Cancel 必须能释放 Try 预留的资源而且要在 Try 没执行的情况下也能安全返回。最后一条是新手上路最容易翻车的地方。Confirm 里如果有if (balance amount) throw new Exception()这种校验一旦余额被其他事务改动了Confirm 就永远成功不了TC 会一直重试直到超时脏数据就留在那里了。正确做法是把校验全部放到 Try 里Confirm 只做无条件的确认操作。LocalTCC public interface AccountTccAction { TwoPhaseBusinessAction(name accountTccAction, commitMethod confirm, rollbackMethod cancel, useTCCFence true) boolean tryFreeze(BusinessActionContextParameter(paramName userId) Long userId, BusinessActionContextParameter(paramName amount) BigDecimal amount, BusinessActionContext context); boolean confirm(BusinessActionContext context); boolean cancel(BusinessActionContext context); }BusinessActionContextParameter是关键它把参数序列化后存进 TC这样 Confirm 和 Cancel 执行时能通过context.getActionContext(amount)拿回原始参数。没有这个注解的参数不会保存Confirm 里就取不到值会直接空指针。这个坑我在第一次写的时候踩了现象是 Confirm 报 NPE日志里看不出所以然最后对着 context 里的 map 打出来才发现是参数没存。3.2 幂等、空回滚、悬挂的工程化处理这三个问题是 TCC 的老三位换任何框架都绕不过去必须一个一个处理。幂等Confirm 和 Cancel 都可能被 TC 重复调用。原因很多——网络超时导致 TC 认为失败并重试、RM 执行成功但响应丢了、TC 侧状态没更新。所以两个方法都必须做成执行一次和执行 N 次结果一样。实现方式通常是靠事务状态表加唯一索引或者靠数据库的幂等条件比如where status 1。空回滚Cancel 先于 Try 到达。典型场景是 Try 阶段还没执行完就超时了TC 判定失败直接下发 Cancel结果 Cancel 执行的时候业务数据还没冻结什么都不该释放。如果不做处理Cancel 里的frozen frozen - amount会把别人的冻结金额减掉直接造成资金错账。悬挂Try 在 Cancel 之后到达。Try 一旦在空回滚之后执行成功预留的资源就永远没人释放了。所以 Try 里必须先检查有没有空回滚记录有的话直接拒绝执行。三个问题的处理可以统一到一张事务状态表上这张表同时也承担了幂等的职责CREATE TABLE IF NOT EXISTS tcc_branch_log ( xid VARCHAR(128) NOT NULL COMMENT 全局事务ID, branch_id BIGINT NOT NULL COMMENT 分支事务ID, action_name VARCHAR(64) NOT NULL COMMENT 动作名, status TINYINT NOT NULL COMMENT 1tried 2confirmed 3cancelled 4empty-rollback, amount DECIMAL(18,2) DEFAULT NULL, gmt_create DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), gmt_modified DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), PRIMARY KEY (xid, branch_id), KEY idx_gmt_modified (gmt_modified) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意主键是(xid, branch_id)的组合这个唯一约束就是幂等的第一道防线。xid 在 Confirm 和 Cancel 执行时可以从context.getXid()拿到branchId 从context.getBranchId()拿两者组合唯一标识一个分支。3.3 fence 表与本地事务的配合Seata 从 1.5 之后给 TCC 提供了 fence 机制本质上就是框架帮你把这套状态表的逻辑做了一层封装官方给的表叫tcc_fence_log。开没开、用不用会直接影响你需不需要自己写状态判断。CREATE TABLE IF NOT EXISTS tcc_fence_log ( xid VARCHAR(128) NOT NULL COMMENT global id, branch_id BIGINT NOT NULL COMMENT branch id, action_name VARCHAR(64) NOT NULL COMMENT action name, status TINYINT NOT NULL COMMENT status(tried:1;committed:2;rolledback:3;rollbacked:4), gmt_create DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), gmt_modified DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), PRIMARY KEY (xid, branch_id), KEY idx_gmt_modified (gmt_modified), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;使用方式是在TwoPhaseBusinessAction上设置useTCCFence true同时 Try 方法必须加上Transactional让 Try 的本地事务和 fence 记录写入在同一个事务里。这样如果 Try 的业务操作成功但 fence 记录写失败整个本地事务回滚不会出现资源冻结了但没有记录的状态。这里有个细节必须说清楚fence 的拦截逻辑依赖本地事务的边界。如果 Try 方法内部又调了别的服务、开了新的数据库连接事务传播行为一旦变成REQUIRES_NEWfence 记录和业务写入就分属两个事务原子性就断了。所以 TCC 的 Try 方法要保持简单的单库操作跨库的事情交给全局事务去做别在 Try 里面又嵌套一层。我自己现在的做法是业务简单、只需要防重和防悬挂的直接用 fence省掉手写状态表业务里需要在 Cancel 时做一些额外动作比如发通知、记审计日志的还是自己维护状态表因为 fence 的状态机是封闭的你没法在它的流程里插入业务逻辑。两种方式不冲突同一个项目里混着用也没问题只是要统一团队认知别几个人写几种风格。4. 两套代码从零跑通的顺序4.1 环境与依赖的准备工作先把 TC 拉起来。用官方的 seata-server 包存储模式选 db 或者 raft。file 模式虽然开箱即用但重启会丢事务状态生产上不能用。db 模式需要建三张表global_table、branch_table、lock_table脚本在 server 包的script/server/db目录下。# 启动 TC指定存储模式和注册中心 sh bin/seata-server.sh -p 8091 -m db \ -Dstore.db.urljdbc:mysql://127.0.0.1:3306/seata?useSSLfalseserverTimezoneAsia/Shanghai \ -Dstore.db.userroot \ -Dstore.db.passwordxxxserverTimezone这个参数别省我见过因为时区不一致导致全局事务超时判断错乱的现象是事务明明几秒就结束了TC 却认为超时日志里全是回滚。业务侧的依赖就是seata-spring-boot-starter版本要和 TC 保持一致。版本不一致的坑很隐蔽客户端和服务端之间的 RPC 协议在跨大版本时可能有变化表现是注册成功但通信报错。统一版本或者至少保证在同一个大版本内是最省事的做法。数据库侧的准备分两套模式需要的表说明XA无额外表只依赖数据库 XA 能力和 InnoDBTCCtcc_fence_log用 fence 时或自建状态表必须在每个参与 TCC 的业务库里建AT对照undo_log用 AT 的时候才需要很多人第一次切 XA 的时候习惯性地去建 undo_log建完发现没用到其实是白折腾。4.2 XA 侧的改造步骤第一步改配置把>Bean public DataSource dataSource(DruidDataSource druidDataSource) { return new DataSourceProxyXA(druidDataSource); }第二步在入口方法上加GlobalTransactional。这个注解加在 TM 的入口上别加在中间层加多了会产生嵌套的全局事务。GlobalTransactional(name create-order-xa, timeoutMills 30000, rollbackFor Exception.class) public Long createOrder(OrderCreateCmd cmd) { Long orderId orderService.save(cmd); inventoryClient.deduct(cmd.getSkuId(), cmd.getCount()); accountClient.debit(cmd.getUserId(), cmd.getAmount()); return orderId; }第三步确认下游服务也被 Seata 代理了。下游服务不需要GlobalTransactional但必须能被 XID 拦截器注入上下文且数据源被代理。验证方法是在下游打个断点看RootContext.getXID()有没有值没有值就说明拦截器没生效。第四步做一次正常的回滚测试。让最后一个分支抛异常看前两个分支的数据有没有被回滚。这个测试必须在联调阶段做完上线后再测成本太高。4.3 TCC 侧的改造步骤TCC 的改造量大一些顺序上建议从资源预留设计开始而不是从写代码开始。先想清楚每类资源怎么预留。库存的预留是可用库存转冻结库存账户的预留是可用余额转冻结余额优惠券的预留是状态从可用改成锁定。这个映射关系定下来代码就顺了。然后是接口定义LocalTCC加在接口上三个方法分别标TwoPhaseBusinessAction、commitMethod、rollbackMethod。参数里凡是 Cancel 和 Confirm 要用到的都必须加BusinessActionContextParameter。Override Transactional(rollbackFor Exception.class) public boolean tryFreeze(Long userId, BigDecimal amount, BusinessActionContext context) { String xid context.getXid(); Long branchId context.getBranchId(); // 防悬挂Cancel 已经先执行过直接拒绝 TccBranchLog existed branchLogMapper.selectOne(xid, branchId); if (existed ! null existed.getStatus() EMPTY_ROLLBACK) { throw new IllegalStateException(branch already rolled back: xid); } // 幂等Try 重复执行直接返回 if (existed ! null existed.getStatus() TRIED) { return true; } int updated accountMapper.freeze(userId, amount); if (updated 0) { throw new BizException(余额不足冻结失败); } branchLogMapper.insert(xid, branchId, accountTccAction, TRIED, amount); return true; }Cancel 这边的顺序要反过来先判断有没有 Try 记录Override Transactional(rollbackFor Exception.class) public boolean cancel(BusinessActionContext context) { String xid context.getXid(); Long branchId context.getBranchId(); TccBranchLog log branchLogMapper.selectOne(xid, branchId); // 空回滚Try 从没执行过记一笔空回滚标记后直接成功 if (log null) { branchLogMapper.insert(xid, branchId, accountTccAction, EMPTY_ROLLBACK, null); return true; } // 幂等已经取消过或已确认直接返回 if (log.getStatus() CANCELLED || log.getStatus() CONFIRMED) { return true; } accountMapper.unfreeze(log.getUserId(), log.getAmount()); branchLogMapper.updateStatus(xid, branchId, CANCELLED); return true; }注意 Cancel 里释放冻结用的金额是从状态表里读的不是从 context 里读的。这样做的好处是即使 context 参数丢失Cancel 也能正确释放坏处是多一次查询。我倾向于读表因为金额这类东西多一次查询换确定性是值得的。Confirm 的实现类似只做无条件确认不做任何业务校验执行完把状态置为 CONFIRMED。5. 故障注入验证确认回滚真的发生了5.1 三种故障点的注入方法代码写完不代表能回滚。分布式事务最难验证的就是异常路径真的走通了所以我习惯在上线前做三轮故障注入。第一轮业务异常回滚。在链路的最后一个分支手动抛异常检查前面分支的数据是否回滚。这一轮验证的是基础链路通不通。XA 模式下看分支表的数据是否恢复原值TCC 模式下看冻结金额是否被释放。第二轮超时回滚。在某个分支里加Thread.sleep把耗时拉到超过全局事务超时时间默认 60 秒我一般在注解上显式设成 30 秒以内。这一轮验证的是 TC 的超时判定和回滚下发是否正常。GlobalTransactional(name create-order-tcc, timeoutMills 15000, rollbackFor Exception.class) public Long createOrder(OrderCreateCmd cmd) { // ... }超时回滚最容易暴露的问题就是空回滚和悬挂。因为超时的时候有些分支可能还没开始执行TC 就下发 Cancel 了正好命中空回滚路径。跑完这一轮去状态表里查status 4的记录有记录说明空回滚逻辑被触发了再看后续 Try 有没有正确拒绝。第三轮进程中断。在 Try 执行完之后、全局事务提交之前把 RM 所在的服务进程 kill 掉。这一轮验证的是 TC 的自动重试能力。重启之后观察日志正常情况下 TC 会按配置的重试次数和间隔反复下发 Confirm最终把所有分支推进到完成状态。重试次数和间隔在 TC 侧配置server.recovery.committing-retry-period是提交重试间隔rollbacking-retry-period是回滚重试间隔超时时间server.recovery.timeout-retry-period决定多久判定为超时。这一轮还有个附带检查项TC 的重试只在事务状态处于 committing 或 rollbacking 时生效如果事务因为超过最大重试次数被标记成 dead 状态就不会再自动推进了。所以第三轮跑完一定要去global_table里看事务状态出现 dead 就要靠人工介入。5.2 观察日志与数据核对验证不能只看接口有没有报错数据必须逐个字段核对。我一般准备三张核对清单检查项XA 模式TCC 模式分支表数据原值未变冻结字段归零状态表无残留 prepared 事务状态为 cancelled 或 confirmed日志RM 收到 rollback 指令Cancel 方法有执行记录连接池无长时间占用无异常增长数据库锁XA RECOVER无记录无行锁等待核对的时候有个小技巧给每次测试用不同的业务单号测完按单号查所有相关表一眼就能看出哪张表没退回去。用同一个单号反复测容易把数据搞混尤其是 TCC 的冻结字段同一个用户多测几轮冻结余额的数值会对不上。日志这边要在 Try、Confirm、Cancel 三个方法入口都打印 xid 和 branchId最好用 MDC 串起来。出问题的时候按 xid 过滤一次整条链路的执行顺序就出来了。TCC 的排查效率几乎完全取决于日志打得好不好因为整个流程散落在三个方法里没有完整日志就只能靠猜。6. 踩坑清单与选型结论6.1 高频报错与定位顺序下面这几个报错我在不同项目里反复见过按出现频率排一下。no available service default found客户端找不到 TC。九成是vgroup-mapping配的和tx-service-group对不上或者 TC 的地址填错了。如果是注册中心模式还要确认客户端和服务端在同一个命名空间、同一个分组下。Global transaction is not active全局事务已经结束了下游还在往这个事务里注册分支。常见于异步调用或者全局事务超时之后下游才慢吞吞地执行。解决办法是把耗时操作挪出全局事务范围或者适当调大超时时间——但调超时是治标根子还是链路太长。Branch transaction already committedTCC 的 Confirm 被重复执行而你的代码里没做幂等。要么状态表判断漏了分支要么BusinessActionContextParameter的参数在重复执行时变了导致状态查不到。按 xid 去状态表捞一下记录就能确认。XAER_RMFAILXA 分支的数据库连接失效通常是数据库重启或者连接超时。这类错误 TC 会重试但如果数据库那边的 prepared 事务已经断了重试也成功不了得靠XA RECOVER兜底。定位顺序我总结成一条先看 xid 有没有正常传递拦截器生效没再看 TC 里这个事务的状态active / committing / rollbacking / dead最后看分支执行日志。这三步能覆盖 80% 的问题剩下的基本是配置和版本不一致。6.2 一张表看清怎么选回到最初的问题到底选哪个。我的判断顺序是这样的判断条件倾向 XA倾向 TCC分支数量2 到 3 个4 个以上链路长单分支耗时毫秒级包含远程调用或复杂计算业务能否改造不想改造可以投入改造资源属性直接扣减需要预留库存、额度、券一致性要求强一致最终一致可接受热点资源少多需要控制锁粒度我的实际做法是分层资金扣减、账务记账这类不能出错的短链路走 XA因为业务改造收益低、风险高库存、优惠券、额度这类天生适合预留的资源走 TCC因为预留本身就是业务上一个自然的动作。同一个系统里两种模式混用是完全正常的Seata 支持同一个全局事务里混跑不同模式的分支。混用的时候有一个必须注意的点AT 的全局锁和 XA 的数据库锁作用范围不一样如果同一个资源表有两个分支分别用 AT 和 XA 操作锁的冲突行为会很混乱。我的原则是一张业务表只允许一种模式访问跨表的事情交给全局事务串。还有一种情况值得单独提一句如果业务能接受最终一致、并且有天然的对账机制那就不一定要上 TCC本地消息表加重试可能更简单。分布式事务框架解决的是过程一致性不是最终正确性有对账兜底的业务把复杂度压到对账链路上往往更划算。我个人在实际操作中的体会是XA 和 TCC 的代码量差距没有想象中那么大真正的差距在排查成本上。XA 出问题时绝大部分线索在数据库层面XA RECOVER加上事务表就能定位个八九不离十TCC 出问题时线索散在三个方法的日志和状态表里你得像拼图一样把执行顺序拼出来。所以如果团队的监控和日志基础比较薄弱哪怕业务适合 TCC也建议先用 XA 顶上等可观测性建好了再迁。反过来如果业务链路长、分支多、性能压测怎么都过不了那该吃的改造代价就得吃别指望在 AT 或 XA 上硬调参数能调出 TCC 的效果。还有一个可以往后延展的方向把 TCC 的 Confirm 和 Cancel 做成非阻塞的异步执行Try 阶段返回之后就让全局事务尽快结束Confirm 走独立线程池这样能进一步压缩资源占用和响应时间代价是对状态机的可靠性要求更高需要考虑任务丢失的补偿。这个思路在小规模验证的时候效果不错但真上生产之前得先把状态表的清理策略想清楚否则那个表会一直长下去。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑