seata AT模式源码(三)

451 阅读5分钟

电商系统中,下单会涉及到库存、订单、账户系统,这是一个典型的分布式事务场景,可能的代码如下:


//当前服务是订单服务,订单服务是当前分布式事务的发起者
@GlobalTransactional
public void order(){

    Order order = new Order();
    order.setUserId(1);
    order.setProductId(1000);
    order.setStatus(1);
    order.setCount(10);
    //生成订单
    orderMapper.insert(order);

    //扣账户
    accountFeignClient.decreaseAccount();
    
    //减库存
    storageFeignClient.decreaseStorage();

}

上篇文章seata AT模式源码(二)只是执行完了orderMapper.insert(order);这行代码,接下来就该调用账户服务的扣账户余额的接口了。问题:账户服务如何知道当前请求处在一个分布式事务当中呢(当然,前提是账户服务的数据源也是交给seata来进行管理)。

1、seata整合feign

在spring-cloud-alibaba-seata模块类路径META-INF/spring.factories文件中,seata向spring容器中导入了SeataRestTemplateAutoConfiguration配置类和SeataHandlerInterceptorConfiguration

1.1 SeataRestTemplateAutoConfiguration

public class SeataRestTemplateAutoConfiguration {

   //向容器中注入一个人拦截器
   @Bean
   public SeataRestTemplateInterceptor seataRestTemplateInterceptor() {
      return new SeataRestTemplateInterceptor();
   }
   
   //注入容器中所有的RestTemplate
   @Autowired(required = false)
   private Collection<RestTemplate> restTemplates;

   //注入上面添加到容器中的bean
   @Autowired
   private SeataRestTemplateInterceptor seataRestTemplateInterceptor;

   @PostConstruct
   public void init() {
      if (this.restTemplates != null) {
         for (RestTemplate restTemplate : restTemplates) {
            List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(
                  restTemplate.getInterceptors());
            //向容器中所有的RestTemplate添加一个拦截器SeataRestTemplateInterceptor
            interceptors.add(this.seataRestTemplateInterceptor);
            restTemplate.setInterceptors(interceptors);
         }
      }
   }

}

SeataRestTemplateInterceptor

public class SeataRestTemplateInterceptor implements ClientHttpRequestInterceptor {

   @Override
   public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
         ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
      HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest);
      
      //拿到threadlocal中的分布式事务id
      String xid = RootContext.getXID();
      
      //将分布式事务id添加到请求的请求头中
      if (!StringUtils.isEmpty(xid)) {
         requestWrapper.getHeaders().add(RootContext.KEY_XID, xid);
      }
      return clientHttpRequestExecution.execute(requestWrapper, bytes);
   }

}

订单服务通过feign调用账户服务,SeataRestTemplateInterceptor会将分布式事务id添加到请求的请求头中。那么,账户服务是否可以通过请求头中的分布式事务id判断该请求处在分布式事务当中呢?

1.2 SeataHandlerInterceptorConfiguration

public class SeataHandlerInterceptorConfiguration implements WebMvcConfigurer {
   
   //向springmvc添加一个拦截器SeataHandlerInterceptor,拦截器拦截所有请求
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(new SeataHandlerInterceptor()).addPathPatterns("/**");
   }

}

SeataHandlerInterceptor

public class SeataHandlerInterceptor implements HandlerInterceptor {

   private static final Logger log = LoggerFactory
         .getLogger(SeataHandlerInterceptor.class);

   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
         Object handler) {
      //拿到threadlocal中的分布式事务id,正常情况下是没有值的
      String xid = RootContext.getXID();
      //拿到名为RootContext.KEY_XID的请求头
      String rpcXid = request.getHeader(RootContext.KEY_XID);
      if (log.isDebugEnabled()) {
         log.debug("xid in RootContext {} xid in RpcContext {}", xid, rpcXid);
      }
      
      if (xid == null && rpcXid != null) {
         //绑定到当前线程
         RootContext.bind(rpcXid);
         if (log.isDebugEnabled()) {
            log.debug("bind {} to RootContext", rpcXid);
         }
      }
      return true;
   }

   @Override
   public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
         Object handler, Exception e) {
      
      //拿到名为RootContext.KEY_XID的请求头
      String rpcXid = request.getHeader(RootContext.KEY_XID);

      if (StringUtils.isEmpty(rpcXid)) {
         return;
      }
      
      //解绑 防止影响到别的分布式事务 servlet容器一般都是线程池处理请求
      String unbindXid = RootContext.unbind();
      if (log.isDebugEnabled()) {
         log.debug("unbind {} from RootContext", unbindXid);
      }
      //处理请求的过程中分布式事务id变了(正常情况下不会)
      //当前线程的分布式事务id和请求头的分布式事务id不一样
      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);
         }
      }
   }

}

账户服务的扣账户余额方法不需要加@GlobalTransaction注解,所以也不会被seata代理,也不会有GlobalTransactionalInterceptor#invoke这段逻辑了,即账户服务和库存服务是这个分布式事务的参与者。所以账户服务接受到请求后,SeataHandlerInterceptor会把请求头中的分布式事务id(如果有的话)绑定到当前线程,账户服务在扣账户余额时,就又会执行到PreparedStatementProxy#execute...生成镜像...注册分支事务。 库存服务也会执行同样的流程,当订单服务的order()方法执行完毕之后,就会根据各个分支事务的情况,提交分布式事务或者回滚分布式事务。

2、扩展

订单服务
@GlobalTransactional
public void order(){

    Order order = new Order();
    order.setUserId(1);
    order.setProductId(1000);
    order.setStatus(1);
    order.setCount(10);
    //生成订单
    orderMapper.insert(order);

    //扣账户
    accountFeignClient.decreaseAccount();
}

订单服务生成了订单之后,就调用了账户服务。

//账户服务
public void decreaseAccount(){

    //扣账户
    accountMapper.decreaseAccount();

    //减库存
    storageFeignClient.decreaseStorage();
}

账户服务扣了账户余额后,调用了库存服务减库存,那如果是这种情况,库存服务还能感知到当前请求处于分布式事务当中吗?

在spring-cloud-alibaba-seata模块类路径META-INF/spring.factories文件中,seata向spring容器中导入了SeataFeignClientAutoConfiguration配置类。

public class SeataFeignClientAutoConfiguration {

    //省略部分代码

  
    protected static class FeignBeanPostProcessorConfiguration {

       //bean的后置处理器
       @Bean
       SeataBeanPostProcessor seataBeanPostProcessor(
             SeataFeignObjectWrapper seataFeignObjectWrapper) {
          return new SeataBeanPostProcessor(seataFeignObjectWrapper);
       }

       //Feign上下文 的后置处理器
       @Bean
       SeataContextBeanPostProcessor seataContextBeanPostProcessor(
             BeanFactory beanFactory) {
          return new SeataContextBeanPostProcessor(beanFactory);
       }

       //feignclient的包装器
       @Bean
       SeataFeignObjectWrapper seataFeignObjectWrapper(BeanFactory beanFactory) {
          return new SeataFeignObjectWrapper(beanFactory);
       }

    }

   

}

2.1 SeataBeanPostProcessor

public class SeataBeanPostProcessor implements BeanPostProcessor {
   //包装对象
   private final SeataFeignObjectWrapper seataFeignObjectWrapper;

   SeataBeanPostProcessor(SeataFeignObjectWrapper seataFeignObjectWrapper) {
      this.seataFeignObjectWrapper = seataFeignObjectWrapper;
   }
    
   //BeanPostProcessor.postProcessBeforeInitialization在bean初始化之前调用
   @Override
   public Object postProcessBeforeInitialization(Object bean, String beanName)
         throws BeansException {
      //对bean进行包装
      return this.seataFeignObjectWrapper.wrap(bean);
   }

   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName)
         throws BeansException {
      return bean;
   }

}

2.2 SeataContextBeanPostProcessor

public class SeataContextBeanPostProcessor implements BeanPostProcessor {

   private final BeanFactory beanFactory;

   private SeataFeignObjectWrapper seataFeignObjectWrapper;

   SeataContextBeanPostProcessor(BeanFactory beanFactory) {
      this.beanFactory = beanFactory;
   }

   //BeanPostProcessor.postProcessBeforeInitialization在bean初始化之前调用
   @Override
   public Object postProcessBeforeInitialization(Object bean, String beanName)
         throws BeansException {
      //如果bean是FeignContext上下文对象
      if (bean instanceof FeignContext && !(bean instanceof SeataFeignContext)) {
         //就进行包装
         return new SeataFeignContext(getSeataFeignObjectWrapper(),
               (FeignContext) bean);
      }
      return bean;
   }

   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName)
         throws BeansException {
      return bean;
   }

   private SeataFeignObjectWrapper getSeataFeignObjectWrapper() {
      if (this.seataFeignObjectWrapper == null) {
         this.seataFeignObjectWrapper = this.beanFactory
               .getBean(SeataFeignObjectWrapper.class);
      }
      return this.seataFeignObjectWrapper;
   }

}

SeataBeanPostProcessor和SeataContextBeanPostProcessor都使用到的SeataFeignObjectWrapper。

public class SeataFeignObjectWrapper {

   private static final Log LOG = LogFactory.getLog(SeataFeignObjectWrapper.class);

   private final BeanFactory beanFactory;

   private CachingSpringLoadBalancerFactory cachingSpringLoadBalancerFactory;

   private SpringClientFactory springClientFactory;

   SeataFeignObjectWrapper(BeanFactory beanFactory) {
      this.beanFactory = beanFactory;
   }

   Object wrap(Object bean) {
       //如果是Client
      if (bean instanceof Client && !(bean instanceof SeataFeignClient)) {
         if (bean instanceof LoadBalancerFeignClient) {
            LoadBalancerFeignClient client = ((LoadBalancerFeignClient) bean);
            //包装
            return new SeataLoadBalancerFeignClient(client.getDelegate(), factory(),
                  clientFactory(), this.beanFactory);
         }
         if (bean.getClass().getName().equals(
               "org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient")) {
            return new SeataFeignBlockingLoadBalancerClient(getClient(bean),
                  beanFactory.getBean(BlockingLoadBalancerClient.class));
         }
         //包装
         return new SeataFeignClient(this.beanFactory, (Client) bean);
      }
      
      //普通bean就直接返回了,相当于没做任何处理
      return bean;
   }

   private Client getClient(Object bean) {
      Field client = null;
      boolean oldAccessible = false;
      try {
         client = bean.getClass().getDeclaredField("delegate");
         oldAccessible = client.isAccessible();
         client.setAccessible(true);
         return (Client) client.get(bean);
      }
      catch (Exception e) {
         LOG.error("get delegate client error", e);
      }
      finally {
         client.setAccessible(oldAccessible);
      }
      return null;
   }

   CachingSpringLoadBalancerFactory factory() {
      if (this.cachingSpringLoadBalancerFactory == null) {
         this.cachingSpringLoadBalancerFactory = this.beanFactory
               .getBean(CachingSpringLoadBalancerFactory.class);
      }
      return this.cachingSpringLoadBalancerFactory;
   }

   SpringClientFactory clientFactory() {
      if (this.springClientFactory == null) {
         this.springClientFactory = this.beanFactory
               .getBean(SpringClientFactory.class);
      }
      return this.springClientFactory;
   }

}

如果是Client类型,就进行包装,这个Client是何方神圣。 springcloud体系中,A服务要调用其他的服务,那么每个服务都会对应一个Feign接口,即对应一个Feign的Client,请求的发起都是通过Client进行的。

接下来我们看看SeataLoadBalancerFeignClient和SeataFeignClient是如何进行包装的。

SeataLoadBalancerFeignClient

public class SeataLoadBalancerFeignClient extends LoadBalancerFeignClient {

   private static final int MAP_SIZE = 16;

   private final BeanFactory beanFactory;

   SeataLoadBalancerFeignClient(Client delegate,
         CachingSpringLoadBalancerFactory lbClientFactory,
         SpringClientFactory clientFactory, BeanFactory beanFactory) {
      super(wrap(delegate, beanFactory), lbClientFactory, clientFactory);
      this.beanFactory = beanFactory;
   }

   @Override
   public Response execute(Request request, Request.Options options) throws IOException {
      //获取修改之后的请求。修改看什么呢?向请求加了一个有分布式事务id的请求头
      Request modifiedRequest = getModifyRequest(request);
      return super.execute(modifiedRequest, options);
   }

   private static Client wrap(Client delegate, BeanFactory beanFactory) {
      return (Client) new SeataFeignObjectWrapper(beanFactory).wrap(delegate);
   }

   private Request getModifyRequest(Request request) {
      //获取threadlocal中的分布式事务Id
      String xid = RootContext.getXID();

      if (StringUtils.isEmpty(xid)) {
         return request;
      }

      Map<String, Collection<String>> headers = new HashMap<>(MAP_SIZE);
      headers.putAll(request.headers());

      List<String> seataXid = new ArrayList<>();
      seataXid.add(xid);
      //添加到请求头中
      headers.put(RootContext.KEY_XID, seataXid);

      return Request.create(request.method(), request.url(), headers, request.body(),
            request.charset());
   }

}

SeataFeignClient

public class SeataFeignClient implements Client {

   private final Client delegate;

   private final BeanFactory beanFactory;

   private static final int MAP_SIZE = 16;

   SeataFeignClient(BeanFactory beanFactory) {
      this.beanFactory = beanFactory;
      this.delegate = new Client.Default(null, null);
   }

   SeataFeignClient(BeanFactory beanFactory, Client delegate) {
      this.delegate = delegate;
      this.beanFactory = beanFactory;
   }

   @Override
   public Response execute(Request request, Request.Options options) throws IOException {
      //获取修改之后的请求。修改什么呢?向请求加了一个有分布式事务id的请求头
      Request modifiedRequest = getModifyRequest(request);
      return this.delegate.execute(modifiedRequest, options);
   }

   private Request getModifyRequest(Request request) {

      //获取threadlocal中的分布式事务Id
      String xid = RootContext.getXID();

      if (StringUtils.isEmpty(xid)) {
         return request;
      }

      Map<String, Collection<String>> headers = new HashMap<>(MAP_SIZE);
      headers.putAll(request.headers());

      List<String> seataXid = new ArrayList<>();
      seataXid.add(xid);
      //添加到请求头中
      headers.put(RootContext.KEY_XID, seataXid);

      return Request.create(request.method(), request.url(), headers, request.body(),
            request.charset());
   }

}

综上,就算是账户服务调用库存服务,库存服务还是能感知到当前请求处于分布式事务当中,完成分支事务注册等操作。