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

以上!

 

posted @ 2020-03-05 22:24  苍舒  阅读(8610)  评论(2编辑  收藏  举报