Dubbo source code analysis practice - the mystery of routing Router

Keywords: Big Data Dubbo Javascript Zookeeper Mac

The second keyword in cluster fault tolerance is Router, which means routing in Chinese The front-end routing is different from the back-end routing, but the idea is basically the same In view of the fact that many technical articles have a criticism, that is, they only talk about concepts, but not application scenarios. In fact, Router has its shadow in application isolation, read-write separation, and gray-scale publishing. Therefore, this article uses the example of gray-scale publishing to pave the way for the earlier stage

Grayscale release

  • Baidu Encyclopedia

When you publish an application, you don't stop external services, which means that users don't feel you are publishing

So let's demonstrate the grayscale Publishing

1. Start Provider on 192.168.56.2 and 192.168.56.3, and then start Consumer, as shown in the following figure

2. Suppose we want to upgrade the service on the 192.168.56.2 server. Then we go to dubbo's console to configure the route and cut off the traffic of 192.168.56.2. After the configuration is completed and started, we can see that only the service of 192.168.56.3 is called at this time

3. Suppose you upgrade the service at 192.168.56.2. After the upgrade, you will start the service again

4. Since the service has been upgraded, we need to cancel the disable route and click disable. However, there are bug s in dubbo's management platform, as shown in the following figure

It's amazing to find that if you click disable, the data will change into two pieces. If you continue to disable, or two pieces, and you can't delete them, it will hurt a lot... But if you can't delete them all the time, it's not the way. The solution is that you can delete the nodes on zookeeper

There seems to be no easy-to-use zoom eeper visualization client tool on Mac, so I used this idea's zoom eeper plug-in Just delete this zookeeper node

Then refresh the console interface, as shown in the figure below, there is only one left

6. At this time, we will see the output of the console, which has returned to normal. The whole gray level publishing process is over

Inheritance system diagram of Router

As you can see from the figure, he has four implementation classes

>The script routing rules support all scripts of the JDK script engine, such as javascript, jruby, groovy, etc. the script type is set through the type=javascript parameter, and the default is JavaScript.

Of course, you may not feel the irreplaceable function of this class here. Notice that there is a ScriptEngine property in this class

So I can give you an application scenario

If there is such an expression as follows:

double d = (1+1-(2-4)*2)/24; //No problem 

// But if the expression is in such a string format, or a more complex operation, then you will not be able to deal with it
// Then the eval method of the ScriptEngine class can handle this kind of string expression well
"(1+1-(2-4)*2)/24"

This part mainly talks about

Conditionrouter (conditional route)

Conditional routing mainly filters the related invoker s according to the routing rules configured by dubbo management console. When we click enable routing rules, the notify method of RegistryDirectory class will be triggered

  @Override
    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.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 &amp;&amp; !configuratorUrls.isEmpty()) {
            this.configurators = toConfigurators(configuratorUrls);
        }
        // routers
        if (routerUrls != null &amp;&amp; !routerUrls.isEmpty()) {
            List<router> routers = toRouters(routerUrls);
            if (routers != null) { // null - do nothing
                setRouters(routers);
            }
        }
        List<configurator> localConfigurators = this.configurators; // local reference
        // merge override parameters
        this.overrideDirectoryUrl = directoryUrl;
        if (localConfigurators != null &amp;&amp; !localConfigurators.isEmpty()) {
            for (Configurator configurator : localConfigurators) {
                this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl);
            }
        }
        // providers
        refreshInvoker(invokerUrls);
    }

Why does the notify method pass in list < URL >? Quote a description of an official website document

>All configurations will eventually be converted to URL representation and generated by the service provider, which will be passed to the consumer through the registration center. See the "corresponding URL parameters" column in the configuration item list for the parameters of each attribute corresponding to the URL

In fact, for Router, what we care about most is how it filters. So let's go through these process codes first

    /**
     * Convert the invokerURL list to the Invoker Map The conversion rules are as follows:
     * 1. If the URL has been converted to an invoker, the URL will no longer be re referenced and retrieved directly from the cache, and note that any parameter changes in the URL will be re referenced.
     * 2. If the incoming invoker list is not empty, it means it is the latest one
     * 3. If the incoming invokerUrl list is empty, it means that the rule only covers the rule or routing rule and needs to be re compared to determine whether to re reference.
     *
     * @Parameter invokerUrls this parameter cannot be empty
     */
    // TODO: 2017/8/31 FIXME should use thread pool to refresh the address, otherwise it may accumulate tasks.
    private void refreshInvoker(List<url> invokerUrls) {
        if (invokerUrls != null &amp;&amp; invokerUrls.size() == 1 &amp;&amp; invokerUrls.get(0) != null
                &amp;&amp; Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
            this.forbidden = true; // No access
            this.methodInvokerMap = null; // Set the method invoker map to null
            destroyAllInvokers(); //Close all invoker s
        } else {
            this.forbidden = false; // allow access to
            Map<string, invoker<t>&gt; oldUrlInvokerMap = this.urlInvokerMap; // Local reference
            if (invokerUrls.isEmpty() &amp;&amp; this.cachedInvokerUrls != null) {
                invokerUrls.addAll(this.cachedInvokerUrls);
            } else {
                this.cachedInvokerUrls = new HashSet<url>();
                this.cachedInvokerUrls.addAll(invokerUrls);// Cached invoker URL for easy comparison
            }
            if (invokerUrls.isEmpty()) {
                return;
            }
            Map<string, invoker<t>&gt; newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
            Map<string, list<invoker<t>&gt;&gt; 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);
            }
        }
    }
    /**
     * Use method to convert the invokers list to a mapping relationship
     *
     * @param invokersMap Invoker Map
     * @return Mapping relation between Invoker and method
     */
    private Map<string, list<invoker<t>&gt;&gt; toMethodInvokers(Map<string, invoker<t>&gt; invokersMap) {
        Map<string, list<invoker<t>&gt;&gt; newMethodInvokerMap = new HashMap&lt;&gt;();
        // Based on the classification of methods declared by the provider URL, these methods are compatible with the registry to perform filtering
        List<invoker<t>&gt; invokersList = new ArrayList<invoker<t>&gt;();
        if (invokersMap != null &amp;&amp; invokersMap.size() &gt; 0) {
            for (Invoker<t> invoker : invokersMap.values()) {
                String parameter = invoker.getUrl().getParameter(Constants.METHODS_KEY);
                if (parameter != null &amp;&amp; parameter.length() &gt; 0) {
                    String[] methods = Constants.COMMA_SPLIT_PATTERN.split(parameter);
                    if (methods != null &amp;&amp; methods.length &gt; 0) {
                        for (String method : methods) {
                            if (method != null &amp;&amp; method.length() &gt; 0
                                    &amp;&amp; !Constants.ANY_VALUE.equals(method)) {
                                List<invoker<t>&gt; methodInvokers = newMethodInvokerMap.get(method);
                                if (methodInvokers == null) {
                                    methodInvokers = new ArrayList<invoker<t>&gt;();
                                    newMethodInvokerMap.put(method, methodInvokers);
                                }
                                methodInvokers.add(invoker);
                            }
                        }
                    }
                }
                invokersList.add(invoker);
            }
        }
        List<invoker<t>&gt; newInvokersList = route(invokersList, null);
        newMethodInvokerMap.put(Constants.ANY_VALUE, newInvokersList);
        if (serviceMethods != null &amp;&amp; serviceMethods.length &gt; 0) {
            for (String method : serviceMethods) {
                List<invoker<t>&gt; methodInvokers = newMethodInvokerMap.get(method);
                if (methodInvokers == null || methodInvokers.isEmpty()) {
                    methodInvokers = newInvokersList;
                }
                newMethodInvokerMap.put(method, route(methodInvokers, method));
            }
        }
        // Sort and not modifiable
        for (String method : new HashSet<string>(newMethodInvokerMap.keySet())) {
            List<invoker<t>&gt; methodInvokers = newMethodInvokerMap.get(method);
            Collections.sort(methodInvokers, InvokerComparator.getComparator());
            newMethodInvokerMap.put(method, Collections.unmodifiableList(methodInvokers));
        }
        return Collections.unmodifiableMap(newMethodInvokerMap);
    }

One of the characteristics of conditional routing is that its getUrl has value

From here, we can see that the implementation class is ConditionRouter at this time. Because the following logic may not be clear enough if you can directly see the source map, I used a high-definition codeless map for the core screening process, and marked it with serial number

The final filtering results are as follows. Because we configured disable 192.168.56.2 in the management background, only 192.168.56.3 was added to the invokers

Reference resources

dubbo source code analysis router >This article is based on the platform of blog one article multiple sending OpenWrite Release! </invoker<t></string></invoker<t></invoker<t></invoker<t></invoker<t></t></invoker<t></invoker<t></string,></string,></string,></string,></string,></url></string,></url></url></configurator></router></url></url></url></url></url></url></url>

Posted by noaksey2000 on Mon, 25 Nov 2019 09:51:42 -0800