Dubbo透传traceId/logid的一种思路
作者:互联网
转载:https://www.cnblogs.com/mumuxinfei/p/9226881.html
前言:
随着dubbo的开源, 以及成为apache顶级项目. dubbo越来越受到国内java developer欢迎, 甚至成为服务化自治的首选方案. 随着微服务的流行, 如何跟踪整个调用链, 成了一个课题. 大家能够达成一致的思路, 在调用中添加traceId/logid信息, 至于如何实现, 各家都有自己的思路.
本文将对比几种方案, 重点讲解利用dubbo的自定义filter的机制, 来实现traceId/logid的透传.
方案一:
这个方案也是最直接的方法, 正如所谓所见即所得, 就是在dubbo的接口参数添加traceId/logid参数.
比如如下的sample代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@Getter
@Setter
class 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的透传.
本文不再详细展开, 有兴趣的可以参看博文: dubbo 服务跟踪.
RpcContext方案:
在具体讲解自定义filter来实现透传traceId/logid的方案前, 我们先来研究下RpcContext对象. 其RpcContext本质上是个ThreadLocal对象, 其维护了一次rpc交互的上下文信息.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public class RpcContext {
// *) 定义了ThreadLocal对象
private static final ThreadLocal<RpcContext> LOCAL = new ThreadLocal() {
protected RpcContext initialValue() {
return new RpcContext();
}
};
// *) 附带属性, 这些属性可以随RpcInvocation对象一起传递
private final Map<String, String> 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接口类:
1 2 3 4 5 |
public interface IEchoService {
String echo(String name);
}
|
编写服务端代码(producer):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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配置.
执行的接入如下:
1 2 3 4 5 6 7 |
服务端输出:
name = lilei, traceId = 100001
name = hanmeimei, traceId = null
客户端输出:
{traceId= 100001 }
{}
|
从服务端的输出信息中, 我们可以惊喜的发现, traceId确实传递过去了, 但是只有第一次有, 第二次没有. 而从客户端对RpcContext的内容输出, 也印证了这个现象, 同时产生这个现象的本质原因是是RpcContext对象的attachment在一次rpc交互后被清空了.
给RpcContext的clearAttachments方法, 设置断点后复现. 我们可以找到如下调用堆栈.
1 2 3 4 5 6 7 8 9 10 11 |
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, 让我们来分析其代码.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
@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, 比如像这样.
1 2 3 4 5 6 7 |
// *) 第一调用
RpcContext.getContext().setAttachment( "traceId" , "100001" );
service.echo( "lilei" );
// *) 第二次调用
RpcContext.getContext().setAttachment( "traceId" , "100001" );
service.echo( "hanmeimei" );
|
只是感觉吃像相对难看了一点, 有没有更加优雅的方案呢? 我们踏着五彩霞云的盖世大英雄马上就要来了.
自定义filter方案:
我们先引入一个工具类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class TraceIdUtils {
private static final ThreadLocal<String> traceIdCache
= new ThreadLocal<String>();
public static String getTraceId() {
return traceIdCache.get();
}
public static void setTraceId(String traceId) {
traceIdCache.set(traceId);
}
public static void clear() {
traceIdCache.remove();
}
}
|
然后我们定义一个filter类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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文件)内容如下:
1 |
traceIdFilter=com.test.dubbo.TraceIdFilter
|
然后我们给dubbo的producer和consumer都配置对应的filter项.
服务端:
1 2 |
<dubbo:service interface = "com.test.dubbo.IEchoService" ref= "echoService" version= "1.0.0"
filter= "traceIdFilter" />
|
客户端:
1 2 |
<dubbo:reference interface = "com.test.dubbo.IEchoService" id= "echoService" version= "1.0.0"
filter= "traceIdFilter" />
|
服务端的测试代码小改为如下:
1 2 3 4 5 6 7 8 9 10 11 |
@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;
}
}
|
客户端的测试代码片段为:
1 2 3 4 5 6 |
// *) 第一调用
RpcContext.getContext().setAttachment( "traceId" , "100001" );
service.echo( "lilei" );
// *) 第二次调用
service.echo( "hanmeimei" );
|
同样的代码, 测试结果如下
1 2 3 4 5 6 7 |
服务端输出:
name = lilei, traceId = 100001
name = hanmeimei, traceId = 100001
客户端输出:
{traceId= 100001 }
{}
|
符合预期, 感觉这个方案就非常优雅了. RpcContext的attachment依旧被清空(ConsumerContextFilter在自定义的Filter后执行), 但是每次rpc交互前, traceId/logid会被重新注入, 保证跟踪线索透传成功.
总结:
关于这个方案, 在服务A, 服务B, 服务C之间连续传递测试, 依旧成功. 总的来说, 该方案还是可行的, dubbo的自定义filter机制也算是dubbo功能扩展的一个补充. 我们可以做很多工作, 比如耗时记录, metric信息的统计, 安全验证工作等等. 值得我们去深入研究.
标签:Dubbo,traceId,RpcContext,String,dubbo,logid,com,public 来源: https://www.cnblogs.com/ceshi2016/p/12016162.html