feign调用服务,被调用服务seata事务不开启或者xid为空

当前场景:app接口服务调用A服务和B服务。app接口内会回滚,A服务和B服务不会回滚。

App接口

    @GlobalTransactional(rollbackFor = Exception.class)
    public Result uploadFile(SaveLogDataReq req) {
        System.out.println("seata全局事务id====================>"+ RootContext.getXID());
        
        servieFeignA.addAAA(req);
        servieFeignB.addBBB(req);
        
        int i = 1/0;
        
        return Resu.ok();
    }

A服务

   
    public Result addAAA(SaveLogDataReq req) {
        System.out.println("seata全局事务id====================>"+ RootContext.getXID());
        
        //todo AAA代码逻辑......
        
        return Resu.ok();
    }

 B服务

 
    public Result addBBB(SaveLogDataReq req) {
        System.out.println("seata全局事务id====================>"+ RootContext.getXID());
        
        //todo BBB代码逻辑......
        
        return Resu.ok();
    }

分析:查看打印日志显示,A服务和B服务的xid为空,或者与APP接口服务的xid不一致。

解决方案:

方案一,在fein远程调用的时候将当前的xid传入header

1,增加request拦截器


public class MultipartSupportConfig implements RequestInterceptor {

    /**
     * 解决服务直接调用请求头不传递的问题
     * @param template
     */
    @Override
    public void apply(RequestTemplate template) {
        //解决不传递请求头中的token
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null){
            HttpServletRequest request = attributes.getRequest();
            Enumeration headerNames = request.getHeaderNames();
            //可以在这里将自定义请求头传递进去, key 请求, value 值
            //处理上游请求头信息,传递时继续携带
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                String values = request.getHeader(name);
                template.header(name, values);
            }
        }

        // 解决seata的xid未传递
        String xid = RootContext.getXID();
        template.header(RootContext.KEY_XID, xid);
    }

}

2,在调用A/B服务的fein注解上加入上面的request拦截器(configuration = MultipartSupportConfig.class)配置。

@FeignClient(contextId="LogDataService",value = "LogDataService",configuration = MultipartSupportConfig.class)
public interface ServieFeignA {

    @PostMapping(value = "/api/logdata/v1/log/save",consumes = MediaType.APPLICATION_JSON_VALUE)
    public Result save(@RequestBody SaveLogDataReq req);

}

这个方案比较繁琐,不优先考虑。

方案二,使用spring-cloud-starter-alibaba-seata自动配置(推荐)

之前使用的是seata-spring-boot-starter配置,这里的seata自动配置不会对feign进行处理,所以fein调用不会传xid,导致全局事务未生效。

		
			io.seata
			seata-spring-boot-starter
			1.4.2
		

所以去掉这个maven配置,使用spring-cloud-starter-alibaba-seata替代。

		
			com.alibaba.cloud
			spring-cloud-starter-alibaba-seata
		

这里会对fein进行seata相关的配置。

feign调用服务,被调用服务seata事务不开启或者xid为空_第1张图片

 

注意:

1,异常需要层层往上抛,如果你在子服务将异常处理的话(比如全局异常处理GlobalExceptionHandler),seata会认为你已经手动处理了异常。

2,出现事务失效的情况下,优先检查 RootContext.getXID() ,xid是否传递且一致。

3,主服务加上@GlobalTransactional注解即可,被调用服务不用加@GlobalTransactional和@Transactional.

4,@GlobalTransactional(rollbackFor = Exception.class)最好加上rollbackFor = Exception.class,表示遇到Exception都回滚,不然遇到有些异常(如自定义异常)则不会回滚。

你可能感兴趣的:(seata,分布式事务,java)