Dubbo 動態規則配置分析

開篇

  • 覆蓋規則是Dubbo設計的在無需重啓應用的情況下,動態調整RPC調用行爲的一種能力。
  • 在Dubbo2.6及更早版本中,所有的服務治理規則都只針對服務粒度(service),如果要把某條規則作用到應用粒度上,需要爲應用下的所有服務(service)配合相同的規則,變更,刪除的時候也需要對應的操作。
  • 2.7.0版本開始,支持從服務和應用兩個粒度來調整動態配置。
  • 這篇文章基於Dubbo-2.6.x描述動態配置的生效過程,下發配置的過程後續單獨分析


配置規則

向註冊中心寫入動態配置覆蓋規則 。該功能通常由監控中心或治理中心的頁面完成。

RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&timeout=1000"));
  • 參考官網的文檔配置規則
    )
  • override:// 表示數據採用覆蓋方式,支持 override 和 absent,可擴展,必填。
    0.0.0.0 表示對所有 IP 地址生效,如果只想覆蓋某個 IP 的數據,請填入具體 IP,必填。
  • com.foo.BarService 表示只對指定服務生效,必填。
  • category=configurators 表示該數據爲動態配置類型,必填。
  • dynamic=false 表示該數據爲持久數據,當註冊方退出時,數據依然保存在註冊中心,必填。
  • enabled=true 覆蓋規則是否生效,可不填,缺省生效。
  • application=foo 表示只對指定應用生效,可不填,表示對所有應用生效。
  • timeout=1000 表示將滿足以上條件的 timeout 參數的值覆蓋爲 1000。如果想覆蓋其它參數,直接加在 override 的 URL 參數上。


流程分析

public class ZookeeperRegistry extends FailbackRegistry {

    // 變量url的值
    // consumer://172.17.32.176/org.apache.dubbo.demo.DemoService?
    // application=dubbo-demo-api-consumer&category=providers,configurators,routers&dubbo=2.0.2
    // &interface=org.apache.dubbo.demo.DemoService&lazy=false&methods=sayHello
    // &pid=58968&side=consumer&sticky=false&timestamp=1571824631224
    public void doSubscribe(final URL url, final NotifyListener listener) {
        try {
            if (ANY_VALUE.equals(url.getServiceInterface())) {
                // 暫時不關注這部分邏輯
            } else {
                List<URL> urls = new ArrayList<>();
                // 處理providers、configurators、routers等路徑
                // /dubbo/org.apache.dubbo.demo.DemoService/providers
                // /dubbo/org.apache.dubbo.demo.DemoService/configurators
                // /dubbo/org.apache.dubbo.demo.DemoService/routers
                for (String path : toCategoriesPath(url)) {
                    ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                    if (listeners == null) {
                        zkListeners.putIfAbsent(url, new ConcurrentHashMap<>());
                        listeners = zkListeners.get(url);
                    }
                    ChildListener zkListener = listeners.get(listener);
                    if (zkListener == null) {
                        // 創建zk節點變化回調監聽器
                        listeners.putIfAbsent(listener, 
                        (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(
                                url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)));
                        zkListener = listeners.get(listener);
                    }
                    // 創建path對應的節點
                    zkClient.create(path, false);
                    // 添加path下的children的監聽
                    List<String> children = zkClient.addChildListener(path, zkListener);
                    // 處理path下的children
                    if (children != null) {
                        urls.addAll(toUrlsWithEmpty(url, path, children));
                    }
                }

                // 通知回調notify動作
                notify(url, listener, urls);
            }
        } catch (Throwable e) {
        }
    }
}
  • 1.consumer在refer過程中訂閱providers/configurators/routers等節點children變更事件,一旦子節點有變化就會觸發consumer側的監聽事件,重新生成服務引用。

public synchronized void notify(List<URL> urls) {
    List<URL> invokerUrls = new ArrayList<URL>();
    List<URL> routerUrls = new ArrayList<URL>();
    List<URL> configuratorUrls = new ArrayList<URL>();
    for (URL url : urls) {
        String protocol = url.getProtocol();
        String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
        if (Constants.ROUTERS_CATEGORY.equals(category)
                || Constants.ROUTE_PROTOCOL.equals(protocol)) {
            routerUrls.add(url);
        } else if (Constants.CONFIGURATORS_CATEGORY.equals(category)
                || Constants.OVERRIDE_PROTOCOL.equals(protocol)) {
            // 配置變更會觸發configuratorUrls的變更
            configuratorUrls.add(url);
        } else if (Constants.PROVIDERS_CATEGORY.equals(category)) {
            invokerUrls.add(url);
        } else {
            logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost());
        }
    }
    // 設置動態配置中心對象configurators
    if (configuratorUrls != null && !configuratorUrls.isEmpty()) {
        this.configurators = toConfigurators(configuratorUrls);
    }

    // 省略非核心代碼

    // providers
    refreshInvoker(invokerUrls);
}
  • 2.獲取配置變更的configuratorUrls,生成configuratorUrls對應的configurators對象,該對象會用在動態改變consumer側的URL鏈接。
  • 3.進入refreshInvoker()方法內部進行invoker的重建過程。


private void refreshInvoker(List<URL> invokerUrls) {
    if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
            && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
        this.forbidden = true; // Forbid to access
        this.methodInvokerMap = null; // Set the method invoker map to null
        destroyAllInvokers(); // Close all invokers
    } else {
        this.forbidden = false; // Allow to access
        Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
        if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
            invokerUrls.addAll(this.cachedInvokerUrls);
        } else {
            this.cachedInvokerUrls = new HashSet<URL>();
            this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
        }
        if (invokerUrls.isEmpty()) {
            return;
        }

        // 轉換provider對應的URL爲對應的invoker對象
        Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
        Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map
        // state change
        // If the calculation is wrong, it is not processed.
        if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
            logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
            return;
        }
        this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
        this.urlInvokerMap = newUrlInvokerMap;
        try {
            destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
        } catch (Exception e) {
            logger.warn("destroyUnusedInvokers error. ", e);
        }
    }
}
    1. refreshInvoker()方法內部的toInvokers()方法執行invokder的重建過程。


private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
    Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
    if (urls == null || urls.isEmpty()) {
        return newUrlInvokerMap;
    }
    Set<String> keys = new HashSet<String>();
    String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY);
    for (URL providerUrl : urls) {
        // 省略非核心代碼

        URL url = mergeUrl(providerUrl);

        String key = url.toFullString(); // The parameter urls are sorted
        if (keys.contains(key)) { // Repeated url
            continue;
        }
        keys.add(key);
        
        // 重新生成invoker對象
        Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
        Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
        if (invoker == null) { // Not in the cache, refer again
            try {
                boolean enabled = true;
                if (url.hasParameter(Constants.DISABLED_KEY)) {
                    enabled = !url.getParameter(Constants.DISABLED_KEY, false);
                } else {
                    enabled = url.getParameter(Constants.ENABLED_KEY, true);
                }
                if (enabled) {
                    invoker = new InvokerDelegate<T>(protocol.refer(serviceType, url), url, providerUrl);
                }
            } catch (Throwable t) {
                logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
            }
            if (invoker != null) { // Put new invoker in cache
                newUrlInvokerMap.put(key, invoker);
            }
        } else {
            newUrlInvokerMap.put(key, invoker);
        }
    }
    keys.clear();
    return newUrlInvokerMap;
}
    1. toInvokers()方法內部的mergeUrl()方法合併consumer、provider、configurator的參數信息,根據合併後的url的重建invoker對象。


// URL參數匹配順序 override > Consumer > Provider
private URL mergeUrl(URL providerUrl) {
    // Merge the consumer side parameters
    providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap); 

    // 合併動態配置導provider的URL當中,合併動態配置
    List<Configurator> localConfigurators = this.configurators; // local reference
    if (localConfigurators != null && !localConfigurators.isEmpty()) {
        for (Configurator configurator : localConfigurators) {
            providerUrl = configurator.configure(providerUrl);
        }
    }

    providerUrl = providerUrl.addParameter(Constants.CHECK_KEY, String.valueOf(false)); // Do not check whether the connection is successful or not, always create Invoker!

    // The combination of directoryUrl and override is at the end of notify, which can't be handled here
    this.overrideDirectoryUrl = this.overrideDirectoryUrl.addParametersIfAbsent(providerUrl.getParameters()); // Merge the provider side parameters

    // 省略非核心代碼
    return providerUrl;
}
    1. URL參數的合併順序按照 override > Consumer > Provider進行合併,通過mergeUrl()方法將動態配置的規則生效在URL當中。
    1. configurator.configure(providerUrl)中的configurator對象就是動態配置生成的對象,作用於provider的url之上。


public URL configure(URL url) {
    if (configuratorUrl == null || configuratorUrl.getHost() == null
            || url == null || url.getHost() == null) {
        return url;
    }
    // If override url has port, means it is a provider address. We want to control a specific provider with this override url, it may take effect on the specific provider instance or on consumers holding this provider instance.
    if (configuratorUrl.getPort() != 0) {
        if (url.getPort() == configuratorUrl.getPort()) {
            return configureIfMatch(url.getHost(), url);
        }
    } else {// override url don't have a port, means the ip override url specify is a consumer address or 0.0.0.0
        // 1.If it is a consumer ip address, the intention is to control a specific consumer instance, it must takes effect at the consumer side, any provider received this override url should ignore;
        // 2.If the ip is 0.0.0.0, this override url can be used on consumer, and also can be used on provider
        if (url.getParameter(Constants.SIDE_KEY, Constants.PROVIDER).equals(Constants.CONSUMER)) {
            return configureIfMatch(NetUtils.getLocalHost(), url);// NetUtils.getLocalHost is the ip address consumer registered to registry.
        } else if (url.getParameter(Constants.SIDE_KEY, Constants.CONSUMER).equals(Constants.PROVIDER)) {
            return configureIfMatch(Constants.ANYHOST_VALUE, url);// take effect on all providers, so address must be 0.0.0.0, otherwise it won't flow to this if branch
        }
    }
    return url;
}
private URL configureIfMatch(String host, URL url) {
    if (Constants.ANYHOST_VALUE.equals(configuratorUrl.getHost()) || host.equals(configuratorUrl.getHost())) {
        String configApplication = configuratorUrl.getParameter(Constants.APPLICATION_KEY,
                configuratorUrl.getUsername());
        String currentApplication = url.getParameter(Constants.APPLICATION_KEY, url.getUsername());
        if (configApplication == null || Constants.ANY_VALUE.equals(configApplication)
                || configApplication.equals(currentApplication)) {
            Set<String> conditionKeys = new HashSet<String>();
            conditionKeys.add(Constants.CATEGORY_KEY);
            conditionKeys.add(Constants.CHECK_KEY);
            conditionKeys.add(Constants.DYNAMIC_KEY);
            conditionKeys.add(Constants.ENABLED_KEY);
            for (Map.Entry<String, String> entry : configuratorUrl.getParameters().entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                if (key.startsWith("~") || Constants.APPLICATION_KEY.equals(key) || Constants.SIDE_KEY.equals(key)) {
                    conditionKeys.add(key);
                    if (value != null && !Constants.ANY_VALUE.equals(value)
                            && !value.equals(url.getParameter(key.startsWith("~") ? key.substring(1) : key))) {
                        return url;
                    }
                }
            }
            return doConfigure(url, configuratorUrl.removeParameters(conditionKeys));
        }
    }
    return url;
}
public class OverrideConfigurator extends AbstractConfigurator {

    public OverrideConfigurator(URL url) {
        super(url);
    }

    @Override
    public URL doConfigure(URL currentUrl, URL configUrl) {
        return currentUrl.addParameters(configUrl.getParameters());
    }
}


public URL addParameters(Map<String, String> parameters) {
    if (parameters == null || parameters.size() == 0) {
        return this;
    }

    boolean hasAndEqual = true;
    for (Map.Entry<String, String> entry : parameters.entrySet()) {
        String value = getParameters().get(entry.getKey());
        if (value == null) {
            if (entry.getValue() != null) {
                hasAndEqual = false;
                break;
            }
        } else {
            if (!value.equals(entry.getValue())) {
                hasAndEqual = false;
                break;
            }
        }
    }
    // return immediately if there's no change
    if (hasAndEqual) return this;

    Map<String, String> map = new HashMap<String, String>(getParameters());
    map.putAll(parameters);
    return new URL(protocol, username, password, host, port, path, map);
}
    1. OverrideConfigurator的整個configure流程,執行一些前置的配置檢查,覆蓋override的參數後返回新的URL對象。


參考文章

源碼分析Dubbo override實現原理
Dubbo配置規則

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章