问:关于参与方如何知道已经存在事务的?(从请求头传入事务组信息和 读取事务组信息。通过拦截器)
答:一般我们都是一接口的方式去调用服务,基本就是restTemplate如果用到spring Cloud还会用到Fegin。框架在进行接口调用时都会通过把事务消息放在header中,后边的服务从header中就会得到当前的事务信息。
ClientHttpRequestInterceptor可以对请求进行拦截,并在其被发送至服务端之前修改请求或是增强相应的信息。
一、添加事务组信息到请求头
1、RestTemplate调用实现拦截
RestTemplateTracingTransmitter实现了ClientHttpRequestInterceptor接口。并把此拦截器加入到restTemplate的拦截器链中,当restTemplate去请求别的资源时,会被RestTemplateTracingTransmitter拦截到,在requestheader中加入groupid信息。
@ConditionalOnClass(RestTemplate.class)
@Component
@Order
public class RestTemplateTracingTransmitter implements ClientHttpRequestInterceptor {
@Autowired
public RestTemplateTracingTransmitter(@Autowired(required = false) List restTemplates) {
if (Objects.nonNull(restTemplates)) {
restTemplates.forEach(restTemplate -> {
List interceptors = restTemplate.getInterceptors();
//加入拦截器链
interceptors.add(interceptors.size(), RestTemplateTracingTransmitter.this);
});
}
}
@Override
@NonNull
public ClientHttpResponse intercept(
@NonNull HttpRequest httpRequest, @NonNull byte[] bytes,
@NonNull ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
//把信息存储到header中
Tracings.transmit(httpRequest.getHeaders()::add);
return clientHttpRequestExecution.execute(httpRequest, bytes);
}
}
/**
* 传输Tracing信息
*
* @param tracingSetter Tracing信息设置器
*/
public static void transmit(TracingSetter tracingSetter) {
if (TracingContext.tracing().hasGroup()) {
log.debug("tracing transmit group:{}", TracingContext.tracing().groupId());
tracingSetter.set(TracingConstants.HEADER_KEY_GROUP_ID, TracingContext.tracing().groupId());
tracingSetter.set(TracingConstants.HEADER_KEY_APP_MAP,
Base64Utils.encodeToString(TracingContext.tracing().appMapString().getBytes(StandardCharsets.UTF_8)));
}
}
2、fegin实现请求拦截 RequestInterceptor可以实现对请求的拦截
@ConditionalOnClass(Feign.class)
@Component
@Order
public class FeignTracingTransmitter implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
//把请求头中的数据 关于groupid的信息放到tracingSetter 信息设置器中
Tracings.transmit(requestTemplate::header);
}
}
/**
* 传输Tracing信息
*
* @param tracingSetter Tracing信息设置器
*/
public static void transmit(TracingSetter tracingSetter) {
if (TracingContext.tracing().hasGroup()) {
log.debug("tracing transmit group:{}", TracingContext.tracing().groupId());
//把信息存储到header中
tracingSetter.set(TracingConstants.HEADER_KEY_GROUP_ID, TracingContext.tracing().groupId());
tracingSetter.set(TracingConstants.HEADER_KEY_APP_MAP,
Base64Utils.encodeToString(TracingContext.tracing().appMapString().getBytes(StandardCharsets.UTF_8)));
}
}
二、从请求头中获取事务信息
spring boot中要实现拦截请求要用HandlerInterceptor,并且需要在WebMvcConfigurer把此拦截器注册到InterceptorRegistry。
@ConditionalOnClass(HandlerInterceptor.class)
@Component
public class SpringTracingApplier implements com.codingapi.txlcn.tracing.http.spring.HandlerInterceptor, WebMvcConfigurer {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
Tracings.apply(request::getHeader);
return true;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this);
}
}
/**
* 获取传输的Tracing信息
*
* @param tracingGetter Tracing信息获取器
*/
public static void apply(TracingGetter tracingGetter) {
String groupId = Optional.ofNullable(tracingGetter.get(TracingConstants.HEADER_KEY_GROUP_ID)).orElse("");
String appList = Optional.ofNullable(tracingGetter.get(TracingConstants.HEADER_KEY_APP_MAP)).orElse("");
//事务组信息加载到本地上下文
TracingContext.init(Maps.newHashMap(TracingConstants.GROUP_ID, groupId, TracingConstants.APP_MAP,
StringUtils.isEmpty(appList) ? appList : new String(Base64Utils.decodeFromString(appList), StandardCharsets.UTF_8)));
if (TracingContext.tracing().hasGroup()) {
log.debug("tracing apply group:{}, app map:{}", groupId, appList);
}
}