某项目中遇到一个问题:dubbo交易总是发到同一个provider上。
排查中发现dubbo一些比较隐蔽的机制,以此博客记录。
问题复现
经过反复尝试,控制单变量比较,找到可以稳定复现问题的关键点:
- 多个
provider的group有相同值 -
consumer端group=*且不设置merger
如此即可复现问题:总是发到同一个provider上。
问题定位
debug跟踪代码,发现“总发到同一个”是如何出现的,见com.alibaba.dubbo.rpc.cluster.support.MergeableClusterInvoker的invoke方法,有如下片段:
for (final Invoker<T> invoker : invokers) {
if (invoker.isAvailable()) {
return invoker.invoke(invocation);
}
}
意即,遍历List<Invoker<T>> invokers,找到第一个可用的就调用。
一个对比试验,暴露一些dubbo底层机制
在“问题复现”的关键点1中,笔者提到“多个provider的group有相同值”,即:有group,且为同一个group。如果有多个group,则不能复现问题。
例如,2个provider设置group="group1",2个provider设置group="group2",就不会有“总发到同一个”的问题,而是总发到同一个group。
回头看初始化代码,发现:
- 如果
group仅有一个,那么List<Invoker<T>> invokers中元素类型为RegistryDirectory$InvokerDelegate,即直接到provider; - 当有多个
group,元素类型为MockClusterInvoker,即会经过LB
详见com.alibaba.dubbo.registry.integration.RegistryDirectory的toMergeMethodInvokerMap方法,有如下片段:
if (groupMap.size() == 1) {
result.put(method, groupMap.values().iterator().next());
} else if (groupMap.size() > 1) {
List<Invoker<T>> groupInvokers = new ArrayList<Invoker<T>>();
for (List<Invoker<T>> groupList : groupMap.values()) {
groupInvokers.add(cluster.join(new StaticDirectory<T>(groupList)));
}
result.put(method, groupInvokers);
}
结论
明确这个问题实际上是两部分造成:
- 若
group=*且merger不设置,则会用第一个可用的Invoker - 若
group仅有1个,则List<Invoker>元素为各provider;若group有多个,则元素为同group的provider包装起来的一个
这样就解释解释了问题:
- 为何总发到一个provider上
- 为何总发到一个group上