Dubbo插件比较简单,此处不做过多说明,阶段Tag:添加Dubbo插件
到目前为止已经有几种比较常见的插件了:Tomcat,Jdbc,Jedis,Dubbo。已经基本涵盖了大部分开发组件,当然还有其他的比如ES、grpc、http、jetty、mongodb、memcached等等,这些如果非特殊不再在本系列中继续添加。
接下来我们研究的问题是这样:
现在在一个服务内部的从用户请求到jedis到jdbc都可以监控到,我们的级联关系使用的是ThreadLocal来存储的,但对于跨服务或者跨机器的情况怎么办呢?可以测试用上面的Tag构建的agent:
{"spanId":1,"startTime":1520999391402,"operationName":"Tomcat:/dubbo2.html","endTime":1520999394407,"parentSpanId":0}
{"spanId":1072,"startTime":1520999391404,"operationName":"Dubbo:Service.TestService2.getData(int)","endTime":1520999394404,"parentSpanId":0}
{"spanId":2,"startTime":1520999394916,"operationName":"Tomcat:/favicon.ico","endTime":1520999394917,"parentSpanId":0}
可以看到虽然收到了tomcat和dubbo的span,但是dubbo的span并没有关联到tomcat的请求上。
比如Tomcat调用另一台机器上的dubbo,dubbo再调用jedis/jdbc,跨机器的时候使用ThreadLocal是无效的,那如何做呢?
首先我们分析一下跨服务涉及到哪些组件:
- tomcat:一般作为服务消费端,调用远程服务;
- dubbo:所谓消费端或者服务端都行。
所以使用一种方法将这种dubbo连接起来就可以了,那就是-
RpcContext
的setAttachments
。
dubbo通过客户端向服务器端传递参数,传递参数时path,group,version,dubbo,token,timeout即可key有特殊处理,不能使用这几个特殊key时服务提供方使用RpcContext.getContext().getAttachments();获取参数,服务器消费方使用RpcContext.getContext().setAttachment(); 传递参数。
-
创建跨进程传递数据的类ContextCarrier
,暂时只传递最简单的数据:spanId。这样consumer调用时将自己的spanId传过来,provider就知道自己的parentSpanId是多少了。
-
Dubbo拦截器的Consumer端进行数据设置
@Override
protected void before(Method method, Object[] allArguments) {
Invoker<?> invoker = (Invoker<?>) allArguments[0];
Invocation invocation = (Invocation) allArguments[1];
URL requestURL = invoker.getUrl();
ContextCarrier contextCarrier = extractContextCarrier();
ContextManager.createSpan(contextCarrier,"Dubbo:" + generateOperateName(requestURL, invocation));
RpcContext rpcContext = RpcContext.getContext();
boolean isConsumer = rpcContext.isConsumerSide();
if (isConsumer) {
ContextCarrier newContextCarrier = new ContextCarrier();
ContextManager.inject(newContextCarrier);
rpcContext.getAttachments().put(DUBBO_ATTACHEMENT_CONTEXTCARRIER, newContextCarrier.serialize());
}
}
- provider端进行解析,before:
ContextCarrier contextCarrier = extractContextCarrier();
ContextManager.createSpan(contextCarrier,"Dubbo:" + generateOperateName(requestURL, invocation));
public ContextCarrier extractContextCarrier() {
RpcContext rpcContext = RpcContext.getContext();
boolean isConsumer = rpcContext.isConsumerSide();
ContextCarrier carrier = null;
if (!isConsumer) {// provider
carrier = new ContextCarrier().deserialize(rpcContext.getAttachments().get(DUBBO_ATTACHEMENT_CONTEXTCARRIER));
}
return carrier;
}

其中,getData有两条,原因是consumer端记录了一条,provider端也记录了一条。
另外:
- 前面将TraceContext直接使用静态变量存储数据是不合适的,应该是每个线程都有自己的TraceContext。
- 本次提交之前的代码运行在提供dubbo服务的tomcat项目中时会出现问题:
NoClassDefFoundError: com/alibaba/dubbo/common/URL
这是由于AppClassLoader类加载器加载DubboInterceptor的原因,由于dubbo的包在WEB-INF/lib文件夹下,这个文件夹下的类加载器一般是WebAppClassLoader,所以应使用WebAppClassLoader来加载拦截类。具体参考:https://github.com/raphw/byte-buddy/issues/218