Seata全局事务回滚-脏数据导致回滚异常修复记录
说下背景吧,公司最近打算用Seata来保证SpringCloud微服务间的全局事务(AT模式),使用的是Seata-server 1.1.0版本。注册方式 Eureka,seata-server在测试环境部署了2台!
异常情况:
测试的时候 A服务为发起端 A->B->C 其中B服务在一次方法调用中 多次调用C服务更改同一条记录(数据的创建以及更改状态new->wait_pay->pay->success)。于是在A发起的请求结束时 B实际调用了3次C服务,导致C服务里有3条undo_log日志,之后 B服务发生了异常,C服务(每次都完成了本地事务)进行数据回滚(全局事务undo_log),此时正常情况应当是按照3条记录 1 2 3创建的后先顺序执行回滚,即 3 2 1 这就能保证数据正常回滚!
然而!!!在多次测试中发现数据回滚有时并不一定是按照 3 2 1 顺序执行,时常 会从2 开始 这就导致 2中的记录值 和当前数据库值的实际值 有偏差(2中记录修改后的状态为pay,实际数据库里的状态是3中更改后记录的状态success)这就导致了回滚镜像对比发现数据异常,认为是脏数据产生了!无法正确造成回滚!!!
找原因:
从结果上看 数据无法回滚 是由于 回滚时候 undo_log执行的顺序异常导致的,以此切入!跟踪seata-server的代码发现,分支事务回滚时 会根据全局事务xid到branch_table中按照记录生成的时间(`gmt_create`) 正序查询所有分支事件记录放入List中,之后从List里倒序取出,挨个执行回滚! 然后惊奇的发现 每次出现上述回滚异常 都是因为 有两条或多条branch_table记录的 gm_create是相同的 以致于后续回滚查询分支事务的时候 无法保证其先后顺序,而后执行回滚的顺序 就一样无法保证,才最终导致 上述错误!
解决方案:
问题找到了,既然按时间顺序查找不靠谱 那找个靠谱的值来查询不就行了,其中发现branch_id其实相对来说是递增的 但是只相对于同一个服务而言,
private static final AtomicLong UUID = new AtomicLong(1000);
/**
* Generate uuid long.
*
* @return the long
*/
public static long generateUUID() {
long id = UUID.incrementAndGet();
if (id >= getMaxUUID()) {
synchronized (UUID) {
if (UUID.get() >= id) {
id -= UUID_INTERNAL;
UUID.set(id);
}
}
}
return id;
}
实际测试环境部署了有2个seata-server服务 所以并不能保证 两个服务产生的branch_id有什么可靠的关联性,
最终考虑下 比较简单的方法是在branch_table里增加一个自增的字段,使用该字段代替gmt_create进行上述分支事务查询的排序依据即可!
(添加了自增的id,之后更改查询语句如下)
经过更改测试后 发现确实 解决了 上述无法顺序回滚带来的问题!
在测试 时间如果只是启动一个seata-server服务 一般是不会产生 上述问题的,时间基本 有先后的明显差别!再者branch_id也是明显增加的 也可以用作 查询排序依据,单多台seater-server的时候 就不能保证了!
本地重新打包各模块后,在seata-server的lib包下替换修改后的新的模块jar即可!
mvn clean install -DskipTests=true
以上!