场景介绍
在目前流行的springcloud微服务架构中,由于会出现多层的服务链路调用问题。如图:
![](https://i-blog.csdnimg.cn/blog_migrate/0fc7574b0bf4318a11bac8afc8d554fe.png)
当这种负责的链路出现异常问题是,排查起来非常的麻烦,为了解决这种问题通常是在最开始发起请求时,在request的请求头内中放入一个链路id,这里我起名叫traceId。在日志查询问题时,只需要根据这个traceId查询就可以将整个链路串联起来。
存在的问题
通常服务中的request类型为HttpServletRequest
,范围是跟线程绑定的。目前网上通常的说法是这样的:
- 先从主线程中获得request。
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
- 然后在子线程再重新设置进去。
RequestContextHolder.setRequestAttributes(requestAttributes);
其实仔细看看RequestContextHolder的原理就知道这样做是存在隐患的。
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
if (attributes == null) {
resetRequestAttributes();
}
else {
if (inheritable) {
inheritableRequestAttributesHolder.set(attributes);
requestAttributesHolder.remove();
}
else {
requestAttributesHolder.set(attributes);
inheritableRequestAttributesHolder.remove();
}
}
}
可以看出本质上还是通过ThreadLocal
和InheritableThreadLocal
来实现父子线程公用一个request。这样做是没问题,前提是多线程或线程池中使用的是future
这种获得异步结果阻塞式的操作,因为这样父线程会等待子线程执行完后,再清除掉request的内容。如果使用线程的start
或者线程池的execute
,那么父线程开启子线程后会继续执行父线程后续业务然后清除掉request的内容,所以这时只要子线程的任务耗时一点就会可能导致request内容获取不到。
案例
控制层TestController
@RequestMapping(value = "/test", method = RequestMethod.POST)
public boolean test() {
return result = testService.test();
}
业务层TestService
public boolean test(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
testRpc.testRpc();
}
fegin调用层TestRpc
@FeignClient(value="testRpc", fallback=TestRpc.class)
public interface TestRpc{
@PostMapping(value = "/testRpc")
public boolean testRpc();
}
fegin的过滤层TestFeignRequestInterceptor
public class TestFeignRequestInterceptor implements RequestInterceptor{
private final static Logger logger = LoggerFactory.getLogger(TestFeignRequestInterceptor.class);
@Override
public void apply(RequestTemplate template) {
try {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if(requestAttributes != null) {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = servletRequestAttributes.getRequest();
String traceId = request.getHeader("traceId");
template.header("traceId", traceId);
}
} catch (Exception e) {
logger.error("FeignBasicAuthRequestInterceptor error",e);
}
}
}
TestFeignRequestInterceptor
的作用是从feign的上游服务中从reqeust取出traceId,然后再传入下游服务的request中。这样就会将feign的服务给串起来。
TestService异步操作
public boolean test(){
TestThreadPool.execute(() -> {
try {
Thread.sleep(1000);
}