参考路径:https://www.cnblogs.com/ytao-blog/p/12704763.html
Dubbo 路由机制是在服务间的调用时,通过将服务提供者按照设定的路由规则来决定调用哪一个具体的服务.

路由服务结构

Dubbo 实现路由都是通过实现 RouterFactory 接口
image.png
路由实现工厂类是在 router 包下
image.png
由于 RouterFactory 是 SPI 接口,同时在获取路由 RouterFactory#getRouter 方法上有 @Adaptive(“protocol”) 注解,所以在获取路由的时候会动态调用需要的工厂类。
image.png
可以看到 getRouter 方法返回的是一个 Router 接口,该接口信息如下
image.png
其中 Router#route 是服务路由的入口,对于不同类型的路由工厂,有特定的 Router 实现类。
image.png
以上就是通过解析 URL,获取到具体的 Router,通过调用 Router#router 过滤出符合当前路由规则的 invokers。

服务路由实现(ConditionRouter)

只对ConditionRouter 条件路由进行具体分析

条件路由参数规则

在分析条件路由前,先了解条件路由的参数配置,官方文档如下:
image.png
条件路由规则内容如下:
Dubbo 路由机制 - 图7
========================================================================

条件路由实现分析(源码分析)

分析路由实现,主要分析工厂类的 xxxRouterFactory#getRouter 和 xxxRouter#route 方法。

ConditionRouterFactory#getRouter

ConditionRouterFactory 中通过创建 ConditionRouter 对象来初始化解析相关参数配置。
Dubbo 路由机制 - 图8
在 ConditionRouter 构造函数中,从 URL 里获取 rule 的字符串格式的规则,解析规则在 ConditionRouter#init 初始化方法中。

  1. public void init(String rule) {
  2. try {
  3. if (rule == null || rule.trim().length() == 0) {
  4. throw new IllegalArgumentException("Illegal route rule!");
  5. }
  6. // 去掉 consumer. 和 provider. 的标识
  7. rule = rule.replace("consumer.", "").replace("provider.", "");
  8. // 获取 消费者匹配条件 和 提供者地址匹配条件 的分隔符
  9. int i = rule.indexOf("=>");
  10. // 消费者匹配条件
  11. String whenRule = i < 0 ? null : rule.substring(0, i).trim();
  12. // 提供者地址匹配条件
  13. String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
  14. // 解析消费者路由规则
  15. Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
  16. // 解析提供者路由规则
  17. Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
  18. // NOTE: It should be determined on the business level whether the `When condition` can be empty or not.
  19. this.whenCondition = when;
  20. this.thenCondition = then;
  21. } catch (ParseException e) {
  22. throw new IllegalStateException(e.getMessage(), e);
  23. }
  24. }

以路由规则字符串中的=>为分隔符,将消费者匹配条件和提供者匹配条件分割,解析两个路由规则后,赋值给当前对象的变量。
调用 parseRule 方法来解析消费者和服务者路由规则。

  1. // 正则验证路由规则
  2. protected static final Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)");
  3. private static Map<String, MatchPair> parseRule(String rule)
  4. throws ParseException {
  5. /**
  6. * 条件变量和条件变量值的映射关系
  7. * 比如 host => 127.0.0.1 则保存着 host 和 127.0.0.1 的映射关系
  8. */
  9. Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
  10. if (StringUtils.isBlank(rule)) {
  11. return condition;
  12. }
  13. // Key-Value pair, stores both match and mismatch conditions
  14. MatchPair pair = null;
  15. // Multiple values
  16. Set<String> values = null;
  17. final Matcher matcher = ROUTE_PATTERN.matcher(rule);
  18. while (matcher.find()) {
  19. // 获取正则前部分匹配(第一个括号)的内容
  20. String separator = matcher.group(1);
  21. // 获取正则后部分匹配(第二个括号)的内容
  22. String content = matcher.group(2);
  23. // 如果获取前部分为空,则表示规则开始位置,则当前 content 必为条件变量
  24. if (StringUtils.isEmpty(separator)) {
  25. pair = new MatchPair();
  26. condition.put(content, pair);
  27. }
  28. // 如果分隔符是 &,则 content 为条件变量
  29. else if ("&".equals(separator)) {
  30. // 当前 content 是条件变量,用来做映射集合的 key 的,如果没有则添加一个元素
  31. if (condition.get(content) == null) {
  32. pair = new MatchPair();
  33. condition.put(content, pair);
  34. } else {
  35. pair = condition.get(content);
  36. }
  37. }
  38. // 如果当前分割符是 = ,则当前 content 为条件变量值
  39. else if ("=".equals(separator)) {
  40. if (pair == null) {
  41. throw new ParseException("Illegal route rule \""
  42. + rule + "\", The error char '" + separator
  43. + "' at index " + matcher.start() + " before \""
  44. + content + "\".", matcher.start());
  45. }
  46. // 由于 pair 还没有被重新初始化,所以还是上一个条件变量的对象,所以可以将当前条件变量值在引用对象上赋值
  47. values = pair.matches;
  48. values.add(content);
  49. }
  50. // 如果当前分割符是 = ,则当前 content 也是条件变量值
  51. else if ("!=".equals(separator)) {
  52. if (pair == null) {
  53. throw new ParseException("Illegal route rule \""
  54. + rule + "\", The error char '" + separator
  55. + "' at index " + matcher.start() + " before \""
  56. + content + "\".", matcher.start());
  57. }
  58. // 与 = 时同理
  59. values = pair.mismatches;
  60. values.add(content);
  61. }
  62. // 如果当前分割符为 ',',则当前 content 也为条件变量值
  63. else if (",".equals(separator)) { // Should be separated by ','
  64. if (values == null || values.isEmpty()) {
  65. throw new ParseException("Illegal route rule \""
  66. + rule + "\", The error char '" + separator
  67. + "' at index " + matcher.start() + " before \""
  68. + content + "\".", matcher.start());
  69. }
  70. // 直接向条件变量值集合中添加数据
  71. values.add(content);
  72. } else {
  73. throw new ParseException("Illegal route rule \"" + rule
  74. + "\", The error char '" + separator + "' at index "
  75. + matcher.start() + " before \"" + content + "\".", matcher.start());
  76. }
  77. }
  78. return condition;
  79. }

上面就是解析条件路由规则的过程,条件变量的值都保存在 MatchPair 中的 matches、mismatches 属性中,=和,的条件变量值放在可以匹配的 matches 中,!=的条件变量值放在不可匹配路由规则的 mismatches 中。赋值过程中,代码还是比较优雅。
Dubbo 路由机制 - 图9

ConditionRouter#route

Router#route的作用就是匹配出符合路由规则的 Invoker 集合。

  1. // 在初始化中进行被复制的变量
  2. // 消费者条件匹配规则
  3. protected Map<String, MatchPair> whenCondition;
  4. // 提供者条件匹配规则
  5. protected Map<String, MatchPair> thenCondition;
  6. public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
  7. throws RpcException {
  8. if (!enabled) {
  9. return invokers;
  10. }
  11. // 验证 invokers 是否为空
  12. if (CollectionUtils.isEmpty(invokers)) {
  13. return invokers;
  14. }
  15. try {
  16. // 校验消费者是否有规则匹配,如果没有则返回传入的 Invoker
  17. if (!matchWhen(url, invocation)) {
  18. return invokers;
  19. }
  20. List<Invoker<T>> result = new ArrayList<Invoker<T>>();
  21. if (thenCondition == null) {
  22. logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
  23. return result;
  24. }
  25. // 遍历传入的 invokers,匹配提供者是否有规则匹配
  26. for (Invoker<T> invoker : invokers) {
  27. if (matchThen(invoker.getUrl(), url)) {
  28. result.add(invoker);
  29. }
  30. }
  31. // 如果 result 不为空,或当前对象 force=true 则返回 result 的 Invoker 列表
  32. if (!result.isEmpty()) {
  33. return result;
  34. } else if (force) {
  35. logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(RULE_KEY));
  36. return result;
  37. }
  38. } catch (Throwable t) {
  39. logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
  40. }
  41. return invokers;
  42. }

上面代码可以看到,只要消费者没有匹配的规则或提供者没有匹配的规则及 force=false 时,不会返回传入的参数的 Invoker。
匹配消费者路由规则和提供者路由规则方法是 matchWhen 和 matchThen
Dubbo 路由机制 - 图10这两个匹配方法都是调用同一个方法 matchCondition 实现的。将消费者或提供者 URL 转为 Map,然后与 whenCondition 或 thenCondition 进行匹配。
匹配过程中,如果 key (即 sampleValue 值)存在对应的值,则通过 MatchPair#isMatch 方法再进行匹配。

  1. private boolean isMatch(String value, URL param) {
  2. // 存在可匹配的规则,不存在不可匹配的规则
  3. if (!matches.isEmpty() && mismatches.isEmpty()) {
  4. // 不可匹配的规则列表为空时,只要可匹配的规则匹配上,直接返回 true
  5. for (String match : matches) {
  6. if (UrlUtils.isMatchGlobPattern(match, value, param)) {
  7. return true;
  8. }
  9. }
  10. return false;
  11. }
  12. // 存在不可匹配的规则,不存在可匹配的规则
  13. if (!mismatches.isEmpty() && matches.isEmpty()) {
  14. // 不可匹配的规则列表中存在,则返回false
  15. for (String mismatch : mismatches) {
  16. if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
  17. return false;
  18. }
  19. }
  20. return true;
  21. }
  22. // 存在可匹配的规则,也存在不可匹配的规则
  23. if (!matches.isEmpty() && !mismatches.isEmpty()) {
  24. // 都不为空时,不可匹配的规则列表中存在,则返回 false
  25. for (String mismatch : mismatches) {
  26. if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
  27. return false;
  28. }
  29. }
  30. for (String match : matches) {
  31. if (UrlUtils.isMatchGlobPattern(match, value, param)) {
  32. return true;
  33. }
  34. }
  35. return false;
  36. }
  37. // 最后剩下的是 可匹配规则和不可匹配规则都为空时
  38. return false;
  39. }

匹配过程再调用 UrlUtils#isMatchGlobPattern 实现

  1. public static boolean isMatchGlobPattern(String pattern, String value, URL param) {
  2. // 如果以 $ 开头,则获取 URL 中对应的值
  3. if (param != null && pattern.startsWith("$")) {
  4. pattern = param.getRawParameter(pattern.substring(1));
  5. }
  6. //
  7. return isMatchGlobPattern(pattern, value);
  8. }
  9. public static boolean isMatchGlobPattern(String pattern, String value) {
  10. if ("*".equals(pattern)) {
  11. return true;
  12. }
  13. if (StringUtils.isEmpty(pattern) && StringUtils.isEmpty(value)) {
  14. return true;
  15. }
  16. if (StringUtils.isEmpty(pattern) || StringUtils.isEmpty(value)) {
  17. return false;
  18. }
  19. // 获取通配符位置
  20. int i = pattern.lastIndexOf('*');
  21. // 如果value中没有 "*" 通配符,则整个字符串值匹配
  22. if (i == -1) {
  23. return value.equals(pattern);
  24. }
  25. // 如果 "*" 在最后面,则匹配字符串 "*" 之前的字符串即可
  26. else if (i == pattern.length() - 1) {
  27. return value.startsWith(pattern.substring(0, i));
  28. }
  29. // 如果 "*" 在最前面,则匹配字符串 "*" 之后的字符串即可
  30. else if (i == 0) {
  31. return value.endsWith(pattern.substring(i + 1));
  32. }
  33. // 如果 "*" 不在字符串两端,则同时匹配字符串 "*" 左右两边的字符串
  34. else {
  35. String prefix = pattern.substring(0, i);
  36. String suffix = pattern.substring(i + 1);
  37. return value.startsWith(prefix) && value.endsWith(suffix);
  38. }
  39. }