What is service routing

Service routing contains a routing rule that determines the invocation target of the service consumer, that is, which service providers can be invoked by the service consumer. Dubbo currently provides three service routing implementations, which are:

  • ConditionRouter: ConditionRouter (most commonly used)
  • Script routing: ScriptRouter
  • TagRoute: TagRouter

Routing rules

Apply granular routing rules:

# application stands for application granularityscope: application # Specifies whether to force the route if the result is empty. If the route result is empty, the routing rule is invalid automatically and no route is used. Default isfalse, it is not mandatory.force: true# specifies whether the routing rule is executed on each invocation. iffalseIt means that the routing rule is pre-executed only when the provider address list changes, and the routing result is cached. When the consumer calls, the routing result is directly obtained from the cache. # iftrueIt indicates that the routing rules must be recalculated for each invocation, which directly affects the invocation performance. Default isfalse.runtime: true
enabled: true# Set the priority of the routing rule. The higher the number, the higher the priority and the higher the execution. Default is0.priority: 0# Routing rules due to the service namekey: governance-conditionrouter-consumer To define specific routing rules, run the following command1To any number of rules.conditions: # app1 consumers can only consume all ports as20880- Application =app1= > address=*:20880# app2 consumers can only consume all ports as20881- Application =app2= > address=*:20881
Copy the code

Service granularity routing rules:

scope: service
force: true
runtime: true
enabled: trueThe name of the specified consumer interfacekey: org.apache.dubbo.samples.governance.api.DemoService
conditionsThe sayHello method of DemoService can only consume all ports as20880- method=sayHello= > address=*:20880The sayHi method of # DemoService can only consume all ports as20881- method=sayHi= > address=*:20881
Copy the code

format

The instance

  1. The blacklist

    The host = 10.20.153.10, 10.20.153.11 = >

    The hosts whose IP addresses are 10.20.153.10 and 10.20.153.11 are disabled.

  2. White list

    host ! = 10.20.153.10, 10.20.153.11 = >

    Disable all hosts whose IP addresses are not 10.20.153.10 and 10.20.153.11.

  3. Only a subset of providers are exposed

    = = > host 172.22.3.1 *, 172.22.3.2 *

    The consumer can access only the provider hosts with IP addresses 172.22.3.1 and 172.22.3.2.

  4. Provide additional machines for critical applications

    application ! = kylin => host ! = 172.22.3.95, 172.22.3.96

    The application whose name is not Kylin cannot access 172.22.3.95 and 172.22.3.96. That is, only the consumer named Kylin can access the two provider hosts 172.22.3.95 and 172.22.3.96. Of course, Kylin has access to other provider hosts, and other consumers have access to all provider hosts except 172.22.3.95 and 172.22.3.96.

  5. Reading and writing separation

    Method = find *, list *, get * is * = > host = 172.22.3.94, 172.22.3.95, 172.22.3.96

    method ! * = find *, list the get *, is * = > host = 172.22.3.97, 172.22.3.98

    Consumer methods starting with find, list, get, and IS are routed to 172.22.3.94, 172.22.3.95, and 172.22.3.96. The other methods are routed to 172.22.3.97 and 172.22.3.98 provider hosts

  6. Front and background separation

    Application = bops = > host = 172.22.3.91, 172.22.3.92, 172.22.3.93

    application ! = bops = > host = 172.22.3.94 172.22.3.95, 172.22.3.96

    Consumers of application name bOPS are routed to 172.22.3.91, 172.22.3.92, and 172.22.3.93 provider hosts. Other consumers are routed to 172.22.3.94, 172.22.3.95, and 172.22.3.96 providers.

  7. Isolate different network segments of the equipment room

    host ! = 172.22.3.* => host! = 172.22.3. *

    Consumers that are not in the 172.22.3 network segment cannot access providers in the 172.22.3 network segment. That is, only consumers on 172.22.3 can access providers on 172.22.3. Of course, consumers in 172.22.3 can also access providers in other network segments.

  8. Access only local services

    => host = $host

    $host gets the consumer host IP in the consumer request. Therefore, this rule means that the consumer can only access the local service.

Add activated RouterFactory to Directory

RegistryProtocol.java

RegistryDirectory.java

public void buildRouterChain(URL url) {
   // Parse the routing information in the URL
    this.setRouterChain(RouterChain.buildChain(url));
}
Copy the code

RouterChain.java

public static <T> RouterChain<T> buildChain(URL url) {
    return new RouterChain<>(url);
}

private RouterChain(URL url) {
    // Get all active class instances of RouterFactory
    List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class)
            .getActivateExtension(url, (String[]) null);

    List<Router> routers = extensionFactories.stream()  // Generate a Stream with factory element
            .map(factory -> factory.getRouter(url)) // Map the factory in the Stream to the router generated by that factory
            .collect(Collectors.toList());  // Change Stream to List
    // Initializes the created router list into the Directory's routerChain
    initWithRouters(routers);
}
Copy the code

Read routing rules in the registry

private List<URL> toUrlsWithEmpty(URL consumer, String path, List<String> providers) {
    // Get all non-empty urls under the current category node
    List<URL> urls = toUrlsWithoutEmpty(consumer, providers);
    // If there are no children under the current classification node, the system creates an empty://... The empty url
    if (urls == null || urls.isEmpty()) {
        int i = path.lastIndexOf(PATH_SEPARATOR);
        String category = i < 0 ? path : path.substring(i + 1);
        URL empty = URLBuilder.from(consumer)
                .setProtocol(EMPTY_PROTOCOL)
                .addParameter(CATEGORY_KEY, category)
                .build();
        urls.add(empty);
    }
    return urls;
}

private List<URL> toUrlsWithoutEmpty(URL consumer, List<String> providers) {
  List<URL> urls = new ArrayList<>();
  if (CollectionUtils.isNotEmpty(providers)) {
    // Iterate over all child nodes
    for (String provider : providers) {
      / / decoding
      provider = URL.decode(provider);
      // Handle the case where the child node names are in the form of urls
      if (provider.contains(PROTOCOL_SEPARATOR)) {
        URL url = URL.valueOf(provider);
        if(UrlUtils.isMatch(consumer, url)) { urls.add(url); }}}}return urls;
}
Copy the code

RegistryDirectory.java

@Override
public synchronized void notify(List<URL> urls) {
    Map<String, List<URL>> categoryUrls = urls.stream()
            .filter(Objects::nonNull)
            .filter(this::isValidCategory)
            .filter(this::isNotCompatibleFor26x)
            .collect(Collectors.groupingBy(url -> {
                if (UrlUtils.isConfigurator(url)) {
                    return CONFIGURATORS_CATEGORY;
                } else if (UrlUtils.isRoute(url)) {
                    return ROUTERS_CATEGORY;
                } else if (UrlUtils.isProvider(url)) {
                    return PROVIDERS_CATEGORY;
                }
                return "";
            }));
    // Handle configurators
    List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
    this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);

    // Process the classified nodes of the Routers
    List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
    toRouters(routerURLs)  / / returns the Optional
            // If Optional encapsulates an object that is not empty, call Lambda's instance method to reference addRouters(), adding the route to directory
            .ifPresent(this::addRouters);

    // providers
    // Handle providers classification nodes
    List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
    // Get the latest provider updates from ZK to directory
    refreshOverrideAndInvoker(providerURLs);
}

private void refreshOverrideAndInvoker(List<URL> urls) {
    // mock zookeeper://xxx? mock=return null
    overrideDirectoryUrl();
    refreshInvoker(urls);  //
}



private Optional<List<Router>> toRouters(List<URL> urls) {
        if (urls == null || urls.isEmpty()) {
            return Optional.empty();
        }

        List<Router> routers = new ArrayList<>();
        for (URL url : urls) {
            // If the current URL is empty as protocol, it is not a valid URL
            if (EMPTY_PROTOCOL.equals(url.getProtocol())) {
                continue;
            }
            // Obtain the router attribute value in the URL. The current value is condition, indicating that this is a conditional route
            String routerType = url.getParameter(ROUTER_KEY);
            if(routerType ! =null && routerType.length() > 0) {
                // the route type is the protocol of the URL, i.e. the current URL is changed to condition://...
                url = url.setProtocol(routerType);
            }
            try {
                // Create a route
                Router router = ROUTER_FACTORY.getRouter(url);
                // Record the route instance
                if (!routers.contains(router)) {
                    routers.add(router);
                }
            } catch (Throwable t) {
                logger.error("convert router url to router error, url: "+ url, t); }}return Optional.of(routers);
    }
Copy the code

Routing Rules Take effect

Occurs when a method is called

InvokerInvocationHandler.java

public class InvokerInvocationHandler implements InvocationHandler {
    private static final Logger logger = LoggerFactory.getLogger(InvokerInvocationHandler.class);
    private finalInvoker<? > invoker;public InvokerInvocationHandler(Invoker
        handler) {
        this.invoker = handler;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Get the method name of the RPC remote callString methodName = method.getName(); Class<? >[] parameterTypes = method.getParameterTypes();// If the current method is an Object method, it is a local method call
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(invoker, args);
        }
        if ("toString".equals(methodName) && parameterTypes.length == 0) {
            return invoker.toString();
        }
        if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
            return invoker.hashCode();
        }
        if ("equals".equals(methodName) && parameterTypes.length == 1) {
            return invoker.equals(args[0]);
        }
        // Remote call
        return invoker.invoke(newRpcInvocation(method, args)).recreate(); }}Copy the code

MockClusterInvoker.java

@Override
public Result invoke(Invocation invocation) throws RpcException {
    Result result = null;
    String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), MOCK_KEY, Boolean.FALSE.toString()).trim();
    if (value.length() == 0 || value.equalsIgnoreCase("false")) {
        //no mock remote invocation
        result = this.invoker.invoke(invocation);
    } else if (value.startsWith("force")) {
        if (logger.isWarnEnabled()) {
            logger.warn("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
        }
        result = doMockInvoke(invocation, null);
    } else {
        //fail-mock
        try {  // Remote call
            result = this.invoker.invoke(invocation);
        } catch (RpcException e) {
            if (e.isBiz()) {
                throw e;
            }

            if (logger.isWarnEnabled()) {
                logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : "+ directory.getUrl(), e); } result = doMockInvoke(invocation, e); }}return result;
}
Copy the code

AbstractClusterInvoker.java

public Result invoke(final Invocation invocation) throws RpcException {
    checkWhetherDestroyed();

    // binding attachments into invocation.
    Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
    if(contextAttachments ! =null&& contextAttachments.size() ! =0) {
        ((RpcInvocation) invocation).addAttachments(contextAttachments);
    }
    // Routing: Filter out invokers that are not available according to routing rules and return the remaining invokers that are available
  	// Trace the list
    List<Invoker<T>> invokers = list(invocation);
    // Obtain the load balancing policy
    LoadBalance loadbalance = initLoadBalance(invokers, invocation);
    RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
    // Invoke doInvoke() with a specific fault-tolerant policy
    return doInvoke(invocation, invokers, loadbalance);
}
Copy the code

RegistryDirectory.java

public List<Invoker<T>> doList(Invocation invocation) {
    if (forbidden) {
        // 1. No service provider 2. Service providers are disabled
        throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "No provider available from registry " +
                getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " +
                NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() +
                ", please check status of providers(disabled, not registered or in blacklist).");
    }

    if (multiGroup) {
        return this.invokers == null ? Collections.emptyList() : this.invokers;
    }

    List<Invoker<T>> invokers = null;
    try {
        // Get invokers from cache, only runtime routers will be executed.
        // Apply routing rules to all invokers to filter out invokers that do not meet the rules
        invokers = routerChain.route(getConsumerUrl(), invocation);
    } catch (Throwable t) {
        logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
    }
    return invokers == null ? Collections.emptyList() : invokers;
}
Copy the code

RouterChain.java

public List<Invoker<T>> route(URL url, Invocation invocation) {
    List<Invoker<T>> finalInvokers = invokers;
    for (Router router : routers) {
        finalInvokers = router.route(finalInvokers, url, invocation);
    }
    return finalInvokers;
}
Copy the code

ConditionRouter.java

public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
        throws RpcException {
    // If the enabled attribute of the routing rule is false(i.e. the current routing rule equals none), all invokers are returned
    if(! enabled) {return invokers;
    }

    // Return null if there is no provider
    if (CollectionUtils.isEmpty(invokers)) {
        return invokers;
    }
    try {
        // The matcherWhen() method is used to determine whether the current consumer matches the previous part of the arrow (=>),
        // If the rule does not match, the current rule does not apply to the current consumer, that is, all invokers are directly returned without routing filtering
        if(! matchWhen(url, invocation)) {return invokers;
        }
        // The current consumer has been matched with the front part of the arrow (=>), now look at the situation behind the arrow (=>)
        // This result set will be used to store invokers filtered by the route
        List<Invoker<T>> result = new ArrayList<Invoker<T>>();
        // thenCondition stores the following part of the arrow (=>).
        // The current rule is black/white, that is, all invokers are unavailable
        if (thenCondition == null) {
            logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
            return result;
        }
        // The current consumer is matched in front of the arrow (=>) and is not empty.
        // Now you need to iterate through all invokers to try to match the rule condition after the arrow (=>)
        for (Invoker<T> invoker : invokers) {
            // matchThen() determines if it matches the condition after the arrow (=>)
            if (matchThen(invoker.getUrl(), url)) {
                // If the match is matched, the route is filtered to indicate that the match is availableresult.add(invoker); }}if(! result.isEmpty()) {return result;
        } else if (force) {
            logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(RULE_KEY));
            returnresult; }}catch (Throwable t) {
        logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
    }
    return invokers;
}
Copy the code