Routing rules for dubbo source code analysis

1. Update of routing rules

If there is a change in the routing configuration information, notify RegistryDirectory#notify(List urls). The following takes Zookeeper as an example to explain.

public synchronized void notify(List<URL> urls) {
    
    
    ...
    List<URL> routerUrls = 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);
        }
        ...
    }
    ...
    // routers
    if (routerUrls != null && !routerUrls.isEmpty()) {
    
    
        // 获取路由
        // 下面详细讲解
        List<Router> routers = toRouters(routerUrls);
        if (routers != null) {
    
     // null - do nothing
            // 设置路由
            setRouters(routers);
        }
    }
    ...
}

Take out the changed url -> get route -> set route. The process of "getting route" is explained in detail below.

// RegistryDirectory#toRouters(List<URL>)
private List<Router> toRouters(List<URL> urls) {
    
    
    List<Router> routers = new ArrayList<Router>();
    if (urls == null || urls.isEmpty()) {
    
    
        return routers;
    }
    if (urls != null && !urls.isEmpty()) {
    
    
        for (URL url : urls) {
    
    
            if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {
    
    
                continue;
            }
            String routerType = url.getParameter(Constants.ROUTER_KEY);
            if (routerType != null && routerType.length() > 0) {
    
    
                url = url.setProtocol(routerType);
            }
            try {
    
    
                // RouterFactory是一个@SPI类,getRouter(URL)方法是@Adaptive("protocol")修饰的,所以通过url.protocol决定实现类
                Router router = routerFactory.getRouter(url);
                if (!routers.contains(router))
                    routers.add(router);
            } catch (Throwable t) {
    
    
                logger.error("convert router url to router error, url: " + url, t);
            }
        }
    }
    return routers;
}
2. Use of routing rules

When obtaining the Invokers list, first select the currently available Invokers list through the Directory, and then filter the Invokers that do not meet the routing rules through the route (List<Invoker>, URL, Invocation) of the Router interface. Let's take a look at the specific implementation.

2.1 Use routing rules to filter Invokers
// AbstractDirectory#list(Invocation invocation)
public List<Invoker<T>> list(Invocation invocation) throws RpcException {
    
    
    ...
    // 通过Directory选出当前可用的Invokers列表
    List<Invoker<T>> invokers = doList(invocation);
    // 路由过滤
    List<Router> localRouters = this.routers; // local reference
    if (localRouters != null && !localRouters.isEmpty()) {
    
    
        for (Router router : localRouters) {
    
    
            try {
    
    
                if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) {
    
    
                    invokers = router.route(invokers, getConsumerUrl(), invocation);
                }
            } catch (Throwable t) {
    
    
                logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
            }
        }
    }
    return invokers;
}
2.2 The first way to implement routing filtering: ConditionRouter#route(List<Invoker>, URL, Invocation)
// ConditionRouter#route(List<Invoker<T>>, URL, Invocation)
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
        throws RpcException {
    
    
    if (invokers == null || invokers.isEmpty()) {
    
    
        return invokers;
    }
    try {
    
    
        // 匹配规则:host = 10.99.1.109 =>  host = 10.20.3.3,就是消费者ip为10.99.1.109的,转向提供者ip为10.20.3.3的服务器
        // when: => 的前半部分,也就是 host = 10.99.1.109
        if (!matchWhen(url, invocation)) {
    
    
            return invokers;
        }
        List<Invoker<T>> result = new ArrayList<Invoker<T>>();
        if (thenCondition == null) {
    
    
            logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
            return result;
        }
        for (Invoker<T> invoker : invokers) {
    
    
            // 匹配then条件
            // then: => 的后半部分,也就是 host = 10.20.3.3
            if (matchThen(invoker.getUrl(), url)) {
    
    
                result.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(Constants.RULE_KEY));
            return result;
        }
    } catch (Throwable t) {
    
    
        logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
    }
    return invokers;
}

The ConditionRouter is to filter according to the configured conditions. The following is how the ScriptRouter filters according to the script.

2.2 The second way to achieve routing filtering: ScriptRouter#route(List<Invoker>, URL, Invocation)
// ScriptRouter#route(List<Invoker<T>>, URL, Invocation)
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
    
    
    try {
    
    
        List<Invoker<T>> invokersCopy = new ArrayList<Invoker<T>>(invokers);
        // 脚本引擎engine = new ScriptEngineManager()
        Compilable compilable = (Compilable) engine;
        Bindings bindings = engine.createBindings();
        bindings.put("invokers", invokersCopy);
        bindings.put("invocation", invocation);
        bindings.put("context", RpcContext.getContext());
        // 编译规则
        // rule是在FileRouterFactory#getRouter()中赋值的
        CompiledScript function = compilable.compile(rule);
        Object obj = function.eval(bindings);
        if (obj instanceof Invoker[]) {
    
    
            invokersCopy = Arrays.asList((Invoker<T>[]) obj);
        } else if (obj instanceof Object[]) {
    
    
            invokersCopy = new ArrayList<Invoker<T>>();
            for (Object inv : (Object[]) obj) {
    
    
                invokersCopy.add((Invoker<T>) inv);
            }
        } else {
    
    
            invokersCopy = (List<Invoker<T>>) obj;
        }
        return invokersCopy;
    } catch (ScriptException e) {
    
    
        //fail then ignore rule .invokers.
        logger.error("route error , rule has been ignored. rule: " + rule + ", method:" + invocation.getMethodName() + ", url: " + RpcContext.getContext().getUrl(), e);
        return invokers;
    }
}

FileRouterFactory reads the rule script through File, creates a new script engine ScriptEngineManager to read the above script, and obtains the list of Invokers. The following explains in detail the process of FileRouterFactory reading script.

// FileRouterFactory#getRouter(URL url)
public Router getRouter(URL url) {
    
    
    try {
    
    
        // Transform File URL into Script Route URL, and Load
        // file:///d:/path/to/route.js?router=script ==> script:///d:/path/to/route.js?type=js&rule=<file-content>
        String protocol = url.getParameter(Constants.ROUTER_KEY, ScriptRouterFactory.NAME); // Replace original protocol (maybe 'file') with 'script'
        String type = null; // Use file suffix to config script type, e.g., js, groovy ...
        // 获取url的path属性
        // TODO 这个path是怎么赋值的
        String path = url.getPath();
        if (path != null) {
    
    
            int i = path.lastIndexOf('.');
            if (i > 0) {
    
    
                type = path.substring(i + 1);
            }
        }
        // 获取过滤规则
        String rule = IOUtils.read(new FileReader(new File(url.getAbsolutePath())));

        boolean runtime = url.getParameter(Constants.RUNTIME_KEY, false);
        // 设置rule属性到url对象中
        URL script = url.setProtocol(protocol).addParameter(Constants.TYPE_KEY, type).addParameter(Constants.RUNTIME_KEY, runtime).addParameterAndEncoded(Constants.RULE_KEY, rule);
        // 获取ScriptRouter的实例
        return routerFactory.getRouter(script);
    } catch (IOException e) {
    
    
        throw new IllegalStateException(e.getMessage(), e);
    }
}

Guess you like

Origin blog.csdn.net/fomeiherz/article/details/101031435