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
- MockInvokersSelector in Dubbo source code analysis (I) - the design of cluster architecture Mentioned here in
- ScriptRouter is useful in dubbo's test cases. The source code of this class is not many, which is 124 lines. Refer to the description on the official website
>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 && !configuratorUrls.isEmpty()) { this.configurators = toConfigurators(configuratorUrls); } // routers if (routerUrls != null && !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 && !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 && invokerUrls.size() == 1 && invokerUrls.get(0) != null && 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>> 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 URL for easy comparison } if (invokerUrls.isEmpty()) { return; } 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); } } }
/** * 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>>> toMethodInvokers(Map<string, invoker<t>> invokersMap) { Map<string, list<invoker<t>>> newMethodInvokerMap = new HashMap<>(); // Based on the classification of methods declared by the provider URL, these methods are compatible with the registry to perform filtering List<invoker<t>> invokersList = new ArrayList<invoker<t>>(); if (invokersMap != null && invokersMap.size() > 0) { for (Invoker<t> invoker : invokersMap.values()) { String parameter = invoker.getUrl().getParameter(Constants.METHODS_KEY); if (parameter != null && parameter.length() > 0) { String[] methods = Constants.COMMA_SPLIT_PATTERN.split(parameter); if (methods != null && methods.length > 0) { for (String method : methods) { if (method != null && method.length() > 0 && !Constants.ANY_VALUE.equals(method)) { List<invoker<t>> methodInvokers = newMethodInvokerMap.get(method); if (methodInvokers == null) { methodInvokers = new ArrayList<invoker<t>>(); newMethodInvokerMap.put(method, methodInvokers); } methodInvokers.add(invoker); } } } } invokersList.add(invoker); } } List<invoker<t>> newInvokersList = route(invokersList, null); newMethodInvokerMap.put(Constants.ANY_VALUE, newInvokersList); if (serviceMethods != null && serviceMethods.length > 0) { for (String method : serviceMethods) { List<invoker<t>> 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>> 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>