如果微服务采用feign或ribbon组件做服务调用,seata框架有和feign、ribbon集成的功能,这是微服务的常用组合;
一、下面分析一下feign相关源码:
因为使用的是spring-cloud-starter-alibaba-seata
,自带了Feign 远程传递xid 的支持,在发起Feign 远程请求时,可以看到将xid 塞入了消息头中。
1、spi入口:
SeataFeignClientAutoConfiguration 点击进入,找到和fegin相关的核心代码:
@Configuration
protected static class FeignBeanPostProcessorConfiguration {
@Bean
SeataBeanPostProcessor seataBeanPostProcessor(
SeataFeignObjectWrapper seataFeignObjectWrapper) {
return new SeataBeanPostProcessor(seataFeignObjectWrapper);
}
@Bean
SeataContextBeanPostProcessor seataContextBeanPostProcessor(
BeanFactory beanFactory) {
return new SeataContextBeanPostProcessor(beanFactory);
}
@Bean
SeataFeignObjectWrapper seataFeignObjectWrapper(BeanFactory beanFactory) {
return new SeataFeignObjectWrapper(beanFactory);//重点代码
}
}
2、SeataFeignObjectWrapper 点击进入,找到核心代码:
SeataFeignObjectWrapper(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
Object wrap(Object bean) {
if (bean instanceof Client && !(bean instanceof SeataFeignClient)) {
if (bean instanceof LoadBalancerFeignClient) {
LoadBalancerFeignClient client = ((LoadBalancerFeignClient) bean);
return new SeataLoadBalancerFeignClient(client.getDelegate(), factory(),
clientFactory(), this.beanFactory);
}
//通过feign调用RM 客户端,核心入口
return new SeataFeignClient(this.beanFactory, (Client) bean);
}
return bean;
}
3、SeataFeignClient 进入,找到核心代码,此类 public class SeataFeignClient implements Client 实现Client类接口的 execute 方法,
@Override
public Response execute(Request request, Request.Options options) throws IOException {
Request modifiedRequest = getModifyRequest(request);//继续进入
return this.delegate.execute(modifiedRequest, options);
}
//此方法通过feign调用RM服务,同时把全局事务id封装到header里,传到下游。
private Request getModifyRequest(Request request) {
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> fescarXid = new ArrayList<>();
fescarXid.add(xid);
headers.put(RootContext.KEY_XID, fescarXid);//xid 封装到header里
//调用RM对象
return Request.create(request.method(), request.url(), headers, request.body(),
request.charset());
}
继续:
继续:
到这里就明白了全局事务id是怎么通过feign组件传播的了
二、seata集成ribbon组件源码入口:
在被调用方,spring-cloud-starter-alibaba-seata
也提供了支持,使用Spring MVC 中的HandlerInterceptor
将消息头中的TX_XID
绑定到RootContext
中。
1、点击进入:SeataRestTemplateAutoConfiguration
@Configuration
public class SeataRestTemplateAutoConfiguration {
//初始化拦截器类
@Bean
public SeataRestTemplateInterceptor seataRestTemplateInterceptor() {
return new SeataRestTemplateInterceptor();
}
//依赖注入
@Autowired(required = false)
private Collection<RestTemplate> restTemplates;
//依赖注入
@Autowired
private SeataRestTemplateInterceptor seataRestTemplateInterceptor;
//初始化接口
@PostConstruct
public void init() {
if (this.restTemplates != null) {
for (RestTemplate restTemplate : restTemplates) {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(
restTemplate.getInterceptors());
interceptors.add(this.seataRestTemplateInterceptor);//核心代码,点击进入
restTemplate.setInterceptors(interceptors);
}
}
}
}
2、点击 SeataRestTemplateInterceptor 进入
public class SeataRestTemplateInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest);
String xid = RootContext.getXID();
if (!StringUtils.isEmpty(xid)) {
//这里同样把全局事务id放到 header里
requestWrapper.getHeaders().add(RootContext.KEY_XID, xid);
}
return clientHttpRequestExecution.execute(requestWrapper, bytes);//继续调用,进入
}
}
3、进入
@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
return nextInterceptor.intercept(request, body, this);
}
else {
HttpMethod method = request.getMethod();
Assert.state(method != null, "No standard HTTP method");
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
if (body.length > 0) {
if (delegate instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
}
else {
StreamUtils.copy(body, delegate.getBody());
}
}
return delegate.execute();
}
}
4、到这里一目了然,知道了全局事务id是怎么传播的了。
和之前一样,远程的分支事务,执行时因为远程这个方法是没有@GlobalTransactional注解的,所以不会进入到拦截器,但是代理了数据源,所以在执行SQL 时,还是会进入到代理的Executor中。执行SQL 时,发现存在xid ,则表明这是一个分布式事务,则会进行分支事务注册处理,之后和第二步一样。调用TC 注册分支事务时,TC 会查询数据库表中全局事务信息,创建分支事务,可以看到被调用方也生成了当前操作记录的全局锁。