解决feign和hystrix集成后多线程调用导致追踪链路参数丢失问题

场景介绍

在目前流行的springcloud微服务架构中,由于会出现多层的服务链路调用问题。如图:

当这种负责的链路出现异常问题是,排查起来非常的麻烦,为了解决这种问题通常是在最开始发起请求时,在request的请求头内中放入一个链路id,这里我起名叫traceId。在日志查询问题时,只需要根据这个traceId查询就可以将整个链路串联起来。

存在的问题

通常服务中的request类型为HttpServletRequest,范围是跟线程绑定的。目前网上通常的说法是这样的:

  1. 先从主线程中获得request。RequestAttributes ra = RequestContextHolder.getRequestAttributes();
  2. 然后在子线程再重新设置进去。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();
		}
	}
}

可以看出本质上还是通过ThreadLocalInheritableThreadLocal来实现父子线程公用一个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);
        }
  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值