老版本Dubbo的一个bug

线上有用户反应P 服务页面时不时就报个错,后来发现都涉及I服务的一个接口方法,这里暂且就叫F 接口。只是时不时报错,那说明可能是某些参数会导致异常,于是我开始查看日志,看看是不是有什么特殊参数导致隐藏 bug 被发现。
结果是报错和不报错的请求参数都一样,那是不是多台机器上运行的代码不一致呢?于是我看了F 接口的代码,发现最近没有迭代记录,而且所有机器上部署的代码均一致。
那有没有可能是环境问题?于是我上 Dubbo 控制台查看,发现 provider 和 consumer 都正常:
DubboAdmin
这个时候我开始有点懵逼了😅。

我又想到,如果是一直存在问题,那么不可能到今天才有用户反馈,所以还是跟近期的什么操作有关。于是我开始排查最早是什么时间出现的问题,发现是 10 月 10 日的 18:00:03,又发现F 接口所在的I服务10 月 10 日的 17:57:28有过发布记录。
release

这下就有点奇怪了,就算I服务所有机器同时发布,可能也就是报一会 no providers错误啊,更不用说是滚动发布的,加上 dubbo 的 failover不会一直报错的啊。

就在这时我看到了一个关键的错误信息:(之前只看到了 NPE 没具体位置):
Error

问题直指DubboInvoker:109。

我们 dubbo的版本是(2.6.6),我拉下dubbo 代码发现这块的代码是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

@Override
public boolean isAvailable() {
if (!super.isAvailable())
return false;
for (ExchangeClient client : clients) {
//这一行是 109,就是这一行报错
if (client.isConnected() && !client.hasAttribute(Constants.CHANNEL_ATTRIBUTE_READONLY_KEY)) {
//cannot write == not Available ?
return true;
}
}
return false;
}

109 行报 NPE 莫非 client 是 null?带上猜测我开始看 client 是怎么来的,最终找到com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#getSharedClient:

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
private ExchangeClient getSharedClient(URL url) {
String key = url.getAddress();
ReferenceCountExchangeClient client = referenceClientMap.get(key);
if (client != null) {
if (!client.isClosed()) {
client.incrementAndGetCount();
return client;
} else {
//3
referenceClientMap.remove(key);//4
}
}

locks.putIfAbsent(key, new Object());
synchronized (locks.get(key)) {//2
if (referenceClientMap.containsKey(key)) {
return referenceClientMap.get(key);//5
}

ExchangeClient exchangeClient = initClient(url);
client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
referenceClientMap.put(key, client);
ghostClientMap.remove(key);
locks.remove(key);
//1
return client;
}
}

又是一段平平无奇的代码😅,看到这里有用到锁,说明开发者考虑到这里可能会存在并发调用。我又看了下日志,10 月 10 日的 18:00:03前后确实出现了并发调用的现象,难道是这里的并发控制有 bug?于是我开始思考各种场景,还真被我发现一种可能出问题的情况,下面我画了个图演示一下:

flow

最后 序号 5 处返回了一个 null 的 client。

由于2.6.6 版本已经有点老,指不定这个问题已经有人提过,于是我到 github 一番搜索,结果找到了另外一个问题:https://github.com/apache/dubbo/issues/6444

这块dubbo 后面也迭代了好几次,已经搞不清楚了。

最后,重启大法好!