专栏名称: 互联网后端架构
主要介绍Java后端架构。其中也会掺杂一些前端、GO、Python、Linux,目标:全栈工程师!---好像很牛叉的样子 ^-^
目录
相关文章推荐
51好读  ›  专栏  ›  互联网后端架构

Dubbo框架如何和调用链整合

互联网后端架构  · 公众号  · 架构  · 2019-10-23 07:40

正文

前言:
随着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






请到「今天看啥」查看全文