一步一步写APM(七)--Dubbo插件及跨服务的链构造

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连接起来就可以了,那就是-RpcContextsetAttachments

dubbo通过客户端向服务器端传递参数,传递参数时path,group,version,dubbo,token,timeout即可key有特殊处理,不能使用这几个特殊key时服务提供方使用RpcContext.getContext().getAttachments();获取参数,服务器消费方使用RpcContext.getContext().setAttachment(); 传递参数。

  1. 创建跨进程传递数据的类ContextCarrier,暂时只传递最简单的数据:spanId。这样consumer调用时将自己的spanId传过来,provider就知道自己的parentSpanId是多少了。

  2. 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());
        }
    }
  1. 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;
    }

image.png

其中,getData有两条,原因是consumer端记录了一条,provider端也记录了一条。

另外:

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

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×