为了使用Jaeger,整合Feign和Hystrix的那些事
文章目录
1. 故事背景
前面有说到过,我们正在进行微服务改造,其中曾经选型Spring Cloud
,种种原因放弃了,最后选择了SpringBoot + Istio
。
原来在Spring Cloud中拥有的分布式追踪功能,就寄希望于Istio了。但原来在Spring Cloud全家桶中,有一个叫做Feign的家伙,专门用于调用API,我们觉得挺好用,所以保留了他。并且他还和Hystrix(熔断库)集成了,在我们的业务场景里也需要。
使用分布式追踪很简单啊,把对应的Tracing Headers传播下去就好啦!真的是这样吗?!真的!对于Feign和Hystrix还真有点儿不一样!
2. 提前准备
使用了Feign和Hystrix的环境,使用了如下版本:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.3.RELEASE</version> </dependency>
3. 实践
在Istio - 分布式跟踪实践中,提到了集成的方式,我们一步步来。
3.1 增加拦截器
使用拦截器,把所有的Tracing Headers放入ThreadLocal里,代码如下:
public class TracingInterceptor extends HandlerInterceptorAdapter {
public final static ThreadLocal<Map> headerThreadLocal = new ThreadLocal();
private static List<String> tracingHeaderKeys = Arrays.asList("x-request-id", "x-b3-traceid", "x-b3-spanid", "x-b3-parentspanid",
"x-b3-sampled", "x-b3-flags", "x-ot-span-context");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
this.handleHeader(request, tracingHeaderKeys);
response.setHeader("traceid", request.getHeader("x-b3-traceid"));
return true;
}
private void handleHeader(HttpServletRequest request, List<String> tracingHeaderKeys) {
Map<String, List> tracingHeaders = new HashMap();
for(String key: tracingHeaderKeys) {
String val = request.getHeader(key);
if ( null != val ) {
tracingHeaders.put(key, Arrays.asList(val));
}
}
if (tracingHeaders.size() > 0) {
headerThreadLocal.set(tracingHeaders);
}
}
}
3.2 增加Feign拦截器
Feign作为调用API的工具,这里也使用TA的拦截器,把上面的Tracing Headers往下传播,其实就是从ThreadLocal里取出来,代码如下:
@Configuration
public class FeignInterceptorConfiguration {
@Bean
public RequestInterceptor headerInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
Map<String, Collection<String>> tracingHeaders = TracingInterceptor.headerThreadLocal.get();
if (null != tracingHeaders) {
requestTemplate.headers(tracingHeaders);
}
}
};
}
}
3.3 一阶段测试
兴高采烈的测试,一切完美,在Jaeger可以正常看到所有的调用链,相当顺利!直到有个同事说,好像Hystrix的熔断没生效!
哦,那也没事,配置加上就成!如下:
feign:
hystrix:
enabled: true
But,第一个问题出现了,Tracing好像传递不去了,在Feign拦截器中ThreadLocal取不到相关信息了。
🤔为啥??
原因在于,Hystrix提供了基于信号量和线程两种隔离模式,默认是线程模式,这就是解释了为什么取不到了,因为Hystrix使用了线程,和请求的主线程不是同一个线程,那是肯定取不到值的。
那该怎么解决??方法有二:
- 使用信号量模式(但官方不推荐)
- 使用Hystrix的插件模式,添加自定义并发策略
既然第一种方法,官方不推荐,我们尝试第二种。
3.4 增加Hystrix自定义并发策略
参考官方文档:Concurrency Strategy
1. 添加自定义并发策略
代码如下:
public class TracingHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
return new TracingAwareCallable<>(callable, TracingInterceptor.headerThreadLocal.get());
}
static class TracingAwareCallable<T> implements Callable<T> {
private final Callable<T> delegate;
private final Map tracingMap;
public TracingAwareCallable(Callable<T> callable, Map tracingMap) {
this.delegate = callable;
this.tracingMap = tracingMap;
}
@Override
public T call() throws Exception {
try {
TracingInterceptor.headerThreadLocal.set(this.tracingMap);
return delegate.call();
} finally {
TracingInterceptor.headerThreadLocal.set(null);
}
}
}
}
- 实现Callable接口,在构造函数里,增加Tracing信息,然后在call方法里,再把对应的Tracing信息放置于ThreadLocal里(为了在Feign拦截器里可以用一致的方式获取)。
- 实现HystrixConcurrencyStrategy类,覆盖方法wrapCallable,实例化刚刚实现的Callable类,把当前的Tracing信息传入。
2. 注册
注册自定义同步策略
@Configuration
public class HystrixConfiguration {
@PostConstruct
public void init() {
HystrixPlugins.getInstance().registerConcurrencyStrategy(new TracingHystrixConcurrencyStrategy());
}
}
3.5 二阶段测试
Tracing信息终于又正常传递下去了,所以如果需要熔断的人儿,可以尝试这种方法。
4. 总结
总的来说,为了达到分布式追踪,对于代码的入侵性还是很小的,主要都是一些拦截器,并不影响业务代码。
我相信在别的语言上,整合这个功能会比JAVA容易的多。
比起Spring Cloud
,TA有个最大的优势,语言无关性,对于以后的扩展性最好,不会那么局限。
文章作者 xifan
上次更新 2018-12-03