注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章。
1. 简介
上一篇文章分析了集群容错的第一部分 – 服务目录 Directory。服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由。上一篇文章关于服务路由相关逻辑没有细致分析,一笔带过了,本篇文章将对此进行详细的分析。首先,先来介绍一下服务目录是什么。服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者。Dubbo 目前提供了三种服务路由实现,分别为条件路由 ConditionRouter、脚本路由 ScriptRouter 和标签路由 TagRouter。其中条件路由是我们最常使用的,标签路由暂未在我所分析的 2.6.4 版本中提供,该实现会在 2.7.0 版本中提供。本篇文章将分析条件路由相关源码,脚本路由和标签路由这里就不分析了。下面进入正题。
2. 源码分析
条件路由规则有两个条件组成,分别用于对服务消费者和提供者进行匹配。比如有这样一条规则:
host = 10.20.153.10 => host = 10.20.153.11
该条规则表示 IP 为 10.20.153.10 的服务消费者只可调用 IP 为 10.20.153.11 机器上的服务,不可调用其他机器上的服务。条件路由规则的格式如下:
[服务消费者匹配条件] => [服务提供者匹配条件]
如果服务消费者匹配条件为空,表示不对服务消费者进行限制。如果服务提供者匹配条件为空,表示对某些服务消费者禁用服务。Dubbo 官方文档对条件路由进行了比较详细的介绍,大家可以参考下,这里就不过多说明了。
条件路由实现类 ConditionRouter 需要对用户配置的路由规则进行解析,得到一系列的条件。然后再根据这些条件对服务进行路由。本章将分两节进行说明,2.1节介绍表达式解析过程。2.2 节介绍服务路由的过程。接下来,我们先从表达式解析过程看起。
2.1 表达式解析
条件路由规则是一条字符串,对于 Dubbo 来说,它并不能直接理解字符串的意思,需要将其解析成内部格式才行。条件表达式的解析过程始于 ConditionRouter 的构造方法,下面一起看一下:
<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ConditionRouter</span><span class="params">(URL url)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.url = url;</span><br><span class="line"> <span class="comment">// 获取 priority 和 force 配置</span></span><br><span class="line"> <span class="keyword">this</span>.priority = url.getParameter(Constants.PRIORITY_KEY, <span class="number">0</span>);</span><br><span class="line"> <span class="keyword">this</span>.force = url.getParameter(Constants.FORCE_KEY, <span class="keyword">false</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 获取路由规则</span></span><br><span class="line"> String rule = url.getParameterAndDecoded(Constants.RULE_KEY);</span><br><span class="line"> <span class="keyword">if</span> (rule == <span class="keyword">null</span> || rule.trim().length() == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"Illegal route rule!"</span>);</span><br><span class="line"> }</span><br><span class="line"> rule = rule.replace(<span class="string">"consumer."</span>, <span class="string">""</span>).replace(<span class="string">"provider."</span>, <span class="string">""</span>);</span><br><span class="line"> <span class="comment">// 定位 => 分隔符</span></span><br><span class="line"> <span class="keyword">int</span> i = rule.indexOf(<span class="string">"=>"</span>);</span><br><span class="line"> <span class="comment">// 分别获取服务消费者和提供者匹配规则</span></span><br><span class="line"> String whenRule = i < <span class="number">0</span> ? <span class="keyword">null</span> : rule.substring(<span class="number">0</span>, i).trim();</span><br><span class="line"> String thenRule = i < <span class="number">0</span> ? rule.trim() : rule.substring(i + <span class="number">2</span>).trim();</span><br><span class="line"> <span class="comment">// 解析服务消费者匹配规则</span></span><br><span class="line"> Map<String, MatchPair> when = </span><br><span class="line"> StringUtils.isBlank(whenRule) || <span class="string">"true"</span>.equals(whenRule) </span><br><span class="line"> ? <span class="keyword">new</span> HashMap<String, MatchPair>() : parseRule(whenRule);</span><br><span class="line"> <span class="comment">// 解析服务提供者匹配规则</span></span><br><span class="line"> Map<String, MatchPair> then = </span><br><span class="line"> StringUtils.isBlank(thenRule) || <span class="string">"false"</span>.equals(thenRule) </span><br><span class="line"> ? <span class="keyword">null</span> : parseRule(thenRule);</span><br><span class="line"> <span class="keyword">this</span>.whenCondition = when;</span><br><span class="line"> <span class="keyword">this</span>.thenCondition = then;</span><br><span class="line"> } <span class="keyword">catch</span> (ParseException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(e.getMessage(), e);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>如上,ConditionRouter 构造方法先是对路由规则做预处理,然后调用 parseRule 方法分别对服务提供者和消费者规则进行解析,最后将解析结果赋值给 whenCondition 和 thenCondition 成员变量。ConditionRouter 构造方法不是很复杂,这里就不多说了。下面我们把重点放在 parseRule 方法上,在详细介绍这个方法之前,我们先来看一个内部类。
<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">MatchPair</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> Set<String> matches = <span class="keyword">new</span> HashSet<String>();</span><br><span class="line"> <span class="keyword">final</span> Set<String> mismatches = <span class="keyword">new</span> HashSet<String>();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>MatchPair 内部包含了两个 Set 型的成员变量,分别用于存放匹配和不匹配的条件。这个类两个成员变量会在 parseRule 方法中被用到,下面来看一下。
<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> Map<String, MatchPair> <span class="title">parseRule</span><span class="params">(String rule)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> ParseException </span>{</span><br><span class="line"> <span class="comment">// 定义条件映射集合</span></span><br><span class="line"> Map<String, MatchPair> condition = <span class="keyword">new</span> HashMap<String, MatchPair>();</span><br><span class="line"> <span class="keyword">if</span> (StringUtils.isBlank(rule)) {</span><br><span class="line"> <span class="keyword">return</span> condition;</span><br><span class="line"> }</span><br><span class="line"> MatchPair pair = <span class="keyword">null</span>;</span><br><span class="line"> Set<String> values = <span class="keyword">null</span>;</span><br><span class="line"> <span class="comment">// 通过正则表达式匹配路由规则,ROUTE_PATTERN = ([&!=,]*)\s*([^&!=,\s]+)</span></span><br><span class="line"> <span class="comment">// 这个表达式看起来不是很好理解,第一个括号内的表达式用于匹配"&", "!", "=" 和 "," 等符号。</span></span><br><span class="line"> <span class="comment">// 第二括号内的用于匹配英文字母,数字等字符。举个例子说明一下:</span></span><br><span class="line"> <span class="comment">// host = 2.2.2.2 & host != 1.1.1.1 & method = hello</span></span><br><span class="line"> <span class="comment">// 匹配结果如下:</span></span><br><span class="line"> <span class="comment">// 括号一 括号二</span></span><br><span class="line"> <span class="comment">// 1. null host</span></span><br><span class="line"> <span class="comment">// 2. = 2.2.2.2</span></span><br><span class="line"> <span class="comment">// 3. & host</span></span><br><span class="line"> <span class="comment">// 4. != 1.1.1.1 </span></span><br><span class="line"> <span class="comment">// 5. & method</span></span><br><span class="line"> <span class="comment">// 6. = hello</span></span><br><span class="line"> <span class="keyword">final</span> Matcher matcher = ROUTE_PATTERN.matcher(rule);</span><br><span class="line"> <span class="keyword">while</span> (matcher.find()) {</span><br><span class="line"> <span class="comment">// 获取括号一内的匹配结果</span></span><br><span class="line"> String separator = matcher.group(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 获取括号二内的匹配结果</span></span><br><span class="line"> String content = matcher.group(<span class="number">2</span>);</span><br><span class="line"> <span class="comment">// 分隔符为空,表示匹配的是表达式的开始部分</span></span><br><span class="line"> <span class="keyword">if</span> (separator == <span class="keyword">null</span> || separator.length() == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 创建 MatchPair 对象</span></span><br><span class="line"> pair = <span class="keyword">new</span> MatchPair();</span><br><span class="line"> <span class="comment">// 存储 <匹配项, MatchPair> 键值对,比如 <host, MatchPair></span></span><br><span class="line"> condition.put(content, pair); </span><br><span class="line"> } </span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 如果分隔符为 &,表明接下来也是一个条件</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">"&"</span>.equals(separator)) {</span><br><span class="line"> <span class="comment">// 尝试从 condition 获取 MatchPair</span></span><br><span class="line"> <span class="keyword">if</span> (condition.get(content) == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 未获取到 MatchPair,重新创建一个,并放入 condition 中</span></span><br><span class="line"> pair = <span class="keyword">new</span> MatchPair();</span><br><span class="line"> condition.put(content, pair);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> pair = condition.get(content);</span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 分隔符为 =</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">"="</span>.equals(separator)) {</span><br><span class="line"> <span class="keyword">if</span> (pair == <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ParseException(<span class="string">"Illegal route rule ..."</span>);</span><br><span class="line"></span><br><span class="line"> values = pair.matches;</span><br><span class="line"> <span class="comment">// 将 content 存入到 MatchPair 的 matches 集合中</span></span><br><span class="line"> values.add(content);</span><br><span class="line"> } </span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 分隔符为 != </span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">"!="</span>.equals(separator)) {</span><br><span class="line"> <span class="keyword">if</span> (pair == <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ParseException(<span class="string">"Illegal route rule ..."</span>);</span><br><span class="line"></span><br><span class="line"> values = pair.mismatches;</span><br><span class="line"> <span class="comment">// 将 content 存入到 MatchPair 的 mismatches 集合中</span></span><br><span class="line"> values.add(content);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 分隔符为 ,</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">","</span>.equals(separator)) {</span><br><span class="line"> <span class="keyword">if</span> (values == <span class="keyword">null</span> || values.isEmpty())</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ParseException(<span class="string">"Illegal route rule ..."</span>);</span><br><span class="line"> <span class="comment">// 将 content 存入到上一步获取到的 values 中,可能是 matches,也可能是 mismatches</span></span><br><span class="line"> values.add(content);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ParseException(<span class="string">"Illegal route rule ..."</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> condition;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>以上就是路由规则的解析逻辑,该逻辑由正则表达式 + 一个 while 循环 + 数个条件分支组成。下面使用一个示例对解析逻辑进行演绎。示例为 host = 2.2.2.2 & host != 1.1.1.1 & method = hello。正则解析结果如下:
现在线程进入 while 循环:
第一次循环:分隔符 separator = null,content = “host”。此时创建 MatchPair 对象,并存入到 condition 中,condition = {“host”: MatchPair@123}
第二次循环:分隔符 separator = “=”,content = “2.2.2.2”,pair = MatchPair@123。此时将 2.2.2.2 放入到 MatchPair@123 对象的 matches 集合中。
第三次循环:分隔符 separator = “&”,content = “host”。host 已存在于 condition 中,因此 pair = MatchPair@123。
第四次循环:分隔符 separator = “!=”,content = “1.1.1.1”,pair = MatchPair@123。此时将 1.1.1.1 放入到 MatchPair@123 对象的 mismatches 集合中。
第五次循环:分隔符 separator = “&”,content = “method”。condition.get(“method”) = null,因此新建一个 MatchPair 对象,并放入到 condition 中。此时 condition = {“host”: MatchPair@123, “method”: MatchPair@ 456}
第六次循环:分隔符 separator = “=”,content = “2.2.2.2”,pair = MatchPair@456。此时将 hello 放入到 MatchPair@456 对象的 matches 集合中。
循环结束,此时 condition 的内容如下:
<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"host"</span>: {</span><br><span class="line"> <span class="attr">"matches"</span>: [<span class="string">"2.2.2.2"</span>],</span><br><span class="line"> <span class="attr">"mismatches"</span>: [<span class="string">"1.1.1.1"</span>]</span><br><span class="line"> },</span><br><span class="line"> <span class="attr">"method"</span>: {</span><br><span class="line"> <span class="attr">"matches"</span>: [<span class="string">"hello"</span>],</span><br><span class="line"> <span class="attr">"mismatches"</span>: []</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>路由规则的解析过程稍微有点复杂,大家可通过 ConditionRouter 的测试类对该逻辑进行测试。并且找一个表达式,对照上面的代码走一遍,加深理解。关于路由规则的解析过程就先到这,我们继续往下看。
2.2 服务路由
服务路由的入口方法是 ConditionRouter 的 router 方法,该方法定义在 Router 接口中。实现代码如下:
<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)</span><br><span class="line"> <span class="keyword">throws</span> RpcException {</span><br><span class="line"> <span class="keyword">if</span> (invokers == <span class="keyword">null</span> || invokers.isEmpty()) {</span><br><span class="line"> <span class="keyword">return</span> invokers;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 先对服务消费者条件进行匹配,如果匹配失败,表明当前消费者 url 不符合匹配规则,</span></span><br><span class="line"> <span class="comment">// 无需进行后续匹配,直接返回 Invoker 列表即可。比如下面的规则:</span></span><br><span class="line"> <span class="comment">// host = 10.20.153.10 => host = 10.0.0.10</span></span><br><span class="line"> <span class="comment">// 这条路由规则希望 IP 为 10.20.153.10 的服务消费者调用 IP 为 10.0.0.10 机器上的服务。</span></span><br><span class="line"> <span class="comment">// 当消费者 ip 为 10.20.153.11 时,matchWhen 返回 false,表明当前这条路由规则不适用于</span></span><br><span class="line"> <span class="comment">// 当前的服务消费者,此时无需再进行后续匹配,直接返回即可。</span></span><br><span class="line"> <span class="keyword">if</span> (!matchWhen(url, invocation)) {</span><br><span class="line"> <span class="keyword">return</span> invokers;</span><br><span class="line"> }</span><br><span class="line"> List<Invoker<T>> result = <span class="keyword">new</span> ArrayList<Invoker<T>>();</span><br><span class="line"> <span class="comment">// 服务提供者匹配条件未配置,表明对指定的服务消费者禁用服务,也就是服务消费者在黑名单中</span></span><br><span class="line"> <span class="keyword">if</span> (thenCondition == <span class="keyword">null</span>) {</span><br><span class="line"> logger.warn(<span class="string">"The current consumer in the service blacklist..."</span>);</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 这里可以简单的把 Invoker 理解为服务提供者,现在使用服务提供者匹配规则对 </span></span><br><span class="line"> <span class="comment">// Invoker 列表进行匹配</span></span><br><span class="line"> <span class="keyword">for</span> (Invoker<T> invoker : invokers) {</span><br><span class="line"> <span class="comment">// 匹配成功,表明当前 Invoker 符合服务提供者匹配规则。</span></span><br><span class="line"> <span class="comment">// 此时将 Invoker 添加到 result 列表中</span></span><br><span class="line"> <span class="keyword">if</span> (matchThen(invoker.getUrl(), url)) {</span><br><span class="line"> result.add(invoker);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 返回匹配结果,如果 result 为空列表,且 force = true,表示强制返回空列表,</span></span><br><span class="line"> <span class="comment">// 否则路由结果为空的路由规则将自动失效</span></span><br><span class="line"> <span class="keyword">if</span> (!result.isEmpty()) {</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (force) {</span><br><span class="line"> logger.warn(<span class="string">"The route result is empty and force execute ..."</span>);</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable t) {</span><br><span class="line"> logger.error(<span class="string">"Failed to execute condition router rule: ..."</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 原样返回,此时 force = false,表示该条路由规则失效</span></span><br><span class="line"> <span class="keyword">return</span> invokers;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>router 方法先是调用 matchWhen 对服务消费者进行匹配,如果匹配失败,直接返回 Invoker 列表。如果匹配成功,再对服务提供者进行匹配,匹配逻辑封装在了 matchThen 方法中。下面来看一下这两个方法的逻辑:
<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">matchWhen</span><span class="params">(URL url, Invocation invocation)</span> </span>{</span><br><span class="line"> <span class="comment">// 服务消费者条件为 null 或空,均返回 true,比如:</span></span><br><span class="line"> <span class="comment">// => host != 172.22.3.91</span></span><br><span class="line"> <span class="comment">// 表示所有的服务消费者都不得调用 IP 为 172.22.3.91 的机器上的服务</span></span><br><span class="line"> <span class="keyword">return</span> whenCondition == <span class="keyword">null</span> || whenCondition.isEmpty() </span><br><span class="line"> || matchCondition(whenCondition, url, <span class="keyword">null</span>, invocation); <span class="comment">// 进行条件匹配</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">matchThen</span><span class="params">(URL url, URL param)</span> </span>{</span><br><span class="line"> <span class="comment">// 服务提供者条件为 null 或空,表示禁用服务</span></span><br><span class="line"> <span class="keyword">return</span> !(thenCondition == <span class="keyword">null</span> || thenCondition.isEmpty()) </span><br><span class="line"> && matchCondition(thenCondition, url, param, <span class="keyword">null</span>); <span class="comment">// 进行条件匹配</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table>这两个方法长的有点像,不过逻辑上还是有差别的,大家注意看。这两个方法均调用了 matchCondition 方法,不过它们所传入的参数是不同的,这个需要特别注意。不然后面的逻辑不好弄懂。下面我们对这几个参数进行溯源。matchWhen 方法向 matchCondition 方法传入的参数为 [whenCondition, url, null, invocation],第一个参数 whenCondition 为服务消费者匹配条件,这个前面分析过。第二个参数 url 源自 route 方法的参数列表,该参数由外部类调用 route 方法时传入。有代码为证,如下:
<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> List<Invoker<T>> route(List<Invoker<T>> invokers, String method) {</span><br><span class="line"> Invocation invocation = <span class="keyword">new</span> RpcInvocation(method, <span class="keyword">new</span> Class<?>[<span class="number">0</span>], <span class="keyword">new</span> Object[<span class="number">0</span>]);</span><br><span class="line"> List<Router> routers = getRouters();</span><br><span class="line"> <span class="keyword">if</span> (routers != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">for</span> (Router router : routers) {</span><br><span class="line"> <span class="keyword">if</span> (router.getUrl() != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 注意第二个参数</span></span><br><span class="line"> invokers = router.route(invokers, getConsumerUrl(), invocation);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> invokers;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>上面这段代码来自 RegistryDirectory,第二个参数表示的是服务消费者 url。matchCondition 的 invocation 参数也是从这里传入的。
接下来再来看看 matchThen 向 matchCondition 方法传入的参数 [thenCondition, url, param, null]。第一个参数不用解释了。第二个和第三个参数来自 matchThen 方法的参数列表,这两个参数分别为服务提供者 url 和服务消费者 url。搞清楚这些参数来源后,接下俩就可以分析 matchCondition 了。
<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">matchCondition</span><span class="params">(Map<String, MatchPair> condition, URL url, URL param, Invocation invocation)</span> </span>{</span><br><span class="line"> <span class="comment">// 将服务提供者或消费者 url 转成 Map</span></span><br><span class="line"> Map<String, String> sample = url.toMap();</span><br><span class="line"> <span class="keyword">boolean</span> result = <span class="keyword">false</span>;</span><br><span class="line"> <span class="comment">// 遍历 condition 列表</span></span><br><span class="line"> <span class="keyword">for</span> (Map.Entry<String, MatchPair> matchPair : condition.entrySet()) {</span><br><span class="line"> <span class="comment">// 获取匹配项名称,比如 host、method 等</span></span><br><span class="line"> String key = matchPair.getKey();</span><br><span class="line"> String sampleValue;</span><br><span class="line"> <span class="comment">// 如果 invocation 不为空,且 key 为 mehtod(s),表示进行方法匹配</span></span><br><span class="line"> <span class="keyword">if</span> (invocation != <span class="keyword">null</span> && (Constants.METHOD_KEY.equals(key) || Constants.METHODS_KEY.equals(key))) {</span><br><span class="line"> <span class="comment">// 从 invocation 获取调用方法名称</span></span><br><span class="line"> sampleValue = invocation.getMethodName();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 从服务提供者或消费者 url 中获取指定字段值,比如 host、application 等</span></span><br><span class="line"> sampleValue = sample.get(key);</span><br><span class="line"> <span class="keyword">if</span> (sampleValue == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 尝试通过 default.xxx 获取相应的值</span></span><br><span class="line"> sampleValue = sample.get(Constants.DEFAULT_KEY_PREFIX + key);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// --------------------✨ 分割线 ✨-------------------- //</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">if</span> (sampleValue != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 调用 MatchPair 的 isMatch 方法进行匹配</span></span><br><span class="line"> <span class="keyword">if</span> (!matchPair.getValue().isMatch(sampleValue, param)) {</span><br><span class="line"> <span class="comment">// 只要有一个规则匹配失败,立即返回 false 结束方法逻辑</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> result = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// sampleValue 为空,表明服务提供者或消费者 url 中不包含相关字段。此时如果 </span></span><br><span class="line"> <span class="comment">// MatchPair 的 matches 不为空,表示匹配失败,返回 false。比如我们有这样</span></span><br><span class="line"> <span class="comment">// 一条匹配条件 loadbalance = random,假设 url 中并不包含 loadbalance 参数,</span></span><br><span class="line"> <span class="comment">// 此时 sampleValue = null。既然路由规则里限制了 loadbalance = random,</span></span><br><span class="line"> <span class="comment">// 但 sampleValue = null,明显不符合规则,因此返回 false</span></span><br><span class="line"> <span class="keyword">if</span> (!matchPair.getValue().matches.isEmpty()) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> result = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>如上,matchCondition 方法看起来有点复杂,这里简单缕缕。分割线以上的代码实际上主要是用于获取 sampleValue 的值,分割线以下才是进行条件匹配。条件匹配调用的逻辑封装在 isMatch 中,代码如下:
<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">isMatch</span><span class="params">(String value, URL param)</span> </span>{</span><br><span class="line"> <span class="comment">// 情况一:matches 非空,mismatches 为空</span></span><br><span class="line"> <span class="keyword">if</span> (!matches.isEmpty() && mismatches.isEmpty()) {</span><br><span class="line"> <span class="comment">// 遍历 matches 集合,检测入参 value 是否能被 matches 集合元素匹配到。</span></span><br><span class="line"> <span class="comment">// 举个例子,如果 value = 10.20.153.11,matches = [10.20.153.*],</span></span><br><span class="line"> <span class="comment">// 此时 isMatchGlobPattern 方法返回 true</span></span><br><span class="line"> <span class="keyword">for</span> (String match : matches) {</span><br><span class="line"> <span class="keyword">if</span> (UrlUtils.isMatchGlobPattern(match, value, param)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 如果所有匹配项都无法匹配到入参,则返回 false</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 情况二:matches 为空,mismatches 非空</span></span><br><span class="line"> <span class="keyword">if</span> (!mismatches.isEmpty() && matches.isEmpty()) {</span><br><span class="line"> <span class="keyword">for</span> (String mismatch : mismatches) {</span><br><span class="line"> <span class="comment">// 只要入参被 mismatches 集合中的任意一个元素匹配到,就返回 false</span></span><br><span class="line"> <span class="keyword">if</span> (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// mismatches 集合中所有元素都无法匹配到入参,此时返回 true</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 情况三:matches 非空,mismatches 非空</span></span><br><span class="line"> <span class="keyword">if</span> (!matches.isEmpty() && !mismatches.isEmpty()) {</span><br><span class="line"> <span class="comment">// matches 和 mismatches 均为非空,此时优先使用 mismatches 集合元素对入参进行匹配。</span></span><br><span class="line"> <span class="comment">// 只要 mismatches 集合中任意一个元素与入参匹配成功,就立即返回 false,结束方法逻辑</span></span><br><span class="line"> <span class="keyword">for</span> (String mismatch : mismatches) {</span><br><span class="line"> <span class="keyword">if</span> (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// mismatches 集合元素无法匹配到入参,此时使用 matches 继续匹配</span></span><br><span class="line"> <span class="keyword">for</span> (String match : matches) {</span><br><span class="line"> <span class="comment">// 只要 matches 集合中任意一个元素与入参匹配成功,就立即返回 true</span></span><br><span class="line"> <span class="keyword">if</span> (UrlUtils.isMatchGlobPattern(match, value, param)) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 情况四:matches 和 mismatches 均为空,此时返回 false</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>isMatch 方法逻辑比较清晰,由三个条件分支组成,用于处理四种情况。这里对四种情况下的匹配逻辑进行简单的总结,如下:
| 条件 | 动作 | |
|---|---|---|
| 情况一 | matches 非空,mismatches 为空 | 遍历 matches 集合元素,并与入参进行匹配。只要有一个元素成功匹配入参,即可返回 true。若全部失配,则返回 false。 |
| 情况二 | matches 为空,mismatches 非空 | 遍历 mismatches 集合元素,并与入参进行匹配。只要有一个元素成功匹配入参,立即 false。若全部失配,则返回 true。 |
| 情况三 | matches 非空,mismatches 非空 | 优先使用 mismatches 集合元素对入参进行匹配,只要任一元素与入参匹配成功,就立即返回 false,结束方法逻辑。否则再使用 matches 中的集合元素进行匹配,只要有任意一个元素匹配成功,即可返回 true。若全部失配,则返回 false |
| 情况四 | matches 为空,mismatches 为空 | 直接返回 false |
isMatch 方法逻辑不是很难理解,大家自己再看看。下面继续分析 isMatchGlobPattern 方法。
<table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isMatchGlobPattern</span><span class="params">(String pattern, String value, URL param)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (param != <span class="keyword">null</span> && pattern.startsWith(<span class="string">"$"</span>)) {</span><br><span class="line"> <span class="comment">// 引用服务消费者参数,param 参数为服务消费者 url</span></span><br><span class="line"> pattern = param.getRawParameter(pattern.substring(<span class="number">1</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 调用重载方法继续比较</span></span><br><span class="line"> <span class="keyword">return</span> isMatchGlobPattern(pattern, value);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isMatchGlobPattern</span><span class="params">(String pattern, String value)</span> </span>{</span><br><span class="line"> <span class="comment">// 对 * 通配符提供支持</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="string">"*"</span>.equals(pattern))</span><br><span class="line"> <span class="comment">// 匹配规则为通配符 *,直接返回 true 即可</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">if</span> ((pattern == <span class="keyword">null</span> || pattern.length() == <span class="number">0</span>)</span><br><span class="line"> && (value == <span class="keyword">null</span> || value.length() == <span class="number">0</span>))</span><br><span class="line"> <span class="comment">// pattern 和 value 均为空,此时可认为两者相等,返回 true</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">if</span> ((pattern == <span class="keyword">null</span> || pattern.length() == <span class="number">0</span>)</span><br><span class="line"> || (value == <span class="keyword">null</span> || value.length() == <span class="number">0</span>))</span><br><span class="line"> <span class="comment">// pattern 和 value 其中有一个为空,两者不相等,返回 false</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 查找 * 通配符位置</span></span><br><span class="line"> <span class="keyword">int</span> i = pattern.lastIndexOf(<span class="string">'*'</span>);</span><br><span class="line"> <span class="keyword">if</span> (i == -<span class="number">1</span>) {</span><br><span class="line"> <span class="comment">// 匹配规则中不包含通配符,此时直接比较 value 和 pattern 是否相等即可,并返回比较结果</span></span><br><span class="line"> <span class="keyword">return</span> value.equals(pattern);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 通配符 "*" 在匹配规则尾部,比如 10.0.21.*</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (i == pattern.length() - <span class="number">1</span>) {</span><br><span class="line"> <span class="comment">// 检测 value 是否以不含通配符的匹配规则开头,并返回结果。比如:</span></span><br><span class="line"> <span class="comment">// pattern = 10.0.21.*,value = 10.0.21.12,此时返回 true</span></span><br><span class="line"> <span class="keyword">return</span> value.startsWith(pattern.substring(<span class="number">0</span>, i));</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 通配符 "*" 在匹配规则头部</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (i == <span class="number">0</span>) {</span><br><span class="line"> <span class="comment">// 检测 value 是否以不含通配符的匹配规则结尾,并返回结果</span></span><br><span class="line"> <span class="keyword">return</span> value.endsWith(pattern.substring(i + <span class="number">1</span>));</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 通配符 "*" 在匹配规则中间位置</span></span><br><span class="line"> <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 通过通配符将 pattern 分成两半,得到 prefix 和 suffix</span></span><br><span class="line"> String prefix = pattern.substring(<span class="number">0</span>, i);</span><br><span class="line"> String suffix = pattern.substring(i + <span class="number">1</span>);</span><br><span class="line"> <span class="comment">// 检测 value 是否以 prefix 变量开头,且以 suffix 变量结尾,并返回结果</span></span><br><span class="line"> <span class="keyword">return</span> value.startsWith(prefix) && value.endsWith(suffix);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table>以上就是 isMatchGlobPattern 两个重载方法的全部逻辑,这两个方法分别对普通的匹配,以及”引用消费者参数“和通配符匹配做了支持。这两个方法的逻辑并不是很复杂,而且我也在代码上进行了比较详细的注释,大家自己看看吧,就不多说了。
3. 总结
本篇文章对条件路由的表达式解析和服务路由过程进行了较为细致的分析。总的来说,条件路由的代码还是有一些复杂的,需要静下心来看。在阅读条件路由代码的过程中,要多调试。一般的框架都会有单元测试,Dubbo 也不例外,因此大家可以直接通过 ConditionRouterTest 对条件路由进行调试,无需自己手写测试用例。
好了,关于条件路由就先分析到这,谢谢阅读。
- 本文链接: https://www.tianxiaobo.com/2018/11/20/Dubbo-源码分析-集群容错之-Router/
- 版权声明: 本博客所有文章除特别声明外,均采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 许可协议。转载请注明出处!
田小波
勿在浮沙筑高台
本文转自 http://www.tianxiaobo.com/2018/11/20/Dubbo-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E9%9B%86%E7%BE%A4%E5%AE%B9%E9%94%99%E4%B9%8B-Router/,如有侵权,请联系删除。

