生产环境,一个简单的事务方法,提交失败,报 Global lock wait timeout
伪代码如下:
@GlobalTransactional(rollbackFor = Exception.class,timeoutMills = 30000,lockRetryInternal=3000,lockRetryTimes=10)
@Override
public Boolean cancel(Long id, Long userId, Long companyId) {
// 保存业务数据
...
// 启动工作流
wkflAppServiceProvider.startProcess(....);
...
}
异常如下:
org.springframework.dao.QueryTimeoutException: JDBC commit; Global lock wait timeout; nested exception is io.seata.rm.datasource.exec.LockWaitTimeoutException: Global lock wait timeout
Caused by: io.seata.rm.datasource.exec.LockWaitTimeoutException: Global lock wait timeout
at io.seata.rm.datasource.exec.LockRetryController.sleep(LockRetryController.java:63)
at io.seata.rm.datasource.ConnectionProxy$LockRetryPolicy.doRetryOnLockConflict(ConnectionProxy.java:346)
at io.seata.rm.datasource.ConnectionProxy$LockRetryPolicy.execute(ConnectionProxy.java:335)
at io.seata.rm.datasource.ConnectionProxy.commit(ConnectionProxy.java:187)
at org.springframework.jdbc.datasource.DataSourceTransactionManager.doCommit(DataSourceTransactionManager.java:333)
... 57 more
Caused by: io.seata.rm.datasource.exec.LockConflictException: get global lock fail, xid:10.222.248.60:8091:2900686326154883760, lockKeys:wkfl_app_auth:12326192,12326193;act_ge_bytearray:6515890,6515891;act_re_procdef:rediscountClickSubmitCancel_UserTask_0yze6zf_5:1:6515892;act_re_deployment:6515889
at io.seata.rm.datasource.ConnectionProxy.recognizeLockKeyConflictException(ConnectionProxy.java:159)
at io.seata.rm.datasource.ConnectionProxy.processGlobalTransactionCommit(ConnectionProxy.java:252)
at io.seata.rm.datasource.ConnectionProxy.doCommit(ConnectionProxy.java:230)
at io.seata.rm.datasource.ConnectionProxy.lambda$commit$0(ConnectionProxy.java:188)
at io.seata.rm.datasource.ConnectionProxy$LockRetryPolicy.doRetryOnLockConflict(ConnectionProxy.java:343)
... 60 more
看到“LockWaitTimeoutException: Global lock wait timeout” 我以为是有资源竞争,导致加锁等待超时。但这个疑虑很快被打消了,因为这是必现的一个问题,每次执行到这个方法都报错,甚至在下班后系统没有人使用的情况下,我一点,还是报这个错,这个时候可以确定就我一个人在用,而且查了数据库没有被锁定的数据和事务,所以应该不是资源竞争导致的获取锁等待超时。
于是,我开始翻源码
数据源被代理,本地事务提交走的是io.seata.rm.datasource.ConnectionProxy#commit()
doCommit()方法是放在io.seata.rm.datasource.ConnectionProxy.LockRetryPolicy#execute()中执行的
由于我们这里client.rm.lock.retryPolicyBranchRollbackOnConflict配置的是false,所以这里失败后会重试,如果是true,则不重试
看到这里,我们找到了“Global lock wait timeout”的出处了,原来是因为doCommit()执行过程中抛异常了,再重试次数用完后就会抛出LockWaitTimeoutException。因此,LockWaitTimeoutException只是表象,并不是最根本的原因,根本原因是doCommit()报错了。
接着doCommit()看,我们知道,分支事务提交要先注册,注册成功后才能提交。而注册就是要获取全局锁。
通过观察DEBUG日志,发现保存业务数据部分的分支注册都是成功的
日志太多,截取关键部分,如图所示
结合代码,发现真正的报错发生在调用远程服务启动工作流那里
查看工作流相关服务的日志,发现一开始分支注册就失败了,部分关键日志如下
工作流那个服务里面,分支注册返回的信息是:Global lock acquire failed xid = ....
幸好之前读过Seata的源码,不然此时肯定手足无措
于是,翻开Seata Server的源码,看看为什么返回的消息是这样的
直接快进到io.seata.server.transaction.at.ATCore#branchSessionLock()
具体参见我的另一篇博文 https://www.cnblogs.com/cjsblog/p/16878067.html
在这里,我们找到了“Global lock acquire failed”这个报错信息的出处
证明,在执行branchSession.lock(autoCommit, skipCheckLock)的时候要么失败返回false,要么抛异常了
根据配置,这里是db,所以是DataBaseLockManager
接下来进入到LockStoreDat