是谁动了我的Seata?

463 阅读5分钟

一、Seata原理

Seata原理大家都懂,我就不多说了。

1658554284740.jpg

二、起因

因为公司项目逐渐变大,对单机集群项目进行了微服务拆分,虽然是单机,但是服务还是在不同module,调用也是用的feign。

拆分就不可避免的产生分布式事务,我们选择了 侵入式最,成熟,free 的Seata.

上海40度的下午,已经完成两个拆分的服务进行调试,理想很美好,现实很残酷。

image.png

分布式调用不是加一个

@GlobalTransactional(rollbackFor = Exception.class)

然后就完成了吗,就这么简单的事情怎么会出问题呢(seata已经配置完成,也连接上了,前置步骤都没有问题)。

三、坑一

做好了两个微服务后,和另外一个同事进行了联调,我和他说,我这边insert一个数据,你insert,然后你报异常我看看我会回滚吗。

结果就是他回滚了,我没有回滚

image.png

我仔细查看代码,发现他的异常被它吃掉了!就是这个东西

image.png

image.png

使用feign的fallback之后他报异常会返回这个fallback函数的内容给我。

我天真的认为我也会收到这个异常,其实我没有,我只收到结果的isSuccess()方法返回false,需要手动回滚(或许根据业务不管他,继续执行)

解决办法1.

每次判断返回结果是否成功,看看是不是影响主业务流程,影响了就回滚他。

我为此写了个工具类简化每次判断抛异常的流程。

/**
 * feign调用处理返回值
 * 调用失败抛出默认异常信息
 * @param result
 * @param <T>
 * @return
 */
public static <T> T feignHandler(R<T> result){
    if (result.isSuccess()) {
        return (T)(result.getData());
    } else {
        throw new ServiceException(ResultCode.FAILURE);
    }
}

解决办法2.

删除fallback函数,这样每次抛异常就会返回我一个FeignException的异常,这样我也收到异常,会自动回滚,不需要手动回滚。

这样也是存在问题的,这样不管是不是主干业务流程调用feign,都会回滚,比如我注册用户,给你发个优惠券,但是我发失败了,用户注册也给我回滚了,这显然不合理,所以我选择第一种

不知道有没有其他办法,大佬们可以给小弟一点意见

四、坑二

他报异常,他回滚了,我也回滚了。很完美

image.png

还有一种就是我插入一条,然后调用他的,他插入一条,然后我主动回滚事务,他能回滚吗。

image.png

果然,都是骗人的,什么注解一加,什么都OK,我回滚了,但是他还是没回滚,我很难受。

我使用Seata官方给的demo案例,没有问题,很简单,加个注解都OK,都是假的。

image.png

根据Seata的流程,我发起到时候向TC注册全局事务,然后TC返回我一个XID,然后我将XID传给另外一个服务,他发XID给TC来和我绑定同一个全局事务。这样我完成业务后和TC说回滚还是提交,TC根据全部事务的结果控制所有的事务是一起提交还是一起回滚。(我自己的理解,可能不太准确哈)

先看看调用方XID

image.png

XID:101.132.130.93:8091:18301242701283109,根据日志调用方回滚成功

再看看被调用方

image.png

很好,和想象中的一样,XID根据header传过来了,下面就是执行了

image.png

突然开始不对劲了,为什么现在XID和我header上的不一样,而且怎么还提交了,我调用方都回滚了呀

赶紧找出官网,他是怎么绑定这个XID的,原来官网使用一个拦截器绑定的

@ConditionalOnWebApplication
public class SeataHandlerInterceptorConfiguration implements WebMvcConfigurer {
    public SeataHandlerInterceptorConfiguration() {
    }

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SeataHandlerInterceptor()).addPathPatterns(new String[]{"/**"});
    }
}

public class SeataHandlerInterceptor implements HandlerInterceptor {
    private static final Logger log = LoggerFactory.getLogger(SeataHandlerInterceptor.class);

    public SeataHandlerInterceptor() {
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String xid = RootContext.getXID();
        String rpcXid = request.getHeader("TX_XID");
        if (log.isDebugEnabled()) {
            log.debug("xid in RootContext {} xid in RpcContext {}", xid, rpcXid);
        }

        if (StringUtils.isBlank(xid) && rpcXid != null) {
            RootContext.bind(rpcXid);
            if (log.isDebugEnabled()) {
                log.debug("bind {} to RootContext", rpcXid);
            }
        }

        return true;
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
        if (StringUtils.isNotBlank(RootContext.getXID())) {
            String rpcXid = request.getHeader("TX_XID");
            if (StringUtils.isEmpty(rpcXid)) {
                return;
            }

            String unbindXid = RootContext.unbind();
            if (log.isDebugEnabled()) {
                log.debug("unbind {} from RootContext", unbindXid);
            }

            if (!rpcXid.equalsIgnoreCase(unbindXid)) {
                log.warn("xid in change during RPC from {} to {}", rpcXid, unbindXid);
                if (unbindXid != null) {
                    RootContext.bind(unbindXid);
                    log.warn("bind {} back to RootContext", unbindXid);
                }
            }
        }

    }
}

他自动帮我从头部拿到XID,然后绑定到这个当前全局事务中,但为什么我的传过来了但是没有生效呢?

我断点到拦截器中,他register成功走了,但是拦截器的preHandle却没有走,我很奇怪,这是怎么回事

SpringMVC执行流程大家都懂吧,所以我找到了执行拦截器的地方,DispatcherServlet

image.png

里面有一个doDispatch方法

image.png

然后里面的applyPreHandle就是遍历拦截器,并且找到合适的

image.png

image.png

惊了,为什么是null,我明明注册了拦截器呀,启动断点也进去了,我debug看也注册成功了呀,这个是为什么

进入getHandler方法看看返回的适配器

image.png

返回这个适配器,为什么他的interceptors是null,不是注册了吗,看看他做了什么

image.png

他new了一个新的映射处理器,怪不得没有了拦截器,这个时候目前只能手动给他设置进去,这样他就生效了。

image.png

只是一个临时解决方案,比较新的需求已经出现了,没得办法继续深究了,以后有机会弄懂他这个原理再来填填坑。

谢谢大家啦!有什么不对的可以指点指点刚出门的小弟啦!