某项目中遇到一个问题: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上