Dubbo2.6.3中的RpcContext和ContextFilter

Dubbo2.6.3中的RpcContext和ContextFilter

本文介绍了Dubbo2.6.3中的RpcContext和ContextFilter,以及在使用调用链的跟踪工具时出现的问题。通过代码分析,发现Dubbo的attachment是通过ThreadLocal来传递信息,因此在节点丢失时会出现错误的调用关系。同时,本文也提供了相关代码。

缘起

起因,我们在使用调用链的跟踪工具的时候发现这么一个情况。

有下面一个三个模块的系统,A调用B,B调用C,调用都是Dubbo接口调用。

1
2
graph LR
A --> B --> C

通常来说现在大部分的调用链工具都是通过字节码增强,通过ThreadLocal来保存Trace的信息,然后通过调用协议的一些特性在各个节点之间传递。

比如在dubbo中,trace信息就是通过attachment来传递。

单在我们实施的时候出来一点意外,忘记给B模块没有安装agent。

就出现下面的一个错误的调用关系。

1
2
graph LR
A --> C

根据往常的经验中,假如在系统中丢了节点,就会导致调用链断开,而不是错误的连接起来。有一份错误的数据带来的危害可能比没有数据更糟糕。

原因

直到看了dubbo的代码才发现问题。在dubbo的设计中attachment就是通过ThreadLocal来传递信息,于是就把Trace的也顺带的带上了。至于后续如何识别出来还需要另外想办法。

代码不多,全部贴上。

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.alibaba.dubbo.rpc.filter;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.Filter;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.RpcInvocation;
import com.alibaba.dubbo.rpc.RpcResult;

import java.util.HashMap;
import java.util.Map;

/**
* ContextInvokerFilter
*/
@Activate(group = Constants.PROVIDER, order = -10000)
public class ContextFilter implements Filter {

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Map<String, String> attachments = invocation.getAttachments();
if (attachments != null) {
attachments = new HashMap<String, String>(attachments);
attachments.remove(Constants.PATH_KEY);
attachments.remove(Constants.GROUP_KEY);
attachments.remove(Constants.VERSION_KEY);
attachments.remove(Constants.DUBBO_VERSION_KEY);
attachments.remove(Constants.TOKEN_KEY);
attachments.remove(Constants.TIMEOUT_KEY);
attachments.remove(Constants.ASYNC_KEY);// Remove async property to avoid being passed to the following invoke chain.
}
RpcContext.getContext()
.setInvoker(invoker)
.setInvocation(invocation)
// .setAttachments(attachments) // merged from dubbox
.setLocalAddress(invoker.getUrl().getHost(),
invoker.getUrl().getPort());

// mreged from dubbox
// we may already added some attachments into RpcContext before this filter (e.g. in rest protocol)
if (attachments != null) {
if (RpcContext.getContext().getAttachments() != null) {
RpcContext.getContext().getAttachments().putAll(attachments);
} else {
RpcContext.getContext().setAttachments(attachments);
}
}

if (invocation instanceof RpcInvocation) {
((RpcInvocation) invocation).setInvoker(invoker);
}
try {
RpcResult result = (RpcResult) invoker.invoke(invocation);
// pass attachments to result
result.addAttachments(RpcContext.getServerContext().getAttachments());
return result;
} finally {
RpcContext.removeContext();
RpcContext.getServerContext().clearAttachments();
}
}
}