实现代码详见github: https://github.com/einarzhang/spring-cloud-chaincontext
经历过大量服务划分之后, 每个服务主要承担自己独立的业务模型和功能, 如何将请求串联等全链路的应用问题越来越多, 常见的场景问题如下:
- 假如网关保存了用户的session和用户信息, 如何让后端无状态的服务知道是哪一个用户操作?
- 如何通过网关的请求后,到所有的服务能知道是同一条链路
- 如何从网关或者某个服务开始, 将某个特征数据一直传递下去
假如我们有A,B,C,D四个服务, 调用请求顺序依次经过GATEWAY->A->B->C->D, 如何保证A, B, C, D四个服务都能知道当前操作的是哪个用户. 在spring cloud里面, 我们可以想到通过header或者参数进行显示传递, 所有需要获取用户ID的接口都需要显示指定接收参数,并在调用其他服务的时候显示设置进去. 基于此原理, 并简化开发者的显示传递和接收工作, 这里根据spring cloud的组件特征进行封装, 实现一套全链路上下文中间件.
spring cloud全链路上下文主要处理如下中间件的填充和传递问题:
- RestTemplate在调用下游服务时如何将链路上下文数据携带?
- Feign在调用下游服务时如何将链路上下文数据携带?
- 服务接收方如何将header或者参数中携带的相关数据设置到链路上下文中?
- 如何保证hystrix线程池的情况下链路上下文依然有效?
实现原理如下所示:
链路上下文定义
spring cloud体系下的许多组件已经有自己的上下文实现, 如RequestContextHolder, zuul RequestContext等, 这里我们仿照zuul的RequestContext定义链路上下文, 主要继承自ConcurrentHashMap, 可以安全存储任何数据, 并采用InheritableThreadLocal保存上下文信息.
public class ChainContextHolder extends ConcurrentHashMap<String, Object> {
protected static Class<? extends ChainContextHolder> contextClass = ChainContextHolder.class;
protected static final ThreadLocal<? extends ChainContextHolder> THREAD_LOCAL = new InheritableThreadLocal<ChainContextHolder>() {
@Override
protected ChainContextHolder initialValue() {
try {
return contextClass.newInstance();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
};
/**
* Override the default ChainRequestContext
*
* @param clazz
*/
public static void setContextClass(Class<? extends ChainContextHolder> clazz) {
contextClass = clazz;
}
/**
* Get the current ChainRequestContext
*
* @return the current ChainRequestContext
*/
public static final ChainContextHolder getCurrentContext() {
return THREAD_LOCAL.get();
}
/**
* Unsets the threadLocal context. Done at the end of the request.
*
* @return
*/
public void unset() {
this.clear();
THREAD_LOCAL.remove();
}