使用brave
对dubbo
做链路跟踪对复杂系统定位问题有重要意义。某项目发现,在rest
协议下,brave
做链路跟踪会失败。
经试验对比,此问题有普遍性。本篇博客从源码层面探索失败原因,并给出一个初步的解决方案。
也欢迎访问笔者github上此问题示例工程test-dubbo-brave
问题定位
首先明确dubbo
和brave
集成的方式为通过dubbo
的Filter
,其中brave
的carrier
为dubbo
的Attachments
,可见brave.dubbo.rpc.TracingFilter
的Invoke
方法,如下代码片段:
if (kind.equals(Kind.CLIENT)) {
span = tracer.nextSpan();
injector.inject(span.context(), invocation.getAttachments());
} else {
TraceContextOrSamplingFlags extracted = extractor.extract(invocation.getAttachments());
span = extracted.context() != null
? tracer.joinSpan(extracted.context())
: tracer.nextSpan(extracted);
}
可知:
- 若当前
dubbo
为消费端,则生成下一个span
,放入Attachments
- 若当前
dubbo
为服务端,则从Attachments
中提取span
另外注意,其中使用的Attachments
是Invocation
中的。
参考笔者另一篇博客
dubbo-protocol-rest下attachments如何传递,可知protocol-rest
下,dubbo
使用的Attachments
是RpcContext
中的。
这样一来就矛盾了:
-
brave
使用Invocation
中的Attachments
,放入或取出Span
-
protocol-rest
使用RpcContext
中的Attachments
,放入或取出HTTP header
这两者一连接,示意图如下:
brave protocol-rest
组件 TracingFilter RpcContextFilter
attachments载体 span Invocation RpcContext HTTP headers
server端attachments赋值方向 <- <-
client端attachments赋值方向 -> ->
造成的问题为:
- 若当前
dubbo
为消费端,则将span
放入Invocation
中的Attachments
,将RpcContext
中的Attachments
放入HTTP header
。新span
无法传入下一个服务,上下游服务span
相同。 - 若当前
dubbo
为服务端,从HTTP header
取出Attachments
放入RpcContext
,从Invocation
的Attachments
提取span
。服务端提取不到span
,总认为是自己是交易入口,起一个新trace
。
修正
这里给出两个方法,分别为是否改源码
修改源码
较简单做法为修改源码,不是使用Invocation
中的Attachments
作为carrier
,而是使用RpcContext
中的。略。
不修改源码
另一种做法为再增加一个Filter
,完成Invocation
中的Attachments
和RpcContext
中的双向赋值。可见笔者github上此问题示例工程test-dubbo-brave,增加后span
和HTTP header
之间的赋值示意图如下:
brave protocol-rest
组件 TracingFilter *新增* RpcContextFilter
attachments载体 span Invocation RpcContext HTTP headers
server端attachments赋值方向 <- *<-* <-
client端attachments赋值方向 -> *->* ->
其中两个关键Filter
为PropagationFromRpcContextToInvocationFilter
及PropagationFromRpcInvocationToContextFilter
,类图为:
另有
META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter
,其内容为:
context2inv=io.dracula.test.dubbo.brave.PropagationFromRpcContextToInvocationFilter
inv2context=io.dracula.test.dubbo.brave.PropagationFromRpcInvocationToContextFilter
使用时需在xml
文件中指定:
<dubbo:provider filter="context2inv,tracing"/>
<dubbo:consumer filter="tracing,inv2context"/>
具体实现方面,在两Filter
中尽可能使用brave
提供的api
,如Extractor
和Injector
,而不是直接从Attachments
中取值再向另一个Attachments
赋值。这主要是考虑到了brave
的Propagation
和Carrier
抽象,同时兼顾灵活性。
brave
的dubbo Filter
,brave.dubbo.rpc.TracingFilter
,代码片段如下:
public final class TracingFilter implements Filter {
TraceContext.Extractor<Map<String, String>> extractor;
TraceContext.Injector<Map<String, String>> injector;
//略
}
可见extractor
和injector
属性为包访问级别,于是需要自己在同包下写一个类,将两属性暴露出来,可见brave.dubbo.rpc.PackageAccessBridge
,其子类io.dracula.test.dubbo.brave.PropagationKeysAssignFilter
中封装了可复用的逻辑,从一个Attachments
用Exractor
提取出span
,再用Injector
将此span
放入另一个Attachments
,代码片段如下:
//Map<String, String> ori, des分别为两个Attachments
TraceContext extractedTraceContext = extractor.extract(ori).context();
injector.inject(extractedTraceContext, des);