1.前言
最近在开发一个消息通知功能,需要将每个功能的dubbo rpc 接口参数通过MQ发送,消费方消费MQ将参数转化成消息通知的必要数据存数据库,用户通过接口获取自己的相关消息.依赖的RPC接口在设计上并没有操作者的参数,需求需要记录操作者.这里有个思路就是利用RpcContext,在一次完整的RPC调用链路中,将需要的参数透传过去.下面讲下使用RpcContext的使用原理以及使用RpcContext所走的坑.
2.上下文信息
RpcContext本质上是一个ThreadLocal,当接收到RPC请求或发起RPC请求时,RpcContext的状态会变化。比如A调用B,B再调用C,则B机器上,在B调用C之前,RpcContext记录的是A调用B的信息,在B调用C之后,RpcContext记录的是B调用C.
3.RpcContext的使用
//服务提供方使用,获取参数
RpcContext.getContext().getAttachments()
//服务器消费方使用,设置参数
RpcContext.getContext().setAttachment()
//调接口时,必须是A直接到B,如果A没有直接到B,而是先到C,再由C到B,那么在B里getAttachment()获取不到值
4 结合dubbo拦截器使用RpcContext
使用dubbo拦截器的原因是主要有两个:
1) 统一入口设置参数,节省代码方便维护
2) 解决一次完整请求调用涉及多次rpc调用时获取不到上下文中设置的参数值.
在一次线程上下文使用拦截器的例子
public class TraceIdFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String traceId = RpcContext.getContext().getAttachment("BizId");
if ( !StringUtils.isEmpty(BizId) ) {
//从RpcContext里获取bizId并保存
BizIdUtils.setBizId(bizId);
} else {
//交互前重新设置traceId, 避免信息丢失
RpcContext.getContext().setAttachment("bizId", BizIdUtils.getBizId());
}
//实际的rpc调用
return invoker.invoke(invocation);
}
}
public class BizIdUtils {
private static final ThreadLocal<String> Cache
= new ThreadLocal<String>();
public static String getBizId() {
return Cache.get();
}
public static void setBizId(String bizId) {
Cache.set(bizId);
}
public static void clear() {
Cache.remove();
}
}
如果拦截的方法使用了异步处理,同个线程上下文下可能获取不到值,这时候dubbo拦截器获取值设置进上下文可能是空的,这一点需要注意.
5.RpcContext原理
RpcContext内部有一个ThreadLocal变量,它是作为ThreadLocalMap的key,表明每个线程有一个RpcContext
public class RpcContext {
private static final ThreadLocal<RpcContext> LOCAL = new ThreadLocal<RpcContext>() {
@Override
protected RpcContext initialValue() {
return new RpcContext();
}
};
//如果get在set之前,则get会返回initialValue()创建的对象
public static RpcContext getContext() {
return LOCAL.get();
}
}