电商系统中,下单会涉及到库存、订单、账户系统,这是一个典型的分布式事务场景,可能的代码如下:
//当前服务是订单服务,订单服务是当前分布式事务的发起者
@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());
}
}
综上,就算是账户服务调用库存服务,库存服务还是能感知到当前请求处于分布式事务当中,完成分支事务注册等操作。