Dubbo服务路由源码阅读

483 阅读7分钟

什么是服务路由

​ 服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者。Dubbo 目前提供了三种服务路由实现,分别为:

  • 条件路由:ConditionRouter (最常使用)
  • 脚本路由:ScriptRouter
  • 标签路由:TagRouter

路由规则

应用粒度路由规则:

# application代表是应用粒度
scope: application
# 指定当路由结果为空时,是否强制执行。如果不强制执行,路由结果为空则路由规则自动失效,不使用路由。缺省为 false,不强制执行。
force: true
# 指定是否在每次调用时执行路由规则。若为 false 则表示只在提供者地址列表变更时会预先执行路由规则,并将路由结果进行缓存,消费者调用时直接从缓存中获取路由结果。
# 若为 true 则表示每次调用都要重新计算路由规则,其将会直接影响调用的性能。缺省为 falseruntime: true
enabled: true
# 用于设置路由规则的优先级,数字越大,优先级越高,越靠前执行。缺省为 0priority: 0
# 路由规则由于的服务名称
key: governance-conditionrouter-consumer
# 规则体。定义具体的路由规则内容,由 1 到任意多条规则组成。
conditions:
# app1 的消费者只能消费所有端口为 20880 的服务提供实例
 - application=app1 => address=*:20880
# app2 的消费者只能消费所有端口为 20881 的服务提供实例
 - application=app2 => address=*:20881

服务粒度路由规则:

scope: service
force: true
runtime: true
enabled: true
# 指定的消费接口名称
key: org.apache.dubbo.samples.governance.api.DemoService
conditions:
# DemoService 的 sayHello 方法只能消费所有端口为 20880 的服务提供者实例
 - method=sayHello => address=*:20880
# DemoService 的 sayHi 方法只能消费所有端口为 20881 的服务提供者实例
 - method=sayHi => address=*:20881

格式

7f1edf897eba4c9da6c723f0ff61c698-1.jpg

实例

  1. 黑名单

    host = 10.20.153.10,10.20.153.11 =>

    IP 为 10.20.153.10 与 10.20.153.11 的主机将被禁用。

  2. 白名单

    host != 10.20.153.10,10.20.153.11 =>

    禁用 IP 为 10.20.153.10 与 10.20.153.11 之外的所有主机。

  3. 只暴露一部分的提供者

    => host = 172.22.3.1*,172.22.3.2*

    消费者只可访问 IP 为 172.22.3.1与 172.22.3.2的提供者主机。

  4. 为重要应用提供额外的机器

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

    应用名称不为 kylin 的应用不能访问 172.22.3.95 与 172.22.3.96 两台提供者主机。即只有名称为 kylin 的消费者可以访问 172.22.3.95 与 172.22.3.96 两台提供者主机。当然,kylin 还可以访问其它提供者主机,而其它消费者也可以访问 172.22.3.95 与 172.22.3.96 之外的所有提供者主机。

  5. 读写分离

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

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

    find、list、get、is 开头的消费者方法会被路由到 172.22.3.94、172.22.3.95 与 172.22.3.96三台提供者主机,而其它方法则会被路由到 172.22.3.97 与 172.22.3.98 两台提供者主机

  6. 前后台分离

    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

    应用名称的 bops 的消费者会被路由到 172.22.3.91、172.22.3.92 与 172.22.3.93 三台提供者主机,而其它消费者则会被路由到 172.22.3.94、172.22.3.95 与 172.22.3.96 三台提供者。

  7. 隔离不同机房网段

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

    不是 172.22.3 网段的消费者是不能访问 172.22.3 网段的提供者的。即只有 172.22.3 网段的消费者才可访问 172.22.3 网段的提供者。当然,172.22.3 网段的消费者也可访问其它网段的提供者。

  8. 只访问本机的服务

    => host = $host

    $host 表示获取消费者请求中的消费者主机 IP。故该规则就表示消费者只能访问本机的服务。

添加激活 RouterFactory 到 Directory

RegistryProtocol.java

image-20210123215413536

RegistryDirectory.java

public void buildRouterChain(URL url) {
   // 解析URL中的路由信息
    this.setRouterChain(RouterChain.buildChain(url));
}

RouterChain.java

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

private RouterChain(URL url) {
    // 获取RouterFactory的所有激活类实例
    List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class)
            .getActivateExtension(url, (String[]) null);

    List<Router> routers = extensionFactories.stream()  // 生成一个Stream,元素是factory
            .map(factory -> factory.getRouter(url)) // 将Stream中的factory映射为该factory生成的router
            .collect(Collectors.toList());  // 将Stream再变为List
    // 将创建的router列表初始化到Directory的routerChain中
    initWithRouters(routers);
}

读取 注册中心 中的路由规则

image-20210125144529894

image-20210125144642057

image-20210125144712347

image-20210125150119934

private List<URL> toUrlsWithEmpty(URL consumer, String path, List<String> providers) {
    // 获取到当前分类节点下的所有非空url
    List<URL> urls = toUrlsWithoutEmpty(consumer, providers);
    // 若当前分类节点下没有子节点,则系统为其创建一个形如 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)) {
    // 遍历所有子节点
    for (String provider : providers) {
      // 解码
      provider = URL.decode(provider);
      // 处理子节点名称为url形式的情况
      if (provider.contains(PROTOCOL_SEPARATOR)) {
        URL url = URL.valueOf(provider);
        if (UrlUtils.isMatch(consumer, url)) {
          urls.add(url);
        }
      }
    }
  }
  return urls;
}

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 "";
            }));
    // 处理configurators分类节点情况
    List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
    this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);

    // 处理routers分类节点情况
    List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
    toRouters(routerURLs)  // 返回Optional
            // 若Optional中封装的对象不空,则调用Lambda的实例方法引用addRouters(),即将这个路由添加到directory
            .ifPresent(this::addRouters);

    // providers
    // 处理providers分类节点情况
    List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
    // 从zk中获取最新的提供者更新到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) {
            // 若当前url以empty作为protocol,则说明其不是一个有效url
            if (EMPTY_PROTOCOL.equals(url.getProtocol())) {
                continue;
            }
            // 获取到url中的router属性值,当前值为condition,表示这是一个条件路由
            String routerType = url.getParameter(ROUTER_KEY);
            if (routerType != null && routerType.length() > 0) {
                // 将路由类型作为url的protocol,即当前的url变为了condition://...
                url = url.setProtocol(routerType);
            }
            try {
                // 创建路由
                Router router = ROUTER_FACTORY.getRouter(url);
                // 将该路由实例记录下来
                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);
    }

路由规则生效

发生在调用方法的时候

InvokerInvocationHandler.java

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

    public InvokerInvocationHandler(Invoker<?> handler) {
        this.invoker = handler;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取到RPC远程调用的方法名
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        // 若当前方法为Object的方法,则为本地方法调用
        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]);
        }
        // 远程调用
        return invoker.invoke(new RpcInvocation(method, args)).recreate();
    }
}

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  远程调用
        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 {  // 远程调用
            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;
}

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);
    }
    // 路由:根据路由规则过滤掉不可用的invoker,返回剩下的可用的invoker
  	// 跟踪这个list
    List<Invoker<T>> invokers = list(invocation);
    // 获取负载均衡策略
    LoadBalance loadbalance = initLoadBalance(invokers, invocation);
    RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
    // 调用具体容错策略的doInvoke()
    return doInvoke(invocation, invokers, loadbalance);
}

image-20210125155320715

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.
        // 将路由规则应用到所有invokers中,过滤掉不满足规则的invoker
        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;
}

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;
}

ConditionRouter.java

public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
        throws RpcException {
    // 若路由规则中的enabled属性为false(即当前路由规则等同于没有),则返回所有invoker
    if (!enabled) {
        return invokers;
    }

    // 若没有提供者,则返回空
    if (CollectionUtils.isEmpty(invokers)) {
        return invokers;
    }
    try {
        // matcherWhen()方法用于判断当前的消费者是否与箭头(=>)前面部分相匹配,
        // 若不匹配,则说明当前规则对当前消费者不起作用,即无需通过路由过滤,直接返回所有invoker
        if (!matchWhen(url, invocation)) {
            return invokers;
        }
        // 代码走到这里,说明当前消费者已经与箭头(=>)前面部分匹配上了,现在看箭头(=>)后面的情况了
        // 该result集合将来用于存放通过路由过滤的invoker
        List<Invoker<T>> result = new ArrayList<Invoker<T>>();
        // thenCondition 中存放的是箭头(=>)后面部分,若其为null,
        // 则说明当前规则为黑/白名单,即所有invoker均不可用
        if (thenCondition == null) {
            logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
            return result;
        }
        // 代码走到这里,说明当前消费者已经匹配上了箭头(=>)前面,且后面也不为空,
        // 此时就需要遍历所有invoker,去尝试着匹配箭头(=>)后的规则条件
        for (Invoker<T> invoker : invokers) {
            // matchThen() 判断是否与箭头(=>)后的条件相匹配
            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(RULE_KEY));
            return result;
        }
    } catch (Throwable t) {
        logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
    }
    return invokers;
}