博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Dubbo透传traceId/logid的一种思路
阅读量:6151 次
发布时间:2019-06-21

本文共 7902 字,大约阅读时间需要 26 分钟。

 

前言:

  随着dubbo的开源, 以及成为apache顶级项目. dubbo越来越受到国内java developer欢迎, 甚至成为服务化自治的首选方案. 随着微服务的流行, 如何跟踪整个调用链, 成了一个课题. 大家能够达成一致的思路, 在调用中添加traceId/logid信息, 至于如何实现, 各家都有自己的思路.
  本文将对比几种方案, 重点讲解利用dubbo的自定义filter的机制, 来实现traceId/logid的透传.

 

方案一:

  这个方案也是最直接的方法, 正如所谓所见即所得, 就是在dubbo的接口参数添加traceId/logid参数.
  比如如下的sample代码:

@Getter@Setterclass EchoReq {    // *) 消息    private String message;    // *) 跟踪ID    private String traceId;}// *) dubbo的接口定义interface EchoService {    String echo1(EchoReq req);    String echo2(String message, String traceId);}

  相信大家一看就明白了其中的思路, 这种思路确实简单粗暴. 对于对于有洁癖的程序员而言, 在业务接口中, 生硬地添加traceId/logid, 显然破坏"无侵入性"原则.

 

方案二:

  该方案需要修改dubbo源码, 通过把traceId/logid注入到RPCInvocation对象(dubbo底层transport实体)中, 从而实现traceId/logid的透传.
  

  本文不再详细展开, 有兴趣的可以参看博文: . 

 

RpcContext方案:

  在具体讲解自定义filter来实现透传traceId/logid的方案前, 我们先来研究下RpcContext对象. 其RpcContext本质上是个ThreadLocal对象, 其维护了一次rpc交互的上下文信息.

public class RpcContext {	// *) 定义了ThreadLocal对象    private static final ThreadLocal
LOCAL = new ThreadLocal() { protected RpcContext initialValue() { return new RpcContext(); } }; // *) 附带属性, 这些属性可以随RpcInvocation对象一起传递 private final Map
attachments = new HashMap(); public static RpcContext getContext() { return (RpcContext)LOCAL.get(); } protected RpcContext() { } public String getAttachment(String key) { return (String)this.attachments.get(key); } public RpcContext setAttachment(String key, String value) { if(value == null) { this.attachments.remove(key); } else { this.attachments.put(key, value); } return this; } public void clearAttachments() { this.attachments.clear(); }}

  注: RpcContext里的attachments信息会填入到RpcInvocation对象中, 一起传递过去.

  因此有人就建议可以简单的把traceId/logid注入到RpcContext中, 这样就可以简单的实现traceId/logid的透传了, 事实是否如此, 先让我们来一起实践一下.

  定义dubbo接口类:

public interface IEchoService {    String echo(String name);}

  编写服务端代码(producer):

@Service("echoService")public class EchoServiceImpl implements IEchoService {    @Override    public String echo(String name) {        String traceId = RpcContext.getContext().getAttachment("traceId");        System.out.println("name = " + name + ", traceId = " + traceId);        return name;    }    public static void main(String[] args) {        ClassPathXmlApplicationContext applicationContext =                new ClassPathXmlApplicationContext("spring-dubbo-test-producer.xml");        System.out.println("server start");        while (true) {            try {                Thread.sleep(1000L);            } catch (InterruptedException e) {            }        }    }}

  编写客户端代码(consumer):

public class EchoServiceConsumer {    public static void main(String[] args) {        ClassPathXmlApplicationContext applicationContext =                new ClassPathXmlApplicationContext("spring-dubbo-test-consumer.xml");        IEchoService service = (IEchoService) applicationContext                .getBean("echoService");        // *) 设置traceId        RpcContext.getContext().setAttachment("traceId", "100001");        System.out.println(RpcContext.getContext().getAttachments());        // *) 第一调用        service.echo("lilei");        // *) 第二次调用        System.out.println(RpcContext.getContext().getAttachments());        service.echo("hanmeimei");    }}

  注: 这边的代码, 暂时忽略掉了dubbo producer/consumer的xml配置.

  执行的接入如下:

服务端输出:name = lilei, traceId = 100001name = hanmeimei, traceId = null客户端输出:{traceId=100001}{}

  从服务端的输出信息中, 我们可以惊喜的发现, traceId确实传递过去了, 但是只有第一次有, 第二次没有. 而从客户端对RpcContext的内容输出, 也印证了这个现象, 同时产生这个现象的本质原因是是RpcContext对象的attachment在一次rpc交互后被清空了.

  给RpcContext的clearAttachments方法, 设置断点后复现. 我们可以找到如下调用堆栈.

java.lang.Thread.State: RUNNABLE	  at com.alibaba.dubbo.rpc.RpcContext.clearAttachments(RpcContext.java:438)	  at com.alibaba.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:50)	  at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91)	  at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:53)	  at com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:77)	  at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:227)	  at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:72)	  at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:52)	  at com.alibaba.dubbo.common.bytecode.proxy0.echo(proxy0.java:-1)	  at com.test.dubbo.EchoServiceConsumer.main(EchoServiceConsumer.java:20)

  其最直接的调用为dubbo自带的ConsumerContextFilter, 让我们来分析其代码.

@Activate(    group = {"consumer"},    order = -10000)public class ConsumerContextFilter implements Filter {    public ConsumerContextFilter() {    }    public Result invoke(Invoker
invoker, Invocation invocation) throws RpcException { RpcContext.getContext().setInvoker(invoker).setInvocation(invocation) .setLocalAddress(NetUtils.getLocalHost(), 0) .setRemoteAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort()); if(invocation instanceof RpcInvocation) { ((RpcInvocation)invocation).setInvoker(invoker); } Result var3; try { var3 = invoker.invoke(invocation); } finally { RpcContext.getContext().clearAttachments(); } return var3; }}

  确实在finally代码片段中, 我们发现RpcContext在每次rpc调用后, 都会清空attachment对象.

  既然我们找到了本质原因, 那么解决方法, 可以在每次调用的时候, 重新设置下traceId, 比如像这样.

// *) 第一调用RpcContext.getContext().setAttachment("traceId", "100001");service.echo("lilei");// *) 第二次调用RpcContext.getContext().setAttachment("traceId", "100001");service.echo("hanmeimei");

  只是感觉吃像相对难看了一点, 有没有更加优雅的方案呢? 我们踏着五彩霞云的盖世大英雄马上就要来了.

 

自定义filter方案:

  我们先引入一个工具类:

public class TraceIdUtils {    private static final ThreadLocal
traceIdCache = new ThreadLocal
(); public static String getTraceId() { return traceIdCache.get(); } public static void setTraceId(String traceId) { traceIdCache.set(traceId); } public static void clear() { traceIdCache.remove(); }}

  然后我们定义一个filter类:

package com.test.dubbo;public class TraceIdFilter implements Filter {    @Override    public Result invoke(Invoker
invoker, Invocation invocation) throws RpcException { String traceId = RpcContext.getContext().getAttachment("traceId"); if ( !StringUtils.isEmpty(traceId) ) { // *) 从RpcContext里获取traceId并保存 TraceIdUtils.setTraceId(traceId); } else { // *) 交互前重新设置traceId, 避免信息丢失 RpcContext.getContext().setAttachment("traceId", TraceIdUtils.getTraceId()); } // *) 实际的rpc调用 return invoker.invoke(invocation); }}

  在resource目录下, 添加META-INF/dubbo目录, 继而添加com.alibaba.dubbo.rpc.Filter文件

  
  编辑(com.alibaba.dubbo.rpc.Filter文件)内容如下:

traceIdFilter=com.test.dubbo.TraceIdFilter

  然后我们给dubbo的producer和consumer都配置对应的filter项.

  服务端:

  客户端:

  服务端的测试代码小改为如下:

@Service("echoService")public class EchoServiceImpl implements IEchoService {    @Override    public String echo(String name) {        String traceId = TraceIdUtils.getTraceId();        System.out.println("name = " + name + ", traceId = " + traceId);        return name;    }}

  客户端的测试代码片段为:

// *) 第一调用RpcContext.getContext().setAttachment("traceId", "100001");service.echo("lilei");// *) 第二次调用service.echo("hanmeimei");

  同样的代码, 测试结果如下

服务端输出:name = lilei, traceId = 100001name = hanmeimei, traceId = 100001客户端输出:{traceId=100001}{}

  符合预期, 感觉这个方案就非常优雅了. RpcContext的attachment依旧被清空(ConsumerContextFilter在自定义的Filter后执行), 但是每次rpc交互前, traceId/logid会被重新注入, 保证跟踪线索透传成功.

 

总结:

  关于这个方案, 在服务A, 服务B, 服务C之间连续传递测试, 依旧成功. 总的来说, 该方案还是可行的, dubbo的自定义filter机制也算是dubbo功能扩展的一个补充. 我们可以做很多工作, 比如耗时记录, metric信息的统计, 安全验证工作等等. 值得我们去深入研究.

 

转载地址:http://ciwfa.baihongyu.com/

你可能感兴趣的文章
企业级负载平衡简介(转)
查看>>
ICCV2017 论文浏览记录
查看>>
科技巨头的交通争夺战
查看>>
Shell基础之-正则表达式
查看>>
JavaScript异步之Generator、async、await
查看>>
讲讲吸顶效果与react-sticky
查看>>
c++面向对象的一些问题1 0
查看>>
直播视频流技术名词
查看>>
IOC —— AOP
查看>>
比特币现金将出新招,推动比特币现金使用
查看>>
MS SQLSERVER通用存储过程分页
查看>>
60.使用Azure AI 自定义视觉服务实现物品识别Demo
查看>>
Oracle 冷备份
查看>>
jq漂亮实用的select,select选中后,显示对应内容
查看>>
C 函数sscanf()的用法
查看>>
python模块之hashlib: md5和sha算法
查看>>
解决ros建***能登录不能访问内网远程桌面的问题
查看>>
售前工程师的成长---一个老员工的经验之谈
查看>>
Get到的优秀博客网址
查看>>
【Git入门之四】操作项目
查看>>