Why My Custom Dubbo LoadBalance Stopped Working and How to Fix It
The article recounts a puzzling case where a custom Dubbo load‑balance extension (XLB) stopped being applied, explains how conflicting XML and Spring‑Boot configurations caused the consumer to ignore the custom setting, and walks through the source‑code investigation that reveals the loading order and caching behavior, ending with a concise summary of the root cause and resolution.
Background
Long ago I wrote a Dubbo load‑balance extension called XLB. Recently the business side reported that XLB was not taking effect.
I thought it was impossible after months of use.
Investigation
I logged into the consumer machine where the extension failed. The XLB extension prints a log line when it is loaded, but no such log appeared, indicating XLB was not loaded.
I asked the developer whether they had configured the load‑balance according to my documentation; they said they followed it exactly, with the consumer loadbalance set in the XML: <dubbo:consumer loadbalance="xlb"/> However, their application.properties also contained dubbo.consumer.check=false. This property creates a full consumer configuration with the default load‑balance, which conflicted with the XML setting.
Because my documentation only showed the XML form, the team, which originally used Spring‑Boot configuration, added an XML snippet that overwrote the Spring‑Boot settings.
Verification
I moved the dubbo.consumer.check setting from application.properties into the XML file; after restarting, XLB was loaded.
I also performed a local test with two configurations:
<!-- case 1 -->
<dubbo:consumer/>
<dubbo:consumer loadbalance="xlb"/>
<!-- case 2 -->
<dubbo:consumer loadbalance="xlb"/>
<dubbo:consumer/>Both configurations contain the same elements but in different order. Case 1 loaded XLB, while case 2 did not, suggesting that the later‑loaded consumer configuration overrides the earlier one.
Source Dive
The load‑balance is initialized in AbstractClusterInvoker.invoke:
@Override
public Result invoke(final Invocation invocation) throws RpcException {
...
List<Invoker<T>> invokers = list(invocation);
LoadBalance loadbalance = initLoadBalance(invokers, invocation);
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}The initLoadBalance method obtains the extension based on the first invoker’s URL parameter, falling back to the default if none is provided.
protected LoadBalance initLoadBalance(List<Invoker<T>> invokers, Invocation invocation) {
if (CollectionUtils.isNotEmpty(invokers)) {
return ExtensionLoader.getExtensionLoader(LoadBalance.class)
.getExtension(invokers.get(0).getUrl()
.getMethodParameter(RpcUtils.getMethodName(invocation), LOADBALANCE_KEY, DEFAULT_LOADBALANCE));
} else {
return ExtensionLoader.getExtensionLoader(LoadBalance.class)
.getExtension(DEFAULT_LOADBALANCE);
}
}The extension loader caches instances:
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}Thus the load‑balance is initialized only when the list of invokers is non‑empty, and the algorithm is chosen from the first invoker’s configuration.
The consumer parameters are merged into the provider URL in RegistryDirectory.toInvokers via mergeUrl, which incorporates the consumer’s query map.
private URL mergeUrl(URL providerUrl) {
// 1. merge consumer parameters
providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap);
// 2. merge configurator parameters
providerUrl = overrideWithConfigurator(providerUrl);
...
return providerUrl;
}The query map originates from the reference configuration, which ultimately obtains the first consumer configuration (whether from XML, Spring‑Boot, or API).
Conclusion
Each consumer definition—whether from XML, Spring‑Boot properties, or programmatic API—creates a ConsumerConfig object.
When a reference is configured, the consumer’s parameters are merged; if multiple consumers exist, the first one loaded is used, but the loading order is not deterministic.
During provider notification, the consumer parameters are merged into the provider URL, producing the final invoker.
The load‑balance algorithm is selected from the first invoker’s configuration on the first call and then cached for subsequent calls.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Xiao Lou's Tech Notes
Backend technology sharing, architecture design, performance optimization, source code reading, troubleshooting, and pitfall practices
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
