dubbo 配置 loadbalance 不生效?擼一把源碼

背景

很久之前我給業務方寫了一個 dubbo loadbalance 的擴展(爲了敘述方便,這個 loadbalance 擴展就叫它 XLB 吧),這兩天業務方反饋說 XLB 不生效了

我心想,不可能啊,都用了大半年了~

排查

於是我登上不生效的 consumer 機器進行排查,還好我留了一手,當 XLB 加載時,會打印一行日誌

看了下這個服務,並沒有打印日誌,說明 XLB 並沒有加載成功

於是,我就去問對應的開發,有按照我的文檔配置 loadbalance 嗎?答覆:完全按照文檔配置

這下我就有點不相信了,但轉念一想,配置 loadbalance 如此簡單,不應該出錯啊,我的文檔和他的應用都在 xml 文件中配置了 consumer 的 loadbalance

<dubbo:consumer loadbalance="xlb"/>

抱着試一試的態度,拉取了他們項目的代碼,發現配置確實如上,但我發現他們的 application.properties 配置文件也配了一個 consumer 的屬性

dubbo.consumer.check=false

以多年和 dubbo 打交道的經驗來說,這裏有問題,又確認了代碼,確實 xml 和 application.properties 都加載了

那這裏可能就有問題了,dubbo 從 xml 加載生成了一個 consumer 配置,dubbo-springboot-starter 又從 application.properties 加載配置生成了一個 consumer 配置,這不就衝突了?

別看只配置了 dubbo.consumer.check,它實際上會生成一個完整的 consumer 配置,只不過 loadbalance 爲默認值

業務方爲什麼會這樣配置?大概率是因爲我的文檔裏只給出了 xml 形式的配置,沒有給 spring-boot 配置,他們原先使用的是 spring-boot 的配置方式,然後看到我的文檔是 xml,結果就不會配置了,也寫了個 xml,和原先的配置衝突

驗證

爲了驗證是這個問題導致,我把他的 application.properties 的 dubbo.consumer.check 配置挪到了 xml 文件中,果然重啓後就加載到了 XLB

隨後我又在本地的測試應用上做了這樣一個驗證:

<!-- case 1 -->
<dubbo:consumer />
<dubbo:consumer loadbalance="xlb"/>

<!-- case 2 -->
<dubbo:consumer loadbalance="xlb"/>
<dubbo:consumer />

兩組配置相同,但順序不同,測試結果爲 case 1 可以加載到 XLB,case 2 不行

於是猜測,dubbo consumer 配置以後加載的爲準

擼源碼

顯然猜測不符合我的風格,下面開擼源碼,不感興趣可以劃過,最下面有總結

首先搞清楚,何時會加載 loadbalance,在 AbstractClusterInvokerinvoke 方法中,加載了 loadbalance

@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);
}

加載代碼如下

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);
    }
}

帶緩存的加載擴展

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;
}

可以看出

於是問題轉移到 invoker 的 loadbalance 從哪來?provider 不會配置 loadbalance,所以這個參數一定是從 consumer 的配置上得到的

順藤摸瓜,在 RegistryDirectorytoInvokers 方法中調用了 mergeUrl,它是在註冊中心通知時被調用,也就是從註冊中心上拿到 provider url 時,還得 merge 一下才能用,merge 了些什麼內容?

private URL mergeUrl(URL providerUrl) {
    // 1. merge consumer 參數
    providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap); 
    // 2. merge configurator 參數
    providerUrl = overrideWithConfigurator(providerUrl);
    ...
    return providerUrl;
}

1 中 merge 了 queryMap 裏的參數,這個 queryMap 其實就是 consumer 的參數,它來自配置的 reference

再看 reference 配置,當 ReferenceConfig 初始化時

// 1
public synchronized void init() {
    ...
    checkAndUpdateSubConfigs();
    ...
    AbstractConfig.appendParameters(map, consumer);
    ...
}

// 2
public void checkAndUpdateSubConfigs() {
    ...
    checkDefault();
    ...
}

// 3
public void checkDefault() throws IllegalStateException {
    if (consumer == null) {
        consumer = ApplicationModel.getConfigManager()
                .getDefaultConsumer()
                .orElse(new ConsumerConfig());
    }
}

// 4
public Optional<ConsumerConfig> getDefaultConsumer() {
    List<ConsumerConfig> consumerConfigs = getDefaultConfigs(getConfigsMap(getTagName(ConsumerConfig.class)));
    if (CollectionUtils.isNotEmpty(consumerConfigs)) {
        return Optional.of(consumerConfigs.get(0));
    }
    return Optional.empty();
}

上面調用鏈從 1 到 44 中獲取了第 1 個 consumer,這就是我們要找的根源

總結

搜索關注微信公衆號 "捉蟲大師",後端技術分享,架構設計、性能優化、源碼閱讀、問題排查、踩坑實踐。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/qgBsR-TWehRbt2M1ToDQQg