背景:
seata偶发,并发下,事务长时间持有锁,后续事务无法获取锁,报错Global lock wait timeout
排查:
- 场景直接重试无复现,偶发问题
- 测试协助压测接口,发现发生概率约为4%
- 看Skywalking发现,底层某接口获取Seata全局事务锁超时具体报错如下
Error updating database. Cause: io.seata.rm.datasource.exec.LockWaitTimeoutException: Global lock wait timeout
- Skywalking查询该接口,发现最长7秒,最短300毫秒报错。
- 查阅seata文档,在AT模式下,默认配置下,@GlobalTransactional注解方法发生时,会尝试获取全局锁,如果新事务发生时,全局锁已存在,则直接抛异常。
- 再查询seata文档,有客户端配置项(不是seata的配置,是客户端,spring boot的配置)client.rm.lock.retryPolicyBranchRollbackOnConflict 默认True, 遇到全局锁时,直接报错;另:client.rm.lock.retryInterval校验或占用全局锁重试间隔默认10ms,client.rm.lock.retryTimes默认30次。
- 后续继续在github issue中发现https://github.com/apache/incubator-seata/issues/6120, 在2.0版本中会有数据修改后不能回退情况。配合自己查看源码和文档测试,发现压测中会有锁roll back fail。 然后这个锁在60秒后释放或一直不释放,在没释放之前所有用到这个条数据的seata全部报错。
- 根据https://github.com/apache/incubator-seata/issues/6120所言,把
本质:
- seata事务过大,事务中内容过多,处理过慢,导致新事务进入时,旧事务未处理完成且新事务未重试
@GlobalTransactional 没有根据具体要修改的内容形成事务,而是类似于直接大锁,锁了全局方法【后续查文档和源码,以及自己测试,实际只锁表中一条数据】- seata自身bug,见https://github.com/apache/incubator-seata/issues/6120和https://github.com/apache/incubator-seata/pull/6121
缓解:
所有seata客户端服务添加配置如下
# 分布式事务 配置项,对应 SeataProperties 类
seata:
client:
rm:
lock:
retryInterval: 500 # 500毫秒重试间隔
retryPolicyBranchRollbackOnConflict: false # 无法获取全局锁时重试
期望处理方式:
由于本质上是@GlobalTransactional没有根据业务来处理锁,而是直接全局锁,所以即便事务很小,在并发大的时候也会有同样问题,故期望于@GlobalTransactional通过某些业务字段或者别的方式锁部分,而非锁全局- 发现seata其实做了具体业务的锁,锁的是具体某个表的某行,但是如果这个数据在一个事务内多次更新,此时报错时,rollback没有按照倒序执行CBA,而是A回退B回退C回退,此时他发现数据被改了,于是rollback fail,不改数据也不释放锁;所以希望于他正常rollback;
解决:
seata版本1.5改为1.7.1
发表回复