广告:Codex Token 低价中转站稳定接口 · 快速接入 · 开发者备用通道
Engineering article

分布式事务Seata使用?零慢查询

我用Seata搞分布式事务,核心问题就是零慢查询。别看网上一堆文档,很多都是水文,实际用起来得小心。假设你用的是MySQL,Seata的AT模式能自动回滚,但慢查询是真痛点,特别是高并发场景下。我见过在订单服务和库存服务之间,事务提交时MySQL日志写入卡顿,导致整体延迟飙升。这时候得从配置下手,尤其是undo log的清理策略和全局事务

分布式事务Seata使用?零慢查询
配图来源于网络和AI生成,仅供参考。
▌ 技术引导
我用Seata搞分布式事务,核心问题就是零慢查询。别看网上一堆文档,很多都是水文,实际用起来得小心。假设你用的是MySQL,Seata的AT模式能自动回滚,但慢查询是真痛点,特别是高并发场景下。我见过在订单服务和库存服务之间,事务提交时MySQL日志写入卡顿,导致整体延迟飙升。这时候得从配置下手,尤其是undo log的清理策略和全局事务日志的写入模式。别以为开个全局事务就能万事大吉,你得知道Seata的TC、TM、RM是怎么协同的,尤其在跨集群、跨数据库时,事务识别和资源协调都得重新设计。还有个关键点,别用单节点TC,分布式事务的TC必须是集群部署,不然一挂就全完。实际部署时,我看到有人直接用本地事务做模拟,结果一压测就翻车,真是不靠谱。记住,Seata的慢查询不是技术本身的问题,是用法和配置没到位。

▌ 技术参考

一 你得知道Seata的AT模式默认会生成undo log,这个log就是用来实现回滚的,但它的写入方式和MySQL的binlog有关,所以别直接用MySQL的slow query log看起来慢,其实是因为undo log在事务结束后写入了磁盘,这会拖慢整体性能。我之前在测试时发现,订单服务和库存服务用AT模式,事务提交时MySQL的write set会变得特别大,尤其是涉及多个表的update,这时候undo log的大小会直接决定事务完成的速度。如果你发现事务提交时响应时间在300ms以上,那大概率是undo log没优化好。

二 配置Seata的undo log清理策略,需要在seata.conf文件中设置undo_log_table和undo_log_delete_strategy。例如,undo_log_table是seata_undo_log,而undo_log_delete_strategy可以设置为“manual”或“automatic”,但别用“automatic”!我这次踩坑,就是用了自动清理,结果事务还没提交,undo log就被清理了,导致回滚失败。手动清理的话,你得定时执行truncate seata_undo_log,不过别用delete,delete会锁表,truncate才不影响性能。用Shell脚本定时执行,比如crontab里加个每小时执行一次truncate的命令,这样既安全又高效。

三 高并发下的事务冲突,是Seata的常见问题。我以前在双11预演时,订单服务和库存服务同时处理大量交易,TC节点没做负载均衡,导致事务编号重复,最终引发异常。这时候得在TC节点上配置多个实例,用IP地址或DNS做负载均衡,比如Nginx或者HAProxy。另外,TM服务在发送全局事务开始请求时,要带上合适的事务分组,否则TC会把事务分配到错误的节点。配置文件里要确保txServiceGroup和service.vgroupMapping配置一致,否则事务就死在中间了。

四 全局事务日志的写入模式,直接影响性能。Seata的TC节点默认是使用File模式写入日志,但面对高并发时,File模式会导致IO瓶颈。我记得在某个项目中,我们改成用MySQL的数据库表来存全局事务日志,结果吞吐量提升了3倍,但代价是数据库压力陡增。这时候得在TC的配置文件里调整storageMode参数,从file换成db,同时把gtx.log.table配置成你的MySQL表。不过别急着改,得在测试环境先压测,看看TPS有没有下降,否则直接上线会成大问题。

五 事务日志的清理策略,不只是undo log的问题,全局事务日志同样需要处理。我们用的是基于时间的清理,比如设置gtx.log.expire_seconds=86400,这样一天之后的事务日志就会被自动清理。但有人直接用delete from gtx_log where gmt_create < now() - interval 1 day,结果锁表锁死了整个TC节点。正确的做法是用truncate或者定期执行清理任务,别随意写delete语句。而且,清理频率别设得太高,否则TC的内存会频繁被回收,影响稳定性。

六 Seata的RM部分,如果你用的是MySQL,别忘了配置autoCommit=1。我之前遇到一个场景,RM在事务中间执行了commit,结果Seata没接收到这个commit,导致事务挂起,最终整个服务卡死。所以,RM的数据库连接池必须配置为不自动提交,这样Seata才能正确控制事务的提交和回滚。你可以在连接池配置里加个参数,比如spring.datasource.default-auto-commit=false,或者在JDBC URL里传jdbc:mysql://...?autoCommit=false。这个细节容易被忽略,但一出问题就很难排查。

七 事务的回滚操作,实际上是对undo log的执行,这需要MySQL的binlog处于开启状态。我去过一个项目,他们因为没开binlog,导致Seata在回滚时无法恢复数据,结果整个事务处理流程变得不可靠。记得在MySQL的my.cnf里配置log-bin=mysql-bin,并且设置binlog_format=ROW,这样undo log才能正确还原数据。否则,Seata会报“无法回滚”之类的错误,而你根本不知道从哪里开始查。

八 有些场景下,Seata的AT模式会因为事务参与方太多,导致性能下降。比如,一个订单事务要涉及15个RM,这时候每个RM都要生成undo log,事务提交时就会变得很慢。这时候可以考虑用TCC模式替代部分RM,尤其是那些不需要强一致性但要快速响应的业务。不过TCC需要你手动实现try、confirm和cancel三个阶段,这在复杂业务中容易出错。我之前用TCC处理支付回调,结果confirm阶段因为网络延迟,导致事务要么重复确认,要么超时,最终数据不一致。

九 如果你用的是Kafka做异步消息队列,记得在Seata的RM配置里加上kafka的相关参数,比如enable-kafka=true,kafka.default-address=host:port。否则,Seata无法识别消息队列作为事务资源,导致事务无法正确提交或回滚。我之前在高并发场景下,因为没配置kafka的地址,导致事务挂起,整个系统响应变慢。配置完后,得测试一下事务是否能正常流转,特别是在消息消费失败的情况下。

十 在Seata的RM中,如果事务不涉及MySQL,而是用其他数据库比如Oracle,那undo log的生成方式就不同了。这时候得在seata.conf里配置存储引擎,比如storage-engine=oracle。如果配置错误,Seata会抛出“无法生成undo log”的异常,而你根本不知道是哪里的问题。另外,Oracle的undo表空间得足够大,否则会频繁报错,影响事务性能。这个细节我之前没注意,直到压测时才发现,差点把整个系统搞崩溃。

十一 事务的隔离级别对Seata的性能影响极大。默认是REPEATABLE READ,但如果你的业务对一致性要求不高,可以改成READ COMMITTED。我之前在某个场景下,把隔离级别调低后,事务的执行速度提升了20%,而且undo log的大小也变小了。不过别轻易改,默认配置可能已经够用了,特别是在数据量大的情况下。这个调整要根据你的业务场景来,不能一概而论。

十二 零慢查询的优化,不只是改配置,还得看你的业务逻辑。比如,订单创建和库存扣减,如果订单服务先发消息,库存服务再响应,这样可以避免事务嵌套。我之前在某个系统里,订单服务直接调用库存服务,导致事务链条过长,MySQL的undo log写入变慢。后来改成异步处理,用消息队列解耦,事务链条缩短,整体性能提升了。但异步处理也会带来数据延迟,得在业务允许的范围内做权衡。

十三 如果你用的是Nacos做注册中心,记得在TC的配置里加上nacos.serverAddr=ip:port,同时确保Nacos的集群配置正确。我之前在某个项目里,TC节点连接到错误的Nacos集群,导致事务注册失败,TC没法感知RM的状态,最终事务全挂。配置正确后,得再检查一下Nacos的心跳策略,避免TC因为心跳超时而断开连接,影响全局事务的协调。

十四 有些情况下,事务的回滚会因为undo log的自动清理策略而失效。比如,你用了自动清理,但事务还没完成,undo log就被删了,这时候回滚就找不到数据。所以,手动清理undo log时,要确保事务已经提交,否则会报错。我之前在测试时,直接用truncate seata_undo_log,结果因为没有事务完成,导致部分数据丢失,之后不得不重新跑整个流程,浪费了很多时间。

十五 Seata的TC节点在高并发下容易出现OOM问题。我之前在某个项目里,TC节点因为undo log堆积太多,导致内存爆掉,整个系统瘫痪。这时候得调整undo log的清理频率和策略,比如开个独立的线程,定时清理旧的undo log,避免内存占用过高。另外,TC的线程池配置也得优化,比如transmit-timeout-millis=30000,这样可以防止事务超时影响性能。这些参数不是随便调的,得根据实际业务量来调整,否则容易踩坑。