| <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Apache Dubbo – Dubbo 源代码分析</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/</link><description>Recent content in Dubbo 源代码分析 on Apache Dubbo</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><atom:link href="https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/index.xml" rel="self" type="application/rss+xml"/><item><title>Docsv2.7: Dubbo SPI</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/dubbo-spi/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/dubbo-spi/</guid><description> |
| <h2 id="1简介">1.简介</h2> |
| <p>SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。</p> |
| <p>需要特别说明的是,本篇文章以及本系列其他文章所分析的源码版本均为 <strong>dubbo-2.6.4</strong>。因此大家在阅读文章的过程中,需注意将代码版本切换到 dubbo-2.6.4 tag 上。</p> |
| <h2 id="2spi-示例">2.SPI 示例</h2> |
| <h3 id="21--java-spi-示例">2.1 Java SPI 示例</h3> |
| <p>前面简单介绍了 SPI 机制的原理,本节通过一个示例演示 Java SPI 的使用方法。首先,我们定义一个接口,名称为 Robot。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">interface</span> <span style="color:#268bd2">Robot</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">sayHello</span>(); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">OptimusPrime</span> <span style="color:#268bd2">implements</span> Robot { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">sayHello</span>() { |
| </span></span><span style="display:flex;"><span> System.out.println(<span style="color:#2aa198">&#34;Hello, I am Optimus Prime.&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">Bumblebee</span> <span style="color:#268bd2">implements</span> Robot { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">sayHello</span>() { |
| </span></span><span style="display:flex;"><span> System.out.println(<span style="color:#2aa198">&#34;Hello, I am Bumblebee.&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>接下来 META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。文件内容为实现类的全限定的类名,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>org.apache.spi.OptimusPrime |
| </span></span><span style="display:flex;"><span>org.apache.spi.Bumblebee |
| </span></span></code></pre></div><p>做好所需的准备工作,接下来编写代码进行测试。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">JavaSPITest</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Test</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">sayHello</span>() <span style="color:#268bd2">throws</span> Exception { |
| </span></span><span style="display:flex;"><span> ServiceLoader<span style="color:#719e07">&lt;</span>Robot<span style="color:#719e07">&gt;</span> serviceLoader <span style="color:#719e07">=</span> ServiceLoader.load(Robot.class); |
| </span></span><span style="display:flex;"><span> System.out.println(<span style="color:#2aa198">&#34;Java SPI&#34;</span>); |
| </span></span><span style="display:flex;"><span> serviceLoader.forEach(Robot::sayHello); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>最后来看一下测试结果,如下:</p> |
| <p><img src="https://dubbo.apache.org/imgs/dev/java-spi-result.jpg" alt="img"></p> |
| <p>从测试结果可以看出,我们的两个实现类被成功的加载,并输出了相应的内容。关于 Java SPI 的演示先到这里,接下来演示 Dubbo SPI。</p> |
| <h3 id="22-dubbo-spi-示例">2.2 Dubbo SPI 示例</h3> |
| <p>Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>optimusPrime = org.apache.spi.OptimusPrime |
| </span></span><span style="display:flex;"><span>bumblebee = org.apache.spi.Bumblebee |
| </span></span></code></pre></div><p>与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。下面来演示 Dubbo SPI 的用法:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">DubboSPITest</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Test</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">sayHello</span>() <span style="color:#268bd2">throws</span> Exception { |
| </span></span><span style="display:flex;"><span> ExtensionLoader<span style="color:#719e07">&lt;</span>Robot<span style="color:#719e07">&gt;</span> extensionLoader <span style="color:#719e07">=</span> |
| </span></span><span style="display:flex;"><span> ExtensionLoader.getExtensionLoader(Robot.class); |
| </span></span><span style="display:flex;"><span> Robot optimusPrime <span style="color:#719e07">=</span> extensionLoader.getExtension(<span style="color:#2aa198">&#34;optimusPrime&#34;</span>); |
| </span></span><span style="display:flex;"><span> optimusPrime.sayHello(); |
| </span></span><span style="display:flex;"><span> Robot bumblebee <span style="color:#719e07">=</span> extensionLoader.getExtension(<span style="color:#2aa198">&#34;bumblebee&#34;</span>); |
| </span></span><span style="display:flex;"><span> bumblebee.sayHello(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>测试结果如下:</p> |
| <p><img src="https://dubbo.apache.org/imgs/dev/dubbo-spi-result.jpg" alt="img"></p> |
| <p>Dubbo SPI 除了支持按需加载接口实现类,还增加了 IOC 和 AOP 等特性,这些特性将会在接下来的源码分析章节中一一进行介绍。</p> |
| <h2 id="3-dubbo-spi-源码分析">3. Dubbo SPI 源码分析</h2> |
| <p>上一章简单演示了 Dubbo SPI 的使用方法。我们首先通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。这其中,getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例。该方法的逻辑比较简单,本章就不进行分析了。下面我们从 ExtensionLoader 的 getExtension 方法作为入口,对拓展类对象的获取过程进行详细的分析。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> T <span style="color:#268bd2">getExtension</span>(String name) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (name <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> name.length() <span style="color:#719e07">==</span> 0) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;Extension name == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#2aa198">&#34;true&#34;</span>.equals(name)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取默认的拓展实现类</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> getDefaultExtension(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// Holder,顾名思义,用于持有目标对象</span> |
| </span></span><span style="display:flex;"><span> Holder<span style="color:#719e07">&lt;</span>Object<span style="color:#719e07">&gt;</span> holder <span style="color:#719e07">=</span> cachedInstances.get(name); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (holder <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> cachedInstances.putIfAbsent(name, <span style="color:#719e07">new</span> Holder<span style="color:#719e07">&lt;</span>Object<span style="color:#719e07">&gt;</span>()); |
| </span></span><span style="display:flex;"><span> holder <span style="color:#719e07">=</span> cachedInstances.get(name); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> Object instance <span style="color:#719e07">=</span> holder.get(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 双重检查</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (instance <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">synchronized</span> (holder) { |
| </span></span><span style="display:flex;"><span> instance <span style="color:#719e07">=</span> holder.get(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (instance <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建拓展实例</span> |
| </span></span><span style="display:flex;"><span> instance <span style="color:#719e07">=</span> createExtension(name); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置实例到 holder 中</span> |
| </span></span><span style="display:flex;"><span> holder.set(instance); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> (T) instance; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。下面我们来看一下创建拓展对象的过程是怎样的。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> T <span style="color:#268bd2">createExtension</span>(String name) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表</span> |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> clazz <span style="color:#719e07">=</span> getExtensionClasses().get(name); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (clazz <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> findException(name); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> T instance <span style="color:#719e07">=</span> (T) EXTENSION_INSTANCES.get(clazz); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (instance <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过反射创建实例</span> |
| </span></span><span style="display:flex;"><span> EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); |
| </span></span><span style="display:flex;"><span> instance <span style="color:#719e07">=</span> (T) EXTENSION_INSTANCES.get(clazz); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 向实例中注入依赖</span> |
| </span></span><span style="display:flex;"><span> injectExtension(instance); |
| </span></span><span style="display:flex;"><span> Set<span style="color:#719e07">&lt;</span>Class<span style="color:#719e07">&lt;?&gt;&gt;</span> wrapperClasses <span style="color:#719e07">=</span> cachedWrapperClasses; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (wrapperClasses <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>wrapperClasses.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 循环创建 Wrapper 实例</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Class<span style="color:#719e07">&lt;?&gt;</span> wrapperClass : wrapperClasses) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量</span> |
| </span></span><span style="display:flex;"><span> instance <span style="color:#719e07">=</span> injectExtension( |
| </span></span><span style="display:flex;"><span> (T) wrapperClass.getConstructor(type).newInstance(instance)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> instance; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:</p> |
| <ol> |
| <li>通过 getExtensionClasses 获取所有的拓展类</li> |
| <li>通过反射创建拓展对象</li> |
| <li>向拓展对象中注入依赖</li> |
| <li>将拓展对象包裹在相应的 Wrapper 对象中</li> |
| </ol> |
| <p>以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。在接下来的章节中,将会重点分析 getExtensionClasses 方法的逻辑,以及简单介绍 Dubbo IOC 的具体实现。</p> |
| <h3 id="31-获取所有的拓展类">3.1 获取所有的拓展类</h3> |
| <p>我们在通过名称获取拓展类之前,首先需要根据配置文件解析出拓展项名称到拓展类的映射关系表(Map&lt;名称, 拓展类&gt;),之后再根据拓展项名称从映射关系表中取出相应的拓展类即可。相关过程的代码分析如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> Map<span style="color:#719e07">&lt;</span>String, Class<span style="color:#719e07">&lt;?&gt;&gt;</span> getExtensionClasses() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从缓存中获取已加载的拓展类</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, Class<span style="color:#719e07">&lt;?&gt;&gt;</span> classes <span style="color:#719e07">=</span> cachedClasses.get(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 双重检查</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (classes <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">synchronized</span> (cachedClasses) { |
| </span></span><span style="display:flex;"><span> classes <span style="color:#719e07">=</span> cachedClasses.get(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (classes <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 加载拓展类</span> |
| </span></span><span style="display:flex;"><span> classes <span style="color:#719e07">=</span> loadExtensionClasses(); |
| </span></span><span style="display:flex;"><span> cachedClasses.set(classes); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> classes; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>这里也是先检查缓存,若缓存未命中,则通过 synchronized 加锁。加锁后再次检查缓存,并判空。此时如果 classes 仍为 null,则通过 loadExtensionClasses 加载拓展类。下面分析 loadExtensionClasses 方法的逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> Map<span style="color:#719e07">&lt;</span>String, Class<span style="color:#719e07">&lt;?&gt;&gt;</span> loadExtensionClasses() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 SPI 注解,这里的 type 变量是在调用 getExtensionLoader 方法时传入的</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> SPI defaultAnnotation <span style="color:#719e07">=</span> type.getAnnotation(SPI.class); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (defaultAnnotation <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> String value <span style="color:#719e07">=</span> defaultAnnotation.value(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((value <span style="color:#719e07">=</span> value.trim()).length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 SPI 注解内容进行切分</span> |
| </span></span><span style="display:flex;"><span> String<span style="color:#719e07">[]</span> names <span style="color:#719e07">=</span> NAME_SEPARATOR.split(value); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 SPI 注解内容是否合法,不合法则抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (names.length <span style="color:#719e07">&gt;</span> 1) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;more than 1 default extension name on extension...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置默认名称,参考 getDefaultExtension 方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (names.length <span style="color:#719e07">==</span> 1) { |
| </span></span><span style="display:flex;"><span> cachedDefaultName <span style="color:#719e07">=</span> names<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, Class<span style="color:#719e07">&lt;?&gt;&gt;</span> extensionClasses <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, Class<span style="color:#719e07">&lt;?&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 加载指定文件夹下的配置文件</span> |
| </span></span><span style="display:flex;"><span> loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); |
| </span></span><span style="display:flex;"><span> loadDirectory(extensionClasses, DUBBO_DIRECTORY); |
| </span></span><span style="display:flex;"><span> loadDirectory(extensionClasses, SERVICES_DIRECTORY); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> extensionClasses; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>loadExtensionClasses 方法总共做了两件事情,一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件。SPI 注解解析过程比较简单,无需多说。下面我们来看一下 loadDirectory 做了哪些事情。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">loadDirectory</span>(Map<span style="color:#719e07">&lt;</span>String, Class<span style="color:#719e07">&lt;?&gt;&gt;</span> extensionClasses, String dir) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// fileName = 文件夹路径 + type 全限定名 </span> |
| </span></span><span style="display:flex;"><span> String fileName <span style="color:#719e07">=</span> dir <span style="color:#719e07">+</span> type.getName(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> Enumeration<span style="color:#719e07">&lt;</span>java.net.URL<span style="color:#719e07">&gt;</span> urls; |
| </span></span><span style="display:flex;"><span> ClassLoader classLoader <span style="color:#719e07">=</span> findClassLoader(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据文件名加载所有的同名文件</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (classLoader <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> urls <span style="color:#719e07">=</span> classLoader.getResources(fileName); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> urls <span style="color:#719e07">=</span> ClassLoader.getSystemResources(fileName); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (urls <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">while</span> (urls.hasMoreElements()) { |
| </span></span><span style="display:flex;"><span> java.net.URL resourceURL <span style="color:#719e07">=</span> urls.nextElement(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 加载资源</span> |
| </span></span><span style="display:flex;"><span> loadResource(extensionClasses, classLoader, resourceURL); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#2aa198">&#34;...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>loadDirectory 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource 方法加载资源。我们继续跟下去,看一下 loadResource 方法的实现。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">loadResource</span>(Map<span style="color:#719e07">&lt;</span>String, Class<span style="color:#719e07">&lt;?&gt;&gt;</span> extensionClasses, |
| </span></span><span style="display:flex;"><span> ClassLoader classLoader, java.net.URL resourceURL) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> BufferedReader reader <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> BufferedReader( |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">new</span> InputStreamReader(resourceURL.openStream(), <span style="color:#2aa198">&#34;utf-8&#34;</span>)); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> String line; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 按行读取配置内容</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">while</span> ((line <span style="color:#719e07">=</span> reader.readLine()) <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 定位 # 字符</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">int</span> ci <span style="color:#719e07">=</span> line.indexOf(<span style="color:#2aa198">&#39;#&#39;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ci <span style="color:#719e07">&gt;=</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 截取 # 之前的字符串,# 之后的内容为注释,需要忽略</span> |
| </span></span><span style="display:flex;"><span> line <span style="color:#719e07">=</span> line.substring(0, ci); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> line <span style="color:#719e07">=</span> line.trim(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (line.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> String name <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> line.indexOf(<span style="color:#2aa198">&#39;=&#39;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (i <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 以等于号 = 为界,截取键与值</span> |
| </span></span><span style="display:flex;"><span> name <span style="color:#719e07">=</span> line.substring(0, i).trim(); |
| </span></span><span style="display:flex;"><span> line <span style="color:#719e07">=</span> line.substring(i <span style="color:#719e07">+</span> 1).trim(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (line.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 加载类,并通过 loadClass 方法对类进行缓存</span> |
| </span></span><span style="display:flex;"><span> loadClass(extensionClasses, resourceURL, |
| </span></span><span style="display:flex;"><span> Class.forName(line, <span style="color:#cb4b16">true</span>, classLoader), name); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> IllegalStateException e <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;Failed to load extension class...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> reader.close(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#2aa198">&#34;Exception when load extension class...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>loadResource 方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass 方法进行其他操作。loadClass 方法用于主要用于操作缓存,该方法的逻辑如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">loadClass</span>(Map<span style="color:#719e07">&lt;</span>String, Class<span style="color:#719e07">&lt;?&gt;&gt;</span> extensionClasses, java.net.URL resourceURL, |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> clazz, String name) <span style="color:#268bd2">throws</span> NoSuchMethodException { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>type.isAssignableFrom(clazz)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测目标类上是否有 Adaptive 注解</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (clazz.isAnnotationPresent(Adaptive.class)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (cachedAdaptiveClass <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 cachedAdaptiveClass缓存</span> |
| </span></span><span style="display:flex;"><span> cachedAdaptiveClass <span style="color:#719e07">=</span> clazz; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>cachedAdaptiveClass.equals(clazz)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 clazz 是否是 Wrapper 类型</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (isWrapperClass(clazz)) { |
| </span></span><span style="display:flex;"><span> Set<span style="color:#719e07">&lt;</span>Class<span style="color:#719e07">&lt;?&gt;&gt;</span> wrappers <span style="color:#719e07">=</span> cachedWrapperClasses; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (wrappers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> cachedWrapperClasses <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashSet<span style="color:#719e07">&lt;</span>Class<span style="color:#719e07">&lt;?&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> wrappers <span style="color:#719e07">=</span> cachedWrapperClasses; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 存储 clazz 到 cachedWrapperClasses 缓存中</span> |
| </span></span><span style="display:flex;"><span> wrappers.add(clazz); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 程序进入此分支,表明 clazz 是一个普通的拓展类</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常</span> |
| </span></span><span style="display:flex;"><span> clazz.getConstructor(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (name <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> name.length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 name</span> |
| </span></span><span style="display:flex;"><span> name <span style="color:#719e07">=</span> findAnnotationName(clazz); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (name.length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 切分 name</span> |
| </span></span><span style="display:flex;"><span> String<span style="color:#719e07">[]</span> names <span style="color:#719e07">=</span> NAME_SEPARATOR.split(name); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (names <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> names.length <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> Activate activate <span style="color:#719e07">=</span> clazz.getAnnotation(Activate.class); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (activate <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果类上有 Activate 注解,则使用 names 数组的第一个元素作为键,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 存储 name 到 Activate 注解对象的映射关系</span> |
| </span></span><span style="display:flex;"><span> cachedActivates.put(names<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>, activate); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (String n : names) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>cachedNames.containsKey(clazz)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 存储 Class 到名称的映射关系</span> |
| </span></span><span style="display:flex;"><span> cachedNames.put(clazz, n); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> c <span style="color:#719e07">=</span> extensionClasses.get(n); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (c <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 存储名称到 Class 的映射关系</span> |
| </span></span><span style="display:flex;"><span> extensionClasses.put(n, clazz); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (c <span style="color:#719e07">!=</span> clazz) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,loadClass 方法操作了不同的缓存,比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等。除此之外,该方法没有其他什么逻辑了。</p> |
| <p>到此,关于缓存类加载的过程就分析完了。整个过程没什么特别复杂的地方,大家按部就班的分析即可,不懂的地方可以调试一下。接下来,我们来聊聊 Dubbo IOC 方面的内容。</p> |
| <h3 id="32-dubbo-ioc">3.2 Dubbo IOC</h3> |
| <p>Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。整个过程对应的代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> T <span style="color:#268bd2">injectExtension</span>(T instance) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (objectFactory <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历目标类的所有方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Method method : instance.getClass().getMethods()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (method.getName().startsWith(<span style="color:#2aa198">&#34;set&#34;</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> method.getParameterTypes().length <span style="color:#719e07">==</span> 1 |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> Modifier.isPublic(method.getModifiers())) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 setter 方法参数类型</span> |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> pt <span style="color:#719e07">=</span> method.getParameterTypes()<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取属性名,比如 setName 方法对应属性名 name</span> |
| </span></span><span style="display:flex;"><span> String property <span style="color:#719e07">=</span> method.getName().length() <span style="color:#719e07">&gt;</span> 3 <span style="color:#719e07">?</span> |
| </span></span><span style="display:flex;"><span> method.getName().substring(3, 4).toLowerCase() <span style="color:#719e07">+</span> |
| </span></span><span style="display:flex;"><span> method.getName().substring(4) : <span style="color:#2aa198">&#34;&#34;</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从 ObjectFactory 中获取依赖对象</span> |
| </span></span><span style="display:flex;"><span> Object object <span style="color:#719e07">=</span> objectFactory.getExtension(pt, property); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (object <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过反射调用 setter 方法设置依赖</span> |
| </span></span><span style="display:flex;"><span> method.invoke(instance, object); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#2aa198">&#34;fail to inject via method...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> logger.error(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> instance; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>在上面代码中,objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。这两个类的类的代码不是很复杂,这里就不一一分析了。</p> |
| <p>Dubbo IOC 目前仅支持 setter 方式注入,总的来说,逻辑比较简单易懂。</p> |
| <h2 id="4总结">4.总结</h2> |
| <p>本篇文章简单分别介绍了 Java SPI 与 Dubbo SPI 用法,并对 Dubbo SPI 的加载拓展类的过程进行了分析。另外,在 Dubbo SPI 中还有一块重要的逻辑这里没有进行分析,即 Dubbo SPI 的扩展点自适应机制。该机制的逻辑较为复杂,我们将会在下一篇文章中进行详细的分析。</p> |
| <p>好了,本篇文章就先到这里了。如果文章中有错误不妥之处,欢迎大家提 issue 进行反馈,或者提 pull request 进行修正。让我们携手共建 Dubbo 社区。</p></description></item><item><title>Docsv2.7: 服务路由</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/router/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/router/</guid><description> |
| <h2 id="1-简介">1. 简介</h2> |
| <p>上一篇文章分析了集群容错的第一部分 — 服务目录 Directory。服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由,筛选出符合路由规则的服务提供者。在详细分析服务路由的源码之前,先来介绍一下服务路由是什么。服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者。Dubbo 目前提供了三种服务路由实现,分别为条件路由 ConditionRouter、脚本路由 ScriptRouter 和标签路由 TagRouter。其中条件路由是我们最常使用的,标签路由是一个新的实现,暂时还未发布,该实现预计会在 2.7.x 版本中发布。本篇文章将分析条件路由相关源码,脚本路由和标签路由这里就不分析了。</p> |
| <h2 id="2-源码分析">2. 源码分析</h2> |
| <p>条件路由规则由两个条件组成,分别用于对服务消费者和提供者进行匹配。比如有这样一条规则:</p> |
| <p><code>host = 10.20.153.10 =&gt; host = 10.20.153.11</code></p> |
| <p>该条规则表示 IP 为 10.20.153.10 的服务消费者<strong>只可</strong>调用 IP 为 10.20.153.11 机器上的服务,不可调用其他机器上的服务。条件路由规则的格式如下:</p> |
| <p><code>[服务消费者匹配条件] =&gt; [服务提供者匹配条件]</code></p> |
| <p>如果服务消费者匹配条件为空,表示不对服务消费者进行限制。如果服务提供者匹配条件为空,表示对某些服务消费者禁用服务。官方文档中对条件路由进行了比较详细的介绍,大家可以参考下,这里就不过多说明了。</p> |
| <p>条件路由实现类 ConditionRouter 在进行工作前,需要先对用户配置的路由规则进行解析,得到一系列的条件。然后再根据这些条件对服务进行路由。本章将分两节进行说明,2.1节介绍表达式解析过程。2.2 节介绍服务路由的过程。下面,我们先从表达式解析过程看起。</p> |
| <h3 id="21-表达式解析">2.1 表达式解析</h3> |
| <p>条件路由规则是一条字符串,对于 Dubbo 来说,它并不能直接理解字符串的意思,需要将其解析成内部格式才行。条件表达式的解析过程始于 ConditionRouter 的构造方法,下面一起看一下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">ConditionRouter</span>(URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.url <span style="color:#719e07">=</span> url; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 priority 和 force 配置</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.priority <span style="color:#719e07">=</span> url.getParameter(Constants.PRIORITY_KEY, 0); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.force <span style="color:#719e07">=</span> url.getParameter(Constants.FORCE_KEY, <span style="color:#cb4b16">false</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取路由规则</span> |
| </span></span><span style="display:flex;"><span> String rule <span style="color:#719e07">=</span> url.getParameterAndDecoded(Constants.RULE_KEY); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (rule <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> rule.trim().length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;Illegal route rule!&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> rule <span style="color:#719e07">=</span> rule.replace(<span style="color:#2aa198">&#34;consumer.&#34;</span>, <span style="color:#2aa198">&#34;&#34;</span>).replace(<span style="color:#2aa198">&#34;provider.&#34;</span>, <span style="color:#2aa198">&#34;&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 定位 =&gt; 分隔符</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> rule.indexOf(<span style="color:#2aa198">&#34;=&gt;&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 分别获取服务消费者和提供者匹配规则</span> |
| </span></span><span style="display:flex;"><span> String whenRule <span style="color:#719e07">=</span> i <span style="color:#719e07">&lt;</span> 0 <span style="color:#719e07">?</span> <span style="color:#cb4b16">null</span> : rule.substring(0, i).trim(); |
| </span></span><span style="display:flex;"><span> String thenRule <span style="color:#719e07">=</span> i <span style="color:#719e07">&lt;</span> 0 <span style="color:#719e07">?</span> rule.trim() : rule.substring(i <span style="color:#719e07">+</span> 2).trim(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 解析服务消费者匹配规则</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, MatchPair<span style="color:#719e07">&gt;</span> when <span style="color:#719e07">=</span> |
| </span></span><span style="display:flex;"><span> StringUtils.isBlank(whenRule) <span style="color:#719e07">||</span> <span style="color:#2aa198">&#34;true&#34;</span>.equals(whenRule) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">?</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, MatchPair<span style="color:#719e07">&gt;</span>() : parseRule(whenRule); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 解析服务提供者匹配规则</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, MatchPair<span style="color:#719e07">&gt;</span> then <span style="color:#719e07">=</span> |
| </span></span><span style="display:flex;"><span> StringUtils.isBlank(thenRule) <span style="color:#719e07">||</span> <span style="color:#2aa198">&#34;false&#34;</span>.equals(thenRule) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">?</span> <span style="color:#cb4b16">null</span> : parseRule(thenRule); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将解析出的匹配规则分别赋值给 whenCondition 和 thenCondition 成员变量</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.whenCondition <span style="color:#719e07">=</span> when; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.thenCondition <span style="color:#719e07">=</span> then; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (ParseException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,ConditionRouter 构造方法先是对路由规则做预处理,然后调用 parseRule 方法分别对服务提供者和消费者规则进行解析,最后将解析结果赋值给 whenCondition 和 thenCondition 成员变量。ConditionRouter 构造方法不是很复杂,这里就不多说了。下面我们把重点放在 parseRule 方法上,在详细介绍这个方法之前,我们先来看一个内部类。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">MatchPair</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> Set<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span> matches <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashSet<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> Set<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span> mismatches <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashSet<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>MatchPair 内部包含了两个 Set 类型的成员变量,分别用于存放匹配和不匹配的条件。这个类两个成员变量会在 parseRule 方法中被用到,下面来看一下。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> Map<span style="color:#719e07">&lt;</span>String, MatchPair<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">parseRule</span>(String rule) |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">throws</span> ParseException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 定义条件映射集合</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, MatchPair<span style="color:#719e07">&gt;</span> condition <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, MatchPair<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (StringUtils.isBlank(rule)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> condition; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> MatchPair pair <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> Set<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span> values <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过正则表达式匹配路由规则,ROUTE_PATTERN = ([&amp;!=,]*)\s*([^&amp;!=,\s]+)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 这个表达式看起来不是很好理解,第一个括号内的表达式用于匹配&#34;&amp;&#34;, &#34;!&#34;, &#34;=&#34; 和 &#34;,&#34; 等符号。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 第二括号内的用于匹配英文字母,数字等字符。举个例子说明一下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// host = 2.2.2.2 &amp; host != 1.1.1.1 &amp; method = hello</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 匹配结果如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 括号一 括号二</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 1. null host</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 2. = 2.2.2.2</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 3. &amp; host</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 4. != 1.1.1.1 </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 5. &amp; method</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 6. = hello</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> Matcher matcher <span style="color:#719e07">=</span> ROUTE_PATTERN.matcher(rule); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">while</span> (matcher.find()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取括号一内的匹配结果</span> |
| </span></span><span style="display:flex;"><span> String separator <span style="color:#719e07">=</span> matcher.group(1); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取括号二内的匹配结果</span> |
| </span></span><span style="display:flex;"><span> String content <span style="color:#719e07">=</span> matcher.group(2); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 分隔符为空,表示匹配的是表达式的开始部分</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (separator <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> separator.length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 MatchPair 对象</span> |
| </span></span><span style="display:flex;"><span> pair <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> MatchPair(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 存储 &lt;匹配项, MatchPair&gt; 键值对,比如 &lt;host, MatchPair&gt;</span> |
| </span></span><span style="display:flex;"><span> condition.put(content, pair); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果分隔符为 &amp;,表明接下来也是一个条件</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (<span style="color:#2aa198">&#34;&amp;&#34;</span>.equals(separator)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 尝试从 condition 获取 MatchPair</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (condition.get(content) <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 未获取到 MatchPair,重新创建一个,并放入 condition 中</span> |
| </span></span><span style="display:flex;"><span> pair <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> MatchPair(); |
| </span></span><span style="display:flex;"><span> condition.put(content, pair); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> pair <span style="color:#719e07">=</span> condition.get(content); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 分隔符为 =</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (<span style="color:#2aa198">&#34;=&#34;</span>.equals(separator)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (pair <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> ParseException(<span style="color:#2aa198">&#34;Illegal route rule ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> values <span style="color:#719e07">=</span> pair.matches; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 content 存入到 MatchPair 的 matches 集合中</span> |
| </span></span><span style="display:flex;"><span> values.add(content); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 分隔符为 != </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (<span style="color:#2aa198">&#34;!=&#34;</span>.equals(separator)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (pair <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> ParseException(<span style="color:#2aa198">&#34;Illegal route rule ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> values <span style="color:#719e07">=</span> pair.mismatches; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 content 存入到 MatchPair 的 mismatches 集合中</span> |
| </span></span><span style="display:flex;"><span> values.add(content); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 分隔符为 ,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (<span style="color:#2aa198">&#34;,&#34;</span>.equals(separator)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (values <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> values.isEmpty()) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> ParseException(<span style="color:#2aa198">&#34;Illegal route rule ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 content 存入到上一步获取到的 values 中,可能是 matches,也可能是 mismatches</span> |
| </span></span><span style="display:flex;"><span> values.add(content); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> ParseException(<span style="color:#2aa198">&#34;Illegal route rule ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> condition; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上就是路由规则的解析逻辑,该逻辑由正则表达式和一个 while 循环以及数个条件分支组成。下面通过一个示例对解析逻辑进行演绎。示例为 <code> host = 2.2.2.2 &amp; host != 1.1.1.1 &amp; method = hello</code>。正则解析结果如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span> 括号一 括号二 |
| </span></span><span style="display:flex;"><span>1. null host |
| </span></span><span style="display:flex;"><span>2. = 2.2.2.2 |
| </span></span><span style="display:flex;"><span>3. &amp; host |
| </span></span><span style="display:flex;"><span>4. != 1.1.1.1 |
| </span></span><span style="display:flex;"><span>5. &amp; method |
| </span></span><span style="display:flex;"><span>6. = hello |
| </span></span></code></pre></div><p>现在线程进入 while 循环:</p> |
| <p>第一次循环:分隔符 separator = null,content = &ldquo;host&rdquo;。此时创建 MatchPair 对象,并存入到 condition 中,condition = {&ldquo;host&rdquo;: MatchPair@123}</p> |
| <p>第二次循环:分隔符 separator = &ldquo;=&quot;,content = &ldquo;2.2.2.2&rdquo;,pair = MatchPair@123。此时将 2.2.2.2 放入到 MatchPair@123 对象的 matches 集合中。</p> |
| <p>第三次循环:分隔符 separator = &ldquo;&amp;&quot;,content = &ldquo;host&rdquo;。host 已存在于 condition 中,因此 pair = MatchPair@123。</p> |
| <p>第四次循环:分隔符 separator = &ldquo;!=&quot;,content = &ldquo;1.1.1.1&rdquo;,pair = MatchPair@123。此时将 1.1.1.1 放入到 MatchPair@123 对象的 mismatches 集合中。</p> |
| <p>第五次循环:分隔符 separator = &ldquo;&amp;&quot;,content = &ldquo;method&rdquo;。condition.get(&ldquo;method&rdquo;) = null,因此新建一个 MatchPair 对象,并放入到 condition 中。此时 condition = {&ldquo;host&rdquo;: MatchPair@123, &ldquo;method&rdquo;: MatchPair@ 456}</p> |
| <p>第六次循环:分隔符 separator = &ldquo;=&quot;,content = &ldquo;2.2.2.2&rdquo;,pair = MatchPair@456。此时将 hello 放入到 MatchPair@456 对象的 matches 集合中。</p> |
| <p>循环结束,此时 condition 的内容如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{ |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&#34;host&#34;</span>: { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&#34;matches&#34;</span>: [<span style="color:#2aa198">&#34;2.2.2.2&#34;</span>], |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&#34;mismatches&#34;</span>: [<span style="color:#2aa198">&#34;1.1.1.1&#34;</span>] |
| </span></span><span style="display:flex;"><span> }, |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&#34;method&#34;</span>: { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&#34;matches&#34;</span>: [<span style="color:#2aa198">&#34;hello&#34;</span>], |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&#34;mismatches&#34;</span>: [] |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>路由规则的解析过程稍微有点复杂,大家可通过 ConditionRouter 的测试类对该逻辑进行测试。并且找一个表达式,对照上面的代码走一遍,加深理解。</p> |
| <h3 id="22-服务路由">2.2 服务路由</h3> |
| <p>服务路由的入口方法是 ConditionRouter 的 route 方法,该方法定义在 Router 接口中。实现代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> <span style="color:#268bd2">route</span>(List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, URL url, Invocation invocation) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> invokers.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 先对服务消费者条件进行匹配,如果匹配失败,表明服务消费者 url 不符合匹配规则,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 无需进行后续匹配,直接返回 Invoker 列表即可。比如下面的规则:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// host = 10.20.153.10 =&gt; host = 10.0.0.10</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 这条路由规则希望 IP 为 10.20.153.10 的服务消费者调用 IP 为 10.0.0.10 机器上的服务。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 当消费者 ip 为 10.20.153.11 时,matchWhen 返回 false,表明当前这条路由规则不适用于</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 当前的服务消费者,此时无需再进行后续匹配,直接返回即可。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>matchWhen(url, invocation)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> result <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 服务提供者匹配条件未配置,表明对指定的服务消费者禁用服务,也就是服务消费者在黑名单中</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (thenCondition <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> logger.warn(<span style="color:#2aa198">&#34;The current consumer in the service blacklist...&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> result; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 这里可以简单的把 Invoker 理解为服务提供者,现在使用服务提供者匹配规则对 </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// Invoker 列表进行匹配</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker : invokers) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 若匹配成功,表明当前 Invoker 符合服务提供者匹配规则。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 此时将 Invoker 添加到 result 列表中</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (matchThen(invoker.getUrl(), url)) { |
| </span></span><span style="display:flex;"><span> result.add(invoker); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回匹配结果,如果 result 为空列表,且 force = true,表示强制返回空列表,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 否则路由结果为空的路由规则将自动失效</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>result.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> result; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (force) { |
| </span></span><span style="display:flex;"><span> logger.warn(<span style="color:#2aa198">&#34;The route result is empty and force execute ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> result; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#2aa198">&#34;Failed to execute condition router rule: ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 原样返回,此时 force = false,表示该条路由规则失效</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>route 方法先是调用 matchWhen 对服务消费者进行匹配,如果匹配失败,直接返回 Invoker 列表。如果匹配成功,再对服务提供者进行匹配,匹配逻辑封装在了 matchThen 方法中。下面来看一下这两个方法的逻辑:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#dc322f">boolean</span> <span style="color:#268bd2">matchWhen</span>(URL url, Invocation invocation) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 服务消费者条件为 null 或空,均返回 true,比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// =&gt; host != 172.22.3.91</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 表示所有的服务消费者都不得调用 IP 为 172.22.3.91 的机器上的服务</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> whenCondition <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> whenCondition.isEmpty() |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">||</span> matchCondition(whenCondition, url, <span style="color:#cb4b16">null</span>, invocation); <span style="color:#586e75">// 进行条件匹配</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">boolean</span> <span style="color:#268bd2">matchThen</span>(URL url, URL param) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 服务提供者条件为 null 或空,表示禁用服务</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">!</span>(thenCondition <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> thenCondition.isEmpty()) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> matchCondition(thenCondition, url, param, <span style="color:#cb4b16">null</span>); <span style="color:#586e75">// 进行条件匹配</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>这两个方法长的有点像,不过逻辑上还是有差别的,大家注意看。这两个方法均调用了 matchCondition 方法,但它们所传入的参数是不同的。这个需要特别注意一下,不然后面的逻辑不好弄懂。下面我们对这几个参数进行溯源。matchWhen 方法向 matchCondition 方法传入的参数为 [whenCondition, url, null, invocation],第一个参数 whenCondition 为服务消费者匹配条件,这个前面分析过。第二个参数 url 源自 route 方法的参数列表,该参数由外部类调用 route 方法时传入。比如:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> <span style="color:#268bd2">route</span>(List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, String method) { |
| </span></span><span style="display:flex;"><span> Invocation invocation <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> RpcInvocation(method, <span style="color:#719e07">new</span> Class<span style="color:#719e07">&lt;?&gt;[</span>0<span style="color:#719e07">]</span>, <span style="color:#719e07">new</span> Object<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>); |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Router<span style="color:#719e07">&gt;</span> routers <span style="color:#719e07">=</span> getRouters(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (routers <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Router router : routers) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (router.getUrl() <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 注意第二个参数</span> |
| </span></span><span style="display:flex;"><span> invokers <span style="color:#719e07">=</span> router.route(invokers, getConsumerUrl(), invocation); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面这段代码来自 RegistryDirectory,第二个参数表示的是服务消费者 url。matchCondition 的 invocation 参数也是从这里传入的。</p> |
| <p>接下来再来看看 matchThen 向 matchCondition 方法传入的参数 [thenCondition, url, param, null]。第一个参数不用解释了。第二个和第三个参数来自 matchThen 方法的参数列表,这两个参数分别为服务提供者 url 和服务消费者 url。搞清楚这些参数来源后,接下来就可以分析 matchCondition 方法了。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">boolean</span> <span style="color:#268bd2">matchCondition</span>(Map<span style="color:#719e07">&lt;</span>String, MatchPair<span style="color:#719e07">&gt;</span> condition, URL url, URL param, Invocation invocation) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将服务提供者或消费者 url 转成 Map</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span> sample <span style="color:#719e07">=</span> url.toMap(); |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> result <span style="color:#719e07">=</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 condition 列表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Map.Entry<span style="color:#719e07">&lt;</span>String, MatchPair<span style="color:#719e07">&gt;</span> matchPair : condition.entrySet()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取匹配项名称,比如 host、method 等</span> |
| </span></span><span style="display:flex;"><span> String key <span style="color:#719e07">=</span> matchPair.getKey(); |
| </span></span><span style="display:flex;"><span> String sampleValue; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 invocation 不为空,且 key 为 method(s),表示进行方法匹配</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invocation <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> (Constants.METHOD_KEY.equals(key) <span style="color:#719e07">||</span> Constants.METHODS_KEY.equals(key))) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从 invocation 获取被调用方法的名称</span> |
| </span></span><span style="display:flex;"><span> sampleValue <span style="color:#719e07">=</span> invocation.getMethodName(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从服务提供者或消费者 url 中获取指定字段值,比如 host、application 等</span> |
| </span></span><span style="display:flex;"><span> sampleValue <span style="color:#719e07">=</span> sample.get(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (sampleValue <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 尝试通过 default.xxx 获取相应的值</span> |
| </span></span><span style="display:flex;"><span> sampleValue <span style="color:#719e07">=</span> sample.get(Constants.DEFAULT_KEY_PREFIX <span style="color:#719e07">+</span> key); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// --------------------✨ 分割线 ✨-------------------- //</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (sampleValue <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 MatchPair 的 isMatch 方法进行匹配</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>matchPair.getValue().isMatch(sampleValue, param)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 只要有一个规则匹配失败,立即返回 false 结束方法逻辑</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> result <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// sampleValue 为空,表明服务提供者或消费者 url 中不包含相关字段。此时如果 </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// MatchPair 的 matches 不为空,表示匹配失败,返回 false。比如我们有这样</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 一条匹配条件 loadbalance = random,假设 url 中并不包含 loadbalance 参数,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 此时 sampleValue = null。既然路由规则里限制了 loadbalance 必须为 random,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 但 sampleValue = null,明显不符合规则,因此返回 false</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>matchPair.getValue().matches.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> result <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> result; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,matchCondition 方法看起来有点复杂,这里简单说明一下。分割线以上的代码实际上用于获取 sampleValue 的值,分割线以下才是进行条件匹配。条件匹配调用的逻辑封装在 isMatch 中,代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">boolean</span> <span style="color:#268bd2">isMatch</span>(String value, URL param) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 情况一:matches 非空,mismatches 为空</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>matches.isEmpty() <span style="color:#719e07">&amp;&amp;</span> mismatches.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 matches 集合,检测入参 value 是否能被 matches 集合元素匹配到。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 举个例子,如果 value = 10.20.153.11,matches = [10.20.153.*],</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 此时 isMatchGlobPattern 方法返回 true</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (String match : matches) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (UrlUtils.isMatchGlobPattern(match, value, param)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果所有匹配项都无法匹配到入参,则返回 false</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 情况二:matches 为空,mismatches 非空</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>mismatches.isEmpty() <span style="color:#719e07">&amp;&amp;</span> matches.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (String mismatch : mismatches) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 只要入参被 mismatches 集合中的任意一个元素匹配到,就返回 false</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (UrlUtils.isMatchGlobPattern(mismatch, value, param)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// mismatches 集合中所有元素都无法匹配到入参,此时返回 true</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 情况三:matches 非空,mismatches 非空</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>matches.isEmpty() <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>mismatches.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// matches 和 mismatches 均为非空,此时优先使用 mismatches 集合元素对入参进行匹配。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 只要 mismatches 集合中任意一个元素与入参匹配成功,就立即返回 false,结束方法逻辑</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (String mismatch : mismatches) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (UrlUtils.isMatchGlobPattern(mismatch, value, param)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// mismatches 集合元素无法匹配到入参,此时再使用 matches 继续匹配</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (String match : matches) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 只要 matches 集合中任意一个元素与入参匹配成功,就立即返回 true</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (UrlUtils.isMatchGlobPattern(match, value, param)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 全部失配,则返回 false</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 情况四:matches 和 mismatches 均为空,此时返回 false</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>isMatch 方法逻辑比较清晰,由三个条件分支组成,用于处理四种情况。这里对四种情况下的匹配逻辑进行简单的总结,如下:</p> |
| <table> |
| <thead> |
| <tr> |
| <th></th> |
| <th>条件</th> |
| <th>过程</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>情况一</td> |
| <td>matches 非空,mismatches 为空</td> |
| <td>遍历 matches 集合元素,并与入参进行匹配。只要有一个元素成功匹配入参,即可返回 true。若全部失配,则返回 false。</td> |
| </tr> |
| <tr> |
| <td>情况二</td> |
| <td>matches 为空,mismatches 非空</td> |
| <td>遍历 mismatches 集合元素,并与入参进行匹配。只要有一个元素成功匹配入参,立即 false。若全部失配,则返回 true。</td> |
| </tr> |
| <tr> |
| <td>情况三</td> |
| <td>matches 非空,mismatches 非空</td> |
| <td>优先使用 mismatches 集合元素对入参进行匹配,只要任一元素与入参匹配成功,就立即返回 false,结束方法逻辑。否则再使用 matches 中的集合元素进行匹配,只要有任意一个元素匹配成功,即可返回 true。若全部失配,则返回 false</td> |
| </tr> |
| <tr> |
| <td>情况四</td> |
| <td>matches 为空,mismatches 为空</td> |
| <td>直接返回 false</td> |
| </tr> |
| </tbody> |
| </table> |
| <p>isMatch 方法是通过 UrlUtils 的 isMatchGlobPattern 方法进行匹配,因此下面我们再来看看 isMatchGlobPattern 方法的逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> <span style="color:#dc322f">boolean</span> <span style="color:#268bd2">isMatchGlobPattern</span>(String pattern, String value, URL param) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (param <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> pattern.startsWith(<span style="color:#2aa198">&#34;$&#34;</span>)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 引用服务消费者参数,param 参数为服务消费者 url</span> |
| </span></span><span style="display:flex;"><span> pattern <span style="color:#719e07">=</span> param.getRawParameter(pattern.substring(1)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用重载方法继续比较</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> isMatchGlobPattern(pattern, value); |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> <span style="color:#dc322f">boolean</span> <span style="color:#268bd2">isMatchGlobPattern</span>(String pattern, String value) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 * 通配符提供支持</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#2aa198">&#34;*&#34;</span>.equals(pattern)) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 匹配规则为通配符 *,直接返回 true 即可</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((pattern <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> pattern.length() <span style="color:#719e07">==</span> 0) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> (value <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> value.length() <span style="color:#719e07">==</span> 0)) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// pattern 和 value 均为空,此时可认为两者相等,返回 true</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((pattern <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> pattern.length() <span style="color:#719e07">==</span> 0) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">||</span> (value <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> value.length() <span style="color:#719e07">==</span> 0)) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// pattern 和 value 其中有一个为空,表明两者不相等,返回 false</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 定位 * 通配符位置</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> pattern.lastIndexOf(<span style="color:#2aa198">&#39;*&#39;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (i <span style="color:#719e07">==</span> <span style="color:#719e07">-</span>1) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 匹配规则中不包含通配符,此时直接比较 value 和 pattern 是否相等即可,并返回比较结果</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> value.equals(pattern); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通配符 &#34;*&#34; 在匹配规则尾部,比如 10.0.21.*</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (i <span style="color:#719e07">==</span> pattern.length() <span style="color:#719e07">-</span> 1) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 value 是否以“不含通配符的匹配规则”开头,并返回结果。比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// pattern = 10.0.21.*,value = 10.0.21.12,此时返回 true</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> value.startsWith(pattern.substring(0, i)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通配符 &#34;*&#34; 在匹配规则头部</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (i <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 value 是否以“不含通配符的匹配规则”结尾,并返回结果</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> value.endsWith(pattern.substring(i <span style="color:#719e07">+</span> 1)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通配符 &#34;*&#34; 在匹配规则中间位置</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过通配符将 pattern 分成两半,得到 prefix 和 suffix</span> |
| </span></span><span style="display:flex;"><span> String prefix <span style="color:#719e07">=</span> pattern.substring(0, i); |
| </span></span><span style="display:flex;"><span> String suffix <span style="color:#719e07">=</span> pattern.substring(i <span style="color:#719e07">+</span> 1); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 value 是否以 prefix 开头,且以 suffix 结尾,并返回结果</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> value.startsWith(prefix) <span style="color:#719e07">&amp;&amp;</span> value.endsWith(suffix); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上就是 isMatchGlobPattern 两个重载方法的全部逻辑,这两个方法分别对普通的匹配过程,以及”引用消费者参数“和通配符匹配等特性提供了支持。这两个方法的逻辑不是很复杂,且代码中也进行了比较详细的注释,因此就不多说了。</p> |
| <h2 id="3-总结">3. 总结</h2> |
| <p>本篇文章对条件路由的表达式解析和服务路由过程进行了较为细致的分析。总的来说,条件路由的代码还是有一些复杂的,需要静下心来看。在阅读条件路由代码的过程中,要多调试。一般的框架都会有单元测试,Dubbo 也不例外,因此大家可以直接通过 ConditionRouterTest 对条件路由进行调试,无需重头构建测试用例。</p></description></item><item><title>Docsv2.7: SPI 自适应拓展</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/adaptive-extension/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/adaptive-extension/</guid><description> |
| <h2 id="1原理">1.原理</h2> |
| <p>在 Dubbo 中,很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载。对于这个矛盾的问题,Dubbo 通过自适应拓展机制很好的解决了。自适应拓展机制的实现逻辑比较复杂,首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类。最后再通过反射创建代理类,整个过程比较复杂。为了让大家对自适应拓展有一个感性的认识,下面我们通过一个示例进行演示。这是一个与汽车相关的例子,我们有一个车轮制造厂接口 WheelMaker:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">interface</span> <span style="color:#268bd2">WheelMaker</span> { |
| </span></span><span style="display:flex;"><span> Wheel <span style="color:#268bd2">makeWheel</span>(URL url); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>WheelMaker 接口的自适应实现类如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">AdaptiveWheelMaker</span> <span style="color:#268bd2">implements</span> WheelMaker { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Wheel <span style="color:#268bd2">makeWheel</span>(URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;url == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 1.从 URL 中获取 WheelMaker 名称</span> |
| </span></span><span style="display:flex;"><span> String wheelMakerName <span style="color:#719e07">=</span> url.getParameter(<span style="color:#2aa198">&#34;Wheel.maker&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (wheelMakerName <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;wheelMakerName == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 2.通过 SPI 加载具体的 WheelMaker</span> |
| </span></span><span style="display:flex;"><span> WheelMaker wheelMaker <span style="color:#719e07">=</span> ExtensionLoader |
| </span></span><span style="display:flex;"><span> .getExtensionLoader(WheelMaker.class).getExtension(wheelMakerName); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 3.调用目标方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> wheelMaker.makeWheel(url); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>AdaptiveWheelMaker 是一个代理类,与传统的代理逻辑不同,AdaptiveWheelMaker 所代理的对象是在 makeWheel 方法中通过 SPI 加载得到的。makeWheel 方法主要做了三件事情:</p> |
| <ol> |
| <li>从 URL 中获取 WheelMaker 名称</li> |
| <li>通过 SPI 加载具体的 WheelMaker 实现类</li> |
| <li>调用目标方法</li> |
| </ol> |
| <p>接下来,我们来看看汽车制造厂 CarMaker 接口与其实现类。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">interface</span> <span style="color:#268bd2">CarMaker</span> { |
| </span></span><span style="display:flex;"><span> Car <span style="color:#268bd2">makeCar</span>(URL url); |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">RaceCarMaker</span> <span style="color:#268bd2">implements</span> CarMaker { |
| </span></span><span style="display:flex;"><span> WheelMaker wheelMaker; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过 setter 注入 AdaptiveWheelMaker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">setWheelMaker</span>(WheelMaker wheelMaker) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.wheelMaker <span style="color:#719e07">=</span> wheelMaker; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Car <span style="color:#268bd2">makeCar</span>(URL url) { |
| </span></span><span style="display:flex;"><span> Wheel wheel <span style="color:#719e07">=</span> wheelMaker.makeWheel(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> RaceCar(wheel, ...); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>RaceCarMaker 持有一个 WheelMaker 类型的成员变量,在程序启动时,我们可以将 AdaptiveWheelMaker 通过 setter 方法注入到 RaceCarMaker 中。在运行时,假设有这样一个 url 参数传入:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>dubbo://192.168.0.101:20880/XxxService?wheel.maker=MichelinWheelMaker |
| </span></span></code></pre></div><p>RaceCarMaker 的 makeCar 方法将上面的 url 作为参数传给 AdaptiveWheelMaker 的 makeWheel 方法,makeWheel 方法从 url 中提取 wheel.maker 参数,得到 MichelinWheelMaker。之后再通过 SPI 加载配置名为 MichelinWheelMaker 的实现类,得到具体的 WheelMaker 实例。</p> |
| <p>上面的示例展示了自适应拓展类的核心实现 &mdash;- 在拓展接口的方法被调用时,通过 SPI 加载具体的拓展实现类,并调用拓展对象的同名方法。接下来,我们深入到源码中,探索自适应拓展类生成的过程。</p> |
| <h2 id="2源码分析">2.源码分析</h2> |
| <p>在对自适应拓展生成过程进行深入分析之前,我们先来看一下与自适应拓展息息相关的一个注解,即 Adaptive 注解。该注解的定义如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">@Documented</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">@Retention</span>(RetentionPolicy.RUNTIME) |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">@Target</span>({ElementType.TYPE, ElementType.METHOD}) |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">@interface</span> Adaptive { |
| </span></span><span style="display:flex;"><span> String<span style="color:#719e07">[]</span> <span style="color:#268bd2">value</span>() <span style="color:#719e07">default</span> {}; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>从上面的代码中可知,Adaptive 可注解在类或方法上。当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理逻辑。Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。Adaptive 注解的地方不同,相应的处理逻辑也是不同的。注解在类上时,处理逻辑比较简单,本文就不分析了。注解在接口方法上时,处理逻辑较为复杂,本章将会重点分析此块逻辑。</p> |
| <h3 id="21-获取自适应拓展">2.1 获取自适应拓展</h3> |
| <p>getAdaptiveExtension 方法是获取自适应拓展的入口方法,因此下面我们从这个方法进行分析。相关代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> T <span style="color:#268bd2">getAdaptiveExtension</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从缓存中获取自适应拓展</span> |
| </span></span><span style="display:flex;"><span> Object instance <span style="color:#719e07">=</span> cachedAdaptiveInstance.get(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (instance <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { <span style="color:#586e75">// 缓存未命中</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (createAdaptiveInstanceError <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">synchronized</span> (cachedAdaptiveInstance) { |
| </span></span><span style="display:flex;"><span> instance <span style="color:#719e07">=</span> cachedAdaptiveInstance.get(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (instance <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建自适应拓展</span> |
| </span></span><span style="display:flex;"><span> instance <span style="color:#719e07">=</span> createAdaptiveExtension(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置自适应拓展到缓存中</span> |
| </span></span><span style="display:flex;"><span> cachedAdaptiveInstance.set(instance); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> createAdaptiveInstanceError <span style="color:#719e07">=</span> t; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;fail to create adaptive instance: ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;fail to create adaptive instance: ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> (T) instance; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>getAdaptiveExtension 方法首先会检查缓存,缓存未命中,则调用 createAdaptiveExtension 方法创建自适应拓展。下面,我们看一下 createAdaptiveExtension 方法的代码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> T <span style="color:#268bd2">createAdaptiveExtension</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取自适应拓展类,并通过反射实例化</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> injectExtension((T) getAdaptiveExtensionClass().newInstance()); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;Can not create adaptive extension ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>createAdaptiveExtension 方法的代码比较少,但却包含了三个逻辑,分别如下:</p> |
| <ol> |
| <li>调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象</li> |
| <li>通过反射进行实例化</li> |
| <li>调用 injectExtension 方法向拓展实例中注入依赖</li> |
| </ol> |
| <p>前两个逻辑比较好理解,第三个逻辑用于向自适应拓展对象中注入依赖。这个逻辑看似多余,但有存在的必要,这里简单说明一下。前面说过,Dubbo 中有两种类型的自适应拓展,一种是手工编码的,一种是自动生成的。手工编码的自适应拓展中可能存在着一些依赖,而自动生成的 Adaptive 拓展则不会依赖其他类。这里调用 injectExtension 方法的目的是为手工编码的自适应拓展注入依赖,这一点需要大家注意一下。关于 injectExtension 方法,前文已经分析过了,这里不再赘述。接下来,分析 getAdaptiveExtensionClass 方法的逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> Class<span style="color:#719e07">&lt;?&gt;</span> getAdaptiveExtensionClass() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过 SPI 获取所有的拓展类</span> |
| </span></span><span style="display:flex;"><span> getExtensionClasses(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检查缓存,若缓存不为空,则直接返回缓存</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (cachedAdaptiveClass <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> cachedAdaptiveClass; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建自适应拓展类</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> cachedAdaptiveClass <span style="color:#719e07">=</span> createAdaptiveExtensionClass(); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>getAdaptiveExtensionClass 方法同样包含了三个逻辑,如下:</p> |
| <ol> |
| <li>调用 getExtensionClasses 获取所有的拓展类</li> |
| <li>检查缓存,若缓存不为空,则返回缓存</li> |
| <li>若缓存为空,则调用 createAdaptiveExtensionClass 创建自适应拓展类</li> |
| </ol> |
| <p>这三个逻辑看起来平淡无奇,似乎没有多讲的必要。但是这些平淡无奇的代码中隐藏了着一些细节,需要说明一下。首先从第一个逻辑说起,getExtensionClasses 这个方法用于获取某个接口的所有实现类。比如该方法可以获取 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等实现类。在获取实现类的过程中,如果某个实现类被 Adaptive 注解修饰了,那么该类就会被赋值给 cachedAdaptiveClass 变量。此时,上面步骤中的第二步条件成立(缓存不为空),直接返回 cachedAdaptiveClass 即可。如果所有的实现类均未被 Adaptive 注解修饰,那么执行第三步逻辑,创建自适应拓展类。相关代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> Class<span style="color:#719e07">&lt;?&gt;</span> createAdaptiveExtensionClass() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 构建自适应拓展代码</span> |
| </span></span><span style="display:flex;"><span> String code <span style="color:#719e07">=</span> createAdaptiveExtensionClassCode(); |
| </span></span><span style="display:flex;"><span> ClassLoader classLoader <span style="color:#719e07">=</span> findClassLoader(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取编译器实现类</span> |
| </span></span><span style="display:flex;"><span> com.alibaba.dubbo.common.compiler.Compiler compiler <span style="color:#719e07">=</span> ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 编译代码,生成 Class</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> compiler.compile(code, classLoader); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>createAdaptiveExtensionClass 方法用于生成自适应拓展类,该方法首先会生成自适应拓展类的源码,然后通过 Compiler 实例(Dubbo 默认使用 javassist 作为编译器)编译源码,得到代理类 Class 实例。接下来,我们把重点放在代理类代码生成的逻辑上,其他逻辑大家自行分析。</p> |
| <h3 id="22-自适应拓展类代码生成">2.2 自适应拓展类代码生成</h3> |
| <p>createAdaptiveExtensionClassCode 方法代码略多,约有两百行代码。因此本节将会对该方法的代码进行拆分分析,以帮助大家更好的理解代码逻辑。</p> |
| <h4 id="221-adaptive-注解检测">2.2.1 Adaptive 注解检测</h4> |
| <p>在生成代理类源码之前,createAdaptiveExtensionClassCode 方法首先会通过反射检测接口方法是否包含 Adaptive 注解。对于要生成自适应拓展的接口,Dubbo 要求该接口至少有一个方法被 Adaptive 注解修饰。若不满足此条件,就会抛出运行时异常。相关代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#586e75">// 通过反射获取所有的方法</span> |
| </span></span><span style="display:flex;"><span>Method<span style="color:#719e07">[]</span> methods <span style="color:#719e07">=</span> type.getMethods(); |
| </span></span><span style="display:flex;"><span><span style="color:#dc322f">boolean</span> hasAdaptiveAnnotation <span style="color:#719e07">=</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span><span style="color:#586e75">// 遍历方法列表</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">for</span> (Method m : methods) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测方法上是否有 Adaptive 注解</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (m.isAnnotationPresent(Adaptive.class)) { |
| </span></span><span style="display:flex;"><span> hasAdaptiveAnnotation <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>hasAdaptiveAnnotation) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 若所有的方法上均无 Adaptive 注解,则抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;No adaptive method on extension ...&#34;</span>); |
| </span></span></code></pre></div><h4 id="222-生成类">2.2.2 生成类</h4> |
| <p>通过 Adaptive 注解检测后,即可开始生成代码。代码生成的顺序与 Java 文件内容顺序一致,首先会生成 package 语句,然后生成 import 语句,紧接着生成类名等代码。整个逻辑如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#586e75">// 生成 package 代码:package + type 所在包</span> |
| </span></span><span style="display:flex;"><span>codeBuilder.append(<span style="color:#2aa198">&#34;package &#34;</span>).append(type.getPackage().getName()).append(<span style="color:#2aa198">&#34;;&#34;</span>); |
| </span></span><span style="display:flex;"><span><span style="color:#586e75">// 生成 import 代码:import + ExtensionLoader 全限定名</span> |
| </span></span><span style="display:flex;"><span>codeBuilder.append(<span style="color:#2aa198">&#34;\nimport &#34;</span>).append(ExtensionLoader.class.getName()).append(<span style="color:#2aa198">&#34;;&#34;</span>); |
| </span></span><span style="display:flex;"><span><span style="color:#586e75">// 生成类代码:public class + type简单名称 + $Adaptive + implements + type全限定名 + {</span> |
| </span></span><span style="display:flex;"><span>codeBuilder.append(<span style="color:#2aa198">&#34;\npublic class &#34;</span>) |
| </span></span><span style="display:flex;"><span> .append(type.getSimpleName()) |
| </span></span><span style="display:flex;"><span> .append(<span style="color:#2aa198">&#34;$Adaptive&#34;</span>) |
| </span></span><span style="display:flex;"><span> .append(<span style="color:#2aa198">&#34; implements &#34;</span>) |
| </span></span><span style="display:flex;"><span> .append(type.getCanonicalName()) |
| </span></span><span style="display:flex;"><span> .append(<span style="color:#2aa198">&#34; {&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75">// ${生成方法}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span>codeBuilder.append(<span style="color:#2aa198">&#34;\n}&#34;</span>); |
| </span></span></code></pre></div><p>这里使用 ${&hellip;} 占位符代表其他代码的生成逻辑,该部分逻辑将在随后进行分析。上面代码不是很难理解,下面直接通过一个例子展示该段代码所生成的内容。以 Dubbo 的 Protocol 接口为例,生成的代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#719e07">package</span> com.alibaba.dubbo.rpc; |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">import</span> com.alibaba.dubbo.common.extension.ExtensionLoader; |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">Protocol$Adaptive</span> <span style="color:#268bd2">implements</span> com.alibaba.dubbo.rpc.Protocol { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略方法代码</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><h4 id="223-生成方法">2.2.3 生成方法</h4> |
| <p>一个方法可以被 Adaptive 注解修饰,也可以不被修饰。这里将未被 Adaptive 注解修饰的方法称为“无 Adaptive 注解方法”,下面我们先来看看此种方法的代码生成逻辑是怎样的。</p> |
| <h5 id="2231-无-adaptive-注解方法代码生成逻辑">2.2.3.1 无 Adaptive 注解方法代码生成逻辑</h5> |
| <p>对于接口方法,我们可以按照需求标注 Adaptive 注解。以 Protocol 接口为例,该接口的 destroy 和 getDefaultPort 未标注 Adaptive 注解,其他方法均标注了 Adaptive 注解。Dubbo 不会为没有标注 Adaptive 注解的方法生成代理逻辑,对于该种类型的方法,仅会生成一句抛出异常的代码。生成逻辑如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#719e07">for</span> (Method method : methods) { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略无关逻辑</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Adaptive adaptiveAnnotation <span style="color:#719e07">=</span> method.getAnnotation(Adaptive.class); |
| </span></span><span style="display:flex;"><span> StringBuilder code <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> StringBuilder(512); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果方法上无 Adaptive 注解,则生成 throw new UnsupportedOperationException(...) 代码</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (adaptiveAnnotation <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成的代码格式如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// throw new UnsupportedOperationException(</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &#34;method &#34; + 方法签名 + of interface + 全限定接口名 + is not adaptive method!”)</span> |
| </span></span><span style="display:flex;"><span> code.append(<span style="color:#2aa198">&#34;throw new UnsupportedOperationException(\&#34;method &#34;</span>) |
| </span></span><span style="display:flex;"><span> .append(method.toString()).append(<span style="color:#2aa198">&#34; of interface &#34;</span>) |
| </span></span><span style="display:flex;"><span> .append(type.getName()).append(<span style="color:#2aa198">&#34; is not adaptive method!\&#34;);&#34;</span>); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略无关逻辑</span> |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略无关逻辑</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以 Protocol 接口的 destroy 方法为例,上面代码生成的内容如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> UnsupportedOperationException( |
| </span></span><span style="display:flex;"><span> <span style="color:#2aa198">&#34;method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!&#34;</span>); |
| </span></span></code></pre></div><h5 id="2232-获取-url-数据">2.2.3.2 获取 URL 数据</h5> |
| <p>前面说过方法代理逻辑会从 URL 中提取目标拓展的名称,因此代码生成逻辑的一个重要的任务是从方法的参数列表或者其他参数中获取 URL 数据。举例说明一下,我们要为 Protocol 接口的 refer 和 export 方法生成代理逻辑。在运行时,通过反射得到的方法定义大致如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>Invoker <span style="color:#268bd2">refer</span>(Class<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> arg0, URL arg1) <span style="color:#268bd2">throws</span> RpcException; |
| </span></span><span style="display:flex;"><span>Exporter <span style="color:#268bd2">export</span>(Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> arg0) <span style="color:#268bd2">throws</span> RpcException; |
| </span></span></code></pre></div><p>对于 refer 方法,通过遍历 refer 的参数列表即可获取 URL 数据,这个还比较简单。对于 export 方法,获取 URL 数据则要麻烦一些。export 参数列表中没有 URL 参数,因此需要从 Invoker 参数中获取 URL 数据。获取方式是调用 Invoker 中可返回 URL 的 getter 方法,比如 getUrl。如果 Invoker 中无相关 getter 方法,此时则会抛出异常。整个逻辑如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#719e07">for</span> (Method method : methods) { |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> rt <span style="color:#719e07">=</span> method.getReturnType(); |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> pts <span style="color:#719e07">=</span> method.getParameterTypes(); |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> ets <span style="color:#719e07">=</span> method.getExceptionTypes(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Adaptive adaptiveAnnotation <span style="color:#719e07">=</span> method.getAnnotation(Adaptive.class); |
| </span></span><span style="display:flex;"><span> StringBuilder code <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> StringBuilder(512); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (adaptiveAnnotation <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${无 Adaptive 注解方法代码生成逻辑}</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> urlTypeIndex <span style="color:#719e07">=</span> <span style="color:#719e07">-</span>1; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历参数列表,确定 URL 参数位置</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> pts.length; <span style="color:#719e07">++</span>i) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (pts<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>.equals(URL.class)) { |
| </span></span><span style="display:flex;"><span> urlTypeIndex <span style="color:#719e07">=</span> i; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// urlTypeIndex != -1,表示参数列表中存在 URL 参数</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (urlTypeIndex <span style="color:#719e07">!=</span> <span style="color:#719e07">-</span>1) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 为 URL 类型参数生成判空代码,格式如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// if (arg + urlTypeIndex == null) </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// throw new IllegalArgumentException(&#34;url == null&#34;);</span> |
| </span></span><span style="display:flex;"><span> String s <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;\nif (arg%d == null) throw new IllegalArgumentException(\&#34;url == null\&#34;);&#34;</span>, |
| </span></span><span style="display:flex;"><span> urlTypeIndex); |
| </span></span><span style="display:flex;"><span> code.append(s); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 为 URL 类型参数生成赋值代码,形如 URL url = arg1</span> |
| </span></span><span style="display:flex;"><span> s <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;\n%s url = arg%d;&#34;</span>, URL.class.getName(), urlTypeIndex); |
| </span></span><span style="display:flex;"><span> code.append(s); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 参数列表中不存在 URL 类型参数</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> String attribMethod <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> LBL_PTS: |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历方法的参数类型列表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> pts.length; <span style="color:#719e07">++</span>i) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取某一类型参数的全部方法</span> |
| </span></span><span style="display:flex;"><span> Method<span style="color:#719e07">[]</span> ms <span style="color:#719e07">=</span> pts<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>.getMethods(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历方法列表,寻找可返回 URL 的 getter 方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Method m : ms) { |
| </span></span><span style="display:flex;"><span> String name <span style="color:#719e07">=</span> m.getName(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 1. 方法名以 get 开头,或方法名大于3个字符</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 2. 方法的访问权限为 public</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 3. 非静态方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 4. 方法参数数量为0</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 5. 方法返回值类型为 URL</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((name.startsWith(<span style="color:#2aa198">&#34;get&#34;</span>) <span style="color:#719e07">||</span> name.length() <span style="color:#719e07">&gt;</span> 3) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> Modifier.isPublic(m.getModifiers()) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>Modifier.isStatic(m.getModifiers()) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> m.getParameterTypes().length <span style="color:#719e07">==</span> 0 |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> m.getReturnType() <span style="color:#719e07">==</span> URL.class) { |
| </span></span><span style="display:flex;"><span> urlTypeIndex <span style="color:#719e07">=</span> i; |
| </span></span><span style="display:flex;"><span> attribMethod <span style="color:#719e07">=</span> name; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 结束 for (int i = 0; i &lt; pts.length; ++i) 循环</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span> LBL_PTS; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (attribMethod <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果所有参数中均不包含可返回 URL 的 getter 方法,则抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;fail to create adaptive class for interface ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 为可返回 URL 的参数生成判空代码,格式如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// if (arg + urlTypeIndex == null) </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// throw new IllegalArgumentException(&#34;参数全限定名 + argument == null&#34;);</span> |
| </span></span><span style="display:flex;"><span> String s <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;\nif (arg%d == null) throw new IllegalArgumentException(\&#34;%s argument == null\&#34;);&#34;</span>, |
| </span></span><span style="display:flex;"><span> urlTypeIndex, pts<span style="color:#719e07">[</span>urlTypeIndex<span style="color:#719e07">]</span>.getName()); |
| </span></span><span style="display:flex;"><span> code.append(s); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 为 getter 方法返回的 URL 生成判空代码,格式如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// if (argN.getter方法名() == null) </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// throw new IllegalArgumentException(参数全限定名 + argument getUrl() == null);</span> |
| </span></span><span style="display:flex;"><span> s <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;\nif (arg%d.%s() == null) throw new IllegalArgumentException(\&#34;%s argument %s() == null\&#34;);&#34;</span>, |
| </span></span><span style="display:flex;"><span> urlTypeIndex, attribMethod, pts<span style="color:#719e07">[</span>urlTypeIndex<span style="color:#719e07">]</span>.getName(), attribMethod); |
| </span></span><span style="display:flex;"><span> code.append(s); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成赋值语句,格式如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// URL全限定名 url = argN.getter方法名(),比如 </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// com.alibaba.dubbo.common.URL url = invoker.getUrl();</span> |
| </span></span><span style="display:flex;"><span> s <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;%s url = arg%d.%s();&#34;</span>, URL.class.getName(), urlTypeIndex, attribMethod); |
| </span></span><span style="display:flex;"><span> code.append(s); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略无关代码</span> |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略无关代码</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码有点多,需要耐心看一下。这段代码主要目的是为了获取 URL 数据,并为之生成判空和赋值代码。以 Protocol 的 refer 和 export 方法为例,上面的代码为它们生成如下内容(代码已格式化):</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>refer: |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">if</span> (arg1 <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;url == null&#34;</span>); |
| </span></span><span style="display:flex;"><span>com.alibaba.dubbo.common.URL url <span style="color:#719e07">=</span> arg1; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span>export: |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">if</span> (arg0 <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;com.alibaba.dubbo.rpc.Invoker argument == null&#34;</span>); |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">if</span> (arg0.getUrl() <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;com.alibaba.dubbo.rpc.Invoker argument getUrl() == null&#34;</span>); |
| </span></span><span style="display:flex;"><span>com.alibaba.dubbo.common.URL url <span style="color:#719e07">=</span> arg0.getUrl(); |
| </span></span></code></pre></div><h5 id="2233-获取-adaptive-注解值">2.2.3.3 获取 Adaptive 注解值</h5> |
| <p>Adaptive 注解值 value 类型为 String[],可填写多个值,默认情况下为空数组。若 value 为非空数组,直接获取数组内容即可。若 value 为空数组,则需进行额外处理。处理过程是将类名转换为字符数组,然后遍历字符数组,并将字符放入 StringBuilder 中。若字符为大写字母,则向 StringBuilder 中添加点号,随后将字符变为小写存入 StringBuilder 中。比如 LoadBalance 经过处理后,得到 load.balance。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#719e07">for</span> (Method method : methods) { |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> rt <span style="color:#719e07">=</span> method.getReturnType(); |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> pts <span style="color:#719e07">=</span> method.getParameterTypes(); |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> ets <span style="color:#719e07">=</span> method.getExceptionTypes(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Adaptive adaptiveAnnotation <span style="color:#719e07">=</span> method.getAnnotation(Adaptive.class); |
| </span></span><span style="display:flex;"><span> StringBuilder code <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> StringBuilder(512); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (adaptiveAnnotation <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${无 Adaptive 注解方法代码生成逻辑}</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${获取 URL 数据}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> String<span style="color:#719e07">[]</span> value <span style="color:#719e07">=</span> adaptiveAnnotation.value(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// value 为空数组</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (value.length <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取类名,并将类名转换为字符数组</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">char</span><span style="color:#719e07">[]</span> charArray <span style="color:#719e07">=</span> type.getSimpleName().toCharArray(); |
| </span></span><span style="display:flex;"><span> StringBuilder sb <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> StringBuilder(128); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历字节数组</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> charArray.length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测当前字符是否为大写字母</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (Character.isUpperCase(charArray<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (i <span style="color:#719e07">!=</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 向 sb 中添加点号</span> |
| </span></span><span style="display:flex;"><span> sb.append(<span style="color:#2aa198">&#34;.&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将字符变为小写,并添加到 sb 中</span> |
| </span></span><span style="display:flex;"><span> sb.append(Character.toLowerCase(charArray<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加字符到 sb 中</span> |
| </span></span><span style="display:flex;"><span> sb.append(charArray<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> value <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> String<span style="color:#719e07">[]</span>{sb.toString()}; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略无关代码</span> |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略无关逻辑</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><h5 id="2234-检测-invocation-参数">2.2.3.4 检测 Invocation 参数</h5> |
| <p>此段逻辑是检测方法列表中是否存在 Invocation 类型的参数,若存在,则为其生成判空代码和其他一些代码。相应的逻辑如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#719e07">for</span> (Method method : methods) { |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> rt <span style="color:#719e07">=</span> method.getReturnType(); |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> pts <span style="color:#719e07">=</span> method.getParameterTypes(); <span style="color:#586e75">// 获取参数类型列表</span> |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> ets <span style="color:#719e07">=</span> method.getExceptionTypes(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Adaptive adaptiveAnnotation <span style="color:#719e07">=</span> method.getAnnotation(Adaptive.class); |
| </span></span><span style="display:flex;"><span> StringBuilder code <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> StringBuilder(512); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (adaptiveAnnotation <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${无 Adaptive 注解方法代码生成逻辑}</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${获取 URL 数据}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${获取 Adaptive 注解值}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> hasInvocation <span style="color:#719e07">=</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历参数类型列表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> pts.length; <span style="color:#719e07">++</span>i) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 判断当前参数名称是否等于 com.alibaba.dubbo.rpc.Invocation</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (pts<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>.getName().equals(<span style="color:#2aa198">&#34;com.alibaba.dubbo.rpc.Invocation&#34;</span>)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 为 Invocation 类型参数生成判空代码</span> |
| </span></span><span style="display:flex;"><span> String s <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;\nif (arg%d == null) throw new IllegalArgumentException(\&#34;invocation == null\&#34;);&#34;</span>, i); |
| </span></span><span style="display:flex;"><span> code.append(s); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成 getMethodName 方法调用代码,格式为:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// String methodName = argN.getMethodName();</span> |
| </span></span><span style="display:flex;"><span> s <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;\nString methodName = arg%d.getMethodName();&#34;</span>, i); |
| </span></span><span style="display:flex;"><span> code.append(s); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 hasInvocation 为 true</span> |
| </span></span><span style="display:flex;"><span> hasInvocation <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略无关逻辑</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><h5 id="2235-生成拓展名获取逻辑">2.2.3.5 生成拓展名获取逻辑</h5> |
| <p>本段逻辑用于根据 SPI 和 Adaptive 注解值生成“获取拓展名逻辑”,同时生成逻辑也受 Invocation 类型参数影响,综合因素导致本段逻辑相对复杂。本段逻辑可能会生成但不限于下面的代码:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String extName <span style="color:#719e07">=</span> (url.getProtocol() <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">?</span> <span style="color:#2aa198">&#34;dubbo&#34;</span> : url.getProtocol()); |
| </span></span></code></pre></div><p>或</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String extName <span style="color:#719e07">=</span> url.getMethodParameter(methodName, <span style="color:#2aa198">&#34;loadbalance&#34;</span>, <span style="color:#2aa198">&#34;random&#34;</span>); |
| </span></span></code></pre></div><p>亦或是</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String extName <span style="color:#719e07">=</span> url.getParameter(<span style="color:#2aa198">&#34;client&#34;</span>, url.getParameter(<span style="color:#2aa198">&#34;transporter&#34;</span>, <span style="color:#2aa198">&#34;netty&#34;</span>)); |
| </span></span></code></pre></div><p>本段逻辑复杂之处在于条件分支比较多,大家在阅读源码时需要知道每个条件分支的意义是什么,否则不太容易看懂相关代码。下面开始分析本段逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#719e07">for</span> (Method method : methods) { |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> rt <span style="color:#719e07">=</span> method.getReturnType(); |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> pts <span style="color:#719e07">=</span> method.getParameterTypes(); |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> ets <span style="color:#719e07">=</span> method.getExceptionTypes(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Adaptive adaptiveAnnotation <span style="color:#719e07">=</span> method.getAnnotation(Adaptive.class); |
| </span></span><span style="display:flex;"><span> StringBuilder code <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> StringBuilder(512); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (adaptiveAnnotation <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// $无 Adaptive 注解方法代码生成逻辑}</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${获取 URL 数据}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${获取 Adaptive 注解值}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${检测 Invocation 参数}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置默认拓展名,cachedDefaultName 源于 SPI 注解值,默认情况下,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// SPI 注解值为空串,此时 cachedDefaultName = null</span> |
| </span></span><span style="display:flex;"><span> String defaultExtName <span style="color:#719e07">=</span> cachedDefaultName; |
| </span></span><span style="display:flex;"><span> String getNameCode <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 value,这里的 value 是 Adaptive 的注解值,2.2.3.3 节分析过 value 变量的获取过程。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 此处循环目的是生成从 URL 中获取拓展名的代码,生成的代码会赋值给 getNameCode 变量。注意这</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 个循环的遍历顺序是由后向前遍历的。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> value.length <span style="color:#719e07">-</span> 1; i <span style="color:#719e07">&gt;=</span> 0; <span style="color:#719e07">--</span>i) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 当 i 为最后一个元素的坐标时</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (i <span style="color:#719e07">==</span> value.length <span style="color:#719e07">-</span> 1) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 默认拓展名非空</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#cb4b16">null</span> <span style="color:#719e07">!=</span> defaultExtName) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// protocol 是 url 的一部分,可通过 getProtocol 方法获取,其他的则是从</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// URL 参数中获取。因为获取方式不同,所以这里要判断 value[i] 是否为 protocol</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span><span style="color:#2aa198">&#34;protocol&#34;</span>.equals(value<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>)) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// hasInvocation 用于标识方法参数列表中是否有 Invocation 类型参数</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (hasInvocation) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成的代码功能等价于下面的代码:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// url.getMethodParameter(methodName, value[i], defaultExtName)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 以 LoadBalance 接口的 select 方法为例,最终生成的代码如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// url.getMethodParameter(methodName, &#34;loadbalance&#34;, &#34;random&#34;)</span> |
| </span></span><span style="display:flex;"><span> getNameCode <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;url.getMethodParameter(methodName, \&#34;%s\&#34;, \&#34;%s\&#34;)&#34;</span>, value<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>, defaultExtName); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成的代码功能等价于下面的代码:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// url.getParameter(value[i], defaultExtName)</span> |
| </span></span><span style="display:flex;"><span> getNameCode <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;url.getParameter(\&#34;%s\&#34;, \&#34;%s\&#34;)&#34;</span>, value<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>, defaultExtName); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成的代码功能等价于下面的代码:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ( url.getProtocol() == null ? defaultExtName : url.getProtocol() )</span> |
| </span></span><span style="display:flex;"><span> getNameCode <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;( url.getProtocol() == null ? \&#34;%s\&#34; : url.getProtocol() )&#34;</span>, defaultExtName); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 默认拓展名为空</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span><span style="color:#2aa198">&#34;protocol&#34;</span>.equals(value<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>)) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (hasInvocation) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成代码格式同上</span> |
| </span></span><span style="display:flex;"><span> getNameCode <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;url.getMethodParameter(methodName, \&#34;%s\&#34;, \&#34;%s\&#34;)&#34;</span>, value<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>, defaultExtName); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成的代码功能等价于下面的代码:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// url.getParameter(value[i])</span> |
| </span></span><span style="display:flex;"><span> getNameCode <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;url.getParameter(\&#34;%s\&#34;)&#34;</span>, value<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成从 url 中获取协议的代码,比如 &#34;dubbo&#34;</span> |
| </span></span><span style="display:flex;"><span> getNameCode <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;url.getProtocol()&#34;</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span><span style="color:#2aa198">&#34;protocol&#34;</span>.equals(value<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>)) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (hasInvocation) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成代码格式同上</span> |
| </span></span><span style="display:flex;"><span> getNameCode <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;url.getMethodParameter(methodName, \&#34;%s\&#34;, \&#34;%s\&#34;)&#34;</span>, value<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>, defaultExtName); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成的代码功能等价于下面的代码:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// url.getParameter(value[i], getNameCode)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 以 Transporter 接口的 connect 方法为例,最终生成的代码如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// url.getParameter(&#34;client&#34;, url.getParameter(&#34;transporter&#34;, &#34;netty&#34;))</span> |
| </span></span><span style="display:flex;"><span> getNameCode <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;url.getParameter(\&#34;%s\&#34;, %s)&#34;</span>, value<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>, getNameCode); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成的代码功能等价于下面的代码:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// url.getProtocol() == null ? getNameCode : url.getProtocol()</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 以 Protocol 接口的 connect 方法为例,最终生成的代码如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// url.getProtocol() == null ? &#34;dubbo&#34; : url.getProtocol()</span> |
| </span></span><span style="display:flex;"><span> getNameCode <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;url.getProtocol() == null ? (%s) : url.getProtocol()&#34;</span>, getNameCode); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成 extName 赋值代码</span> |
| </span></span><span style="display:flex;"><span> code.append(<span style="color:#2aa198">&#34;\nString extName = &#34;</span>).append(getNameCode).append(<span style="color:#2aa198">&#34;;&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成 extName 判空代码</span> |
| </span></span><span style="display:flex;"><span> String s <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;\nif(extName == null) &#34;</span> <span style="color:#719e07">+</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#2aa198">&#34;throw new IllegalStateException(\&#34;Fail to get extension(%s) name from url(\&#34; + url.toString() + \&#34;) use keys(%s)\&#34;);&#34;</span>, |
| </span></span><span style="display:flex;"><span> type.getName(), Arrays.toString(value)); |
| </span></span><span style="display:flex;"><span> code.append(s); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略无关逻辑</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码比较复杂,不是很好理解。对于这段代码,建议大家写点测试用例,对 Protocol、LoadBalance 以及 Transporter 等接口的自适应拓展类代码生成过程进行调试。这里我以 Transporter 接口的自适应拓展类代码生成过程举例说明。首先看一下 Transporter 接口的定义,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">@SPI</span>(<span style="color:#2aa198">&#34;netty&#34;</span>) |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">interface</span> <span style="color:#268bd2">Transporter</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// @Adaptive({server, transporter})</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Adaptive</span>({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY}) |
| </span></span><span style="display:flex;"><span> Server <span style="color:#268bd2">bind</span>(URL url, ChannelHandler handler) <span style="color:#268bd2">throws</span> RemotingException; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// @Adaptive({client, transporter})</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Adaptive</span>({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) |
| </span></span><span style="display:flex;"><span> Client <span style="color:#268bd2">connect</span>(URL url, ChannelHandler handler) <span style="color:#268bd2">throws</span> RemotingException; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>下面对 connect 方法代理逻辑生成的过程进行分析,此时生成代理逻辑所用到的变量如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String defaultExtName <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;netty&#34;</span>; |
| </span></span><span style="display:flex;"><span><span style="color:#dc322f">boolean</span> hasInvocation <span style="color:#719e07">=</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span>String getNameCode <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span>String<span style="color:#719e07">[]</span> value <span style="color:#719e07">=</span> <span style="color:#719e07">[</span><span style="color:#2aa198">&#34;client&#34;</span>, <span style="color:#2aa198">&#34;transporter&#34;</span><span style="color:#719e07">]</span>; |
| </span></span></code></pre></div><p>下面对 value 数组进行遍历,此时 i = 1, value[i] = &ldquo;transporter&rdquo;,生成的代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>getNameCode <span style="color:#719e07">=</span> url.getParameter(<span style="color:#2aa198">&#34;transporter&#34;</span>, <span style="color:#2aa198">&#34;netty&#34;</span>); |
| </span></span></code></pre></div><p>接下来,for 循环继续执行,此时 i = 0, value[i] = &ldquo;client&rdquo;,生成的代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>getNameCode <span style="color:#719e07">=</span> url.getParameter(<span style="color:#2aa198">&#34;client&#34;</span>, url.getParameter(<span style="color:#2aa198">&#34;transporter&#34;</span>, <span style="color:#2aa198">&#34;netty&#34;</span>)); |
| </span></span></code></pre></div><p>for 循环结束运行,现在为 extName 变量生成赋值和判空代码,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>String extName <span style="color:#719e07">=</span> url.getParameter(<span style="color:#2aa198">&#34;client&#34;</span>, url.getParameter(<span style="color:#2aa198">&#34;transporter&#34;</span>, <span style="color:#2aa198">&#34;netty&#34;</span>)); |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">if</span> (extName <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException( |
| </span></span><span style="display:flex;"><span> <span style="color:#2aa198">&#34;Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(&#34;</span> <span style="color:#719e07">+</span> url.toString() |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;) use keys([client, transporter])&#34;</span>); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><h5 id="2236-生成拓展加载与目标方法调用逻辑">2.2.3.6 生成拓展加载与目标方法调用逻辑</h5> |
| <p>本段代码逻辑用于根据拓展名加载拓展实例,并调用拓展实例的目标方法。相关逻辑如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#719e07">for</span> (Method method : methods) { |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> rt <span style="color:#719e07">=</span> method.getReturnType(); |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> pts <span style="color:#719e07">=</span> method.getParameterTypes(); |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> ets <span style="color:#719e07">=</span> method.getExceptionTypes(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Adaptive adaptiveAnnotation <span style="color:#719e07">=</span> method.getAnnotation(Adaptive.class); |
| </span></span><span style="display:flex;"><span> StringBuilder code <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> StringBuilder(512); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (adaptiveAnnotation <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// $无 Adaptive 注解方法代码生成逻辑}</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${获取 URL 数据}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${获取 Adaptive 注解值}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${检测 Invocation 参数}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${生成拓展名获取逻辑}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成拓展获取代码,格式如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// type全限定名 extension = (type全限定名)ExtensionLoader全限定名</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// .getExtensionLoader(type全限定名.class).getExtension(extName);</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// Tips: 格式化字符串中的 %&lt;s 表示使用前一个转换符所描述的参数,即 type 全限定名</span> |
| </span></span><span style="display:flex;"><span> s <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;\n%s extension = (%&lt;s)%s.getExtensionLoader(%s.class).getExtension(extName);&#34;</span>, |
| </span></span><span style="display:flex;"><span> type.getName(), ExtensionLoader.class.getSimpleName(), type.getName()); |
| </span></span><span style="display:flex;"><span> code.append(s); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果方法返回值类型非 void,则生成 return 语句。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>rt.equals(<span style="color:#dc322f">void</span>.class)) { |
| </span></span><span style="display:flex;"><span> code.append(<span style="color:#2aa198">&#34;\nreturn &#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成目标方法调用逻辑,格式为:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// extension.方法名(arg0, arg2, ..., argN);</span> |
| </span></span><span style="display:flex;"><span> s <span style="color:#719e07">=</span> String.format(<span style="color:#2aa198">&#34;extension.%s(&#34;</span>, method.getName()); |
| </span></span><span style="display:flex;"><span> code.append(s); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> pts.length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (i <span style="color:#719e07">!=</span> 0) |
| </span></span><span style="display:flex;"><span> code.append(<span style="color:#2aa198">&#34;, &#34;</span>); |
| </span></span><span style="display:flex;"><span> code.append(<span style="color:#2aa198">&#34;arg&#34;</span>).append(i); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> code.append(<span style="color:#2aa198">&#34;);&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略无关逻辑</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以 Protocol 接口举例说明,上面代码生成的内容如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>com.alibaba.dubbo.rpc.Protocol extension <span style="color:#719e07">=</span> (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader |
| </span></span><span style="display:flex;"><span> .getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">return</span> extension.refer(arg0, arg1); |
| </span></span></code></pre></div><h5 id="2237-生成完整的方法">2.2.3.7 生成完整的方法</h5> |
| <p>本节进行代码生成的收尾工作,主要用于生成方法定义的代码。相关逻辑如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#719e07">for</span> (Method method : methods) { |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> rt <span style="color:#719e07">=</span> method.getReturnType(); |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> pts <span style="color:#719e07">=</span> method.getParameterTypes(); |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> ets <span style="color:#719e07">=</span> method.getExceptionTypes(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Adaptive adaptiveAnnotation <span style="color:#719e07">=</span> method.getAnnotation(Adaptive.class); |
| </span></span><span style="display:flex;"><span> StringBuilder code <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> StringBuilder(512); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (adaptiveAnnotation <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// $无 Adaptive 注解方法代码生成逻辑}</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${获取 URL 数据}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${获取 Adaptive 注解值}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${检测 Invocation 参数}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${生成拓展名获取逻辑}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${生成拓展加载与目标方法调用逻辑}</span> |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75">// public + 返回值全限定名 + 方法名 + (</span> |
| </span></span><span style="display:flex;"><span>codeBuilder.append(<span style="color:#2aa198">&#34;\npublic &#34;</span>) |
| </span></span><span style="display:flex;"><span> .append(rt.getCanonicalName()) |
| </span></span><span style="display:flex;"><span> .append(<span style="color:#2aa198">&#34; &#34;</span>) |
| </span></span><span style="display:flex;"><span> .append(method.getName()) |
| </span></span><span style="display:flex;"><span> .append(<span style="color:#2aa198">&#34;(&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75">// 添加参数列表代码</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> pts.length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (i <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> codeBuilder.append(<span style="color:#2aa198">&#34;, &#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> codeBuilder.append(pts<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>.getCanonicalName()); |
| </span></span><span style="display:flex;"><span> codeBuilder.append(<span style="color:#2aa198">&#34; &#34;</span>); |
| </span></span><span style="display:flex;"><span> codeBuilder.append(<span style="color:#2aa198">&#34;arg&#34;</span>).append(i); |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span>codeBuilder.append(<span style="color:#2aa198">&#34;)&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75">// 添加异常抛出代码</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">if</span> (ets.length <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> codeBuilder.append(<span style="color:#2aa198">&#34; throws &#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> ets.length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (i <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> codeBuilder.append(<span style="color:#2aa198">&#34;, &#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> codeBuilder.append(ets<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>.getCanonicalName()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span>codeBuilder.append(<span style="color:#2aa198">&#34; {&#34;</span>); |
| </span></span><span style="display:flex;"><span>codeBuilder.append(code.toString()); |
| </span></span><span style="display:flex;"><span>codeBuilder.append(<span style="color:#2aa198">&#34;\n}&#34;</span>); |
| </span></span></code></pre></div><p>以 Protocol 的 refer 方法为例,上面代码生成的内容如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> com.alibaba.dubbo.rpc.Invoker <span style="color:#268bd2">refer</span>(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 方法体</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><h2 id="3总结">3.总结</h2> |
| <p>到此,关于自适应拓展的原理,实现就分析完了。总的来说自适应拓展整个逻辑还是很复杂的,并不是很容易弄懂。因此,大家在阅读该部分源码时,耐心一些。同时多进行调试,也可以通过生成好的代码思考代码的生成逻辑。好了,本篇文章就分析到这里。</p></description></item><item><title>Docsv2.7: 服务导出</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/export-service/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/export-service/</guid><description> |
| <h2 id="1简介">1.简介</h2> |
| <p>本篇文章,我们来研究一下 Dubbo 导出服务的过程。Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑。整个逻辑大致可分为三个部分,第一部分是前置工作,主要用于检查参数,组装 URL。第二部分是导出服务,包含导出服务到本地 (JVM),和导出服务到远程两个过程。第三部分是向注册中心注册服务,用于服务发现。本篇文章将会对这三个部分代码进行详细的分析。</p> |
| <h2 id="2源码分析">2.源码分析</h2> |
| <p>服务导出的入口方法是 ServiceBean 的 onApplicationEvent。onApplicationEvent 是一个事件响应方法,该方法会在收到 Spring 上下文刷新事件后执行服务导出操作。方法代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">onApplicationEvent</span>(ContextRefreshedEvent event) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 是否有延迟导出 &amp;&amp; 是否已导出 &amp;&amp; 是不是已被取消导出</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (isDelay() <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>isExported() <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>isUnexported()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 导出服务</span> |
| </span></span><span style="display:flex;"><span> export(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>这个方法首先会根据条件决定是否导出服务,比如有些服务设置了延时导出,那么此时就不应该在此处导出。还有一些服务已经被导出了,或者当前服务被取消导出了,此时也不能再次导出相关服务。注意这里的 isDelay 方法,这个方法字面意思是“是否延迟导出服务”,返回 true 表示延迟导出,false 表示不延迟导出。但是该方法真实意思却并非如此,当方法返回 true 时,表示无需延迟导出。返回 false 时,表示需要延迟导出。与字面意思恰恰相反,这个需要大家注意一下。下面我们来看一下这个方法的逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#586e75">// -☆- ServiceBean</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">boolean</span> <span style="color:#268bd2">isDelay</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 delay</span> |
| </span></span><span style="display:flex;"><span> Integer delay <span style="color:#719e07">=</span> getDelay(); |
| </span></span><span style="display:flex;"><span> ProviderConfig provider <span style="color:#719e07">=</span> getProvider(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (delay <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> provider <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果前面获取的 delay 为空,这里继续获取</span> |
| </span></span><span style="display:flex;"><span> delay <span style="color:#719e07">=</span> provider.getDelay(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 判断 delay 是否为空,或者等于 -1</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> supportedApplicationListener <span style="color:#719e07">&amp;&amp;</span> (delay <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> delay <span style="color:#719e07">==</span> <span style="color:#719e07">-</span>1); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>暂时忽略 supportedApplicationListener 这个条件,当 delay 为空,或者等于-1时,该方法返回 true,而不是 false。这个方法的返回值让人有点困惑。该方法目前已被重构,详细请参考 <a href="https://github.com/apache/dubbo/pull/2686">dubbo #2686</a>。</p> |
| <p>现在解释一下 supportedApplicationListener 变量含义,该变量用于表示当前的 Spring 容器是否支持 ApplicationListener,这个值初始为 false。在 Spring 容器将自己设置到 ServiceBean 中时,ServiceBean 的 setApplicationContext 方法会检测 Spring 容器是否支持 ApplicationListener。若支持,则将 supportedApplicationListener 置为 true。ServiceBean 是 Dubbo 与 Spring 框架进行整合的关键,可以看做是两个框架之间的桥梁。具有同样作用的类还有 ReferenceBean。</p> |
| <p>现在我们知道了 Dubbo 服务导出过程的起点,接下来对服务导出的前置逻辑进行分析。</p> |
| <h3 id="21-前置工作">2.1 前置工作</h3> |
| <p>前置工作主要包含两个部分,分别是配置检查,以及 URL 装配。在导出服务之前,Dubbo 需要检查用户的配置是否合理,或者为用户补充缺省配置。配置检查完成后,接下来需要根据这些配置组装 URL。在 Dubbo 中,URL 的作用十分重要。Dubbo 使用 URL 作为配置载体,所有的拓展点都是通过 URL 获取配置。这一点,官方文档中有所说明。</p> |
| <blockquote> |
| <p>采用 URL 作为配置信息的统一格式,所有扩展点都通过传递 URL 携带配置信息。</p> |
| </blockquote> |
| <p>接下来,我们先来分析配置检查部分的源码,随后再来分析 URL 组装部分的源码。</p> |
| <h4 id="211-检查配置">2.1.1 检查配置</h4> |
| <p>本节我们接着前面的源码向下分析,前面说过 onApplicationEvent 方法在经过一些判断后,会决定是否调用 export 方法导出服务。那么下面我们从 export 方法开始进行分析,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">synchronized</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">export</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (provider <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 export 和 delay 配置</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (export <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> export <span style="color:#719e07">=</span> provider.getExport(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (delay <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> delay <span style="color:#719e07">=</span> provider.getDelay(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 export 为 false,则不导出服务</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (export <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>export) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// delay &gt; 0,延时导出服务</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (delay <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> delay <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> delayExportExecutor.schedule(<span style="color:#719e07">new</span> Runnable() { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">run</span>() { |
| </span></span><span style="display:flex;"><span> doExport(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> }, delay, TimeUnit.MILLISECONDS); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 立即导出服务</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> doExport(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>export 方法对两项配置进行了检查,并根据配置执行相应的动作。首先是 export 配置,这个配置决定了是否导出服务。有时候我们只是想本地启动服务进行一些调试工作,我们并不希望把本地启动的服务暴露出去给别人调用。此时,我们可通过配置 export 禁止服务导出,比如:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:provider</span> export=<span style="color:#2aa198">&#34;false&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>delay 配置顾名思义,用于延迟导出服务,这个就不分析了。下面,我们继续分析源码,这次要分析的是 doExport 方法。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">protected</span> <span style="color:#268bd2">synchronized</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">doExport</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (unexported) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;Already unexported!&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (exported) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> exported <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 interfaceName 是否合法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (interfaceName <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> interfaceName.length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;interface not allow null!&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 provider 是否为空,为空则新建一个,并通过系统变量为其初始化</span> |
| </span></span><span style="display:flex;"><span> checkDefault(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 下面几个 if 语句用于检测 provider、application 等核心配置类对象是否为空,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 若为空,则尝试从其他配置类对象中获取相应的实例。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (provider <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (application <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> application <span style="color:#719e07">=</span> provider.getApplication(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (module <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> module <span style="color:#719e07">=</span> provider.getModule(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (registries <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) {...} |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (monitor <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) {...} |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (protocols <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) {...} |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (module <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (registries <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> registries <span style="color:#719e07">=</span> module.getRegistries(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (monitor <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) {...} |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (application <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (registries <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> registries <span style="color:#719e07">=</span> application.getRegistries(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (monitor <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) {...} |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 ref 是否为泛化服务类型</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ref <span style="color:#719e07">instanceof</span> GenericService) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 interfaceClass 为 GenericService.class</span> |
| </span></span><span style="display:flex;"><span> interfaceClass <span style="color:#719e07">=</span> GenericService.class; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (StringUtils.isEmpty(generic)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 generic = &#34;true&#34;</span> |
| </span></span><span style="display:flex;"><span> generic <span style="color:#719e07">=</span> Boolean.TRUE.toString(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ref 非 GenericService 类型</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> interfaceClass <span style="color:#719e07">=</span> Class.forName(interfaceName, <span style="color:#cb4b16">true</span>, Thread.currentThread() |
| </span></span><span style="display:flex;"><span> .getContextClassLoader()); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (ClassNotFoundException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 interfaceClass,以及 &lt;dubbo:method&gt; 标签中的必要字段进行检查</span> |
| </span></span><span style="display:flex;"><span> checkInterfaceAndMethods(interfaceClass, methods); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 ref 合法性进行检测</span> |
| </span></span><span style="display:flex;"><span> checkRef(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 generic = &#34;false&#34;</span> |
| </span></span><span style="display:flex;"><span> generic <span style="color:#719e07">=</span> Boolean.FALSE.toString(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// local 和 stub 在功能应该是一致的,用于配置本地存根</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (local <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#2aa198">&#34;true&#34;</span>.equals(local)) { |
| </span></span><span style="display:flex;"><span> local <span style="color:#719e07">=</span> interfaceName <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;Local&#34;</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> localClass; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取本地存根类</span> |
| </span></span><span style="display:flex;"><span> localClass <span style="color:#719e07">=</span> ClassHelper.forNameWithThreadContextClassLoader(local); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (ClassNotFoundException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测本地存根类是否可赋值给接口类,若不可赋值则会抛出异常,提醒使用者本地存根类类型不合法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>interfaceClass.isAssignableFrom(localClass)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;The local implementation class &#34;</span> <span style="color:#719e07">+</span> localClass.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34; not implement interface &#34;</span> <span style="color:#719e07">+</span> interfaceName); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (stub <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 此处的代码和上一个 if 分支的代码基本一致,这里省略</span> |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测各种对象是否为空,为空则新建,或者抛出异常</span> |
| </span></span><span style="display:flex;"><span> checkApplication(); |
| </span></span><span style="display:flex;"><span> checkRegistry(); |
| </span></span><span style="display:flex;"><span> checkProtocol(); |
| </span></span><span style="display:flex;"><span> appendProperties(<span style="color:#719e07">this</span>); |
| </span></span><span style="display:flex;"><span> checkStubAndMock(interfaceClass); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (path <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> path.length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> path <span style="color:#719e07">=</span> interfaceName; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 导出服务</span> |
| </span></span><span style="display:flex;"><span> doExportUrls(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ProviderModel 表示服务提供者模型,此对象中存储了与服务提供者相关的信息。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 比如服务的配置信息,服务实例等。每个被导出的服务对应一个 ProviderModel。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ApplicationModel 持有所有的 ProviderModel。</span> |
| </span></span><span style="display:flex;"><span> ProviderModel providerModel <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ProviderModel(getUniqueServiceName(), <span style="color:#719e07">this</span>, ref); |
| </span></span><span style="display:flex;"><span> ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上就是配置检查的相关分析,代码比较多,需要大家耐心看一下。下面对配置检查的逻辑进行简单的总结,如下:</p> |
| <ol> |
| <li>检测 &lt;dubbo:service&gt; 标签的 interface 属性合法性,不合法则抛出异常</li> |
| <li>检测 ProviderConfig、ApplicationConfig 等核心配置类对象是否为空,若为空,则尝试从其他配置类对象中获取相应的实例。</li> |
| <li>检测并处理泛化服务和普通服务类</li> |
| <li>检测本地存根配置,并进行相应的处理</li> |
| <li>对 ApplicationConfig、RegistryConfig 等配置类进行检测,为空则尝试创建,若无法创建则抛出异常</li> |
| </ol> |
| <p>配置检查并非本文重点,因此这里不打算对 doExport 方法所调用的方法进行分析(doExportUrls 方法除外)。在这些方法中,除了 appendProperties 方法稍微复杂一些,其他方法逻辑不是很复杂。因此,大家可自行分析。</p> |
| <h4 id="212-多协议多注册中心导出服务">2.1.2 多协议多注册中心导出服务</h4> |
| <p>Dubbo 允许我们使用不同的协议导出服务,也允许我们向多个注册中心注册服务。Dubbo 在 doExportUrls 方法中对多协议,多注册中心进行了支持。相关代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">doExportUrls</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 加载注册中心链接</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> registryURLs <span style="color:#719e07">=</span> loadRegistries(<span style="color:#cb4b16">true</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 protocols,并在每个协议下导出服务</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (ProtocolConfig protocolConfig : protocols) { |
| </span></span><span style="display:flex;"><span> doExportUrlsFor1Protocol(protocolConfig, registryURLs); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码首先是通过 loadRegistries 加载注册中心链接,然后再遍历 ProtocolConfig 集合导出每个服务。并在导出服务的过程中,将服务注册到注册中心。下面,我们先来看一下 loadRegistries 方法的逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">protected</span> List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">loadRegistries</span>(<span style="color:#dc322f">boolean</span> provider) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测是否存在注册中心配置类,不存在则抛出异常</span> |
| </span></span><span style="display:flex;"><span> checkRegistry(); |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> registryList <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (registries <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>registries.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (RegistryConfig config : registries) { |
| </span></span><span style="display:flex;"><span> String address <span style="color:#719e07">=</span> config.getAddress(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (address <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> address.length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 若 address 为空,则将其设为 0.0.0.0</span> |
| </span></span><span style="display:flex;"><span> address <span style="color:#719e07">=</span> Constants.ANYHOST_VALUE; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从系统属性中加载注册中心地址</span> |
| </span></span><span style="display:flex;"><span> String sysaddress <span style="color:#719e07">=</span> System.getProperty(<span style="color:#2aa198">&#34;dubbo.registry.address&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (sysaddress <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> sysaddress.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> address <span style="color:#719e07">=</span> sysaddress; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 address 是否合法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (address.length() <span style="color:#719e07">&gt;</span> 0 <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) { |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span> map <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 ApplicationConfig 中的字段信息到 map 中</span> |
| </span></span><span style="display:flex;"><span> appendParameters(map, application); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 RegistryConfig 字段信息到 map 中</span> |
| </span></span><span style="display:flex;"><span> appendParameters(map, config); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 path、pid,protocol 等信息到 map 中</span> |
| </span></span><span style="display:flex;"><span> map.put(<span style="color:#2aa198">&#34;path&#34;</span>, RegistryService.class.getName()); |
| </span></span><span style="display:flex;"><span> map.put(<span style="color:#2aa198">&#34;dubbo&#34;</span>, Version.getProtocolVersion()); |
| </span></span><span style="display:flex;"><span> map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis())); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ConfigUtils.getPid() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid())); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>map.containsKey(<span style="color:#2aa198">&#34;protocol&#34;</span>)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension(<span style="color:#2aa198">&#34;remote&#34;</span>)) { |
| </span></span><span style="display:flex;"><span> map.put(<span style="color:#2aa198">&#34;protocol&#34;</span>, <span style="color:#2aa198">&#34;remote&#34;</span>); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> map.put(<span style="color:#2aa198">&#34;protocol&#34;</span>, <span style="color:#2aa198">&#34;dubbo&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 解析得到 URL 列表,address 可能包含多个注册中心 ip,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 因此解析得到的是一个 URL 列表</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> urls <span style="color:#719e07">=</span> UrlUtils.parseURLs(address, map); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (URL url : urls) { |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.addParameter(Constants.REGISTRY_KEY, url.getProtocol()); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 URL 协议头设置为 registry</span> |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.setProtocol(Constants.REGISTRY_PROTOCOL); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过判断条件,决定是否添加 url 到 registryList 中,条件如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// (服务提供者 &amp;&amp; register = true 或 null) </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// || (非服务提供者 &amp;&amp; subscribe = true 或 null)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((provider <span style="color:#719e07">&amp;&amp;</span> url.getParameter(Constants.REGISTER_KEY, <span style="color:#cb4b16">true</span>)) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">||</span> (<span style="color:#719e07">!</span>provider <span style="color:#719e07">&amp;&amp;</span> url.getParameter(Constants.SUBSCRIBE_KEY, <span style="color:#cb4b16">true</span>))) { |
| </span></span><span style="display:flex;"><span> registryList.add(url); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> registryList; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>loadRegistries 方法主要包含如下的逻辑:</p> |
| <ol> |
| <li>检测是否存在注册中心配置类,不存在则抛出异常</li> |
| <li>构建参数映射集合,也就是 map</li> |
| <li>构建注册中心链接列表</li> |
| <li>遍历链接列表,并根据条件决定是否将其添加到 registryList 中</li> |
| </ol> |
| <p>关于多协议多注册中心导出服务就先分析到这,代码不是很多,接下来分析 URL 组装过程。</p> |
| <h4 id="213-组装-url">2.1.3 组装 URL</h4> |
| <p>配置检查完毕后,紧接着要做的事情是根据配置,以及其他一些信息组装 URL。前面说过,URL 是 Dubbo 配置的载体,通过 URL 可让 Dubbo 的各种配置在各个模块之间传递。URL 之于 Dubbo,犹如水之于鱼,非常重要。大家在阅读 Dubbo 服务导出相关源码的过程中,要注意 URL 内容的变化。既然 URL 如此重要,那么下面我们来了解一下 URL 组装的过程。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">doExportUrlsFor1Protocol</span>(ProtocolConfig protocolConfig, List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> registryURLs) { |
| </span></span><span style="display:flex;"><span> String name <span style="color:#719e07">=</span> protocolConfig.getName(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果协议名为空,或空串,则将协议名变量设置为 dubbo</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (name <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> name.length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> name <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;dubbo&#34;</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span> map <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 side、版本、时间戳以及进程号等信息到 map 中</span> |
| </span></span><span style="display:flex;"><span> map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE); |
| </span></span><span style="display:flex;"><span> map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion()); |
| </span></span><span style="display:flex;"><span> map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis())); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ConfigUtils.getPid() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid())); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过反射将对象的字段信息添加到 map 中</span> |
| </span></span><span style="display:flex;"><span> appendParameters(map, application); |
| </span></span><span style="display:flex;"><span> appendParameters(map, module); |
| </span></span><span style="display:flex;"><span> appendParameters(map, provider, Constants.DEFAULT_KEY); |
| </span></span><span style="display:flex;"><span> appendParameters(map, protocolConfig); |
| </span></span><span style="display:flex;"><span> appendParameters(map, <span style="color:#719e07">this</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// methods 为 MethodConfig 集合,MethodConfig 中存储了 &lt;dubbo:method&gt; 标签的配置信息</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (methods <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>methods.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 这段代码用于添加 Callback 配置到 map 中,代码太长,待会单独分析</span> |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 generic 是否为 &#34;true&#34;,并根据检测结果向 map 中添加不同的信息</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ProtocolUtils.isGeneric(generic)) { |
| </span></span><span style="display:flex;"><span> map.put(Constants.GENERIC_KEY, generic); |
| </span></span><span style="display:flex;"><span> map.put(Constants.METHODS_KEY, Constants.ANY_VALUE); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> String revision <span style="color:#719e07">=</span> Version.getVersion(interfaceClass, version); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (revision <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> revision.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> map.put(<span style="color:#2aa198">&#34;revision&#34;</span>, revision); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 为接口生成包裹类 Wrapper,Wrapper 中包含了接口的详细信息,比如接口方法名数组,字段信息等</span> |
| </span></span><span style="display:flex;"><span> String<span style="color:#719e07">[]</span> methods <span style="color:#719e07">=</span> Wrapper.getWrapper(interfaceClass).getMethodNames(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加方法名到 map 中,如果包含多个方法名,则用逗号隔开,比如 method = init,destroy</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (methods.length <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> logger.warn(<span style="color:#2aa198">&#34;NO method found in service interface ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> map.put(Constants.METHODS_KEY, Constants.ANY_VALUE); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将逗号作为分隔符连接方法名,并将连接后的字符串放入 map 中</span> |
| </span></span><span style="display:flex;"><span> map.put(Constants.METHODS_KEY, StringUtils.join(<span style="color:#719e07">new</span> HashSet<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span>(Arrays.asList(methods)), <span style="color:#2aa198">&#34;,&#34;</span>)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 token 到 map 中</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>ConfigUtils.isEmpty(token)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ConfigUtils.isDefault(token)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 随机生成 token</span> |
| </span></span><span style="display:flex;"><span> map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString()); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> map.put(Constants.TOKEN_KEY, token); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 判断协议名是否为 injvm</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) { |
| </span></span><span style="display:flex;"><span> protocolConfig.setRegister(<span style="color:#cb4b16">false</span>); |
| </span></span><span style="display:flex;"><span> map.put(<span style="color:#2aa198">&#34;notify&#34;</span>, <span style="color:#2aa198">&#34;false&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取上下文路径</span> |
| </span></span><span style="display:flex;"><span> String contextPath <span style="color:#719e07">=</span> protocolConfig.getContextpath(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((contextPath <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> contextPath.length() <span style="color:#719e07">==</span> 0) <span style="color:#719e07">&amp;&amp;</span> provider <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> contextPath <span style="color:#719e07">=</span> provider.getContextpath(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 host 和 port</span> |
| </span></span><span style="display:flex;"><span> String host <span style="color:#719e07">=</span> <span style="color:#719e07">this</span>.findConfigedHosts(protocolConfig, registryURLs, map); |
| </span></span><span style="display:flex;"><span> Integer port <span style="color:#719e07">=</span> <span style="color:#719e07">this</span>.findConfigedPorts(protocolConfig, name, map); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 组装 URL</span> |
| </span></span><span style="display:flex;"><span> URL url <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> URL(name, host, port, (contextPath <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> contextPath.length() <span style="color:#719e07">==</span> 0 <span style="color:#719e07">?</span> <span style="color:#2aa198">&#34;&#34;</span> : contextPath <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;/&#34;</span>) <span style="color:#719e07">+</span> path, map); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略无关代码</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面的代码首先是将一些信息,比如版本、时间戳、方法名以及各种配置对象的字段信息放入到 map 中,map 中的内容将作为 URL 的查询字符串。构建好 map 后,紧接着是获取上下文路径、主机名以及端口号等信息。最后将 map 和主机名等数据传给 URL 构造方法创建 URL 对象。需要注意的是,这里出现的 URL 并非 java.net.URL,而是 com.alibaba.dubbo.common.URL。</p> |
| <p>上面省略了一段代码,这里简单分析一下。这段代码用于检测 &lt;dubbo:method&gt; 标签中的配置信息,并将相关配置添加到 map 中。代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">doExportUrlsFor1Protocol</span>(ProtocolConfig protocolConfig, List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> registryURLs) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ...</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// methods 为 MethodConfig 集合,MethodConfig 中存储了 &lt;dubbo:method&gt; 标签的配置信息</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (methods <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>methods.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (MethodConfig method : methods) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 MethodConfig 对象的字段信息到 map 中,键 = 方法名.属性名。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 比如存储 &lt;dubbo:method name=&#34;sayHello&#34; retries=&#34;2&#34;&gt; 对应的 MethodConfig,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 键 = sayHello.retries,map = {&#34;sayHello.retries&#34;: 2, &#34;xxx&#34;: &#34;yyy&#34;}</span> |
| </span></span><span style="display:flex;"><span> appendParameters(map, method, method.getName()); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> String retryKey <span style="color:#719e07">=</span> method.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.retry&#34;</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (map.containsKey(retryKey)) { |
| </span></span><span style="display:flex;"><span> String retryValue <span style="color:#719e07">=</span> map.remove(retryKey); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 MethodConfig retry 是否为 false,若是,则设置重试次数为0</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#2aa198">&#34;false&#34;</span>.equals(retryValue)) { |
| </span></span><span style="display:flex;"><span> map.put(method.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.retries&#34;</span>, <span style="color:#2aa198">&#34;0&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 ArgumentConfig 列表</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>ArgumentConfig<span style="color:#719e07">&gt;</span> arguments <span style="color:#719e07">=</span> method.getArguments(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (arguments <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>arguments.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (ArgumentConfig argument : arguments) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 type 属性是否为空,或者空串(分支1 ⭐️)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (argument.getType() <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> argument.getType().length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> Method<span style="color:#719e07">[]</span> methods <span style="color:#719e07">=</span> interfaceClass.getMethods(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (methods <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> methods.length <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> methods.length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> String methodName <span style="color:#719e07">=</span> methods<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>.getName(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 比对方法名,查找目标方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (methodName.equals(method.getName())) { |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> argtypes <span style="color:#719e07">=</span> methods<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>.getParameterTypes(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (argument.getIndex() <span style="color:#719e07">!=</span> <span style="color:#719e07">-</span>1) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 ArgumentConfig 中的 type 属性与方法参数列表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 中的参数名称是否一致,不一致则抛出异常(分支2 ⭐️)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (argtypes<span style="color:#719e07">[</span>argument.getIndex()<span style="color:#719e07">]</span>.getName().equals(argument.getType())) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 ArgumentConfig 字段信息到 map 中,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 键前缀 = 方法名.index,比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// map = {&#34;sayHello.3&#34;: true}</span> |
| </span></span><span style="display:flex;"><span> appendParameters(map, argument, method.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.&#34;</span> <span style="color:#719e07">+</span> argument.getIndex()); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;argument config error: ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { <span style="color:#586e75">// 分支3 ⭐️</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> j <span style="color:#719e07">=</span> 0; j <span style="color:#719e07">&lt;</span> argtypes.length; j<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> argclazz <span style="color:#719e07">=</span> argtypes<span style="color:#719e07">[</span>j<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从参数类型列表中查找类型名称为 argument.type 的参数</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (argclazz.getName().equals(argument.getType())) { |
| </span></span><span style="display:flex;"><span> appendParameters(map, argument, method.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.&#34;</span> <span style="color:#719e07">+</span> j); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (argument.getIndex() <span style="color:#719e07">!=</span> <span style="color:#719e07">-</span>1 <span style="color:#719e07">&amp;&amp;</span> argument.getIndex() <span style="color:#719e07">!=</span> j) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;argument config error: ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 用户未配置 type 属性,但配置了 index 属性,且 index != -1</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (argument.getIndex() <span style="color:#719e07">!=</span> <span style="color:#719e07">-</span>1) { <span style="color:#586e75">// 分支4 ⭐️</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 ArgumentConfig 字段信息到 map 中</span> |
| </span></span><span style="display:flex;"><span> appendParameters(map, argument, method.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.&#34;</span> <span style="color:#719e07">+</span> argument.getIndex()); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;argument config must set index or type&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ...</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面这段代码 for 循环和 if else 分支嵌套太多,导致层次太深,不利于阅读,需要耐心看一下。大家在看这段代码时,注意把几个重要的条件分支找出来。只要理解了这几个分支的意图,就可以弄懂这段代码。请注意上面代码中⭐️符号,这几个符号标识出了4个重要的分支,下面用伪代码解释一下这几个分支的含义。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#586e75">// 获取 ArgumentConfig 列表</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">for</span> (遍历 ArgumentConfig 列表) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (type 不为 <span style="color:#cb4b16">null</span>,也不为空串) { <span style="color:#586e75">// 分支1</span> |
| </span></span><span style="display:flex;"><span> 1. 通过反射获取 interfaceClass 的方法列表 |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">for</span> (遍历方法列表) { |
| </span></span><span style="display:flex;"><span> 1. 比对方法名,查找目标方法 |
| </span></span><span style="display:flex;"><span> 2. 通过反射获取目标方法的参数类型数组 argtypes |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">if</span> (index <span style="color:#719e07">!=</span> <span style="color:#719e07">-</span>1) { <span style="color:#586e75">// 分支2</span> |
| </span></span><span style="display:flex;"><span> 1. 从 argtypes 数组中获取下标 index 处的元素 argType |
| </span></span><span style="display:flex;"><span> 2. 检测 argType 的名称与 ArgumentConfig 中的 type 属性是否一致 |
| </span></span><span style="display:flex;"><span> 3. 添加 ArgumentConfig 字段信息到 map 中,或抛出异常 |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { <span style="color:#586e75">// 分支3</span> |
| </span></span><span style="display:flex;"><span> 1. 遍历参数类型数组 argtypes,查找 argument.type 类型的参数 |
| </span></span><span style="display:flex;"><span> 2. 添加 ArgumentConfig 字段信息到 map 中 |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (index <span style="color:#719e07">!=</span> <span style="color:#719e07">-</span>1) { <span style="color:#586e75">// 分支4</span> |
| </span></span><span style="display:flex;"><span> 1. 添加 ArgumentConfig 字段信息到 map 中 |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>在本节分析的源码中,appendParameters 这个方法出现的次数比较多,该方法用于将对象字段信息添加到 map 中。实现上则是通过反射获取目标对象的 getter 方法,并调用该方法获取属性值。然后再通过 getter 方法名解析出属性名,比如从方法名 getName 中可解析出属性 name。如果用户传入了属性名前缀,此时需要将属性名加入前缀内容。最后将 &lt;属性名,属性值&gt; 键值对存入到 map 中就行了。限于篇幅原因,这里就不分析 appendParameters 方法的源码了,大家请自行分析。</p> |
| <h3 id="22-导出-dubbo-服务">2.2 导出 Dubbo 服务</h3> |
| <p>前置工作做完,接下来就可以进行服务导出了。服务导出分为导出到本地 (JVM),和导出到远程。在深入分析服务导出的源码前,我们先来从宏观层面上看一下服务导出逻辑。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">doExportUrlsFor1Protocol</span>(ProtocolConfig protocolConfig, List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> registryURLs) { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略无关代码</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) |
| </span></span><span style="display:flex;"><span> .hasExtension(url.getProtocol())) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 加载 ConfiguratorFactory,并生成 Configurator 实例,然后通过实例配置 url</span> |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class) |
| </span></span><span style="display:flex;"><span> .getExtension(url.getProtocol()).getConfigurator(url).configure(url); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> String scope <span style="color:#719e07">=</span> url.getParameter(Constants.SCOPE_KEY); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 scope = none,则什么都不做</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// scope != remote,导出到本地</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) { |
| </span></span><span style="display:flex;"><span> exportLocal(url); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// scope != local,导出到远程</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (registryURLs <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>registryURLs.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (URL registryURL : registryURLs) { |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY)); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 加载监视器链接</span> |
| </span></span><span style="display:flex;"><span> URL monitorUrl <span style="color:#719e07">=</span> loadMonitor(registryURL); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (monitorUrl <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将监视器链接作为参数添加到 url 中</span> |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> String proxy <span style="color:#719e07">=</span> url.getParameter(Constants.PROXY_KEY); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (StringUtils.isNotEmpty(proxy)) { |
| </span></span><span style="display:flex;"><span> registryURL <span style="color:#719e07">=</span> registryURL.addParameter(Constants.PROXY_KEY, proxy); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 为服务提供类(ref)生成 Invoker</span> |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;?&gt;</span> invoker <span style="color:#719e07">=</span> proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig</span> |
| </span></span><span style="display:flex;"><span> DelegateProviderMetaDataInvoker wrapperInvoker <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> DelegateProviderMetaDataInvoker(invoker, <span style="color:#719e07">this</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 导出服务,并生成 Exporter</span> |
| </span></span><span style="display:flex;"><span> Exporter<span style="color:#719e07">&lt;?&gt;</span> exporter <span style="color:#719e07">=</span> protocol.export(wrapperInvoker); |
| </span></span><span style="display:flex;"><span> exporters.add(exporter); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 不存在注册中心,仅导出服务</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;?&gt;</span> invoker <span style="color:#719e07">=</span> proxyFactory.getInvoker(ref, (Class) interfaceClass, url); |
| </span></span><span style="display:flex;"><span> DelegateProviderMetaDataInvoker wrapperInvoker <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> DelegateProviderMetaDataInvoker(invoker, <span style="color:#719e07">this</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Exporter<span style="color:#719e07">&lt;?&gt;</span> exporter <span style="color:#719e07">=</span> protocol.export(wrapperInvoker); |
| </span></span><span style="display:flex;"><span> exporters.add(exporter); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.urls.add(url); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码根据 url 中的 scope 参数决定服务导出方式,分别如下:</p> |
| <ul> |
| <li>scope = none,不导出服务</li> |
| <li>scope != remote,导出到本地</li> |
| <li>scope != local,导出到远程</li> |
| </ul> |
| <p>不管是导出到本地,还是远程。进行服务导出之前,均需要先创建 Invoker,这是一个很重要的步骤。因此下面先来分析 Invoker 的创建过程。</p> |
| <h3 id="221-invoker-创建过程">2.2.1 Invoker 创建过程</h3> |
| <p>在 Dubbo 中,Invoker 是一个非常重要的模型。在服务提供端,以及服务引用端均会出现 Invoker。Dubbo 官方文档中对 Invoker 进行了说明,这里引用一下。</p> |
| <blockquote> |
| <p>Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。</p> |
| </blockquote> |
| <p>既然 Invoker 如此重要,那么我们很有必要搞清楚 Invoker 的用途。Invoker 是由 ProxyFactory 创建而来,Dubbo 默认的 ProxyFactory 实现类是 JavassistProxyFactory。下面我们到 JavassistProxyFactory 代码中,探索 Invoker 的创建过程。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">getInvoker</span>(T proxy, Class<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> type, URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 为目标类创建 Wrapper</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> Wrapper wrapper <span style="color:#719e07">=</span> Wrapper.getWrapper(proxy.getClass().getName().indexOf(<span style="color:#2aa198">&#39;$&#39;</span>) <span style="color:#719e07">&lt;</span> 0 <span style="color:#719e07">?</span> proxy.getClass() : type); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建匿名 Invoker 类对象,并实现 doInvoke 方法。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> AbstractProxyInvoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>(proxy, type, url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> Object <span style="color:#268bd2">doInvoke</span>(T proxy, String methodName, |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> parameterTypes, |
| </span></span><span style="display:flex;"><span> Object<span style="color:#719e07">[]</span> arguments) <span style="color:#268bd2">throws</span> Throwable { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 Wrapper 的 invokeMethod 方法,invokeMethod 最终会调用目标方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> }; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,JavassistProxyFactory 创建了一个继承自 AbstractProxyInvoker 类的匿名对象,并覆写了抽象方法 doInvoke。覆写后的 doInvoke 逻辑比较简单,仅是将调用请求转发给了 Wrapper 类的 invokeMethod 方法。Wrapper 用于“包裹”目标类,Wrapper 是一个抽象类,仅可通过 getWrapper(Class) 方法创建子类。在创建 Wrapper 子类的过程中,子类代码生成逻辑会对 getWrapper 方法传入的 Class 对象进行解析,拿到诸如类方法,类成员变量等信息。以及生成 invokeMethod 方法代码和其他一些方法代码。代码生成完毕后,通过 Javassist 生成 Class 对象,最后再通过反射创建 Wrapper 实例。相关的代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> Wrapper <span style="color:#268bd2">getWrapper</span>(Class<span style="color:#719e07">&lt;?&gt;</span> c) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">while</span> (ClassGenerator.isDynamicClass(c)) |
| </span></span><span style="display:flex;"><span> c <span style="color:#719e07">=</span> c.getSuperclass(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (c <span style="color:#719e07">==</span> Object.class) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> OBJECT_WRAPPER; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从缓存中获取 Wrapper 实例</span> |
| </span></span><span style="display:flex;"><span> Wrapper ret <span style="color:#719e07">=</span> WRAPPER_MAP.get(c); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ret <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 缓存未命中,创建 Wrapper</span> |
| </span></span><span style="display:flex;"><span> ret <span style="color:#719e07">=</span> makeWrapper(c); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 写入缓存</span> |
| </span></span><span style="display:flex;"><span> WRAPPER_MAP.put(c, ret); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> ret; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>getWrapper 方法仅包含一些缓存操作逻辑,不难理解。下面我们看一下 makeWrapper 方法。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> Wrapper <span style="color:#268bd2">makeWrapper</span>(Class<span style="color:#719e07">&lt;?&gt;</span> c) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 c 是否为基本类型,若是则抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (c.isPrimitive()) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;Can not create wrapper for primitive type: &#34;</span> <span style="color:#719e07">+</span> c); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> String name <span style="color:#719e07">=</span> c.getName(); |
| </span></span><span style="display:flex;"><span> ClassLoader cl <span style="color:#719e07">=</span> ClassHelper.getClassLoader(c); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// c1 用于存储 setPropertyValue 方法代码</span> |
| </span></span><span style="display:flex;"><span> StringBuilder c1 <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> StringBuilder(<span style="color:#2aa198">&#34;public void setPropertyValue(Object o, String n, Object v){ &#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// c2 用于存储 getPropertyValue 方法代码</span> |
| </span></span><span style="display:flex;"><span> StringBuilder c2 <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> StringBuilder(<span style="color:#2aa198">&#34;public Object getPropertyValue(Object o, String n){ &#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// c3 用于存储 invokeMethod 方法代码</span> |
| </span></span><span style="display:flex;"><span> StringBuilder c3 <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> StringBuilder(<span style="color:#2aa198">&#34;public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws &#34;</span> <span style="color:#719e07">+</span> InvocationTargetException.class.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;{ &#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成类型转换代码及异常捕捉代码,比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// DemoService w; try { w = ((DemoServcie) $1); }}catch(Throwable e){ throw new IllegalArgumentException(e); }</span> |
| </span></span><span style="display:flex;"><span> c1.append(name).append(<span style="color:#2aa198">&#34; w; try{ w = ((&#34;</span>).append(name).append(<span style="color:#2aa198">&#34;)$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }&#34;</span>); |
| </span></span><span style="display:flex;"><span> c2.append(name).append(<span style="color:#2aa198">&#34; w; try{ w = ((&#34;</span>).append(name).append(<span style="color:#2aa198">&#34;)$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }&#34;</span>); |
| </span></span><span style="display:flex;"><span> c3.append(name).append(<span style="color:#2aa198">&#34; w; try{ w = ((&#34;</span>).append(name).append(<span style="color:#2aa198">&#34;)$1); }catch(Throwable e){ throw new IllegalArgumentException(e); }&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// pts 用于存储成员变量名和类型</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, Class<span style="color:#719e07">&lt;?&gt;&gt;</span> pts <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, Class<span style="color:#719e07">&lt;?&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ms 用于存储方法描述信息(可理解为方法签名)及 Method 实例</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, Method<span style="color:#719e07">&gt;</span> ms <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> LinkedHashMap<span style="color:#719e07">&lt;</span>String, Method<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// mns 为方法名列表</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span> mns <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// dmns 用于存储“定义在当前类中的方法”的名称</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span> dmns <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// --------------------------------✨ 分割线1 ✨-------------------------------------</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 public 访问级别的字段,并为所有字段生成条件判断语句</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Field f : c.getFields()) { |
| </span></span><span style="display:flex;"><span> String fn <span style="color:#719e07">=</span> f.getName(); |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> ft <span style="color:#719e07">=</span> f.getType(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (Modifier.isStatic(f.getModifiers()) <span style="color:#719e07">||</span> Modifier.isTransient(f.getModifiers())) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 忽略关键字 static 或 transient 修饰的变量</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">continue</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成条件判断及赋值语句,比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// if( $2.equals(&#34;name&#34;) ) { w.name = (java.lang.String) $3; return;}</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// if( $2.equals(&#34;age&#34;) ) { w.age = ((Number) $3).intValue(); return;}</span> |
| </span></span><span style="display:flex;"><span> c1.append(<span style="color:#2aa198">&#34; if( $2.equals(\&#34;&#34;</span>).append(fn).append(<span style="color:#2aa198">&#34;\&#34;) ){ w.&#34;</span>).append(fn).append(<span style="color:#2aa198">&#34;=&#34;</span>).append(arg(ft, <span style="color:#2aa198">&#34;$3&#34;</span>)).append(<span style="color:#2aa198">&#34;; return; }&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成条件判断及返回语句,比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// if( $2.equals(&#34;name&#34;) ) { return ($w)w.name; }</span> |
| </span></span><span style="display:flex;"><span> c2.append(<span style="color:#2aa198">&#34; if( $2.equals(\&#34;&#34;</span>).append(fn).append(<span style="color:#2aa198">&#34;\&#34;) ){ return ($w)w.&#34;</span>).append(fn).append(<span style="color:#2aa198">&#34;; }&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 存储 &lt;字段名, 字段类型&gt; 键值对到 pts 中</span> |
| </span></span><span style="display:flex;"><span> pts.put(fn, ft); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// --------------------------------✨ 分割线2 ✨-------------------------------------</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Method<span style="color:#719e07">[]</span> methods <span style="color:#719e07">=</span> c.getMethods(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 c 中是否包含在当前类中声明的方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> hasMethod <span style="color:#719e07">=</span> hasMethods(methods); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (hasMethod) { |
| </span></span><span style="display:flex;"><span> c3.append(<span style="color:#2aa198">&#34; try{&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Method m : methods) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (m.getDeclaringClass() <span style="color:#719e07">==</span> Object.class) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 忽略 Object 中定义的方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">continue</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> String mn <span style="color:#719e07">=</span> m.getName(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成方法名判断语句,比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// if ( &#34;sayHello&#34;.equals( $2 )</span> |
| </span></span><span style="display:flex;"><span> c3.append(<span style="color:#2aa198">&#34; if( \&#34;&#34;</span>).append(mn).append(<span style="color:#2aa198">&#34;\&#34;.equals( $2 ) &#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> len <span style="color:#719e07">=</span> m.getParameterTypes().length; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成“运行时传入的参数数量与方法参数列表长度”判断语句,比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &amp;&amp; $3.length == 2</span> |
| </span></span><span style="display:flex;"><span> c3.append(<span style="color:#2aa198">&#34; &amp;&amp; &#34;</span>).append(<span style="color:#2aa198">&#34; $3.length == &#34;</span>).append(len); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> override <span style="color:#719e07">=</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Method m2 : methods) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测方法是否存在重载情况,条件为:方法对象不同 &amp;&amp; 方法名相同</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (m <span style="color:#719e07">!=</span> m2 <span style="color:#719e07">&amp;&amp;</span> m.getName().equals(m2.getName())) { |
| </span></span><span style="display:flex;"><span> override <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对重载方法进行处理,考虑下面的方法:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 1. void sayHello(Integer, String)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 2. void sayHello(Integer, Integer)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 方法名相同,参数列表长度也相同,因此不能仅通过这两项判断两个方法是否相等。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 需要进一步判断方法的参数类型</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (override) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (len <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> l <span style="color:#719e07">=</span> 0; l <span style="color:#719e07">&lt;</span> len; l<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成参数类型进行检测代码,比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &amp;&amp; $3[0].getName().equals(&#34;java.lang.Integer&#34;) </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &amp;&amp; $3[1].getName().equals(&#34;java.lang.String&#34;)</span> |
| </span></span><span style="display:flex;"><span> c3.append(<span style="color:#2aa198">&#34; &amp;&amp; &#34;</span>).append(<span style="color:#2aa198">&#34; $3[&#34;</span>).append(l).append(<span style="color:#2aa198">&#34;].getName().equals(\&#34;&#34;</span>) |
| </span></span><span style="display:flex;"><span> .append(m.getParameterTypes()<span style="color:#719e07">[</span>l<span style="color:#719e07">]</span>.getName()).append(<span style="color:#2aa198">&#34;\&#34;)&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 ) {,完成方法判断语句,此时生成的代码可能如下(已格式化):</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// if (&#34;sayHello&#34;.equals($2) </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &amp;&amp; $3.length == 2</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &amp;&amp; $3[0].getName().equals(&#34;java.lang.Integer&#34;) </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &amp;&amp; $3[1].getName().equals(&#34;java.lang.String&#34;)) {</span> |
| </span></span><span style="display:flex;"><span> c3.append(<span style="color:#2aa198">&#34; ) { &#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据返回值类型生成目标方法调用语句</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (m.getReturnType() <span style="color:#719e07">==</span> Void.TYPE) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); return null;</span> |
| </span></span><span style="display:flex;"><span> c3.append(<span style="color:#2aa198">&#34; w.&#34;</span>).append(mn).append(<span style="color:#2aa198">&#39;(&#39;</span>).append(args(m.getParameterTypes(), <span style="color:#2aa198">&#34;$4&#34;</span>)).append(<span style="color:#2aa198">&#34;);&#34;</span>).append(<span style="color:#2aa198">&#34; return null;&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// return w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]);</span> |
| </span></span><span style="display:flex;"><span> c3.append(<span style="color:#2aa198">&#34; return ($w)w.&#34;</span>).append(mn).append(<span style="color:#2aa198">&#39;(&#39;</span>).append(args(m.getParameterTypes(), <span style="color:#2aa198">&#34;$4&#34;</span>)).append(<span style="color:#2aa198">&#34;);&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 }, 生成的代码形如(已格式化):</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// if (&#34;sayHello&#34;.equals($2) </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &amp;&amp; $3.length == 2</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &amp;&amp; $3[0].getName().equals(&#34;java.lang.Integer&#34;) </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &amp;&amp; $3[1].getName().equals(&#34;java.lang.String&#34;)) {</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">//</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// w.sayHello((java.lang.Integer)$4[0], (java.lang.String)$4[1]); </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// return null;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// }</span> |
| </span></span><span style="display:flex;"><span> c3.append(<span style="color:#2aa198">&#34; }&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加方法名到 mns 集合中</span> |
| </span></span><span style="display:flex;"><span> mns.add(mn); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测当前方法是否在 c 中被声明的</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (m.getDeclaringClass() <span style="color:#719e07">==</span> c) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 若是,则将当前方法名添加到 dmns 中</span> |
| </span></span><span style="display:flex;"><span> dmns.add(mn); |
| </span></span><span style="display:flex;"><span> ms.put(ReflectUtils.getDesc(m), m); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (hasMethod) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加异常捕捉语句</span> |
| </span></span><span style="display:flex;"><span> c3.append(<span style="color:#2aa198">&#34; } catch(Throwable e) { &#34;</span>); |
| </span></span><span style="display:flex;"><span> c3.append(<span style="color:#2aa198">&#34; throw new java.lang.reflect.InvocationTargetException(e); &#34;</span>); |
| </span></span><span style="display:flex;"><span> c3.append(<span style="color:#2aa198">&#34; }&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 NoSuchMethodException 异常抛出代码</span> |
| </span></span><span style="display:flex;"><span> c3.append(<span style="color:#2aa198">&#34; throw new &#34;</span> <span style="color:#719e07">+</span> NoSuchMethodException.class.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;(\&#34;Not found method \\\&#34;\&#34;+$2+\&#34;\\\&#34; in class &#34;</span> <span style="color:#719e07">+</span> c.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.\&#34;); }&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// --------------------------------✨ 分割线3 ✨-------------------------------------</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Matcher matcher; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 处理 get/set 方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Map.Entry<span style="color:#719e07">&lt;</span>String, Method<span style="color:#719e07">&gt;</span> entry : ms.entrySet()) { |
| </span></span><span style="display:flex;"><span> String md <span style="color:#719e07">=</span> entry.getKey(); |
| </span></span><span style="display:flex;"><span> Method method <span style="color:#719e07">=</span> (Method) entry.getValue(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 匹配以 get 开头的方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((matcher <span style="color:#719e07">=</span> ReflectUtils.GETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取属性名</span> |
| </span></span><span style="display:flex;"><span> String pn <span style="color:#719e07">=</span> propertyName(matcher.group(1)); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成属性判断以及返回语句,示例如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// if( $2.equals(&#34;name&#34;) ) { return ($w).w.getName(); }</span> |
| </span></span><span style="display:flex;"><span> c2.append(<span style="color:#2aa198">&#34; if( $2.equals(\&#34;&#34;</span>).append(pn).append(<span style="color:#2aa198">&#34;\&#34;) ){ return ($w)w.&#34;</span>).append(method.getName()).append(<span style="color:#2aa198">&#34;(); }&#34;</span>); |
| </span></span><span style="display:flex;"><span> pts.put(pn, method.getReturnType()); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 匹配以 is/has/can 开头的方法</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> ((matcher <span style="color:#719e07">=</span> ReflectUtils.IS_HAS_CAN_METHOD_DESC_PATTERN.matcher(md)).matches()) { |
| </span></span><span style="display:flex;"><span> String pn <span style="color:#719e07">=</span> propertyName(matcher.group(1)); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成属性判断以及返回语句,示例如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// if( $2.equals(&#34;dream&#34;) ) { return ($w).w.hasDream(); }</span> |
| </span></span><span style="display:flex;"><span> c2.append(<span style="color:#2aa198">&#34; if( $2.equals(\&#34;&#34;</span>).append(pn).append(<span style="color:#2aa198">&#34;\&#34;) ){ return ($w)w.&#34;</span>).append(method.getName()).append(<span style="color:#2aa198">&#34;(); }&#34;</span>); |
| </span></span><span style="display:flex;"><span> pts.put(pn, method.getReturnType()); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 匹配以 set 开头的方法</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> ((matcher <span style="color:#719e07">=</span> ReflectUtils.SETTER_METHOD_DESC_PATTERN.matcher(md)).matches()) { |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> pt <span style="color:#719e07">=</span> method.getParameterTypes()<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> String pn <span style="color:#719e07">=</span> propertyName(matcher.group(1)); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成属性判断以及 setter 调用语句,示例如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// if( $2.equals(&#34;name&#34;) ) { w.setName((java.lang.String)$3); return; }</span> |
| </span></span><span style="display:flex;"><span> c1.append(<span style="color:#2aa198">&#34; if( $2.equals(\&#34;&#34;</span>).append(pn).append(<span style="color:#2aa198">&#34;\&#34;) ){ w.&#34;</span>).append(method.getName()).append(<span style="color:#2aa198">&#34;(&#34;</span>).append(arg(pt, <span style="color:#2aa198">&#34;$3&#34;</span>)).append(<span style="color:#2aa198">&#34;); return; }&#34;</span>); |
| </span></span><span style="display:flex;"><span> pts.put(pn, pt); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 NoSuchPropertyException 异常抛出代码</span> |
| </span></span><span style="display:flex;"><span> c1.append(<span style="color:#2aa198">&#34; throw new &#34;</span> <span style="color:#719e07">+</span> NoSuchPropertyException.class.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;(\&#34;Not found property \\\&#34;\&#34;+$2+\&#34;\\\&#34; filed or setter method in class &#34;</span> <span style="color:#719e07">+</span> c.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.\&#34;); }&#34;</span>); |
| </span></span><span style="display:flex;"><span> c2.append(<span style="color:#2aa198">&#34; throw new &#34;</span> <span style="color:#719e07">+</span> NoSuchPropertyException.class.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;(\&#34;Not found property \\\&#34;\&#34;+$2+\&#34;\\\&#34; filed or setter method in class &#34;</span> <span style="color:#719e07">+</span> c.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.\&#34;); }&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// --------------------------------✨ 分割线4 ✨-------------------------------------</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">long</span> id <span style="color:#719e07">=</span> WRAPPER_CLASS_COUNTER.getAndIncrement(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建类生成器</span> |
| </span></span><span style="display:flex;"><span> ClassGenerator cc <span style="color:#719e07">=</span> ClassGenerator.newInstance(cl); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置类名及超类</span> |
| </span></span><span style="display:flex;"><span> cc.setClassName((Modifier.isPublic(c.getModifiers()) <span style="color:#719e07">?</span> Wrapper.class.getName() : c.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;$sw&#34;</span>) <span style="color:#719e07">+</span> id); |
| </span></span><span style="display:flex;"><span> cc.setSuperClass(Wrapper.class); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加默认构造方法</span> |
| </span></span><span style="display:flex;"><span> cc.addDefaultConstructor(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加字段</span> |
| </span></span><span style="display:flex;"><span> cc.addField(<span style="color:#2aa198">&#34;public static String[] pns;&#34;</span>); |
| </span></span><span style="display:flex;"><span> cc.addField(<span style="color:#2aa198">&#34;public static &#34;</span> <span style="color:#719e07">+</span> Map.class.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34; pts;&#34;</span>); |
| </span></span><span style="display:flex;"><span> cc.addField(<span style="color:#2aa198">&#34;public static String[] mns;&#34;</span>); |
| </span></span><span style="display:flex;"><span> cc.addField(<span style="color:#2aa198">&#34;public static String[] dmns;&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0, len <span style="color:#719e07">=</span> ms.size(); i <span style="color:#719e07">&lt;</span> len; i<span style="color:#719e07">++</span>) |
| </span></span><span style="display:flex;"><span> cc.addField(<span style="color:#2aa198">&#34;public static Class[] mts&#34;</span> <span style="color:#719e07">+</span> i <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;;&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加方法代码</span> |
| </span></span><span style="display:flex;"><span> cc.addMethod(<span style="color:#2aa198">&#34;public String[] getPropertyNames(){ return pns; }&#34;</span>); |
| </span></span><span style="display:flex;"><span> cc.addMethod(<span style="color:#2aa198">&#34;public boolean hasProperty(String n){ return pts.containsKey($1); }&#34;</span>); |
| </span></span><span style="display:flex;"><span> cc.addMethod(<span style="color:#2aa198">&#34;public Class getPropertyType(String n){ return (Class)pts.get($1); }&#34;</span>); |
| </span></span><span style="display:flex;"><span> cc.addMethod(<span style="color:#2aa198">&#34;public String[] getMethodNames(){ return mns; }&#34;</span>); |
| </span></span><span style="display:flex;"><span> cc.addMethod(<span style="color:#2aa198">&#34;public String[] getDeclaredMethodNames(){ return dmns; }&#34;</span>); |
| </span></span><span style="display:flex;"><span> cc.addMethod(c1.toString()); |
| </span></span><span style="display:flex;"><span> cc.addMethod(c2.toString()); |
| </span></span><span style="display:flex;"><span> cc.addMethod(c3.toString()); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成类</span> |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> wc <span style="color:#719e07">=</span> cc.toClass(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置字段值</span> |
| </span></span><span style="display:flex;"><span> wc.getField(<span style="color:#2aa198">&#34;pts&#34;</span>).set(<span style="color:#cb4b16">null</span>, pts); |
| </span></span><span style="display:flex;"><span> wc.getField(<span style="color:#2aa198">&#34;pns&#34;</span>).set(<span style="color:#cb4b16">null</span>, pts.keySet().toArray(<span style="color:#719e07">new</span> String<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>)); |
| </span></span><span style="display:flex;"><span> wc.getField(<span style="color:#2aa198">&#34;mns&#34;</span>).set(<span style="color:#cb4b16">null</span>, mns.toArray(<span style="color:#719e07">new</span> String<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>)); |
| </span></span><span style="display:flex;"><span> wc.getField(<span style="color:#2aa198">&#34;dmns&#34;</span>).set(<span style="color:#cb4b16">null</span>, dmns.toArray(<span style="color:#719e07">new</span> String<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>)); |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> ix <span style="color:#719e07">=</span> 0; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Method m : ms.values()) |
| </span></span><span style="display:flex;"><span> wc.getField(<span style="color:#2aa198">&#34;mts&#34;</span> <span style="color:#719e07">+</span> ix<span style="color:#719e07">++</span>).set(<span style="color:#cb4b16">null</span>, m.getParameterTypes()); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 Wrapper 实例</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> (Wrapper) wc.newInstance(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (RuntimeException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> e; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RuntimeException(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> cc.release(); |
| </span></span><span style="display:flex;"><span> ms.clear(); |
| </span></span><span style="display:flex;"><span> mns.clear(); |
| </span></span><span style="display:flex;"><span> dmns.clear(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码很长,大家耐心看一下。我们在上面代码中做了大量的注释,并按功能对代码进行了分块,以帮助大家理解代码逻辑。下面对这段代码进行讲解。首先我们把目光移到分割线1之上的代码,这段代码主要用于进行一些初始化操作。比如创建 c1、c2、c3 以及 pts、ms、mns 等变量,以及向 c1、c2、c3 中添加方法定义和类型转换代码。接下来是分割线1到分割线2之间的代码,这段代码用于为 public 级别的字段生成条件判断取值与赋值代码。这段代码不是很难看懂,就不多说了。继续向下看,分割线2和分隔线3之间的代码用于为定义在当前类中的方法生成判断语句,和方法调用语句。因为需要对方法重载进行校验,因此到这这段代码看起来有点复杂。不过耐心看一下,也不是很难理解。接下来是分割线3和分隔线4之间的代码,这段代码用于处理 getter、setter 以及以 is/has/can 开头的方法。处理方式是通过正则表达式获取方法类型(get/set/is/&hellip;),以及属性名。之后为属性名生成判断语句,然后为方法生成调用语句。最后我们再来看一下分隔线4以下的代码,这段代码通过 ClassGenerator 为刚刚生成的代码构建 Class 类,并通过反射创建对象。ClassGenerator 是 Dubbo 自己封装的,该类的核心是 toClass() 的重载方法 toClass(ClassLoader, ProtectionDomain),该方法通过 javassist 构建 Class。这里就不分析 toClass 方法了,大家请自行分析。</p> |
| <p>阅读 Wrapper 类代码需要对 javassist 框架有所了解。关于 javassist,大家如果不熟悉,请自行查阅资料,本节不打算介绍 javassist 相关内容。</p> |
| <p>好了,关于 Wrapper 类生成过程就分析到这。如果大家看的不是很明白,可以单独为 Wrapper 创建单元测试,然后单步调试。并将生成的代码拷贝出来,格式化后再进行观察和理解。</p> |
| <h3 id="222-导出服务到本地">2.2.2 导出服务到本地</h3> |
| <p>本节我们来看一下服务导出相关的代码,按照代码执行顺序,本节先来分析导出服务到本地的过程。相关代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">exportLocal</span>(URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 URL 的协议头等于 injvm,说明已经导出到本地了,无需再次导出</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { |
| </span></span><span style="display:flex;"><span> URL local <span style="color:#719e07">=</span> URL.valueOf(url.toFullString()) |
| </span></span><span style="display:flex;"><span> .setProtocol(Constants.LOCAL_PROTOCOL) <span style="color:#586e75">// 设置协议头为 injvm</span> |
| </span></span><span style="display:flex;"><span> .setHost(LOCALHOST) |
| </span></span><span style="display:flex;"><span> .setPort(0); |
| </span></span><span style="display:flex;"><span> ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref)); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 Invoker,并导出服务,这里的 protocol 会在运行时调用 InjvmProtocol 的 export 方法</span> |
| </span></span><span style="display:flex;"><span> Exporter<span style="color:#719e07">&lt;?&gt;</span> exporter <span style="color:#719e07">=</span> protocol.export( |
| </span></span><span style="display:flex;"><span> proxyFactory.getInvoker(ref, (Class) interfaceClass, local)); |
| </span></span><span style="display:flex;"><span> exporters.add(exporter); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>exportLocal 方法比较简单,首先根据 URL 协议头决定是否导出服务。若需导出,则创建一个新的 URL 并将协议头、主机名以及端口设置成新的值。然后创建 Invoker,并调用 InjvmProtocol 的 export 方法导出服务。下面我们来看一下 InjvmProtocol 的 export 方法都做了哪些事情。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Exporter<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">export</span>(Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 InjvmExporter</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> InjvmExporter<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>(invoker, invoker.getUrl().getServiceKey(), exporterMap); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,InjvmProtocol 的 export 方法仅创建了一个 InjvmExporter,无其他逻辑。到此导出服务到本地就分析完了,接下来,我们继续分析导出服务到远程的过程。</p> |
| <h3 id="223-导出服务到远程">2.2.3 导出服务到远程</h3> |
| <p>与导出服务到本地相比,导出服务到远程的过程要复杂不少,其包含了服务导出与服务注册两个过程。这两个过程涉及到了大量的调用,比较复杂。按照代码执行顺序,本节先来分析服务导出逻辑,服务注册逻辑将在下一节进行分析。下面开始分析,我们把目光移动到 RegistryProtocol 的 export 方法上。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Exporter<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">export</span>(<span style="color:#268bd2">final</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> originInvoker) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 导出服务</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> ExporterChangeableWrapper<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> exporter <span style="color:#719e07">=</span> doLocalExport(originInvoker); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取注册中心 URL,以 zookeeper 注册中心为例,得到的示例 URL 如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&amp;dubbo=2.0.2&amp;export=dubbo%3A%2F%2F172.17.48.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider</span> |
| </span></span><span style="display:flex;"><span> URL registryUrl <span style="color:#719e07">=</span> getRegistryUrl(originInvoker); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> Registry registry <span style="color:#719e07">=</span> getRegistry(originInvoker); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取已注册的服务提供者 URL,比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// dubbo://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&amp;application=demo-provider&amp;dubbo=2.0.2&amp;generic=false&amp;interface=com.alibaba.dubbo.demo.DemoService&amp;methods=sayHello</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> URL registeredProviderUrl <span style="color:#719e07">=</span> getRegisteredProviderUrl(originInvoker); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 register 参数</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> register <span style="color:#719e07">=</span> registeredProviderUrl.getParameter(<span style="color:#2aa198">&#34;register&#34;</span>, <span style="color:#cb4b16">true</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 向服务提供者与消费者注册表中注册服务提供者</span> |
| </span></span><span style="display:flex;"><span> ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据 register 的值决定是否注册服务</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (register) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 向注册中心注册服务</span> |
| </span></span><span style="display:flex;"><span> register(registryUrl, registeredProviderUrl); |
| </span></span><span style="display:flex;"><span> ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(<span style="color:#cb4b16">true</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取订阅 URL,比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// provider://172.17.48.52:20880/com.alibaba.dubbo.demo.DemoService?category=configurators&amp;check=false&amp;anyhost=true&amp;application=demo-provider&amp;dubbo=2.0.2&amp;generic=false&amp;interface=com.alibaba.dubbo.demo.DemoService&amp;methods=sayHello</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> URL overrideSubscribeUrl <span style="color:#719e07">=</span> getSubscribedOverrideUrl(registeredProviderUrl); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建监听器</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> OverrideListener overrideSubscribeListener <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> OverrideListener(overrideSubscribeUrl, originInvoker); |
| </span></span><span style="display:flex;"><span> overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 向注册中心进行订阅 override 数据</span> |
| </span></span><span style="display:flex;"><span> registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建并返回 DestroyableExporter</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> DestroyableExporter<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码看起来比较复杂,主要做如下一些操作:</p> |
| <ol> |
| <li>调用 doLocalExport 导出服务</li> |
| <li>向注册中心注册服务</li> |
| <li>向注册中心进行订阅 override 数据</li> |
| <li>创建并返回 DestroyableExporter</li> |
| </ol> |
| <p>在以上操作中,除了创建并返回 DestroyableExporter 没什么难度外,其他几步操作都不是很简单。这其中,导出服务和注册服务是本章要重点分析的逻辑。 订阅 override 数据并非本文重点内容,后面会简单介绍一下。下面先来分析 doLocalExport 方法的逻辑,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> ExporterChangeableWrapper<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">doLocalExport</span>(<span style="color:#268bd2">final</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> originInvoker) { |
| </span></span><span style="display:flex;"><span> String key <span style="color:#719e07">=</span> getCacheKey(originInvoker); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 访问缓存</span> |
| </span></span><span style="display:flex;"><span> ExporterChangeableWrapper<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> exporter <span style="color:#719e07">=</span> (ExporterChangeableWrapper<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>) bounds.get(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (exporter <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">synchronized</span> (bounds) { |
| </span></span><span style="display:flex;"><span> exporter <span style="color:#719e07">=</span> (ExporterChangeableWrapper<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>) bounds.get(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (exporter <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 Invoker 为委托类对象</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> Invoker<span style="color:#719e07">&lt;?&gt;</span> invokerDelegete <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> InvokerDelegete<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>(originInvoker, getProviderUrl(originInvoker)); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 protocol 的 export 方法导出服务</span> |
| </span></span><span style="display:flex;"><span> exporter <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ExporterChangeableWrapper<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>((Exporter<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>) protocol.export(invokerDelegete), originInvoker); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 写缓存</span> |
| </span></span><span style="display:flex;"><span> bounds.put(key, exporter); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> exporter; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面的代码是典型的双重检查锁,大家在阅读 Dubbo 的源码中,会多次见到。接下来,我们把重点放在 Protocol 的 export 方法上。假设运行时协议为 dubbo,此处的 protocol 变量会在运行时加载 DubboProtocol,并调用 DubboProtocol 的 export 方法。所以,接下来我们目光转移到 DubboProtocol 的 export 方法上,相关分析如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Exporter<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">export</span>(Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> URL url <span style="color:#719e07">=</span> invoker.getUrl(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取服务标识,理解成服务坐标也行。由服务组名,服务名,服务版本号以及端口组成。比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// demoGroup/com.alibaba.dubbo.demo.DemoService:1.0.1:20880</span> |
| </span></span><span style="display:flex;"><span> String key <span style="color:#719e07">=</span> serviceKey(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 DubboExporter</span> |
| </span></span><span style="display:flex;"><span> DubboExporter<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> exporter <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> DubboExporter<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>(invoker, key, exporterMap); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 &lt;key, exporter&gt; 键值对放入缓存中</span> |
| </span></span><span style="display:flex;"><span> exporterMap.put(key, exporter); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 本地存根相关代码</span> |
| </span></span><span style="display:flex;"><span> Boolean isStubSupportEvent <span style="color:#719e07">=</span> url.getParameter(Constants.STUB_EVENT_KEY, Constants.DEFAULT_STUB_EVENT); |
| </span></span><span style="display:flex;"><span> Boolean isCallbackservice <span style="color:#719e07">=</span> url.getParameter(Constants.IS_CALLBACK_SERVICE, <span style="color:#cb4b16">false</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (isStubSupportEvent <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>isCallbackservice) { |
| </span></span><span style="display:flex;"><span> String stubServiceMethods <span style="color:#719e07">=</span> url.getParameter(Constants.STUB_EVENT_METHODS_KEY); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (stubServiceMethods <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> stubServiceMethods.length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略日志打印代码</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 启动服务器</span> |
| </span></span><span style="display:flex;"><span> openServer(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 优化序列化</span> |
| </span></span><span style="display:flex;"><span> optimizeSerialization(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> exporter; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,我们重点关注 DubboExporter 的创建以及 openServer 方法,其他逻辑看不懂也没关系,不影响理解服务导出过程。另外,DubboExporter 的代码比较简单,就不分析了。下面分析 openServer 方法。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">openServer</span>(URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 host:port,并将其作为服务器实例的 key,用于标识当前的服务器实例</span> |
| </span></span><span style="display:flex;"><span> String key <span style="color:#719e07">=</span> url.getAddress(); |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> isServer <span style="color:#719e07">=</span> url.getParameter(Constants.IS_SERVER_KEY, <span style="color:#cb4b16">true</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (isServer) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 访问缓存</span> |
| </span></span><span style="display:flex;"><span> ExchangeServer server <span style="color:#719e07">=</span> serverMap.get(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (server <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建服务器实例</span> |
| </span></span><span style="display:flex;"><span> serverMap.put(key, createServer(url)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 服务器已创建,则根据 url 中的配置重置服务器</span> |
| </span></span><span style="display:flex;"><span> server.reset(url); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,在同一台机器上(单网卡),同一个端口上仅允许启动一个服务器实例。若某个端口上已有服务器实例,此时则调用 reset 方法重置服务器的一些配置。考虑到篇幅问题,关于服务器实例重置的代码就不分析了。接下来分析服务器实例的创建过程。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> ExchangeServer <span style="color:#268bd2">createServer</span>(URL url) { |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加心跳检测配置到 url 中</span> |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT)); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 server 参数,默认为 netty</span> |
| </span></span><span style="display:flex;"><span> String str <span style="color:#719e07">=</span> url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过 SPI 检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (str <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> str.length() <span style="color:#719e07">&gt;</span> 0 <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(<span style="color:#2aa198">&#34;Unsupported server type: &#34;</span> <span style="color:#719e07">+</span> str <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;, url: &#34;</span> <span style="color:#719e07">+</span> url); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加编码解码器参数</span> |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME); |
| </span></span><span style="display:flex;"><span> ExchangeServer server; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 ExchangeServer</span> |
| </span></span><span style="display:flex;"><span> server <span style="color:#719e07">=</span> Exchangers.bind(url, requestHandler); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (RemotingException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(<span style="color:#2aa198">&#34;Fail to start server...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 client 参数,可指定 netty,mina</span> |
| </span></span><span style="display:flex;"><span> str <span style="color:#719e07">=</span> url.getParameter(Constants.CLIENT_KEY); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (str <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> str.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取所有的 Transporter 实现类名称集合,比如 supportedTypes = [netty, mina]</span> |
| </span></span><span style="display:flex;"><span> Set<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span> supportedTypes <span style="color:#719e07">=</span> ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测当前 Dubbo 所支持的 Transporter 实现类名称列表中,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 是否包含 client 所表示的 Transporter,若不包含,则抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>supportedTypes.contains(str)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(<span style="color:#2aa198">&#34;Unsupported client type...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> server; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,createServer 包含三个核心的逻辑。第一是检测是否存在 server 参数所代表的 Transporter 拓展,不存在则抛出异常。第二是创建服务器实例。第三是检测是否支持 client 参数所表示的 Transporter 拓展,不存在也是抛出异常。两次检测操作所对应的代码比较直白了,无需多说。但创建服务器的操作目前还不是很清晰,我们继续往下看。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> ExchangeServer <span style="color:#268bd2">bind</span>(URL url, ExchangeHandler handler) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;url == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (handler <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;handler == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.addParameterIfAbsent(Constants.CODEC_KEY, <span style="color:#2aa198">&#34;exchange&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 Exchanger,默认为 HeaderExchanger。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 紧接着调用 HeaderExchanger 的 bind 方法创建 ExchangeServer 实例</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> getExchanger(url).bind(url, handler); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码比较简单,就不多说了。下面看一下 HeaderExchanger 的 bind 方法。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> ExchangeServer <span style="color:#268bd2">bind</span>(URL url, ExchangeHandler handler) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 HeaderExchangeServer 实例,该方法包含了多个逻辑,分别如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 1. new HeaderExchangeHandler(handler)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 2. new DecodeHandler(new HeaderExchangeHandler(handler))</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 3. Transporters.bind(url, new DecodeHandler(new HeaderExchangeHandler(handler)))</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> HeaderExchangeServer(Transporters.bind(url, <span style="color:#719e07">new</span> DecodeHandler(<span style="color:#719e07">new</span> HeaderExchangeHandler(handler)))); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>HeaderExchanger 的 bind 方法包含的逻辑比较多,但目前我们仅需关心 Transporters 的 bind 方法逻辑即可。该方法的代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> Server <span style="color:#268bd2">bind</span>(URL url, ChannelHandler... handlers) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;url == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (handlers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> handlers.length <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;handlers == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> ChannelHandler handler; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (handlers.length <span style="color:#719e07">==</span> 1) { |
| </span></span><span style="display:flex;"><span> handler <span style="color:#719e07">=</span> handlers<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 handlers 元素数量大于1,则创建 ChannelHandler 分发器</span> |
| </span></span><span style="display:flex;"><span> handler <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ChannelHandlerDispatcher(handlers); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取自适应 Transporter 实例,并调用实例方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> getTransporter().bind(url, handler); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,getTransporter() 方法获取的 Transporter 是在运行时动态创建的,类名为 Transporter$Adaptive,也就是自适应拓展类。Transporter$Adaptive 会在运行时根据传入的 URL 参数决定加载什么类型的 Transporter,默认为 NettyTransporter。下面我们继续跟下去,这次分析的是 NettyTransporter 的 bind 方法。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> Server <span style="color:#268bd2">bind</span>(URL url, ChannelHandler listener) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 NettyServer</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> NettyServer(url, listener); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>这里仅有一句创建 NettyServer 的代码,无需多说,我们继续向下看。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">NettyServer</span> <span style="color:#268bd2">extends</span> AbstractServer <span style="color:#268bd2">implements</span> Server { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">NettyServer</span>(URL url, ChannelHandler handler) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用父类构造方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">super</span>(url, ChannelHandlers.wrap(handler, ExecutorUtil.setThreadName(url, SERVER_THREAD_POOL_NAME))); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">abstract</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">AbstractServer</span> <span style="color:#268bd2">extends</span> AbstractEndpoint <span style="color:#268bd2">implements</span> Server { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">AbstractServer</span>(URL url, ChannelHandler handler) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用父类构造方法,这里就不用跟进去了,没什么复杂逻辑</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">super</span>(url, handler); |
| </span></span><span style="display:flex;"><span> localAddress <span style="color:#719e07">=</span> getUrl().toInetSocketAddress(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 ip 和端口</span> |
| </span></span><span style="display:flex;"><span> String bindIp <span style="color:#719e07">=</span> getUrl().getParameter(Constants.BIND_IP_KEY, getUrl().getHost()); |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> bindPort <span style="color:#719e07">=</span> getUrl().getParameter(Constants.BIND_PORT_KEY, getUrl().getPort()); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url.getParameter(Constants.ANYHOST_KEY, <span style="color:#cb4b16">false</span>) <span style="color:#719e07">||</span> NetUtils.isInvalidLocalHost(bindIp)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 ip 为 0.0.0.0</span> |
| </span></span><span style="display:flex;"><span> bindIp <span style="color:#719e07">=</span> NetUtils.ANYHOST; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> bindAddress <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> InetSocketAddress(bindIp, bindPort); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取最大可接受连接数</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.accepts <span style="color:#719e07">=</span> url.getParameter(Constants.ACCEPTS_KEY, Constants.DEFAULT_ACCEPTS); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.idleTimeout <span style="color:#719e07">=</span> url.getParameter(Constants.IDLE_TIMEOUT_KEY, Constants.DEFAULT_IDLE_TIMEOUT); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用模板方法 doOpen 启动服务器</span> |
| </span></span><span style="display:flex;"><span> doOpen(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RemotingException(<span style="color:#2aa198">&#34;Failed to bind &#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> DataStore dataStore <span style="color:#719e07">=</span> ExtensionLoader.getExtensionLoader(DataStore.class).getDefaultExtension(); |
| </span></span><span style="display:flex;"><span> executor <span style="color:#719e07">=</span> (ExecutorService) dataStore.get(Constants.EXECUTOR_SERVICE_COMPONENT_KEY, Integer.toString(url.getPort())); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">abstract</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">doOpen</span>() <span style="color:#268bd2">throws</span> Throwable; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">abstract</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">doClose</span>() <span style="color:#268bd2">throws</span> Throwable; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码多为赋值代码,不需要多讲。我们重点关注 doOpen 抽象方法,该方法需要子类实现。下面回到 NettyServer 中。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">protected</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">doOpen</span>() <span style="color:#268bd2">throws</span> Throwable { |
| </span></span><span style="display:flex;"><span> NettyHelper.setNettyLoggerFactory(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 boss 和 worker 线程池</span> |
| </span></span><span style="display:flex;"><span> ExecutorService boss <span style="color:#719e07">=</span> Executors.newCachedThreadPool(<span style="color:#719e07">new</span> NamedThreadFactory(<span style="color:#2aa198">&#34;NettyServerBoss&#34;</span>, <span style="color:#cb4b16">true</span>)); |
| </span></span><span style="display:flex;"><span> ExecutorService worker <span style="color:#719e07">=</span> Executors.newCachedThreadPool(<span style="color:#719e07">new</span> NamedThreadFactory(<span style="color:#2aa198">&#34;NettyServerWorker&#34;</span>, <span style="color:#cb4b16">true</span>)); |
| </span></span><span style="display:flex;"><span> ChannelFactory channelFactory <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS)); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 ServerBootstrap</span> |
| </span></span><span style="display:flex;"><span> bootstrap <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ServerBootstrap(channelFactory); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> NettyHandler nettyHandler <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> NettyHandler(getUrl(), <span style="color:#719e07">this</span>); |
| </span></span><span style="display:flex;"><span> channels <span style="color:#719e07">=</span> nettyHandler.getChannels(); |
| </span></span><span style="display:flex;"><span> bootstrap.setOption(<span style="color:#2aa198">&#34;child.tcpNoDelay&#34;</span>, <span style="color:#cb4b16">true</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 PipelineFactory</span> |
| </span></span><span style="display:flex;"><span> bootstrap.setPipelineFactory(<span style="color:#719e07">new</span> ChannelPipelineFactory() { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> ChannelPipeline <span style="color:#268bd2">getPipeline</span>() { |
| </span></span><span style="display:flex;"><span> NettyCodecAdapter adapter <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this); |
| </span></span><span style="display:flex;"><span> ChannelPipeline pipeline <span style="color:#719e07">=</span> Channels.pipeline(); |
| </span></span><span style="display:flex;"><span> pipeline.addLast(<span style="color:#2aa198">&#34;decoder&#34;</span>, adapter.getDecoder()); |
| </span></span><span style="display:flex;"><span> pipeline.addLast(<span style="color:#2aa198">&#34;encoder&#34;</span>, adapter.getEncoder()); |
| </span></span><span style="display:flex;"><span> pipeline.addLast(<span style="color:#2aa198">&#34;handler&#34;</span>, nettyHandler); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> pipeline; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> }); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 绑定到指定的 ip 和端口上</span> |
| </span></span><span style="display:flex;"><span> channel <span style="color:#719e07">=</span> bootstrap.bind(getBindAddress()); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上就是 NettyServer 创建的过程,dubbo 默认使用的 NettyServer 是基于 netty 3.x 版本实现的,比较老了。因此 Dubbo 另外提供了 netty 4.x 版本的 NettyServer,大家可在使用 Dubbo 的过程中按需进行配置。</p> |
| <p>到此,关于服务导出的过程就分析完了。整个过程比较复杂,大家在分析的过程中耐心一些。并且多写 Demo 进行调试,以便能够更好的理解代码逻辑。</p> |
| <p>本节内容先到这里,接下来分析服务导出的另一块逻辑 — 服务注册。</p> |
| <h3 id="224-服务注册">2.2.4 服务注册</h3> |
| <p>本节我们来分析服务注册过程,服务注册操作对于 Dubbo 来说不是必需的,通过服务直连的方式就可以绕过注册中心。但通常我们不会这么做,直连方式不利于服务治理,仅推荐在测试服务时使用。对于 Dubbo 来说,注册中心虽不是必需,但却是必要的。因此,关于注册中心以及服务注册相关逻辑,我们也需要搞懂。</p> |
| <p>本节内容以 Zookeeper 注册中心作为分析目标,其他类型注册中心大家可自行分析。下面从服务注册的入口方法开始分析,我们把目光再次移到 RegistryProtocol 的 export 方法上。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Exporter<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">export</span>(<span style="color:#268bd2">final</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> originInvoker) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ${导出服务}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略其他代码</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> register <span style="color:#719e07">=</span> registeredProviderUrl.getParameter(<span style="color:#2aa198">&#34;register&#34;</span>, <span style="color:#cb4b16">true</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (register) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 注册服务</span> |
| </span></span><span style="display:flex;"><span> register(registryUrl, registeredProviderUrl); |
| </span></span><span style="display:flex;"><span> ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(<span style="color:#cb4b16">true</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> URL overrideSubscribeUrl <span style="color:#719e07">=</span> getSubscribedOverrideUrl(registeredProviderUrl); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> OverrideListener overrideSubscribeListener <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> OverrideListener(overrideSubscribeUrl, originInvoker); |
| </span></span><span style="display:flex;"><span> overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 订阅 override 数据</span> |
| </span></span><span style="display:flex;"><span> registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略部分代码</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>RegistryProtocol 的 export 方法包含了服务导出,注册,以及数据订阅等逻辑。其中服务导出逻辑上一节已经分析过了,本节将分析服务注册逻辑,相关代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">register</span>(URL registryUrl, URL registedProviderUrl) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 Registry</span> |
| </span></span><span style="display:flex;"><span> Registry registry <span style="color:#719e07">=</span> registryFactory.getRegistry(registryUrl); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 注册服务</span> |
| </span></span><span style="display:flex;"><span> registry.register(registedProviderUrl); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>register 方法包含两步操作,第一步是获取注册中心实例,第二步是向注册中心注册服务。接下来分两节内容对这两步操作进行分析。</p> |
| <h4 id="2241-创建注册中心">2.2.4.1 创建注册中心</h4> |
| <p>本节内容以 Zookeeper 注册中心为例进行分析。下面先来看一下 getRegistry 方法的源码,这个方法由 AbstractRegistryFactory 实现。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> Registry <span style="color:#268bd2">getRegistry</span>(URL url) { |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.setPath(RegistryService.class.getName()) |
| </span></span><span style="display:flex;"><span> .addParameter(Constants.INTERFACE_KEY, RegistryService.class.getName()) |
| </span></span><span style="display:flex;"><span> .removeParameters(Constants.EXPORT_KEY, Constants.REFER_KEY); |
| </span></span><span style="display:flex;"><span> String key <span style="color:#719e07">=</span> url.toServiceString(); |
| </span></span><span style="display:flex;"><span> LOCK.lock(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 访问缓存</span> |
| </span></span><span style="display:flex;"><span> Registry registry <span style="color:#719e07">=</span> REGISTRIES.get(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (registry <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> registry; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 缓存未命中,创建 Registry 实例</span> |
| </span></span><span style="display:flex;"><span> registry <span style="color:#719e07">=</span> createRegistry(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (registry <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;Can not create registry...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 写入缓存</span> |
| </span></span><span style="display:flex;"><span> REGISTRIES.put(key, registry); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> registry; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> LOCK.unlock(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">protected</span> <span style="color:#268bd2">abstract</span> Registry <span style="color:#268bd2">createRegistry</span>(URL url); |
| </span></span></code></pre></div><p>如上,getRegistry 方法先访问缓存,缓存未命中则调用 createRegistry 创建 Registry,然后写入缓存。这里的 createRegistry 是一个模板方法,由具体的子类实现。因此,下面我们到 ZookeeperRegistryFactory 中探究一番。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">ZookeeperRegistryFactory</span> <span style="color:#268bd2">extends</span> AbstractRegistryFactory { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// zookeeperTransporter 由 SPI 在运行时注入,类型为 ZookeeperTransporter$Adaptive</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> ZookeeperTransporter zookeeperTransporter; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">setZookeeperTransporter</span>(ZookeeperTransporter zookeeperTransporter) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.zookeeperTransporter <span style="color:#719e07">=</span> zookeeperTransporter; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Registry <span style="color:#268bd2">createRegistry</span>(URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 ZookeeperRegistry</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> ZookeeperRegistry(url, zookeeperTransporter); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>ZookeeperRegistryFactory 的 createRegistry 方法仅包含一句代码,无需解释,继续跟下去。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">ZookeeperRegistry</span>(URL url, ZookeeperTransporter zookeeperTransporter) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">super</span>(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url.isAnyHost()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;registry address == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取组名,默认为 dubbo</span> |
| </span></span><span style="display:flex;"><span> String group <span style="color:#719e07">=</span> url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>group.startsWith(Constants.PATH_SEPARATOR)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// group = &#34;/&#34; + group</span> |
| </span></span><span style="display:flex;"><span> group <span style="color:#719e07">=</span> Constants.PATH_SEPARATOR <span style="color:#719e07">+</span> group; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.root <span style="color:#719e07">=</span> group; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 Zookeeper 客户端,默认为 CuratorZookeeperTransporter</span> |
| </span></span><span style="display:flex;"><span> zkClient <span style="color:#719e07">=</span> zookeeperTransporter.connect(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加状态监听器</span> |
| </span></span><span style="display:flex;"><span> zkClient.addStateListener(<span style="color:#719e07">new</span> StateListener() { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">stateChanged</span>(<span style="color:#dc322f">int</span> state) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (state <span style="color:#719e07">==</span> RECONNECTED) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> recover(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> logger.error(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> }); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>在上面的代码代码中,我们重点关注 ZookeeperTransporter 的 connect 方法调用,这个方法用于创建 Zookeeper 客户端。创建好 Zookeeper 客户端,意味着注册中心的创建过程就结束了。接下来,再来分析一下 Zookeeper 客户端的创建过程。</p> |
| <p>前面说过,这里的 zookeeperTransporter 类型为自适应拓展类,因此 connect 方法会在被调用时决定加载什么类型的 ZookeeperTransporter 拓展,默认为 CuratorZookeeperTransporter。下面我们到 CuratorZookeeperTransporter 中看一看。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> ZookeeperClient <span style="color:#268bd2">connect</span>(URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 CuratorZookeeperClient</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> CuratorZookeeperClient(url); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>继续向下看。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">CuratorZookeeperClient</span> <span style="color:#268bd2">extends</span> AbstractZookeeperClient<span style="color:#719e07">&lt;</span>CuratorWatcher<span style="color:#719e07">&gt;</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> CuratorFramework client; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">CuratorZookeeperClient</span>(URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">super</span>(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 CuratorFramework 构造器</span> |
| </span></span><span style="display:flex;"><span> CuratorFrameworkFactory.Builder builder <span style="color:#719e07">=</span> CuratorFrameworkFactory.builder() |
| </span></span><span style="display:flex;"><span> .connectString(url.getBackupAddress()) |
| </span></span><span style="display:flex;"><span> .retryPolicy(<span style="color:#719e07">new</span> RetryNTimes(1, 1000)) |
| </span></span><span style="display:flex;"><span> .connectionTimeoutMs(5000); |
| </span></span><span style="display:flex;"><span> String authority <span style="color:#719e07">=</span> url.getAuthority(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (authority <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> authority.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> builder <span style="color:#719e07">=</span> builder.authorization(<span style="color:#2aa198">&#34;digest&#34;</span>, authority.getBytes()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 构建 CuratorFramework 实例</span> |
| </span></span><span style="display:flex;"><span> client <span style="color:#719e07">=</span> builder.build(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加监听器</span> |
| </span></span><span style="display:flex;"><span> client.getConnectionStateListenable().addListener(<span style="color:#719e07">new</span> ConnectionStateListener() { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">stateChanged</span>(CuratorFramework client, ConnectionState state) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (state <span style="color:#719e07">==</span> ConnectionState.LOST) { |
| </span></span><span style="display:flex;"><span> CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (state <span style="color:#719e07">==</span> ConnectionState.CONNECTED) { |
| </span></span><span style="display:flex;"><span> CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (state <span style="color:#719e07">==</span> ConnectionState.RECONNECTED) { |
| </span></span><span style="display:flex;"><span> CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> }); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 启动客户端</span> |
| </span></span><span style="display:flex;"><span> client.start(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>CuratorZookeeperClient 构造方法主要用于创建和启动 CuratorFramework 实例。以上基本上都是 Curator 框架的代码,大家如果对 Curator 框架不是很了解,可以参考 Curator 官方文档。</p> |
| <p>本节分析了 ZookeeperRegistry 实例的创建过程,整个过程并不是很复杂。大家在看完分析后,可以自行调试,以加深理解。现在注册中心实例创建好了,接下来要做的事情是向注册中心注册服务,我们继续往下看。</p> |
| <h4 id="2242-节点创建">2.2.4.2 节点创建</h4> |
| <p>以 Zookeeper 为例,所谓的服务注册,本质上是将服务配置数据写入到 Zookeeper 的某个路径的节点下。为了让大家有一个直观的了解,下面我们将 Dubbo 的 demo 跑起来,然后通过 Zookeeper 可视化客户端 <a href="https://github.com/apache/zookeeper/tree/b79af153d0f98a4f3f3516910ed47234d7b3d74e/src/contrib/zooinspector">ZooInspector</a> 查看节点数据。如下:</p> |
| <p><img src="https://dubbo.apache.org/imgs/dev/service-registry.png" alt="img"></p> |
| <p>从上图中可以看到 com.alibaba.dubbo.demo.DemoService 这个服务对应的配置信息(存储在 URL 中)最终被注册到了 /dubbo/com.alibaba.dubbo.demo.DemoService/providers/ 节点下。搞懂了服务注册的本质,那么接下来我们就可以去阅读服务注册的代码了。服务注册的接口为 register(URL),这个方法定义在 FailbackRegistry 抽象类中。代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">register</span>(URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">super</span>.register(url); |
| </span></span><span style="display:flex;"><span> failedRegistered.remove(url); |
| </span></span><span style="display:flex;"><span> failedUnregistered.remove(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 模板方法,由子类实现</span> |
| </span></span><span style="display:flex;"><span> doRegister(url); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> Throwable t <span style="color:#719e07">=</span> e; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 check 参数,若 check = true 将会直接抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> check <span style="color:#719e07">=</span> getUrl().getParameter(Constants.CHECK_KEY, <span style="color:#cb4b16">true</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> url.getParameter(Constants.CHECK_KEY, <span style="color:#cb4b16">true</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>Constants.CONSUMER_PROTOCOL.equals(url.getProtocol()); |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> skipFailback <span style="color:#719e07">=</span> t <span style="color:#719e07">instanceof</span> SkipFailbackWrapperException; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (check <span style="color:#719e07">||</span> skipFailback) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (skipFailback) { |
| </span></span><span style="display:flex;"><span> t <span style="color:#719e07">=</span> t.getCause(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;Failed to register&#34;</span>); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#2aa198">&#34;Failed to register&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 记录注册失败的链接</span> |
| </span></span><span style="display:flex;"><span> failedRegistered.add(url); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">protected</span> <span style="color:#268bd2">abstract</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">doRegister</span>(URL url); |
| </span></span></code></pre></div><p>如上,我们重点关注 doRegister 方法调用即可,其他的代码先忽略。doRegister 方法是一个模板方法,因此我们到 FailbackRegistry 子类 ZookeeperRegistry 中进行分析。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">protected</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">doRegister</span>(URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过 Zookeeper 客户端创建节点,节点路径由 toUrlPath 方法生成,路径格式如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// /${group}/${serviceInterface}/providers/${url}</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 比如</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// /dubbo/org.apache.dubbo.DemoService/providers/dubbo%3A%2F%2F127.0.0.1......</span> |
| </span></span><span style="display:flex;"><span> zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, <span style="color:#cb4b16">true</span>)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(<span style="color:#2aa198">&#34;Failed to register...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,ZookeeperRegistry 在 doRegister 中调用了 Zookeeper 客户端创建服务节点。节点路径由 toUrlPath 方法生成,该方法逻辑不难理解,就不分析了。接下来分析 create 方法,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">create</span>(String path, <span style="color:#dc322f">boolean</span> ephemeral) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>ephemeral) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果要创建的节点类型非临时节点,那么这里要检测节点是否存在</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (checkExists(path)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> path.lastIndexOf(<span style="color:#2aa198">&#39;/&#39;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (i <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 递归创建上一级路径</span> |
| </span></span><span style="display:flex;"><span> create(path.substring(0, i), <span style="color:#cb4b16">false</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据 ephemeral 的值创建临时或持久节点</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ephemeral) { |
| </span></span><span style="display:flex;"><span> createEphemeral(path); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> createPersistent(path); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面方法先是通过递归创建当前节点的上一级路径,然后再根据 ephemeral 的值决定创建临时还是持久节点。createEphemeral 和 createPersistent 这两个方法都比较简单,这里简单分析其中的一个。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">createEphemeral</span>(String path) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过 Curator 框架创建节点</span> |
| </span></span><span style="display:flex;"><span> client.create().withMode(CreateMode.EPHEMERAL).forPath(path); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (NodeExistsException e) { |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>好了,到此关于服务注册的过程就分析完了。整个过程可简单总结为:先创建注册中心实例,之后再通过注册中心实例注册服务。本节先到这,接下来分析数据订阅过程。</p> |
| <h3 id="225-订阅-override-数据">2.2.5 订阅 override 数据</h3> |
| <p>// 待补充</p> |
| <h2 id="3总结">3.总结</h2> |
| <p>本篇文章详细分析了 Dubbo 服务导出过程,包括配置检测,URL 组装,Invoker 创建过程、导出服务以及注册服务等等。篇幅比较大,需要大家耐心阅读。本篇文章先就到这,如果文章有不妥错误之处,希望大家能够进行反馈或修正。</p></description></item><item><title>Docsv2.7: 服务引用</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/refer-service/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/refer-service/</guid><description> |
| <h2 id="1-简介">1. 简介</h2> |
| <p>上一篇文章详细分析了服务导出的过程,本篇文章我们趁热打铁,继续分析服务引用过程。在 Dubbo 中,我们可以通过两种方式引用远程服务。第一种是使用服务直连的方式引用服务,第二种方式是基于注册中心进行引用。服务直连的方式仅适合在调试或测试服务的场景下使用,不适合在线上环境使用。因此,本文我将重点分析通过注册中心引用服务的过程。从注册中心中获取服务配置只是服务引用过程中的一环,除此之外,服务消费者还需要经历 Invoker 创建、代理类创建等步骤。这些步骤,将在后续章节中一一进行分析。</p> |
| <h2 id="2服务引用原理">2.服务引用原理</h2> |
| <p>Dubbo 服务引用的时机有两个,第一个是在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,第二个是在 ReferenceBean 对应的服务被注入到其他类中时引用。这两个引用服务的时机区别在于,第一个是饿汉式的,第二个是懒汉式的。默认情况下,Dubbo 使用懒汉式引用服务。如果需要使用饿汉式,可通过配置 &lt;dubbo:reference&gt; 的 init 属性开启。下面我们按照 Dubbo 默认配置进行分析,整个分析过程从 ReferenceBean 的 getObject 方法开始。当我们的服务被注入到其他类中时,Spring 会第一时间调用 getObject 方法,并由该方法执行服务引用逻辑。按照惯例,在进行具体工作之前,需先进行配置检查与收集工作。接着根据收集到的信息决定服务用的方式,有三种,第一种是引用本地 (JVM) 服务,第二是通过直连方式引用远程服务,第三是通过注册中心引用远程服务。不管是哪种引用方式,最后都会得到一个 Invoker 实例。如果有多个注册中心,多个服务提供者,这个时候会得到一组 Invoker 实例,此时需要通过集群管理类 Cluster 将多个 Invoker 合并成一个实例。合并后的 Invoker 实例已经具备调用本地或远程服务的能力了,但并不能将此实例暴露给用户使用,这会对用户业务代码造成侵入。此时框架还需要通过代理工厂类 (ProxyFactory) 为服务接口生成代理类,并让代理类去调用 Invoker 逻辑。避免了 Dubbo 框架代码对业务代码的侵入,同时也让框架更容易使用。</p> |
| <p>以上就是服务引用的大致原理,下面我们深入到代码中,详细分析服务引用细节。</p> |
| <h2 id="3源码分析">3.源码分析</h2> |
| <p>服务引用的入口方法为 ReferenceBean 的 getObject 方法,该方法定义在 Spring 的 FactoryBean 接口中,ReferenceBean 实现了这个方法。实现代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> Object <span style="color:#268bd2">getObject</span>() <span style="color:#268bd2">throws</span> Exception { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> get(); |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">synchronized</span> T <span style="color:#268bd2">get</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (destroyed) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;Already destroyed!&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 ref 是否为空,为空则通过 init 方法创建</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ref <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// init 方法主要用于处理配置,以及调用 createProxy 生成代理类</span> |
| </span></span><span style="display:flex;"><span> init(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> ref; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上两个方法的代码比较简短,并不难理解。这里需要特别说明一下,如果你对 2.6.4 及以下版本的 getObject 方法进行调试时,会碰到比较奇怪的的问题。这里假设你使用 IDEA,且保持了 IDEA 的默认配置。当你面调试到 get 方法的<code>if (ref == null)</code>时,你会发现 ref 不为空,导致你无法进入到 init 方法中继续调试。导致这个现象的原因是 Dubbo 框架本身有一些小问题。该问题已经在 pull request <a href="https://github.com/apache/dubbo/pull/2754">#2754</a> 修复了此问题,并跟随 2.6.5 版本发布了。如果你正在学习 2.6.4 及以下版本,可通过修改 IDEA 配置规避这个问题。首先 IDEA 配置弹窗中搜索 toString,然后取消<code>Enable 'toString' object view</code>勾选。具体如下:</p> |
| <p><img src="https://dubbo.apache.org/imgs/docsv2.7/dev/source/refer-service/15417503733794.jpg" alt="img"></p> |
| <h3 id="31-处理配置">3.1 处理配置</h3> |
| <p>Dubbo 提供了丰富的配置,用于调整和优化框架行为,性能等。Dubbo 在引用或导出服务时,首先会对这些配置进行检查和处理,以保证配置的正确性。配置解析逻辑封装在 ReferenceConfig 的 init 方法中,下面进行分析。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">init</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 避免重复初始化</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (initialized) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> initialized <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测接口名合法性</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (interfaceName <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> interfaceName.length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;interface not allow null!&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 consumer 变量是否为空,为空则创建</span> |
| </span></span><span style="display:flex;"><span> checkDefault(); |
| </span></span><span style="display:flex;"><span> appendProperties(<span style="color:#719e07">this</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (getGeneric() <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> getConsumer() <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 generic</span> |
| </span></span><span style="display:flex;"><span> setGeneric(getConsumer().getGeneric()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测是否为泛化接口</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ProtocolUtils.isGeneric(getGeneric())) { |
| </span></span><span style="display:flex;"><span> interfaceClass <span style="color:#719e07">=</span> GenericService.class; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 加载类</span> |
| </span></span><span style="display:flex;"><span> interfaceClass <span style="color:#719e07">=</span> Class.forName(interfaceName, <span style="color:#cb4b16">true</span>, Thread.currentThread() |
| </span></span><span style="display:flex;"><span> .getContextClassLoader()); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (ClassNotFoundException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> checkInterfaceAndMethods(interfaceClass, methods); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// -------------------------------✨ 分割线1 ✨------------------------------</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从系统变量中获取与接口名对应的属性值</span> |
| </span></span><span style="display:flex;"><span> String resolve <span style="color:#719e07">=</span> System.getProperty(interfaceName); |
| </span></span><span style="display:flex;"><span> String resolveFile <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (resolve <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> resolve.length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从系统属性中获取解析文件路径</span> |
| </span></span><span style="display:flex;"><span> resolveFile <span style="color:#719e07">=</span> System.getProperty(<span style="color:#2aa198">&#34;dubbo.resolve.file&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (resolveFile <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> resolveFile.length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从指定位置加载配置文件</span> |
| </span></span><span style="display:flex;"><span> File userResolveFile <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> File(<span style="color:#719e07">new</span> File(System.getProperty(<span style="color:#2aa198">&#34;user.home&#34;</span>)), <span style="color:#2aa198">&#34;dubbo-resolve.properties&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (userResolveFile.exists()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取文件绝对路径</span> |
| </span></span><span style="display:flex;"><span> resolveFile <span style="color:#719e07">=</span> userResolveFile.getAbsolutePath(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (resolveFile <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> resolveFile.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> Properties properties <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Properties(); |
| </span></span><span style="display:flex;"><span> FileInputStream fis <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> fis <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> FileInputStream(<span style="color:#719e07">new</span> File(resolveFile)); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从文件中加载配置</span> |
| </span></span><span style="display:flex;"><span> properties.load(fis); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (IOException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;Unload ..., cause:...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#cb4b16">null</span> <span style="color:#719e07">!=</span> fis) fis.close(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (IOException e) { |
| </span></span><span style="display:flex;"><span> logger.warn(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取与接口名对应的配置</span> |
| </span></span><span style="display:flex;"><span> resolve <span style="color:#719e07">=</span> properties.getProperty(interfaceName); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (resolve <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> resolve.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 resolve 赋值给 url</span> |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> resolve; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// -------------------------------✨ 分割线2 ✨------------------------------</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (consumer <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (application <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从 consumer 中获取 Application 实例,下同</span> |
| </span></span><span style="display:flex;"><span> application <span style="color:#719e07">=</span> consumer.getApplication(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (module <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> module <span style="color:#719e07">=</span> consumer.getModule(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (registries <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> registries <span style="color:#719e07">=</span> consumer.getRegistries(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (monitor <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> monitor <span style="color:#719e07">=</span> consumer.getMonitor(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (module <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (registries <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> registries <span style="color:#719e07">=</span> module.getRegistries(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (monitor <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> monitor <span style="color:#719e07">=</span> module.getMonitor(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (application <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (registries <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> registries <span style="color:#719e07">=</span> application.getRegistries(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (monitor <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> monitor <span style="color:#719e07">=</span> application.getMonitor(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 Application 合法性</span> |
| </span></span><span style="display:flex;"><span> checkApplication(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测本地存根配置合法性</span> |
| </span></span><span style="display:flex;"><span> checkStubAndMock(interfaceClass); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// -------------------------------✨ 分割线3 ✨------------------------------</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span> map <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>Object, Object<span style="color:#719e07">&gt;</span> attributes <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>Object, Object<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 side、协议版本信息、时间戳和进程号等信息到 map 中</span> |
| </span></span><span style="display:flex;"><span> map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE); |
| </span></span><span style="display:flex;"><span> map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion()); |
| </span></span><span style="display:flex;"><span> map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis())); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ConfigUtils.getPid() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid())); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 非泛化服务</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>isGeneric()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取版本</span> |
| </span></span><span style="display:flex;"><span> String revision <span style="color:#719e07">=</span> Version.getVersion(interfaceClass, version); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (revision <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> revision.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> map.put(<span style="color:#2aa198">&#34;revision&#34;</span>, revision); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取接口方法列表,并添加到 map 中</span> |
| </span></span><span style="display:flex;"><span> String<span style="color:#719e07">[]</span> methods <span style="color:#719e07">=</span> Wrapper.getWrapper(interfaceClass).getMethodNames(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (methods.length <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> map.put(<span style="color:#2aa198">&#34;methods&#34;</span>, Constants.ANY_VALUE); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> map.put(<span style="color:#2aa198">&#34;methods&#34;</span>, StringUtils.join(<span style="color:#719e07">new</span> HashSet<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span>(Arrays.asList(methods)), <span style="color:#2aa198">&#34;,&#34;</span>)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> map.put(Constants.INTERFACE_KEY, interfaceName); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 ApplicationConfig、ConsumerConfig、ReferenceConfig 等对象的字段信息添加到 map 中</span> |
| </span></span><span style="display:flex;"><span> appendParameters(map, application); |
| </span></span><span style="display:flex;"><span> appendParameters(map, module); |
| </span></span><span style="display:flex;"><span> appendParameters(map, consumer, Constants.DEFAULT_KEY); |
| </span></span><span style="display:flex;"><span> appendParameters(map, <span style="color:#719e07">this</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// -------------------------------✨ 分割线4 ✨------------------------------</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> String prefix <span style="color:#719e07">=</span> StringUtils.getServiceKey(map); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (methods <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>methods.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 MethodConfig 列表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (MethodConfig method : methods) { |
| </span></span><span style="display:flex;"><span> appendParameters(map, method, method.getName()); |
| </span></span><span style="display:flex;"><span> String retryKey <span style="color:#719e07">=</span> method.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.retry&#34;</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 map 是否包含 methodName.retry</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (map.containsKey(retryKey)) { |
| </span></span><span style="display:flex;"><span> String retryValue <span style="color:#719e07">=</span> map.remove(retryKey); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#2aa198">&#34;false&#34;</span>.equals(retryValue)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加重试次数配置 methodName.retries</span> |
| </span></span><span style="display:flex;"><span> map.put(method.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.retries&#34;</span>, <span style="color:#2aa198">&#34;0&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 MethodConfig 中的“属性”字段到 attributes</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 比如 onreturn、onthrow、oninvoke 等</span> |
| </span></span><span style="display:flex;"><span> appendAttributes(attributes, method, prefix <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.&#34;</span> <span style="color:#719e07">+</span> method.getName()); |
| </span></span><span style="display:flex;"><span> checkAndConvertImplicitConfig(method, map, attributes); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// -------------------------------✨ 分割线5 ✨------------------------------</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取服务消费者 ip 地址</span> |
| </span></span><span style="display:flex;"><span> String hostToRegistry <span style="color:#719e07">=</span> ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (hostToRegistry <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> hostToRegistry.length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> hostToRegistry <span style="color:#719e07">=</span> NetUtils.getLocalHost(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (isInvalidLocalHost(hostToRegistry)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;Specified invalid registry ip from property...&#34;</span> ); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> map.put(Constants.REGISTER_IP_KEY, hostToRegistry); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 存储 attributes 到系统上下文中</span> |
| </span></span><span style="display:flex;"><span> StaticContext.getSystemContext().putAll(attributes); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建代理类</span> |
| </span></span><span style="display:flex;"><span> ref <span style="color:#719e07">=</span> createProxy(map); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据服务名,ReferenceConfig,代理类构建 ConsumerModel,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 并将 ConsumerModel 存入到 ApplicationModel 中</span> |
| </span></span><span style="display:flex;"><span> ConsumerModel consumerModel <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConsumerModel(getUniqueServiceName(), <span style="color:#719e07">this</span>, ref, interfaceClass.getMethods()); |
| </span></span><span style="display:flex;"><span> ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面的代码很长,做的事情比较多。这里根据代码逻辑,对代码进行了分块,下面我们一起来看一下。</p> |
| <p>首先是方法开始到分割线1之间的代码。这段代码主要用于检测 ConsumerConfig 实例是否存在,如不存在则创建一个新的实例,然后通过系统变量或 dubbo.properties 配置文件填充 ConsumerConfig 的字段。接着是检测泛化配置,并根据配置设置 interfaceClass 的值。接着来看分割线1到分割线2之间的逻辑。这段逻辑用于从系统属性或配置文件中加载与接口名相对应的配置,并将解析结果赋值给 url 字段。url 字段的作用一般是用于点对点调用。继续向下看,分割线2和分割线3之间的代码用于检测几个核心配置类是否为空,为空则尝试从其他配置类中获取。分割线3与分割线4之间的代码主要用于收集各种配置,并将配置存储到 map 中。分割线4和分割线5之间的代码用于处理 MethodConfig 实例。该实例包含了事件通知配置,比如 onreturn、onthrow、oninvoke 等。分割线5到方法结尾的代码主要用于解析服务消费者 ip,以及调用 createProxy 创建代理对象。关于该方法的详细分析,将会在接下来的章节中展开。</p> |
| <h3 id="32-引用服务">3.2 引用服务</h3> |
| <p>本节我们要从 createProxy 开始看起。从字面意思上来看,createProxy 似乎只是用于创建代理对象的。但实际上并非如此,该方法还会调用其他方法构建以及合并 Invoker 实例。具体细节如下。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> T <span style="color:#268bd2">createProxy</span>(Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span> map) { |
| </span></span><span style="display:flex;"><span> URL tmpUrl <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> URL(<span style="color:#2aa198">&#34;temp&#34;</span>, <span style="color:#2aa198">&#34;localhost&#34;</span>, 0, map); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">boolean</span> isJvmRefer; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (isInjvm() <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// url 配置被指定,则不做本地引用</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> url.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> isJvmRefer <span style="color:#719e07">=</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据 url 的协议、scope 以及 injvm 等参数检测是否需要本地引用</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 比如如果用户显式配置了 scope=local,此时 isInjvmRefer 返回 true</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) { |
| </span></span><span style="display:flex;"><span> isJvmRefer <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> isJvmRefer <span style="color:#719e07">=</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 injvm 配置值</span> |
| </span></span><span style="display:flex;"><span> isJvmRefer <span style="color:#719e07">=</span> isInjvm().booleanValue(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 本地引用</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (isJvmRefer) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成本地引用 URL,协议为 injvm</span> |
| </span></span><span style="display:flex;"><span> URL url <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 refer 方法构建 InjvmInvoker 实例</span> |
| </span></span><span style="display:flex;"><span> invoker <span style="color:#719e07">=</span> refprotocol.refer(interfaceClass, url); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 远程引用</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// url 不为空,表明用户可能想进行点对点调用</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> url.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 当需要配置多个 url 时,可用分号进行分割,这里会进行切分</span> |
| </span></span><span style="display:flex;"><span> String<span style="color:#719e07">[]</span> us <span style="color:#719e07">=</span> Constants.SEMICOLON_SPLIT_PATTERN.split(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (us <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> us.length <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (String u : us) { |
| </span></span><span style="display:flex;"><span> URL url <span style="color:#719e07">=</span> URL.valueOf(u); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url.getPath() <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> url.getPath().length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置接口全限定名为 url 路径</span> |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.setPath(interfaceName); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 url 协议是否为 registry,若是,表明用户想使用指定的注册中心</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 map 转换为查询字符串,并作为 refer 参数的值添加到 url 中</span> |
| </span></span><span style="display:flex;"><span> urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map))); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 合并 url,移除服务提供者的一些配置(这些配置来源于用户配置的 url 属性),</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 比如线程池相关配置。并保留服务提供者的部分配置,比如版本,group,时间戳等</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 最后将合并后的配置设置为 url 查询字符串中。</span> |
| </span></span><span style="display:flex;"><span> urls.add(ClusterUtils.mergeUrl(url, map)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 加载注册中心 url</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> us <span style="color:#719e07">=</span> loadRegistries(<span style="color:#cb4b16">false</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (us <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>us.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (URL u : us) { |
| </span></span><span style="display:flex;"><span> URL monitorUrl <span style="color:#719e07">=</span> loadMonitor(u); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (monitorUrl <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString())); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 refer 参数到 url 中,并将 url 添加到 urls 中</span> |
| </span></span><span style="display:flex;"><span> urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map))); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 未配置注册中心,抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (urls.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;No such any registry to reference...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 单个注册中心或服务提供者(服务直连,下同)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (urls.size() <span style="color:#719e07">==</span> 1) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 RegistryProtocol 的 refer 构建 Invoker 实例</span> |
| </span></span><span style="display:flex;"><span> invoker <span style="color:#719e07">=</span> refprotocol.refer(interfaceClass, urls.get(0)); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 多个注册中心或多个服务提供者,或者两者混合</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;?&gt;&gt;</span> invokers <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;?&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> URL registryURL <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取所有的 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (URL url : urls) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过 refprotocol 调用 refer 构建 Invoker,refprotocol 会在运行时</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据 url 协议头加载指定的 Protocol 实例,并调用实例的 refer 方法</span> |
| </span></span><span style="display:flex;"><span> invokers.add(refprotocol.refer(interfaceClass, url)); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { |
| </span></span><span style="display:flex;"><span> registryURL <span style="color:#719e07">=</span> url; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (registryURL <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果注册中心链接不为空,则将使用 AvailableCluster</span> |
| </span></span><span style="display:flex;"><span> URL u <span style="color:#719e07">=</span> registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 StaticDirectory 实例,并由 Cluster 对多个 Invoker 进行合并</span> |
| </span></span><span style="display:flex;"><span> invoker <span style="color:#719e07">=</span> cluster.join(<span style="color:#719e07">new</span> StaticDirectory(u, invokers)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> invoker <span style="color:#719e07">=</span> cluster.join(<span style="color:#719e07">new</span> StaticDirectory(invokers)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Boolean c <span style="color:#719e07">=</span> check; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (c <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> consumer <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> c <span style="color:#719e07">=</span> consumer.isCheck(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (c <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> c <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// invoker 可用性检查</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (c <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>invoker.isAvailable()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;No provider available for the service...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成代理类</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> (T) proxyFactory.getProxy(invoker); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码很多,不过逻辑比较清晰。首先根据配置检查是否为本地调用,若是,则调用 InjvmProtocol 的 refer 方法生成 InjvmInvoker 实例。若不是,则读取直连配置项,或注册中心 url,并将读取到的 url 存储到 urls 中。然后根据 urls 元素数量进行后续操作。若 urls 元素数量为1,则直接通过 Protocol 自适应拓展类构建 Invoker 实例接口。若 urls 元素数量大于1,即存在多个注册中心或服务直连 url,此时先根据 url 构建 Invoker。然后再通过 Cluster 合并多个 Invoker,最后调用 ProxyFactory 生成代理类。Invoker 的构建过程以及代理类的过程比较重要,因此接下来将分两小节对这两个过程进行分析。</p> |
| <h4 id="321-创建-invoker">3.2.1 创建 Invoker</h4> |
| <p>Invoker 是 Dubbo 的核心模型,代表一个可执行体。在服务提供方,Invoker 用于调用服务提供类。在服务消费方,Invoker 用于执行远程调用。Invoker 是由 Protocol 实现类构建而来。Protocol 实现类有很多,本节会分析最常用的两个,分别是 RegistryProtocol 和 DubboProtocol,其他的大家自行分析。下面先来分析 DubboProtocol 的 refer 方法源码。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">refer</span>(Class<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> serviceType, URL url) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> optimizeSerialization(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 DubboInvoker</span> |
| </span></span><span style="display:flex;"><span> DubboInvoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> DubboInvoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>(serviceType, url, getClients(url), invokers); |
| </span></span><span style="display:flex;"><span> invokers.add(invoker); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invoker; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面方法看起来比较简单,不过这里有一个调用需要我们注意一下,即 getClients。这个方法用于获取客户端实例,实例类型为 ExchangeClient。ExchangeClient 实际上并不具备通信能力,它需要基于更底层的客户端实例进行通信。比如 NettyClient、MinaClient 等,默认情况下,Dubbo 使用 NettyClient 进行通信。接下来,我们简单看一下 getClients 方法的逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> ExchangeClient<span style="color:#719e07">[]</span> <span style="color:#268bd2">getClients</span>(URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 是否共享连接</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> service_share_connect <span style="color:#719e07">=</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取连接数,默认为0,表示未配置</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> connections <span style="color:#719e07">=</span> url.getParameter(Constants.CONNECTIONS_KEY, 0); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果未配置 connections,则共享连接</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (connections <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> service_share_connect <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> connections <span style="color:#719e07">=</span> 1; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> ExchangeClient<span style="color:#719e07">[]</span> clients <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ExchangeClient<span style="color:#719e07">[</span>connections<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> clients.length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (service_share_connect) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取共享客户端</span> |
| </span></span><span style="display:flex;"><span> clients<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> getSharedClient(url); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 初始化新的客户端</span> |
| </span></span><span style="display:flex;"><span> clients<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> initClient(url); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> clients; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>这里根据 connections 数量决定是获取共享客户端还是创建新的客户端实例,默认情况下,使用共享客户端实例。getSharedClient 方法中也会调用 initClient 方法,因此下面我们一起看一下这两个方法。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> ExchangeClient <span style="color:#268bd2">getSharedClient</span>(URL url) { |
| </span></span><span style="display:flex;"><span> String key <span style="color:#719e07">=</span> url.getAddress(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取带有“引用计数”功能的 ExchangeClient</span> |
| </span></span><span style="display:flex;"><span> ReferenceCountExchangeClient client <span style="color:#719e07">=</span> referenceClientMap.get(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (client <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>client.isClosed()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 增加引用计数</span> |
| </span></span><span style="display:flex;"><span> client.incrementAndGetCount(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> client; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> referenceClientMap.remove(key); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> locks.putIfAbsent(key, <span style="color:#719e07">new</span> Object()); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">synchronized</span> (locks.get(key)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (referenceClientMap.containsKey(key)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> referenceClientMap.get(key); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 ExchangeClient 客户端</span> |
| </span></span><span style="display:flex;"><span> ExchangeClient exchangeClient <span style="color:#719e07">=</span> initClient(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 ExchangeClient 实例传给 ReferenceCountExchangeClient,这里使用了装饰模式</span> |
| </span></span><span style="display:flex;"><span> client <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ReferenceCountExchangeClient(exchangeClient, ghostClientMap); |
| </span></span><span style="display:flex;"><span> referenceClientMap.put(key, client); |
| </span></span><span style="display:flex;"><span> ghostClientMap.remove(key); |
| </span></span><span style="display:flex;"><span> locks.remove(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> client; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面方法先访问缓存,若缓存未命中,则通过 initClient 方法创建新的 ExchangeClient 实例,并将该实例传给 ReferenceCountExchangeClient 构造方法创建一个带有引用计数功能的 ExchangeClient 实例。ReferenceCountExchangeClient 内部实现比较简单,就不分析了。下面我们再来看一下 initClient 方法的代码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> ExchangeClient <span style="color:#268bd2">initClient</span>(URL url) { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取客户端类型,默认为 netty</span> |
| </span></span><span style="display:flex;"><span> String str <span style="color:#719e07">=</span> url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT)); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加编解码和心跳包参数到 url 中</span> |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME); |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT)); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测客户端类型是否存在,不存在则抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (str <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> str.length() <span style="color:#719e07">&gt;</span> 0 <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(<span style="color:#2aa198">&#34;Unsupported client type: ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> ExchangeClient client; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 lazy 配置,并根据配置值决定创建的客户端类型</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url.getParameter(Constants.LAZY_CONNECT_KEY, <span style="color:#cb4b16">false</span>)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建懒加载 ExchangeClient 实例</span> |
| </span></span><span style="display:flex;"><span> client <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> LazyConnectExchangeClient(url, requestHandler); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建普通 ExchangeClient 实例</span> |
| </span></span><span style="display:flex;"><span> client <span style="color:#719e07">=</span> Exchangers.connect(url, requestHandler); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (RemotingException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(<span style="color:#2aa198">&#34;Fail to create remoting client for service...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> client; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>initClient 方法首先获取用户配置的客户端类型,默认为 netty。然后检测用户配置的客户端类型是否存在,不存在则抛出异常。最后根据 lazy 配置决定创建什么类型的客户端。这里的 LazyConnectExchangeClient 代码并不是很复杂,该类会在 request 方法被调用时通过 Exchangers 的 connect 方法创建 ExchangeClient 客户端,该类的代码本节就不分析了。下面我们分析一下 Exchangers 的 connect 方法。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> ExchangeClient <span style="color:#268bd2">connect</span>(URL url, ExchangeHandler handler) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;url == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (handler <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;handler == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.addParameterIfAbsent(Constants.CODEC_KEY, <span style="color:#2aa198">&#34;exchange&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 Exchanger 实例,默认为 HeaderExchangeClient</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> getExchanger(url).connect(url, handler); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,getExchanger 会通过 SPI 加载 HeaderExchanger 实例,这个方法比较简单,大家自己看一下吧。接下来分析 HeaderExchanger.connect 的实现。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> ExchangeClient <span style="color:#268bd2">connect</span>(URL url, ExchangeHandler handler) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 这里包含了多个调用,分别如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 1. 创建 HeaderExchangeHandler 对象</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 2. 创建 DecodeHandler 对象</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 3. 通过 Transporters 构建 Client 实例</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 4. 创建 HeaderExchangeClient 对象</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> HeaderExchangeClient(Transporters.connect(url, <span style="color:#719e07">new</span> DecodeHandler(<span style="color:#719e07">new</span> HeaderExchangeHandler(handler))), <span style="color:#cb4b16">true</span>); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>这里的调用比较多,我们这里重点看一下 Transporters 的 connect 方法。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> Client <span style="color:#268bd2">connect</span>(URL url, ChannelHandler... handlers) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;url == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> ChannelHandler handler; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (handlers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> handlers.length <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> handler <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ChannelHandlerAdapter(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (handlers.length <span style="color:#719e07">==</span> 1) { |
| </span></span><span style="display:flex;"><span> handler <span style="color:#719e07">=</span> handlers<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 handler 数量大于1,则创建一个 ChannelHandler 分发器</span> |
| </span></span><span style="display:flex;"><span> handler <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ChannelHandlerDispatcher(handlers); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 Transporter 自适应拓展类,并调用 connect 方法生成 Client 实例</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> getTransporter().connect(url, handler); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,getTransporter 方法返回的是自适应拓展类,该类会在运行时根据客户端类型加载指定的 Transporter 实现类。若用户未配置客户端类型,则默认加载 NettyTransporter,并调用该类的 connect 方法。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> Client <span style="color:#268bd2">connect</span>(URL url, ChannelHandler listener) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 NettyClient 对象</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> NettyClient(url, listener); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>到这里就不继续跟下去了,在往下就是通过 Netty 提供的 API 构建 Netty 客户端了,大家有兴趣自己看看。到这里,关于 DubboProtocol 的 refer 方法就分析完了。接下来,继续分析 RegistryProtocol 的 refer 方法逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">refer</span>(Class<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> type, URL url) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 取 registry 参数值,并将其设置为协议头</span> |
| </span></span><span style="display:flex;"><span> url <span style="color:#719e07">=</span> url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取注册中心实例</span> |
| </span></span><span style="display:flex;"><span> Registry registry <span style="color:#719e07">=</span> registryFactory.getRegistry(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (RegistryService.class.equals(type)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> proxyFactory.getInvoker((T) registry, type, url); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 url 查询字符串转为 Map</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span> qs <span style="color:#719e07">=</span> StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY)); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 group 配置</span> |
| </span></span><span style="display:flex;"><span> String group <span style="color:#719e07">=</span> qs.get(Constants.GROUP_KEY); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (group <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> group.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((Constants.COMMA_SPLIT_PATTERN.split(group)).length <span style="color:#719e07">&gt;</span> 1 |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">||</span> <span style="color:#2aa198">&#34;*&#34;</span>.equals(group)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过 SPI 加载 MergeableCluster 实例,并调用 doRefer 继续执行服务引用逻辑</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> doRefer(getMergeableCluster(), registry, type, url); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 doRefer 继续执行服务引用逻辑</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> doRefer(cluster, registry, type, url); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码首先为 url 设置协议头,然后根据 url 参数加载注册中心实例。然后获取 group 配置,根据 group 配置决定 doRefer 第一个参数的类型。这里的重点是 doRefer 方法,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">doRefer</span>(Cluster cluster, Registry registry, Class<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> type, URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 RegistryDirectory 实例</span> |
| </span></span><span style="display:flex;"><span> RegistryDirectory<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> directory <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> RegistryDirectory<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>(type, url); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置注册中心和协议</span> |
| </span></span><span style="display:flex;"><span> directory.setRegistry(registry); |
| </span></span><span style="display:flex;"><span> directory.setProtocol(protocol); |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span> parameters <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span>(directory.getUrl().getParameters()); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成服务消费者链接</span> |
| </span></span><span style="display:flex;"><span> URL subscribeUrl <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 注册服务消费者,在 consumers 目录下新节点</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>Constants.ANY_VALUE.equals(url.getServiceInterface()) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> url.getParameter(Constants.REGISTER_KEY, <span style="color:#cb4b16">true</span>)) { |
| </span></span><span style="display:flex;"><span> registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY, |
| </span></span><span style="display:flex;"><span> Constants.CHECK_KEY, String.valueOf(<span style="color:#cb4b16">false</span>))); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 订阅 providers、configurators、routers 等节点数据</span> |
| </span></span><span style="display:flex;"><span> directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, |
| </span></span><span style="display:flex;"><span> Constants.PROVIDERS_CATEGORY |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;,&#34;</span> <span style="color:#719e07">+</span> Constants.CONFIGURATORS_CATEGORY |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;,&#34;</span> <span style="color:#719e07">+</span> Constants.ROUTERS_CATEGORY)); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 一个注册中心可能有多个服务提供者,因此这里需要将多个服务提供者合并为一个</span> |
| </span></span><span style="display:flex;"><span> Invoker invoker <span style="color:#719e07">=</span> cluster.join(directory); |
| </span></span><span style="display:flex;"><span> ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invoker; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,doRefer 方法创建一个 RegistryDirectory 实例,然后生成服务者消费者链接,并向注册中心进行注册。注册完毕后,紧接着订阅 providers、configurators、routers 等节点下的数据。完成订阅后,RegistryDirectory 会收到这几个节点下的子节点信息。由于一个服务可能部署在多台服务器上,这样就会在 providers 产生多个节点,这个时候就需要 Cluster 将多个服务节点合并为一个,并生成一个 Invoker。关于 RegistryDirectory 和 Cluster,本文不打算进行分析,相关分析将会在随后的文章中展开。</p> |
| <h4 id="322-创建代理">3.2.2 创建代理</h4> |
| <p>Invoker 创建完毕后,接下来要做的事情是为服务接口生成代理对象。有了代理对象,即可进行远程调用。代理对象生成的入口方法为 ProxyFactory 的 getProxy,接下来进行分析。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> T <span style="color:#268bd2">getProxy</span>(Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用重载方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> getProxy(invoker, <span style="color:#cb4b16">false</span>); |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> T <span style="color:#268bd2">getProxy</span>(Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker, <span style="color:#dc322f">boolean</span> generic) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> interfaces <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取接口列表</span> |
| </span></span><span style="display:flex;"><span> String config <span style="color:#719e07">=</span> invoker.getUrl().getParameter(<span style="color:#2aa198">&#34;interfaces&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (config <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> config.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 切分接口列表</span> |
| </span></span><span style="display:flex;"><span> String<span style="color:#719e07">[]</span> types <span style="color:#719e07">=</span> Constants.COMMA_SPLIT_PATTERN.split(config); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (types <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> types.length <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> interfaces <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Class<span style="color:#719e07">&lt;?&gt;[</span>types.length <span style="color:#719e07">+</span> 2<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置服务接口类和 EchoService.class 到 interfaces 中</span> |
| </span></span><span style="display:flex;"><span> interfaces<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> invoker.getInterface(); |
| </span></span><span style="display:flex;"><span> interfaces<span style="color:#719e07">[</span>1<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> EchoService.class; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> types.length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 加载接口类</span> |
| </span></span><span style="display:flex;"><span> interfaces<span style="color:#719e07">[</span>i <span style="color:#719e07">+</span> 1<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> ReflectUtils.forName(types<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (interfaces <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> interfaces <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Class<span style="color:#719e07">&lt;?&gt;[]</span>{invoker.getInterface(), EchoService.class}; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 为 http 和 hessian 协议提供泛化调用支持,参考 pull request #1827</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>invoker.getInterface().equals(GenericService.class) <span style="color:#719e07">&amp;&amp;</span> generic) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> len <span style="color:#719e07">=</span> interfaces.length; |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> temp <span style="color:#719e07">=</span> interfaces; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建新的 interfaces 数组</span> |
| </span></span><span style="display:flex;"><span> interfaces <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Class<span style="color:#719e07">&lt;?&gt;[</span>len <span style="color:#719e07">+</span> 1<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> System.arraycopy(temp, 0, interfaces, 0, len); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 GenericService.class 到数组中</span> |
| </span></span><span style="display:flex;"><span> interfaces<span style="color:#719e07">[</span>len<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> GenericService.class; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用重载方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> getProxy(invoker, interfaces); |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">abstract</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> T <span style="color:#268bd2">getProxy</span>(Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker, Class<span style="color:#719e07">&lt;?&gt;[]</span> types); |
| </span></span></code></pre></div><p>如上,上面大段代码都是用来获取 interfaces 数组的,我们继续往下看。getProxy(Invoker, Class<?>[]) 这个方法是一个抽象方法,下面我们到 JavassistProxyFactory 类中看一下该方法的实现代码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> T <span style="color:#268bd2">getProxy</span>(Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker, Class<span style="color:#719e07">&lt;?&gt;[]</span> interfaces) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成 Proxy 子类(Proxy 是抽象类)。并调用 Proxy 子类的 newInstance 方法创建 Proxy 实例</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> (T) Proxy.getProxy(interfaces).newInstance(<span style="color:#719e07">new</span> InvokerInvocationHandler(invoker)); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码并不多,首先是通过 Proxy 的 getProxy 方法获取 Proxy 子类,然后创建 InvokerInvocationHandler 对象,并将该对象传给 newInstance 生成 Proxy 实例。InvokerInvocationHandler 实现 JDK 的 InvocationHandler 接口,具体的用途是拦截接口类调用。该类逻辑比较简单,这里就不分析了。下面我们重点关注一下 Proxy 的 getProxy 方法,如下。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> Proxy <span style="color:#268bd2">getProxy</span>(Class<span style="color:#719e07">&lt;?&gt;</span>... ics) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用重载方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> getProxy(ClassHelper.getClassLoader(Proxy.class), ics); |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> Proxy <span style="color:#268bd2">getProxy</span>(ClassLoader cl, Class<span style="color:#719e07">&lt;?&gt;</span>... ics) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ics.length <span style="color:#719e07">&gt;</span> 65535) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;interface limit exceeded&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> StringBuilder sb <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> StringBuilder(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历接口列表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> ics.length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> String itf <span style="color:#719e07">=</span> ics<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>.getName(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测类型是否为接口</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>ics<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>.isInterface()) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RuntimeException(itf <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34; is not a interface.&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> tmp <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 重新加载接口类</span> |
| </span></span><span style="display:flex;"><span> tmp <span style="color:#719e07">=</span> Class.forName(itf, <span style="color:#cb4b16">false</span>, cl); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (ClassNotFoundException e) { |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测接口是否相同,这里 tmp 有可能为空</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (tmp <span style="color:#719e07">!=</span> ics<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(ics<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span> <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34; is not visible from class loader&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 拼接接口全限定名,分隔符为 ;</span> |
| </span></span><span style="display:flex;"><span> sb.append(itf).append(<span style="color:#2aa198">&#39;;&#39;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 使用拼接后的接口名作为 key</span> |
| </span></span><span style="display:flex;"><span> String key <span style="color:#719e07">=</span> sb.toString(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, Object<span style="color:#719e07">&gt;</span> cache; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">synchronized</span> (ProxyCacheMap) { |
| </span></span><span style="display:flex;"><span> cache <span style="color:#719e07">=</span> ProxyCacheMap.get(cl); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (cache <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> cache <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, Object<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> ProxyCacheMap.put(cl, cache); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Proxy proxy <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">synchronized</span> (cache) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">do</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从缓存中获取 Reference&lt;Proxy&gt; 实例</span> |
| </span></span><span style="display:flex;"><span> Object value <span style="color:#719e07">=</span> cache.get(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (value <span style="color:#719e07">instanceof</span> Reference<span style="color:#719e07">&lt;?&gt;</span>) { |
| </span></span><span style="display:flex;"><span> proxy <span style="color:#719e07">=</span> (Proxy) ((Reference<span style="color:#719e07">&lt;?&gt;</span>) value).get(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (proxy <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> proxy; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 并发控制,保证只有一个线程可以进行后续操作</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (value <span style="color:#719e07">==</span> PendingGenerationMarker) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 其他线程在此处进行等待</span> |
| </span></span><span style="display:flex;"><span> cache.wait(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (InterruptedException e) { |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 放置标志位到缓存中,并跳出 while 循环进行后续操作</span> |
| </span></span><span style="display:flex;"><span> cache.put(key, PendingGenerationMarker); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">while</span> (<span style="color:#cb4b16">true</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">long</span> id <span style="color:#719e07">=</span> PROXY_CLASS_COUNTER.getAndIncrement(); |
| </span></span><span style="display:flex;"><span> String pkg <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> ClassGenerator ccp <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>, ccm <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 ClassGenerator 对象</span> |
| </span></span><span style="display:flex;"><span> ccp <span style="color:#719e07">=</span> ClassGenerator.newInstance(cl); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Set<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span> worked <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashSet<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Method<span style="color:#719e07">&gt;</span> methods <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>Method<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> ics.length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测接口访问级别是否为 protected 或 private</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>Modifier.isPublic(ics<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>.getModifiers())) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取接口包名</span> |
| </span></span><span style="display:flex;"><span> String npkg <span style="color:#719e07">=</span> ics<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>.getPackage().getName(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (pkg <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> pkg <span style="color:#719e07">=</span> npkg; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>pkg.equals(npkg)) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 非 public 级别的接口必须在同一个包下,否者抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;non-public interfaces from different packages&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加接口到 ClassGenerator 中</span> |
| </span></span><span style="display:flex;"><span> ccp.addInterface(ics<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历接口方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Method method : ics<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>.getMethods()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取方法描述,可理解为方法签名</span> |
| </span></span><span style="display:flex;"><span> String desc <span style="color:#719e07">=</span> ReflectUtils.getDesc(method); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果方法描述字符串已在 worked 中,则忽略。考虑这种情况,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// A 接口和 B 接口中包含一个完全相同的方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (worked.contains(desc)) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">continue</span>; |
| </span></span><span style="display:flex;"><span> worked.add(desc); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> ix <span style="color:#719e07">=</span> methods.size(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取方法返回值类型</span> |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> rt <span style="color:#719e07">=</span> method.getReturnType(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取参数列表</span> |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> pts <span style="color:#719e07">=</span> method.getParameterTypes(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成 Object[] args = new Object[1...N]</span> |
| </span></span><span style="display:flex;"><span> StringBuilder code <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> StringBuilder(<span style="color:#2aa198">&#34;Object[] args = new Object[&#34;</span>).append(pts.length).append(<span style="color:#2aa198">&#34;];&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> j <span style="color:#719e07">=</span> 0; j <span style="color:#719e07">&lt;</span> pts.length; j<span style="color:#719e07">++</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成 args[1...N] = ($w)$1...N;</span> |
| </span></span><span style="display:flex;"><span> code.append(<span style="color:#2aa198">&#34; args[&#34;</span>).append(j).append(<span style="color:#2aa198">&#34;] = ($w)$&#34;</span>).append(j <span style="color:#719e07">+</span> 1).append(<span style="color:#2aa198">&#34;;&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成 InvokerHandler 接口的 invoker 方法调用语句,如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// Object ret = handler.invoke(this, methods[1...N], args);</span> |
| </span></span><span style="display:flex;"><span> code.append(<span style="color:#2aa198">&#34; Object ret = handler.invoke(this, methods[&#34;</span> <span style="color:#719e07">+</span> ix <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;], args);&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回值不为 void</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>Void.TYPE.equals(rt)) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成返回语句,形如 return (java.lang.String) ret;</span> |
| </span></span><span style="display:flex;"><span> code.append(<span style="color:#2aa198">&#34; return &#34;</span>).append(asArgument(rt, <span style="color:#2aa198">&#34;ret&#34;</span>)).append(<span style="color:#2aa198">&#34;;&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> methods.add(method); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加方法名、访问控制符、参数列表、方法代码等信息到 ClassGenerator 中 </span> |
| </span></span><span style="display:flex;"><span> ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (pkg <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) |
| </span></span><span style="display:flex;"><span> pkg <span style="color:#719e07">=</span> PACKAGE_NAME; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 构建接口代理类名称:pkg + &#34;.proxy&#34; + id,比如 org.apache.dubbo.proxy0</span> |
| </span></span><span style="display:flex;"><span> String pcn <span style="color:#719e07">=</span> pkg <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.proxy&#34;</span> <span style="color:#719e07">+</span> id; |
| </span></span><span style="display:flex;"><span> ccp.setClassName(pcn); |
| </span></span><span style="display:flex;"><span> ccp.addField(<span style="color:#2aa198">&#34;public static java.lang.reflect.Method[] methods;&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成 private java.lang.reflect.InvocationHandler handler;</span> |
| </span></span><span style="display:flex;"><span> ccp.addField(<span style="color:#2aa198">&#34;private &#34;</span> <span style="color:#719e07">+</span> InvocationHandler.class.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34; handler;&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 为接口代理类添加带有 InvocationHandler 参数的构造方法,比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// porxy0(java.lang.reflect.InvocationHandler arg0) {</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// handler=$1;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// }</span> |
| </span></span><span style="display:flex;"><span> ccp.addConstructor(Modifier.PUBLIC, <span style="color:#719e07">new</span> Class<span style="color:#719e07">&lt;?&gt;[]</span>{InvocationHandler.class}, <span style="color:#719e07">new</span> Class<span style="color:#719e07">&lt;?&gt;[</span>0<span style="color:#719e07">]</span>, <span style="color:#2aa198">&#34;handler=$1;&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 为接口代理类添加默认构造方法</span> |
| </span></span><span style="display:flex;"><span> ccp.addDefaultConstructor(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成接口代理类</span> |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> clazz <span style="color:#719e07">=</span> ccp.toClass(); |
| </span></span><span style="display:flex;"><span> clazz.getField(<span style="color:#2aa198">&#34;methods&#34;</span>).set(<span style="color:#cb4b16">null</span>, methods.toArray(<span style="color:#719e07">new</span> Method<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>)); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 构建 Proxy 子类名称,比如 Proxy1,Proxy2 等</span> |
| </span></span><span style="display:flex;"><span> String fcn <span style="color:#719e07">=</span> Proxy.class.getName() <span style="color:#719e07">+</span> id; |
| </span></span><span style="display:flex;"><span> ccm <span style="color:#719e07">=</span> ClassGenerator.newInstance(cl); |
| </span></span><span style="display:flex;"><span> ccm.setClassName(fcn); |
| </span></span><span style="display:flex;"><span> ccm.addDefaultConstructor(); |
| </span></span><span style="display:flex;"><span> ccm.setSuperClass(Proxy.class); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 为 Proxy 的抽象方法 newInstance 生成实现代码,形如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// public Object newInstance(java.lang.reflect.InvocationHandler h) { </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// return new org.apache.dubbo.proxy0($1);</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// }</span> |
| </span></span><span style="display:flex;"><span> ccm.addMethod(<span style="color:#2aa198">&#34;public Object newInstance(&#34;</span> <span style="color:#719e07">+</span> InvocationHandler.class.getName() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34; h){ return new &#34;</span> <span style="color:#719e07">+</span> pcn <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;($1); }&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 生成 Proxy 实现类</span> |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;</span> pc <span style="color:#719e07">=</span> ccm.toClass(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过反射创建 Proxy 实例</span> |
| </span></span><span style="display:flex;"><span> proxy <span style="color:#719e07">=</span> (Proxy) pc.newInstance(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (RuntimeException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> e; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RuntimeException(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ccp <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 释放资源</span> |
| </span></span><span style="display:flex;"><span> ccp.release(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ccm <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) |
| </span></span><span style="display:flex;"><span> ccm.release(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">synchronized</span> (cache) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (proxy <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) |
| </span></span><span style="display:flex;"><span> cache.remove(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 写缓存</span> |
| </span></span><span style="display:flex;"><span> cache.put(key, <span style="color:#719e07">new</span> WeakReference<span style="color:#719e07">&lt;</span>Proxy<span style="color:#719e07">&gt;</span>(proxy)); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 唤醒其他等待线程</span> |
| </span></span><span style="display:flex;"><span> cache.notifyAll(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> proxy; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码比较复杂,我们写了大量的注释。大家在阅读这段代码时,要搞清楚 ccp 和 ccm 的用途,不然会被搞晕。ccp 用于为服务接口生成代理类,比如我们有一个 DemoService 接口,这个接口代理类就是由 ccp 生成的。ccm 则是用于为 org.apache.dubbo.common.bytecode.Proxy 抽象类生成子类,主要是实现 Proxy 类的抽象方法。下面以 org.apache.dubbo.demo.DemoService 这个接口为例,来看一下该接口代理类代码大致是怎样的(忽略 EchoService 接口)。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#719e07">package</span> org.apache.dubbo.common.bytecode; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">proxy0</span> <span style="color:#268bd2">implements</span> org.apache.dubbo.demo.DemoService { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> java.lang.reflect.Method<span style="color:#719e07">[]</span> methods; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> java.lang.reflect.InvocationHandler handler; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">proxy0</span>() { |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">proxy0</span>(java.lang.reflect.InvocationHandler arg0) { |
| </span></span><span style="display:flex;"><span> handler <span style="color:#719e07">=</span> $1; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> java.lang.String <span style="color:#268bd2">sayHello</span>(java.lang.String arg0) { |
| </span></span><span style="display:flex;"><span> Object<span style="color:#719e07">[]</span> args <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Object<span style="color:#719e07">[</span>1<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> args<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> ($w) $1; |
| </span></span><span style="display:flex;"><span> Object ret <span style="color:#719e07">=</span> handler.invoke(<span style="color:#719e07">this</span>, methods<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>, args); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> (java.lang.String) ret; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>好了,到这里代理类生成逻辑就分析完了。整个过程比较复杂,大家需要耐心看一下。</p> |
| <h2 id="4总结">4.总结</h2> |
| <p>本篇文章对服务引用的过程进行了较为详尽的分析,还有一些逻辑暂时没有分析到,比如 Directory、Cluster。这些接口及实现类功能比较独立,后续会单独成文进行分析。暂时我们可以先把这些类看成黑盒,只要知道这些类的用途即可。关于服务引用过程就分析到这里。</p></description></item><item><title>Docsv2.7: 服务调用过程</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/service-invoking-process/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/service-invoking-process/</guid><description> |
| <h2 id="1-简介">1. 简介</h2> |
| <p>在前面的文章中,我们分析了 Dubbo SPI、服务导出与引入、以及集群容错方面的代码。经过前文的铺垫,本篇文章我们终于可以分析服务调用过程了。Dubbo 服务调用过程比较复杂,包含众多步骤,比如发送请求、编解码、服务降级、过滤器链处理、序列化、线程派发以及响应请求等步骤。限于篇幅原因,本篇文章无法对所有的步骤一一进行分析。本篇文章将会重点分析请求的发送与接收、编解码、线程派发以及响应的发送与接收等过程,至于服务降级、过滤器链和序列化大家自行进行分析,也可以将其当成一个黑盒,暂时忽略也没关系。介绍完本篇文章要分析的内容,接下来我们进入正题吧。</p> |
| <h2 id="2-源码分析">2. 源码分析</h2> |
| <p>在进行源码分析之前,我们先来通过一张图了解 Dubbo 服务调用过程。</p> |
| <p><img src="https://dubbo.apache.org/imgs/dev/send-request-process.jpg" alt="img"></p> |
| <p>首先服务消费者通过代理对象 Proxy 发起远程调用,接着通过网络客户端 Client 将编码后的请求发送给服务提供方的网络层上,也就是 Server。Server 在收到请求后,首先要做的事情是对数据包进行解码。然后将解码后的请求发送至分发器 Dispatcher,再由分发器将请求派发到指定的线程池上,最后由线程池调用具体的服务。这就是一个远程调用请求的发送与接收过程。至于响应的发送与接收过程,这张图中没有表现出来。对于这两个过程,我们也会进行详细分析。</p> |
| <h3 id="21-服务调用方式">2.1 服务调用方式</h3> |
| <p>Dubbo 支持同步和异步两种调用方式,其中异步调用还可细分为“有返回值”的异步调用和“无返回值”的异步调用。所谓“无返回值”异步调用是指服务消费方只管调用,但不关心调用结果,此时 Dubbo 会直接返回一个空的 RpcResult。若要使用异步特性,需要服务消费方手动进行配置。默认情况下,Dubbo 使用同步调用方式。</p> |
| <p>本节以及其他章节将会使用 Dubbo 官方提供的 Demo 分析整个调用过程,下面我们从 DemoService 接口的代理类开始进行分析。Dubbo 默认使用 Javassist 框架为服务接口生成动态代理类,因此我们需要先将代理类进行反编译才能看到源码。这里使用阿里开源 Java 应用诊断工具 <a href="https://github.com/alibaba/arthas">Arthas</a> 反编译代理类,结果如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#586e75">/** |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * Arthas 反编译步骤: |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * 1. 启动 Arthas |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * java -jar arthas-boot.jar |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * 2. 输入编号选择进程 |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * Arthas 启动后,会打印 Java 应用进程列表,如下: |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * [1]: 11232 org.jetbrains.jps.cmdline.Launcher |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * [2]: 22370 org.jetbrains.jps.cmdline.Launcher |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * [3]: 22371 com.alibaba.dubbo.demo.consumer.Consumer |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * [4]: 22362 com.alibaba.dubbo.demo.provider.Provider |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * [5]: 2074 org.apache.zookeeper.server.quorum.QuorumPeerMain |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * 这里输入编号 3,让 Arthas 关联到启动类为 com.....Consumer 的 Java 进程上 |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * 3. 由于 Demo 项目中只有一个服务接口,因此此接口的代理类类名为 proxy0,此时使用 sc 命令搜索这个类名。 |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * $ sc *.proxy0 |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * com.alibaba.dubbo.common.bytecode.proxy0 |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * 4. 使用 jad 命令反编译 com.alibaba.dubbo.common.bytecode.proxy0 |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * $ jad com.alibaba.dubbo.common.bytecode.proxy0 |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * 更多使用方法请参考 Arthas 官方文档: |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> * https://arthas.aliyun.com/doc/quick-start.html |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"> */</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">proxy0</span> <span style="color:#268bd2">implements</span> ClassGenerator.DC, EchoService, DemoService { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 方法数组</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> Method<span style="color:#719e07">[]</span> methods; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> InvocationHandler handler; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">proxy0</span>(InvocationHandler invocationHandler) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.handler <span style="color:#719e07">=</span> invocationHandler; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">proxy0</span>() { |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> String <span style="color:#268bd2">sayHello</span>(String string) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将参数存储到 Object 数组中</span> |
| </span></span><span style="display:flex;"><span> Object<span style="color:#719e07">[]</span> arrobject <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Object<span style="color:#719e07">[]</span>{string}; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 InvocationHandler 实现类的 invoke 方法得到调用结果</span> |
| </span></span><span style="display:flex;"><span> Object object <span style="color:#719e07">=</span> <span style="color:#719e07">this</span>.handler.invoke(<span style="color:#719e07">this</span>, methods<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>, arrobject); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回调用结果</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> (String)object; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">/** 回声测试方法 */</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Object <span style="color:#268bd2">$echo</span>(Object object) { |
| </span></span><span style="display:flex;"><span> Object<span style="color:#719e07">[]</span> arrobject <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Object<span style="color:#719e07">[]</span>{object}; |
| </span></span><span style="display:flex;"><span> Object object2 <span style="color:#719e07">=</span> <span style="color:#719e07">this</span>.handler.invoke(<span style="color:#719e07">this</span>, methods<span style="color:#719e07">[</span>1<span style="color:#719e07">]</span>, arrobject); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> object2; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,代理类的逻辑比较简单。首先将运行时参数存储到数组中,然后调用 InvocationHandler 接口实现类的 invoke 方法,得到调用结果,最后将结果转型并返回给调用方。关于代理类的逻辑就说这么多,继续向下分析。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">InvokerInvocationHandler</span> <span style="color:#268bd2">implements</span> InvocationHandler { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Invoker<span style="color:#719e07">&lt;?&gt;</span> invoker; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">InvokerInvocationHandler</span>(Invoker<span style="color:#719e07">&lt;?&gt;</span> handler) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.invoker <span style="color:#719e07">=</span> handler; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Object <span style="color:#268bd2">invoke</span>(Object proxy, Method method, Object<span style="color:#719e07">[]</span> args) <span style="color:#268bd2">throws</span> Throwable { |
| </span></span><span style="display:flex;"><span> String methodName <span style="color:#719e07">=</span> method.getName(); |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> parameterTypes <span style="color:#719e07">=</span> method.getParameterTypes(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 拦截定义在 Object 类中的方法(未被子类重写),比如 wait/notify</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (method.getDeclaringClass() <span style="color:#719e07">==</span> Object.class) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> method.invoke(invoker, args); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 toString、hashCode 和 equals 等方法被子类重写了,这里也直接调用</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#2aa198">&#34;toString&#34;</span>.equals(methodName) <span style="color:#719e07">&amp;&amp;</span> parameterTypes.length <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invoker.toString(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#2aa198">&#34;hashCode&#34;</span>.equals(methodName) <span style="color:#719e07">&amp;&amp;</span> parameterTypes.length <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invoker.hashCode(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#2aa198">&#34;equals&#34;</span>.equals(methodName) <span style="color:#719e07">&amp;&amp;</span> parameterTypes.length <span style="color:#719e07">==</span> 1) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invoker.equals(args<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 method 和 args 封装到 RpcInvocation 中,并执行后续的调用</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invoker.invoke(<span style="color:#719e07">new</span> RpcInvocation(method, args)).recreate(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>InvokerInvocationHandler 中的 invoker 成员变量类型为 MockClusterInvoker,MockClusterInvoker 内部封装了服务降级逻辑。下面简单看一下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">MockClusterInvoker</span><span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">implements</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Result <span style="color:#268bd2">invoke</span>(Invocation invocation) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> Result result <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 mock 配置值</span> |
| </span></span><span style="display:flex;"><span> String value <span style="color:#719e07">=</span> directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (value.length() <span style="color:#719e07">==</span> 0 <span style="color:#719e07">||</span> value.equalsIgnoreCase(<span style="color:#2aa198">&#34;false&#34;</span>)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 无 mock 逻辑,直接调用其他 Invoker 对象的 invoke 方法,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 比如 FailoverClusterInvoker</span> |
| </span></span><span style="display:flex;"><span> result <span style="color:#719e07">=</span> <span style="color:#719e07">this</span>.invoker.invoke(invocation); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (value.startsWith(<span style="color:#2aa198">&#34;force&#34;</span>)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// force:xxx 直接执行 mock 逻辑,不发起远程调用</span> |
| </span></span><span style="display:flex;"><span> result <span style="color:#719e07">=</span> doMockInvoke(invocation, <span style="color:#cb4b16">null</span>); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// fail:xxx 表示消费方对调用服务失败后,再执行 mock 逻辑,不抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用其他 Invoker 对象的 invoke 方法</span> |
| </span></span><span style="display:flex;"><span> result <span style="color:#719e07">=</span> <span style="color:#719e07">this</span>.invoker.invoke(invocation); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (RpcException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (e.isBiz()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> e; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用失败,执行 mock 逻辑</span> |
| </span></span><span style="display:flex;"><span> result <span style="color:#719e07">=</span> doMockInvoke(invocation, e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> result; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略其他方法</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>服务降级不是本文重点,因此这里就不分析 doMockInvoke 方法了。考虑到前文已经详细分析过 FailoverClusterInvoker,因此本节略过 FailoverClusterInvoker,直接分析 DubboInvoker。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">abstract</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">AbstractInvoker</span><span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">implements</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Result <span style="color:#268bd2">invoke</span>(Invocation inv) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (destroyed.get()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(<span style="color:#2aa198">&#34;Rpc invoker for service ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> RpcInvocation invocation <span style="color:#719e07">=</span> (RpcInvocation) inv; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 Invoker</span> |
| </span></span><span style="display:flex;"><span> invocation.setInvoker(<span style="color:#719e07">this</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (attachment <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> attachment.size() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 attachment</span> |
| </span></span><span style="display:flex;"><span> invocation.addAttachmentsIfAbsent(attachment); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span> contextAttachments <span style="color:#719e07">=</span> RpcContext.getContext().getAttachments(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (contextAttachments <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> contextAttachments.size() <span style="color:#719e07">!=</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 contextAttachments 到 RpcInvocation#attachment 变量中</span> |
| </span></span><span style="display:flex;"><span> invocation.addAttachments(contextAttachments); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, <span style="color:#cb4b16">false</span>)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置异步信息到 RpcInvocation#attachment 中</span> |
| </span></span><span style="display:flex;"><span> invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 抽象方法,由子类实现</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> doInvoke(invocation); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (InvocationTargetException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ...</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (RpcException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ...</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> RpcResult(e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">abstract</span> Result <span style="color:#268bd2">doInvoke</span>(Invocation invocation) <span style="color:#268bd2">throws</span> Throwable; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略其他方法</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面的代码来自 AbstractInvoker 类,其中大部分代码用于添加信息到 RpcInvocation#attachment 变量中,添加完毕后,调用 doInvoke 执行后续的调用。doInvoke 是一个抽象方法,需要由子类实现,下面到 DubboInvoker 中看一下。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">DubboInvoker</span><span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">extends</span> AbstractInvoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ExchangeClient<span style="color:#719e07">[]</span> clients; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> Result <span style="color:#268bd2">doInvoke</span>(<span style="color:#268bd2">final</span> Invocation invocation) <span style="color:#268bd2">throws</span> Throwable { |
| </span></span><span style="display:flex;"><span> RpcInvocation inv <span style="color:#719e07">=</span> (RpcInvocation) invocation; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> String methodName <span style="color:#719e07">=</span> RpcUtils.getMethodName(invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 path 和 version 到 attachment 中</span> |
| </span></span><span style="display:flex;"><span> inv.setAttachment(Constants.PATH_KEY, getUrl().getPath()); |
| </span></span><span style="display:flex;"><span> inv.setAttachment(Constants.VERSION_KEY, version); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> ExchangeClient currentClient; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (clients.length <span style="color:#719e07">==</span> 1) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从 clients 数组中获取 ExchangeClient</span> |
| </span></span><span style="display:flex;"><span> currentClient <span style="color:#719e07">=</span> clients<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> currentClient <span style="color:#719e07">=</span> clients<span style="color:#719e07">[</span>index.getAndIncrement() <span style="color:#719e07">%</span> clients.length<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取异步配置</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> isAsync <span style="color:#719e07">=</span> RpcUtils.isAsync(getUrl(), invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// isOneway 为 true,表示“单向”通信</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> isOneway <span style="color:#719e07">=</span> RpcUtils.isOneway(getUrl(), invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> timeout <span style="color:#719e07">=</span> getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 异步无返回值</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (isOneway) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> isSent <span style="color:#719e07">=</span> getUrl().getMethodParameter(methodName, Constants.SENT_KEY, <span style="color:#cb4b16">false</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 发送请求</span> |
| </span></span><span style="display:flex;"><span> currentClient.send(inv, isSent); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置上下文中的 future 字段为 null</span> |
| </span></span><span style="display:flex;"><span> RpcContext.getContext().setFuture(<span style="color:#cb4b16">null</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回一个空的 RpcResult</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> RpcResult(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 异步有返回值</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (isAsync) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 发送请求,并得到一个 ResponseFuture 实例</span> |
| </span></span><span style="display:flex;"><span> ResponseFuture future <span style="color:#719e07">=</span> currentClient.request(inv, timeout); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 future 到上下文中</span> |
| </span></span><span style="display:flex;"><span> RpcContext.getContext().setFuture(<span style="color:#719e07">new</span> FutureAdapter<span style="color:#719e07">&lt;</span>Object<span style="color:#719e07">&gt;</span>(future)); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 暂时返回一个空结果</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> RpcResult(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 同步调用</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> RpcContext.getContext().setFuture(<span style="color:#cb4b16">null</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 发送请求,得到一个 ResponseFuture 实例,并调用该实例的 get 方法进行等待</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> (Result) currentClient.request(inv, timeout).get(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (TimeoutException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(..., <span style="color:#2aa198">&#34;Invoke remote method timeout....&#34;</span>); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (RemotingException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(..., <span style="color:#2aa198">&#34;Failed to invoke remote method: ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略其他方法</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面的代码包含了 Dubbo 对同步和异步调用的处理逻辑,搞懂了上面的代码,会对 Dubbo 的同步和异步调用方式有更深入的了解。Dubbo 实现同步和异步调用比较关键的一点就在于由谁调用 ResponseFuture 的 get 方法。同步调用模式下,由框架自身调用 ResponseFuture 的 get 方法。异步调用模式下,则由用户调用该方法。ResponseFuture 是一个接口,下面我们来看一下它的默认实现类 DefaultFuture 的源码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">DefaultFuture</span> <span style="color:#268bd2">implements</span> ResponseFuture { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>Long, Channel<span style="color:#719e07">&gt;</span> CHANNELS <span style="color:#719e07">=</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;</span>Long, Channel<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>Long, DefaultFuture<span style="color:#719e07">&gt;</span> FUTURES <span style="color:#719e07">=</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;</span>Long, DefaultFuture<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">long</span> id; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Channel channel; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Request request; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">int</span> timeout; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Lock lock <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ReentrantLock(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Condition done <span style="color:#719e07">=</span> lock.newCondition(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">volatile</span> Response response; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">DefaultFuture</span>(Channel channel, Request request, <span style="color:#dc322f">int</span> timeout) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.channel <span style="color:#719e07">=</span> channel; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.request <span style="color:#719e07">=</span> request; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取请求 id,这个 id 很重要,后面还会见到</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.id <span style="color:#719e07">=</span> request.getId(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.timeout <span style="color:#719e07">=</span> timeout <span style="color:#719e07">&gt;</span> 0 <span style="color:#719e07">?</span> timeout : channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 存储 &lt;requestId, DefaultFuture&gt; 映射关系到 FUTURES 中</span> |
| </span></span><span style="display:flex;"><span> FUTURES.put(id, <span style="color:#719e07">this</span>); |
| </span></span><span style="display:flex;"><span> CHANNELS.put(id, channel); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Object <span style="color:#268bd2">get</span>() <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> get(timeout); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Object <span style="color:#268bd2">get</span>(<span style="color:#dc322f">int</span> timeout) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (timeout <span style="color:#719e07">&lt;=</span> 0) { |
| </span></span><span style="display:flex;"><span> timeout <span style="color:#719e07">=</span> Constants.DEFAULT_TIMEOUT; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测服务提供方是否成功返回了调用结果</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>isDone()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">long</span> start <span style="color:#719e07">=</span> System.currentTimeMillis(); |
| </span></span><span style="display:flex;"><span> lock.lock(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 循环检测服务提供方是否成功返回了调用结果</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">while</span> (<span style="color:#719e07">!</span>isDone()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果调用结果尚未返回,这里等待一段时间</span> |
| </span></span><span style="display:flex;"><span> done.await(timeout, TimeUnit.MILLISECONDS); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果调用结果成功返回,或等待超时,此时跳出 while 循环,执行后续的逻辑</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (isDone() <span style="color:#719e07">||</span> System.currentTimeMillis() <span style="color:#719e07">-</span> start <span style="color:#719e07">&gt;</span> timeout) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (InterruptedException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RuntimeException(e); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> lock.unlock(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果调用结果仍未返回,则抛出超时异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>isDone()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> TimeoutException(sent <span style="color:#719e07">&gt;</span> 0, channel, getTimeoutMessage(<span style="color:#cb4b16">false</span>)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回调用结果</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> returnFromResponse(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">boolean</span> <span style="color:#268bd2">isDone</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过检测 response 字段为空与否,判断是否收到了调用结果</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> response <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> Object <span style="color:#268bd2">returnFromResponse</span>() <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> Response res <span style="color:#719e07">=</span> response; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (res <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;response cannot be null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果调用结果的状态为 Response.OK,则表示调用过程正常,服务提供方成功返回了调用结果</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (res.getStatus() <span style="color:#719e07">==</span> Response.OK) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> res.getResult(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (res.getStatus() <span style="color:#719e07">==</span> Response.CLIENT_TIMEOUT <span style="color:#719e07">||</span> res.getStatus() <span style="color:#719e07">==</span> Response.SERVER_TIMEOUT) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> TimeoutException(res.getStatus() <span style="color:#719e07">==</span> Response.SERVER_TIMEOUT, channel, res.getErrorMessage()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RemotingException(channel, res.getErrorMessage()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略其他方法</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,当服务消费者还未接收到调用结果时,用户线程调用 get 方法会被阻塞住。同步调用模式下,框架获得 DefaultFuture 对象后,会立即调用 get 方法进行等待。而异步模式下则是将该对象封装到 FutureAdapter 实例中,并将 FutureAdapter 实例设置到 RpcContext 中,供用户使用。FutureAdapter 是一个适配器,用于将 Dubbo 中的 ResponseFuture 与 JDK 中的 Future 进行适配。这样当用户线程调用 Future 的 get 方法时,经过 FutureAdapter 适配,最终会调用 ResponseFuture 实现类对象的 get 方法,也就是 DefaultFuture 的 get 方法。</p> |
| <p>到这里关于 Dubbo 几种调用方式的代码逻辑就分析完了,下面来分析请求数据的发送与接收,以及响应数据的发送与接收过程。</p> |
| <h3 id="22-服务消费方发送请求">2.2 服务消费方发送请求</h3> |
| <h4 id="221-发送请求">2.2.1 发送请求</h4> |
| <p>本节我们来看一下同步调用模式下,服务消费方是如何发送调用请求的。在深入分析源码前,我们先来看一张图。</p> |
| <p><img src="https://dubbo.apache.org/imgs/dev/send-request-thread-stack.jpg" alt="img"></p> |
| <p>这张图展示了服务消费方发送请求过程的部分调用栈,略为复杂。从上图可以看出,经过多次调用后,才将请求数据送至 Netty NioClientSocketChannel。这样做的原因是通过 Exchange 层为框架引入 Request 和 Response 语义,这一点会在接下来的源码分析过程中会看到。其他的就不多说了,下面开始进行分析。首先分析 ReferenceCountExchangeClient 的源码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">final</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">ReferenceCountExchangeClient</span> <span style="color:#268bd2">implements</span> ExchangeClient { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> URL url; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> AtomicInteger referenceCount <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> AtomicInteger(0); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">ReferenceCountExchangeClient</span>(ExchangeClient client, ConcurrentMap<span style="color:#719e07">&lt;</span>String, LazyConnectExchangeClient<span style="color:#719e07">&gt;</span> ghostClientMap) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.client <span style="color:#719e07">=</span> client; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 引用计数自增</span> |
| </span></span><span style="display:flex;"><span> referenceCount.incrementAndGet(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.url <span style="color:#719e07">=</span> client.getUrl(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ...</span> |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> ResponseFuture <span style="color:#268bd2">request</span>(Object request) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 直接调用被装饰对象的同签名方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> client.request(request); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> ResponseFuture <span style="color:#268bd2">request</span>(Object request, <span style="color:#dc322f">int</span> timeout) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 直接调用被装饰对象的同签名方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> client.request(request, timeout); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">/** 引用计数自增,该方法由外部调用 */</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">incrementAndGetCount</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// referenceCount 自增</span> |
| </span></span><span style="display:flex;"><span> referenceCount.incrementAndGet(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">close</span>(<span style="color:#dc322f">int</span> timeout) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// referenceCount 自减</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (referenceCount.decrementAndGet() <span style="color:#719e07">&lt;=</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (timeout <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> client.close(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> client.close(timeout); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> client <span style="color:#719e07">=</span> replaceWithLazyClient(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略部分方法</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>ReferenceCountExchangeClient 内部定义了一个引用计数变量 referenceCount,每当该对象被引用一次 referenceCount 都会进行自增。每当 close 方法被调用时,referenceCount 进行自减。ReferenceCountExchangeClient 内部仅实现了一个引用计数的功能,其他方法并无复杂逻辑,均是直接调用被装饰对象的相关方法。所以这里就不多说了,继续向下分析,这次是 HeaderExchangeClient。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">HeaderExchangeClient</span> <span style="color:#268bd2">implements</span> ExchangeClient { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> ScheduledThreadPoolExecutor scheduled <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ScheduledThreadPoolExecutor(2, <span style="color:#719e07">new</span> NamedThreadFactory(<span style="color:#2aa198">&#34;dubbo-remoting-client-heartbeat&#34;</span>, <span style="color:#cb4b16">true</span>)); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Client client; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ExchangeChannel channel; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> ScheduledFuture<span style="color:#719e07">&lt;?&gt;</span> heartbeatTimer; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">int</span> heartbeat; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">int</span> heartbeatTimeout; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">HeaderExchangeClient</span>(Client client, <span style="color:#dc322f">boolean</span> needHeartbeat) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (client <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;client == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.client <span style="color:#719e07">=</span> client; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 HeaderExchangeChannel 对象</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.channel <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HeaderExchangeChannel(client); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 以下代码均与心跳检测逻辑有关</span> |
| </span></span><span style="display:flex;"><span> String dubbo <span style="color:#719e07">=</span> client.getUrl().getParameter(Constants.DUBBO_VERSION_KEY); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.heartbeat <span style="color:#719e07">=</span> client.getUrl().getParameter(Constants.HEARTBEAT_KEY, dubbo <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> dubbo.startsWith(<span style="color:#2aa198">&#34;1.0.&#34;</span>) <span style="color:#719e07">?</span> Constants.DEFAULT_HEARTBEAT : 0); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.heartbeatTimeout <span style="color:#719e07">=</span> client.getUrl().getParameter(Constants.HEARTBEAT_TIMEOUT_KEY, heartbeat <span style="color:#719e07">*</span> 3); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (heartbeatTimeout <span style="color:#719e07">&lt;</span> heartbeat <span style="color:#719e07">*</span> 2) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;heartbeatTimeout &lt; heartbeatInterval * 2&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (needHeartbeat) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 开启心跳检测定时器</span> |
| </span></span><span style="display:flex;"><span> startHeartbeatTimer(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> ResponseFuture <span style="color:#268bd2">request</span>(Object request) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 直接 HeaderExchangeChannel 对象的同签名方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> channel.request(request); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> ResponseFuture <span style="color:#268bd2">request</span>(Object request, <span style="color:#dc322f">int</span> timeout) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 直接 HeaderExchangeChannel 对象的同签名方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> channel.request(request, timeout); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">close</span>() { |
| </span></span><span style="display:flex;"><span> doClose(); |
| </span></span><span style="display:flex;"><span> channel.close(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">doClose</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 停止心跳检测定时器</span> |
| </span></span><span style="display:flex;"><span> stopHeartbeatTimer(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">startHeartbeatTimer</span>() { |
| </span></span><span style="display:flex;"><span> stopHeartbeatTimer(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (heartbeat <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> heartbeatTimer <span style="color:#719e07">=</span> scheduled.scheduleWithFixedDelay( |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">new</span> HeartBeatTask(<span style="color:#719e07">new</span> HeartBeatTask.ChannelProvider() { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Collection<span style="color:#719e07">&lt;</span>Channel<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">getChannels</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> Collections.<span style="color:#719e07">&lt;</span>Channel<span style="color:#719e07">&gt;</span>singletonList(HeaderExchangeClient.this); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> }, heartbeat, heartbeatTimeout), |
| </span></span><span style="display:flex;"><span> heartbeat, heartbeat, TimeUnit.MILLISECONDS); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">stopHeartbeatTimer</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (heartbeatTimer <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>heartbeatTimer.isCancelled()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> heartbeatTimer.cancel(<span style="color:#cb4b16">true</span>); |
| </span></span><span style="display:flex;"><span> scheduled.purge(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (logger.isWarnEnabled()) { |
| </span></span><span style="display:flex;"><span> logger.warn(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> heartbeatTimer <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略部分方法</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>HeaderExchangeClient 中很多方法只有一行代码,即调用 HeaderExchangeChannel 对象的同签名方法。那 HeaderExchangeClient 有什么用处呢?答案是封装了一些关于心跳检测的逻辑。心跳检测并非本文所关注的点,因此就不多说了,继续向下看。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">final</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">HeaderExchangeChannel</span> <span style="color:#268bd2">implements</span> ExchangeChannel { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Channel channel; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> HeaderExchangeChannel(Channel channel) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (channel <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;channel == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 这里的 channel 指向的是 NettyClient</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.channel <span style="color:#719e07">=</span> channel; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> ResponseFuture <span style="color:#268bd2">request</span>(Object request) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> request(request, channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> ResponseFuture <span style="color:#268bd2">request</span>(Object request, <span style="color:#dc322f">int</span> timeout) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (closed) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RemotingException(..., <span style="color:#2aa198">&#34;Failed to send request ...); |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> } |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> // 创建 Request 对象 |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> Request req = new Request(); |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> req.setVersion(Version.getProtocolVersion()); |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> // 设置双向通信标志为 true |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> req.setTwoWay(true); |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> // 这里的 request 变量类型为 RpcInvocation |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> req.setData(request); |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> // 创建 DefaultFuture 对象 |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> DefaultFuture future = new DefaultFuture(channel, req, timeout); |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> try { |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> // 调用 NettyClient 的 send 方法发送请求 |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> channel.send(req); |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> } catch (RemotingException e) { |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> future.cancel(); |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> throw e; |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> } |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> // 返回 DefaultFuture 对象 |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> return future; |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198"> } |
| </span></span></span><span style="display:flex;"><span><span style="color:#2aa198">} |
| </span></span></span></code></pre></div><p>到这里大家终于看到了 Request 语义了,上面的方法首先定义了一个 Request 对象,然后再将该对象传给 NettyClient 的 send 方法,进行后续的调用。需要说明的是,NettyClient 中并未实现 send 方法,该方法继承自父类 AbstractPeer,下面直接分析 AbstractPeer 的代码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">abstract</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">AbstractPeer</span> <span style="color:#268bd2">implements</span> Endpoint, ChannelHandler { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">send</span>(Object message) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 该方法由 AbstractClient 类实现</span> |
| </span></span><span style="display:flex;"><span> send(message, url.getParameter(Constants.SENT_KEY, <span style="color:#cb4b16">false</span>)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略其他方法</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">abstract</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">AbstractClient</span> <span style="color:#268bd2">extends</span> AbstractEndpoint <span style="color:#268bd2">implements</span> Client { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">send</span>(Object message, <span style="color:#dc322f">boolean</span> sent) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (send_reconnect <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>isConnected()) { |
| </span></span><span style="display:flex;"><span> connect(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 Channel,getChannel 是一个抽象方法,具体由子类实现</span> |
| </span></span><span style="display:flex;"><span> Channel channel <span style="color:#719e07">=</span> getChannel(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (channel <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> <span style="color:#719e07">!</span>channel.isConnected()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RemotingException(<span style="color:#719e07">this</span>, <span style="color:#2aa198">&#34;message can not send ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 继续向下调用</span> |
| </span></span><span style="display:flex;"><span> channel.send(message, sent); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">abstract</span> Channel <span style="color:#268bd2">getChannel</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略其他方法</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>默认情况下,Dubbo 使用 Netty 作为底层的通信框架,因此下面我们到 NettyClient 类中看一下 getChannel 方法的实现逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">NettyClient</span> <span style="color:#268bd2">extends</span> AbstractClient { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 这里的 Channel 全限定名称为 org.jboss.netty.channel.Channel</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">volatile</span> Channel channel; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> com.alibaba.dubbo.remoting.Channel <span style="color:#268bd2">getChannel</span>() { |
| </span></span><span style="display:flex;"><span> Channel c <span style="color:#719e07">=</span> channel; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (c <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> <span style="color:#719e07">!</span>c.isConnected()) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取一个 NettyChannel 类型对象</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> NettyChannel.getOrAddChannel(c, getUrl(), <span style="color:#719e07">this</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">final</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">NettyChannel</span> <span style="color:#268bd2">extends</span> AbstractChannel { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> ConcurrentMap<span style="color:#719e07">&lt;</span>org.jboss.netty.channel.Channel, NettyChannel<span style="color:#719e07">&gt;</span> channelMap <span style="color:#719e07">=</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;</span>org.jboss.netty.channel.Channel, NettyChannel<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> org.jboss.netty.channel.Channel channel; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">/** 私有构造方法 */</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">NettyChannel</span>(org.jboss.netty.channel.Channel channel, URL url, ChannelHandler handler) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">super</span>(url, handler); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (channel <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;netty channel == null;&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.channel <span style="color:#719e07">=</span> channel; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">static</span> NettyChannel <span style="color:#268bd2">getOrAddChannel</span>(org.jboss.netty.channel.Channel ch, URL url, ChannelHandler handler) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ch <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 尝试从集合中获取 NettyChannel 实例</span> |
| </span></span><span style="display:flex;"><span> NettyChannel ret <span style="color:#719e07">=</span> channelMap.get(ch); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ret <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 ret = null,则创建一个新的 NettyChannel 实例</span> |
| </span></span><span style="display:flex;"><span> NettyChannel nc <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> NettyChannel(ch, url, handler); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ch.isConnected()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 &lt;Channel, NettyChannel&gt; 键值对存入 channelMap 集合中</span> |
| </span></span><span style="display:flex;"><span> ret <span style="color:#719e07">=</span> channelMap.putIfAbsent(ch, nc); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ret <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> ret <span style="color:#719e07">=</span> nc; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> ret; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>获取到 NettyChannel 实例后,即可进行后续的调用。下面看一下 NettyChannel 的 send 方法。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">send</span>(Object message, <span style="color:#dc322f">boolean</span> sent) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">super</span>.send(message, sent); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> success <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> timeout <span style="color:#719e07">=</span> 0; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 发送消息(包含请求和响应消息)</span> |
| </span></span><span style="display:flex;"><span> ChannelFuture future <span style="color:#719e07">=</span> channel.write(message); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// sent 的值源于 &lt;dubbo:method sent=&#34;true/false&#34; /&gt; 中 sent 的配置值,有两种配置值:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 1. true: 等待消息发出,消息发送失败将抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 2. false: 不等待消息发出,将消息放入 IO 队列,即刻返回</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 默认情况下 sent = false;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (sent) { |
| </span></span><span style="display:flex;"><span> timeout <span style="color:#719e07">=</span> getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 等待消息发出,若在规定时间没能发出,success 会被置为 false</span> |
| </span></span><span style="display:flex;"><span> success <span style="color:#719e07">=</span> future.await(timeout); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> Throwable cause <span style="color:#719e07">=</span> future.getCause(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (cause <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> cause; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RemotingException(<span style="color:#719e07">this</span>, <span style="color:#2aa198">&#34;Failed to send message ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 若 success 为 false,这里抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>success) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RemotingException(<span style="color:#719e07">this</span>, <span style="color:#2aa198">&#34;Failed to send message ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>经历多次调用,到这里请求数据的发送过程就结束了,过程漫长。为了便于大家阅读代码,这里以 DemoService 为例,将 sayHello 方法的整个调用路径贴出来。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>proxy0#sayHello(String) |
| </span></span><span style="display:flex;"><span> —&gt; InvokerInvocationHandler#invoke(Object, Method, Object[]) |
| </span></span><span style="display:flex;"><span> —&gt; MockClusterInvoker#invoke(Invocation) |
| </span></span><span style="display:flex;"><span> —&gt; AbstractClusterInvoker#invoke(Invocation) |
| </span></span><span style="display:flex;"><span> —&gt; FailoverClusterInvoker#doInvoke(Invocation, List&lt;Invoker&lt;T&gt;&gt;, LoadBalance) |
| </span></span><span style="display:flex;"><span> —&gt; Filter#invoke(Invoker, Invocation) // 包含多个 Filter 调用 |
| </span></span><span style="display:flex;"><span> —&gt; ListenerInvokerWrapper#invoke(Invocation) |
| </span></span><span style="display:flex;"><span> —&gt; AbstractInvoker#invoke(Invocation) |
| </span></span><span style="display:flex;"><span> —&gt; DubboInvoker#doInvoke(Invocation) |
| </span></span><span style="display:flex;"><span> —&gt; ReferenceCountExchangeClient#request(Object, int) |
| </span></span><span style="display:flex;"><span> —&gt; HeaderExchangeClient#request(Object, int) |
| </span></span><span style="display:flex;"><span> —&gt; HeaderExchangeChannel#request(Object, int) |
| </span></span><span style="display:flex;"><span> —&gt; AbstractPeer#send(Object) |
| </span></span><span style="display:flex;"><span> —&gt; AbstractClient#send(Object, boolean) |
| </span></span><span style="display:flex;"><span> —&gt; NettyChannel#send(Object, boolean) |
| </span></span><span style="display:flex;"><span> —&gt; NioClientSocketChannel#write(Object) |
| </span></span></code></pre></div><p>在 Netty 中,出站数据在发出之前还需要进行编码操作,接下来我们来分析一下请求数据的编码逻辑。</p> |
| <h4 id="222-请求编码">2.2.2 请求编码</h4> |
| <p>在分析请求编码逻辑之前,我们先来看一下 Dubbo 数据包结构。</p> |
| <p><img src="https://dubbo.apache.org/imgs/dev/data-format.jpg" alt="img"></p> |
| <p>Dubbo 数据包分为消息头和消息体,消息头用于存储一些元信息,比如魔数(Magic),数据包类型(Request/Response),消息体长度(Data Length)等。消息体中用于存储具体的调用消息,比如方法名称,参数列表等。下面简单列举一下消息头的内容。</p> |
| <table> |
| <thead> |
| <tr> |
| <th>偏移量(Bit)</th> |
| <th>字段</th> |
| <th>取值</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>0 ~ 7</td> |
| <td>魔数高位</td> |
| <td>0xda00</td> |
| </tr> |
| <tr> |
| <td>8 ~ 15</td> |
| <td>魔数低位</td> |
| <td>0xbb</td> |
| </tr> |
| <tr> |
| <td>16</td> |
| <td>数据包类型</td> |
| <td>0 - Response, 1 - Request</td> |
| </tr> |
| <tr> |
| <td>17</td> |
| <td>调用方式</td> |
| <td>仅在第16位被设为1的情况下有效,0 - 单向调用,1 - 双向调用</td> |
| </tr> |
| <tr> |
| <td>18</td> |
| <td>事件标识</td> |
| <td>0 - 当前数据包是请求或响应包,1 - 当前数据包是心跳包</td> |
| </tr> |
| <tr> |
| <td>19 ~ 23</td> |
| <td>序列化器编号</td> |
| <td>2 - Hessian2Serialization<br>3 - JavaSerialization<br/>4 - CompactedJavaSerialization<br/>6 - FastJsonSerialization<br/>7 - NativeJavaSerialization<br/>8 - KryoSerialization<br/>9 - FstSerialization</td> |
| </tr> |
| <tr> |
| <td>24 ~ 31</td> |
| <td>状态</td> |
| <td>20 - OK<br/>30 - CLIENT_TIMEOUT<br/>31 - SERVER_TIMEOUT<br/>40 - BAD_REQUEST<br/>50 - BAD_RESPONSE<br/>&hellip;&hellip;</td> |
| </tr> |
| <tr> |
| <td>32 ~ 95</td> |
| <td>请求编号</td> |
| <td>共8字节,运行时生成</td> |
| </tr> |
| <tr> |
| <td>96 ~ 127</td> |
| <td>消息体长度</td> |
| <td>运行时计算</td> |
| </tr> |
| </tbody> |
| </table> |
| <p>了解了 Dubbo 数据包格式,接下来我们就可以探索编码过程了。这次我们开门见山,直接分析编码逻辑所在类。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">ExchangeCodec</span> <span style="color:#268bd2">extends</span> TelnetCodec { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 消息头长度</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">int</span> HEADER_LENGTH <span style="color:#719e07">=</span> 16; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 魔数内容</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">short</span> MAGIC <span style="color:#719e07">=</span> (<span style="color:#dc322f">short</span>) 0xdabb; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">byte</span> MAGIC_HIGH <span style="color:#719e07">=</span> Bytes.short2bytes(MAGIC)<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">byte</span> MAGIC_LOW <span style="color:#719e07">=</span> Bytes.short2bytes(MAGIC)<span style="color:#719e07">[</span>1<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">byte</span> FLAG_REQUEST <span style="color:#719e07">=</span> (<span style="color:#dc322f">byte</span>) 0x80; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">byte</span> FLAG_TWOWAY <span style="color:#719e07">=</span> (<span style="color:#dc322f">byte</span>) 0x40; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">byte</span> FLAG_EVENT <span style="color:#719e07">=</span> (<span style="color:#dc322f">byte</span>) 0x20; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">int</span> SERIALIZATION_MASK <span style="color:#719e07">=</span> 0x1f; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> Logger logger <span style="color:#719e07">=</span> LoggerFactory.getLogger(ExchangeCodec.class); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Short <span style="color:#268bd2">getMagicCode</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> MAGIC; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">encode</span>(Channel channel, ChannelBuffer buffer, Object msg) <span style="color:#268bd2">throws</span> IOException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (msg <span style="color:#719e07">instanceof</span> Request) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 Request 对象进行编码</span> |
| </span></span><span style="display:flex;"><span> encodeRequest(channel, buffer, (Request) msg); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (msg <span style="color:#719e07">instanceof</span> Response) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 Response 对象进行编码,后面分析</span> |
| </span></span><span style="display:flex;"><span> encodeResponse(channel, buffer, (Response) msg); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">super</span>.encode(channel, buffer, msg); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">encodeRequest</span>(Channel channel, ChannelBuffer buffer, Request req) <span style="color:#268bd2">throws</span> IOException { |
| </span></span><span style="display:flex;"><span> Serialization serialization <span style="color:#719e07">=</span> getSerialization(channel); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建消息头字节数组,长度为 16</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">byte</span><span style="color:#719e07">[]</span> header <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> <span style="color:#dc322f">byte</span><span style="color:#719e07">[</span>HEADER_LENGTH<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置魔数</span> |
| </span></span><span style="display:flex;"><span> Bytes.short2bytes(MAGIC, header); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置数据包类型(Request/Response)和序列化器编号</span> |
| </span></span><span style="display:flex;"><span> header<span style="color:#719e07">[</span>2<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> (<span style="color:#dc322f">byte</span>) (FLAG_REQUEST <span style="color:#719e07">|</span> serialization.getContentTypeId()); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置通信方式(单向/双向)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (req.isTwoWay()) { |
| </span></span><span style="display:flex;"><span> header<span style="color:#719e07">[</span>2<span style="color:#719e07">]</span> <span style="color:#719e07">|=</span> FLAG_TWOWAY; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置事件标识</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (req.isEvent()) { |
| </span></span><span style="display:flex;"><span> header<span style="color:#719e07">[</span>2<span style="color:#719e07">]</span> <span style="color:#719e07">|=</span> FLAG_EVENT; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置请求编号,8个字节,从第4个字节开始设置</span> |
| </span></span><span style="display:flex;"><span> Bytes.long2bytes(req.getId(), header, 4); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 buffer 当前的写位置</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> savedWriteIndex <span style="color:#719e07">=</span> buffer.writerIndex(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 更新 writerIndex,为消息头预留 16 个字节的空间</span> |
| </span></span><span style="display:flex;"><span> buffer.writerIndex(savedWriteIndex <span style="color:#719e07">+</span> HEADER_LENGTH); |
| </span></span><span style="display:flex;"><span> ChannelBufferOutputStream bos <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ChannelBufferOutputStream(buffer); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建序列化器,比如 Hessian2ObjectOutput</span> |
| </span></span><span style="display:flex;"><span> ObjectOutput out <span style="color:#719e07">=</span> serialization.serialize(channel.getUrl(), bos); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (req.isEvent()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对事件数据进行序列化操作</span> |
| </span></span><span style="display:flex;"><span> encodeEventData(channel, out, req.getData()); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对请求数据进行序列化操作</span> |
| </span></span><span style="display:flex;"><span> encodeRequestData(channel, out, req.getData(), req.getVersion()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> out.flushBuffer(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (out <span style="color:#719e07">instanceof</span> Cleanable) { |
| </span></span><span style="display:flex;"><span> ((Cleanable) out).cleanup(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> bos.flush(); |
| </span></span><span style="display:flex;"><span> bos.close(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取写入的字节数,也就是消息体长度</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> len <span style="color:#719e07">=</span> bos.writtenBytes(); |
| </span></span><span style="display:flex;"><span> checkPayload(channel, len); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将消息体长度写入到消息头中</span> |
| </span></span><span style="display:flex;"><span> Bytes.int2bytes(len, header, 12); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 buffer 指针移动到 savedWriteIndex,为写消息头做准备</span> |
| </span></span><span style="display:flex;"><span> buffer.writerIndex(savedWriteIndex); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从 savedWriteIndex 下标处写入消息头</span> |
| </span></span><span style="display:flex;"><span> buffer.writeBytes(header); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置新的 writerIndex,writerIndex = 原写下标 + 消息头长度 + 消息体长度</span> |
| </span></span><span style="display:flex;"><span> buffer.writerIndex(savedWriteIndex <span style="color:#719e07">+</span> HEADER_LENGTH <span style="color:#719e07">+</span> len); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略其他方法</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上就是请求对象的编码过程,该过程首先会通过位运算将消息头写入到 header 数组中。然后对 Request 对象的 data 字段执行序列化操作,序列化后的数据最终会存储到 ChannelBuffer 中。序列化操作执行完后,可得到数据序列化后的长度 len,紧接着将 len 写入到 header 指定位置处。最后再将消息头字节数组 header 写入到 ChannelBuffer 中,整个编码过程就结束了。本节的最后,我们再来看一下 Request 对象的 data 字段序列化过程,也就是 encodeRequestData 方法的逻辑,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">DubboCodec</span> <span style="color:#268bd2">extends</span> ExchangeCodec <span style="color:#268bd2">implements</span> Codec2 { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">encodeRequestData</span>(Channel channel, ObjectOutput out, Object data, String version) <span style="color:#268bd2">throws</span> IOException { |
| </span></span><span style="display:flex;"><span> RpcInvocation inv <span style="color:#719e07">=</span> (RpcInvocation) data; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 依次序列化 dubbo version、path、version</span> |
| </span></span><span style="display:flex;"><span> out.writeUTF(version); |
| </span></span><span style="display:flex;"><span> out.writeUTF(inv.getAttachment(Constants.PATH_KEY)); |
| </span></span><span style="display:flex;"><span> out.writeUTF(inv.getAttachment(Constants.VERSION_KEY)); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 序列化调用方法名</span> |
| </span></span><span style="display:flex;"><span> out.writeUTF(inv.getMethodName()); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将参数类型转换为字符串,并进行序列化</span> |
| </span></span><span style="display:flex;"><span> out.writeUTF(ReflectUtils.getDesc(inv.getParameterTypes())); |
| </span></span><span style="display:flex;"><span> Object<span style="color:#719e07">[]</span> args <span style="color:#719e07">=</span> inv.getArguments(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (args <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> args.length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对运行时参数进行序列化</span> |
| </span></span><span style="display:flex;"><span> out.writeObject(encodeInvocationArgument(channel, inv, i)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 序列化 attachments</span> |
| </span></span><span style="display:flex;"><span> out.writeObject(inv.getAttachments()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>至此,关于服务消费方发送请求的过程就分析完了,接下来我们来看一下服务提供方是如何接收请求的。</p> |
| <h3 id="23-服务提供方接收请求">2.3 服务提供方接收请求</h3> |
| <p>前面说过,默认情况下 Dubbo 使用 Netty 作为底层的通信框架。Netty 检测到有数据入站后,首先会通过解码器对数据进行解码,并将解码后的数据传递给下一个入站处理器的指定方法。所以在进行后续的分析之前,我们先来看一下数据解码过程。</p> |
| <h4 id="231-请求解码">2.3.1 请求解码</h4> |
| <p>这里直接分析请求数据的解码逻辑,忽略中间过程,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">ExchangeCodec</span> <span style="color:#268bd2">extends</span> TelnetCodec { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Object <span style="color:#268bd2">decode</span>(Channel channel, ChannelBuffer buffer) <span style="color:#268bd2">throws</span> IOException { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> readable <span style="color:#719e07">=</span> buffer.readableBytes(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建消息头字节数组</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">byte</span><span style="color:#719e07">[]</span> header <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> <span style="color:#dc322f">byte</span><span style="color:#719e07">[</span>Math.min(readable, HEADER_LENGTH)<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 读取消息头数据</span> |
| </span></span><span style="display:flex;"><span> buffer.readBytes(header); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用重载方法进行后续解码工作</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> decode(channel, buffer, readable, header); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> Object <span style="color:#268bd2">decode</span>(Channel channel, ChannelBuffer buffer, <span style="color:#dc322f">int</span> readable, <span style="color:#dc322f">byte</span><span style="color:#719e07">[]</span> header) <span style="color:#268bd2">throws</span> IOException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检查魔数是否相等</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (readable <span style="color:#719e07">&gt;</span> 0 <span style="color:#719e07">&amp;&amp;</span> header<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span> <span style="color:#719e07">!=</span> MAGIC_HIGH |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">||</span> readable <span style="color:#719e07">&gt;</span> 1 <span style="color:#719e07">&amp;&amp;</span> header<span style="color:#719e07">[</span>1<span style="color:#719e07">]</span> <span style="color:#719e07">!=</span> MAGIC_LOW) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> length <span style="color:#719e07">=</span> header.length; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (header.length <span style="color:#719e07">&lt;</span> readable) { |
| </span></span><span style="display:flex;"><span> header <span style="color:#719e07">=</span> Bytes.copyOf(header, readable); |
| </span></span><span style="display:flex;"><span> buffer.readBytes(header, length, readable <span style="color:#719e07">-</span> length); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 1; i <span style="color:#719e07">&lt;</span> header.length <span style="color:#719e07">-</span> 1; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (header<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span> <span style="color:#719e07">==</span> MAGIC_HIGH <span style="color:#719e07">&amp;&amp;</span> header<span style="color:#719e07">[</span>i <span style="color:#719e07">+</span> 1<span style="color:#719e07">]</span> <span style="color:#719e07">==</span> MAGIC_LOW) { |
| </span></span><span style="display:flex;"><span> buffer.readerIndex(buffer.readerIndex() <span style="color:#719e07">-</span> header.length <span style="color:#719e07">+</span> i); |
| </span></span><span style="display:flex;"><span> header <span style="color:#719e07">=</span> Bytes.copyOf(header, i); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过 telnet 命令行发送的数据包不包含消息头,所以这里</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 TelnetCodec 的 decode 方法对数据包进行解码</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#268bd2">super</span>.decode(channel, buffer, readable, header); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测可读数据量是否少于消息头长度,若小于则立即返回 DecodeResult.NEED_MORE_INPUT</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (readable <span style="color:#719e07">&lt;</span> HEADER_LENGTH) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> DecodeResult.NEED_MORE_INPUT; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从消息头中获取消息体长度</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> len <span style="color:#719e07">=</span> Bytes.bytes2int(header, 12); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测消息体长度是否超出限制,超出则抛出异常</span> |
| </span></span><span style="display:flex;"><span> checkPayload(channel, len); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> tt <span style="color:#719e07">=</span> len <span style="color:#719e07">+</span> HEADER_LENGTH; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测可读的字节数是否小于实际的字节数</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (readable <span style="color:#719e07">&lt;</span> tt) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> DecodeResult.NEED_MORE_INPUT; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> ChannelBufferInputStream is <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ChannelBufferInputStream(buffer, len); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 继续进行解码工作</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> decodeBody(channel, is, header); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (is.available() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> StreamUtils.skipUnusedStream(is); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (IOException e) { |
| </span></span><span style="display:flex;"><span> logger.warn(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面方法通过检测消息头中的魔数是否与规定的魔数相等,提前拦截掉非常规数据包,比如通过 telnet 命令行发出的数据包。接着再对消息体长度,以及可读字节数进行检测。最后调用 decodeBody 方法进行后续的解码工作,ExchangeCodec 中实现了 decodeBody 方法,但因其子类 DubboCodec 覆写了该方法,所以在运行时 DubboCodec 中的 decodeBody 方法会被调用。下面我们来看一下该方法的代码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">DubboCodec</span> <span style="color:#268bd2">extends</span> ExchangeCodec <span style="color:#268bd2">implements</span> Codec2 { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> Object <span style="color:#268bd2">decodeBody</span>(Channel channel, InputStream is, <span style="color:#dc322f">byte</span><span style="color:#719e07">[]</span> header) <span style="color:#268bd2">throws</span> IOException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取消息头中的第三个字节,并通过逻辑与运算得到序列化器编号</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">byte</span> flag <span style="color:#719e07">=</span> header<span style="color:#719e07">[</span>2<span style="color:#719e07">]</span>, proto <span style="color:#719e07">=</span> (<span style="color:#dc322f">byte</span>) (flag <span style="color:#719e07">&amp;</span> SERIALIZATION_MASK); |
| </span></span><span style="display:flex;"><span> Serialization s <span style="color:#719e07">=</span> CodecSupport.getSerialization(channel.getUrl(), proto); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取调用编号</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">long</span> id <span style="color:#719e07">=</span> Bytes.bytes2long(header, 4); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过逻辑与运算得到调用类型,0 - Response,1 - Request</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((flag <span style="color:#719e07">&amp;</span> FLAG_REQUEST) <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对响应结果进行解码,得到 Response 对象。这个非本节内容,后面再分析</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ...</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 Request 对象</span> |
| </span></span><span style="display:flex;"><span> Request req <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Request(id); |
| </span></span><span style="display:flex;"><span> req.setVersion(Version.getProtocolVersion()); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过逻辑与运算得到通信方式,并设置到 Request 对象中</span> |
| </span></span><span style="display:flex;"><span> req.setTwoWay((flag <span style="color:#719e07">&amp;</span> FLAG_TWOWAY) <span style="color:#719e07">!=</span> 0); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过位运算检测数据包是否为事件类型</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((flag <span style="color:#719e07">&amp;</span> FLAG_EVENT) <span style="color:#719e07">!=</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置心跳事件到 Request 对象中</span> |
| </span></span><span style="display:flex;"><span> req.setEvent(Request.HEARTBEAT_EVENT); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> Object data; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (req.isHeartbeat()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对心跳包进行解码,该方法已被标注为废弃</span> |
| </span></span><span style="display:flex;"><span> data <span style="color:#719e07">=</span> decodeHeartbeatData(channel, deserialize(s, channel.getUrl(), is)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (req.isEvent()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对事件数据进行解码</span> |
| </span></span><span style="display:flex;"><span> data <span style="color:#719e07">=</span> decodeEventData(channel, deserialize(s, channel.getUrl(), is)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> DecodeableRpcInvocation inv; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据 url 参数判断是否在 IO 线程上对消息体进行解码</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (channel.getUrl().getParameter( |
| </span></span><span style="display:flex;"><span> Constants.DECODE_IN_IO_THREAD_KEY, |
| </span></span><span style="display:flex;"><span> Constants.DEFAULT_DECODE_IN_IO_THREAD)) { |
| </span></span><span style="display:flex;"><span> inv <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> DecodeableRpcInvocation(channel, req, is, proto); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 在当前线程,也就是 IO 线程上进行后续的解码工作。此工作完成后,可将</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用方法名、attachment、以及调用参数解析出来</span> |
| </span></span><span style="display:flex;"><span> inv.decode(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 仅创建 DecodeableRpcInvocation 对象,但不在当前线程上执行解码逻辑</span> |
| </span></span><span style="display:flex;"><span> inv <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> DecodeableRpcInvocation(channel, req, |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">new</span> UnsafeByteArrayInputStream(readMessageData(is)), proto); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> data <span style="color:#719e07">=</span> inv; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 data 到 Request 对象中</span> |
| </span></span><span style="display:flex;"><span> req.setData(data); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 若解码过程中出现异常,则将 broken 字段设为 true,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 并将异常对象设置到 Reqeust 对象中</span> |
| </span></span><span style="display:flex;"><span> req.setBroken(<span style="color:#cb4b16">true</span>); |
| </span></span><span style="display:flex;"><span> req.setData(t); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> req; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,decodeBody 对部分字段进行了解码,并将解码得到的字段封装到 Request 中。随后会调用 DecodeableRpcInvocation 的 decode 方法进行后续的解码工作。此工作完成后,可将调用方法名、attachment、以及调用参数解析出来。下面我们来看一下 DecodeableRpcInvocation 的 decode 方法逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">DecodeableRpcInvocation</span> <span style="color:#268bd2">extends</span> RpcInvocation <span style="color:#268bd2">implements</span> Codec, Decodeable { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Object <span style="color:#268bd2">decode</span>(Channel channel, InputStream input) <span style="color:#268bd2">throws</span> IOException { |
| </span></span><span style="display:flex;"><span> ObjectInput in <span style="color:#719e07">=</span> CodecSupport.getSerialization(channel.getUrl(), serializationType) |
| </span></span><span style="display:flex;"><span> .deserialize(channel.getUrl(), input); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过反序列化得到 dubbo version,并保存到 attachments 变量中</span> |
| </span></span><span style="display:flex;"><span> String dubboVersion <span style="color:#719e07">=</span> in.readUTF(); |
| </span></span><span style="display:flex;"><span> request.setVersion(dubboVersion); |
| </span></span><span style="display:flex;"><span> setAttachment(Constants.DUBBO_VERSION_KEY, dubboVersion); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过反序列化得到 path,version,并保存到 attachments 变量中</span> |
| </span></span><span style="display:flex;"><span> setAttachment(Constants.PATH_KEY, in.readUTF()); |
| </span></span><span style="display:flex;"><span> setAttachment(Constants.VERSION_KEY, in.readUTF()); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过反序列化得到调用方法名</span> |
| </span></span><span style="display:flex;"><span> setMethodName(in.readUTF()); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> Object<span style="color:#719e07">[]</span> args; |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> pts; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过反序列化得到参数类型字符串,比如 Ljava/lang/String;</span> |
| </span></span><span style="display:flex;"><span> String desc <span style="color:#719e07">=</span> in.readUTF(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (desc.length() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> pts <span style="color:#719e07">=</span> DubboCodec.EMPTY_CLASS_ARRAY; |
| </span></span><span style="display:flex;"><span> args <span style="color:#719e07">=</span> DubboCodec.EMPTY_OBJECT_ARRAY; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 desc 解析为参数类型数组</span> |
| </span></span><span style="display:flex;"><span> pts <span style="color:#719e07">=</span> ReflectUtils.desc2classArray(desc); |
| </span></span><span style="display:flex;"><span> args <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Object<span style="color:#719e07">[</span>pts.length<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> args.length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 解析运行时参数</span> |
| </span></span><span style="display:flex;"><span> args<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> in.readObject(pts<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (log.isWarnEnabled()) { |
| </span></span><span style="display:flex;"><span> log.warn(<span style="color:#2aa198">&#34;Decode argument failed: &#34;</span> <span style="color:#719e07">+</span> e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置参数类型数组</span> |
| </span></span><span style="display:flex;"><span> setParameterTypes(pts); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过反序列化得到原 attachment 的内容</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span> map <span style="color:#719e07">=</span> (Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span>) in.readObject(Map.class); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (map <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> map.size() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span> attachment <span style="color:#719e07">=</span> getAttachments(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (attachment <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> attachment <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 map 与当前对象中的 attachment 集合进行融合</span> |
| </span></span><span style="display:flex;"><span> attachment.putAll(map); |
| </span></span><span style="display:flex;"><span> setAttachments(attachment); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 callback 类型的参数进行处理</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> args.length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> args<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> decodeInvocationArgument(channel, <span style="color:#719e07">this</span>, pts, i, args<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置参数列表</span> |
| </span></span><span style="display:flex;"><span> setArguments(args); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (ClassNotFoundException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IOException(StringUtils.toString(<span style="color:#2aa198">&#34;Read invocation data failed.&#34;</span>, e)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (in <span style="color:#719e07">instanceof</span> Cleanable) { |
| </span></span><span style="display:flex;"><span> ((Cleanable) in).cleanup(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">this</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面的方法通过反序列化将诸如 path、version、调用方法名、参数列表等信息依次解析出来,并设置到相应的字段中,最终得到一个具有完整调用信息的 DecodeableRpcInvocation 对象。</p> |
| <p>到这里,请求数据解码的过程就分析完了。此时我们得到了一个 Request 对象,这个对象会被传送到下一个入站处理器中,我们继续往下看。</p> |
| <h4 id="232-调用服务">2.3.2 调用服务</h4> |
| <p>解码器将数据包解析成 Request 对象后,NettyHandler 的 messageReceived 方法紧接着会收到这个对象,并将这个对象继续向下传递。这期间该对象会被依次传递给 NettyServer、MultiMessageHandler、HeartbeatHandler 以及 AllChannelHandler。最后由 AllChannelHandler 将该对象封装到 Runnable 实现类对象中,并将 Runnable 放入线程池中执行后续的调用逻辑。整个调用栈如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>NettyHandler#messageReceived(ChannelHandlerContext, MessageEvent) |
| </span></span><span style="display:flex;"><span> —&gt; AbstractPeer#received(Channel, Object) |
| </span></span><span style="display:flex;"><span> —&gt; MultiMessageHandler#received(Channel, Object) |
| </span></span><span style="display:flex;"><span> —&gt; HeartbeatHandler#received(Channel, Object) |
| </span></span><span style="display:flex;"><span> —&gt; AllChannelHandler#received(Channel, Object) |
| </span></span><span style="display:flex;"><span> —&gt; ExecutorService#execute(Runnable) // 由线程池执行后续的调用逻辑 |
| </span></span></code></pre></div><p>考虑到篇幅,以及很多中间调用的逻辑并非十分重要,所以这里就不对调用栈中的每个方法都进行分析了。这里我们直接分析调用栈中的分析第一个和最后一个调用方法逻辑。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">@Sharable</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">NettyHandler</span> <span style="color:#268bd2">extends</span> SimpleChannelHandler { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Map<span style="color:#719e07">&lt;</span>String, Channel<span style="color:#719e07">&gt;</span> channels <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;</span>String, Channel<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> URL url; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ChannelHandler handler; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">NettyHandler</span>(URL url, ChannelHandler handler) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;url == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (handler <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;handler == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.url <span style="color:#719e07">=</span> url; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 这里的 handler 类型为 NettyServer</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.handler <span style="color:#719e07">=</span> handler; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">messageReceived</span>(ChannelHandlerContext ctx, MessageEvent e) <span style="color:#268bd2">throws</span> Exception { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 NettyChannel</span> |
| </span></span><span style="display:flex;"><span> NettyChannel channel <span style="color:#719e07">=</span> NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 继续向下调用</span> |
| </span></span><span style="display:flex;"><span> handler.received(channel, e.getMessage()); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> NettyChannel.removeChannelIfDisconnected(ctx.getChannel()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,NettyHandler 中的 messageReceived 逻辑比较简单。首先根据一些信息获取 NettyChannel 实例,然后将 NettyChannel 实例以及 Request 对象向下传递。下面再来看看 AllChannelHandler 的逻辑,在详细分析代码之前,我们先来了解一下 Dubbo 中的线程派发模型。</p> |
| <h5 id="2321-线程派发模型">2.3.2.1 线程派发模型</h5> |
| <p>Dubbo 将底层通信框架中接收请求的线程称为 IO 线程。如果一些事件处理逻辑可以很快执行完,比如只在内存打一个标记,此时直接在 IO 线程上执行该段逻辑即可。但如果事件的处理逻辑比较耗时,比如该段逻辑会发起数据库查询或者 HTTP 请求。此时我们就不应该让事件处理逻辑在 IO 线程上执行,而是应该派发到线程池中去执行。原因也很简单,IO 线程主要用于接收请求,如果 IO 线程被占满,将导致它不能接收新的请求。</p> |
| <p>以上就是线程派发的背景,下面我们再来通过 Dubbo 调用图,看一下线程派发器所处的位置。</p> |
| <p><img src="https://dubbo.apache.org/imgs/dev/dispatcher-location.jpg" alt="img"></p> |
| <p>如上图,红框中的 Dispatcher 就是线程派发器。需要说明的是,Dispatcher 真实的职责创建具有线程派发能力的 ChannelHandler,比如 AllChannelHandler、MessageOnlyChannelHandler 和 ExecutionChannelHandler 等,其本身并不具备线程派发能力。Dubbo 支持 5 种不同的线程派发策略,下面通过一个表格列举一下。</p> |
| <table> |
| <thead> |
| <tr> |
| <th>策略</th> |
| <th>用途</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>all</td> |
| <td>所有消息都派发到线程池,包括请求,响应,连接事件,断开事件等</td> |
| </tr> |
| <tr> |
| <td>direct</td> |
| <td>所有消息都不派发到线程池,全部在 IO 线程上直接执行</td> |
| </tr> |
| <tr> |
| <td>message</td> |
| <td>只有<strong>请求</strong>和<strong>响应</strong>消息派发到线程池,其它消息均在 IO 线程上执行</td> |
| </tr> |
| <tr> |
| <td>execution</td> |
| <td>只有<strong>请求</strong>消息派发到线程池,不含响应。其它消息均在 IO 线程上执行</td> |
| </tr> |
| <tr> |
| <td>connection</td> |
| <td>在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池</td> |
| </tr> |
| </tbody> |
| </table> |
| <p>默认配置下,Dubbo 使用 <code>all</code> 派发策略,即将所有的消息都派发到线程池中。下面我们来分析一下 AllChannelHandler 的代码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">AllChannelHandler</span> <span style="color:#268bd2">extends</span> WrappedChannelHandler { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">AllChannelHandler</span>(ChannelHandler handler, URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">super</span>(handler, url); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">/** 处理连接事件 */</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">connected</span>(Channel channel) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取线程池</span> |
| </span></span><span style="display:flex;"><span> ExecutorService cexecutor <span style="color:#719e07">=</span> getExecutorService(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将连接事件派发到线程池中处理</span> |
| </span></span><span style="display:flex;"><span> cexecutor.execute(<span style="color:#719e07">new</span> ChannelEventRunnable(channel, handler, ChannelState.CONNECTED)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> ExecutionException(..., <span style="color:#2aa198">&#34; error when process connected event .&#34;</span>, t); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">/** 处理断开事件 */</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">disconnected</span>(Channel channel) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> ExecutorService cexecutor <span style="color:#719e07">=</span> getExecutorService(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> cexecutor.execute(<span style="color:#719e07">new</span> ChannelEventRunnable(channel, handler, ChannelState.DISCONNECTED)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> ExecutionException(..., <span style="color:#2aa198">&#34;error when process disconnected event .&#34;</span>, t); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">/** 处理请求和响应消息,这里的 message 变量类型可能是 Request,也可能是 Response */</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">received</span>(Channel channel, Object message) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> ExecutorService cexecutor <span style="color:#719e07">=</span> getExecutorService(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将请求和响应消息派发到线程池中处理</span> |
| </span></span><span style="display:flex;"><span> cexecutor.execute(<span style="color:#719e07">new</span> ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span>(message <span style="color:#719e07">instanceof</span> Request <span style="color:#719e07">&amp;&amp;</span> t <span style="color:#719e07">instanceof</span> RejectedExecutionException){ |
| </span></span><span style="display:flex;"><span> Request request <span style="color:#719e07">=</span> (Request)message; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果通信方式为双向通信,此时将 Server side ... threadpool is exhausted </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 错误信息封装到 Response 中,并返回给服务消费方。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span>(request.isTwoWay()){ |
| </span></span><span style="display:flex;"><span> String msg <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;Server side(&#34;</span> <span style="color:#719e07">+</span> url.getIp() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;,&#34;</span> <span style="color:#719e07">+</span> url.getPort() |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;) threadpool is exhausted ,detail msg:&#34;</span> <span style="color:#719e07">+</span> t.getMessage(); |
| </span></span><span style="display:flex;"><span> Response response <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Response(request.getId(), request.getVersion()); |
| </span></span><span style="display:flex;"><span> response.setStatus(Response.SERVER_THREADPOOL_EXHAUSTED_ERROR); |
| </span></span><span style="display:flex;"><span> response.setErrorMessage(msg); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回包含错误信息的 Response 对象</span> |
| </span></span><span style="display:flex;"><span> channel.send(response); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> ExecutionException(..., <span style="color:#2aa198">&#34; error when process received event .&#34;</span>, t); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">/** 处理异常信息 */</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">caught</span>(Channel channel, Throwable exception) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> ExecutorService cexecutor <span style="color:#719e07">=</span> getExecutorService(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> cexecutor.execute(<span style="color:#719e07">new</span> ChannelEventRunnable(channel, handler, ChannelState.CAUGHT, exception)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> ExecutionException(..., <span style="color:#2aa198">&#34;error when process caught event ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,请求对象会被封装 ChannelEventRunnable 中,ChannelEventRunnable 将会是服务调用过程的新起点。所以接下来我们以 ChannelEventRunnable 为起点向下探索。</p> |
| <h5 id="2322-调用服务">2.3.2.2 调用服务</h5> |
| <p>本小节,我们从 ChannelEventRunnable 开始分析,该类的主要代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">ChannelEventRunnable</span> <span style="color:#268bd2">implements</span> Runnable { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ChannelHandler handler; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Channel channel; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ChannelState state; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Throwable exception; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Object message; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">run</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测通道状态,对于请求或响应消息,此时 state = RECEIVED</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (state <span style="color:#719e07">==</span> ChannelState.RECEIVED) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 channel 和 message 传给 ChannelHandler 对象,进行后续的调用</span> |
| </span></span><span style="display:flex;"><span> handler.received(channel, message); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> logger.warn(<span style="color:#2aa198">&#34;... operation error, channel is ... message is ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 其他消息类型通过 switch 进行处理</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">switch</span> (state) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">case</span> CONNECTED: |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> handler.connected(channel); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> logger.warn(<span style="color:#2aa198">&#34;... operation error, channel is ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">case</span> DISCONNECTED: |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ...</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">case</span> SENT: |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ...</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">case</span> CAUGHT: |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ...</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">default</span>: |
| </span></span><span style="display:flex;"><span> logger.warn(<span style="color:#2aa198">&#34;unknown state: &#34;</span> <span style="color:#719e07">+</span> state <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;, message is &#34;</span> <span style="color:#719e07">+</span> message); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,请求和响应消息出现频率明显比其他类型消息高,所以这里对该类型的消息进行了针对性判断。ChannelEventRunnable 仅是一个中转站,它的 run 方法中并不包含具体的调用逻辑,仅用于将参数传给其他 ChannelHandler 对象进行处理,该对象类型为 DecodeHandler。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">DecodeHandler</span> <span style="color:#268bd2">extends</span> AbstractChannelHandlerDelegate { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">DecodeHandler</span>(ChannelHandler handler) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">super</span>(handler); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">received</span>(Channel channel, Object message) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (message <span style="color:#719e07">instanceof</span> Decodeable) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 Decodeable 接口实现类对象进行解码</span> |
| </span></span><span style="display:flex;"><span> decode(message); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (message <span style="color:#719e07">instanceof</span> Request) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 Request 的 data 字段进行解码</span> |
| </span></span><span style="display:flex;"><span> decode(((Request) message).getData()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (message <span style="color:#719e07">instanceof</span> Response) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 Request 的 result 字段进行解码</span> |
| </span></span><span style="display:flex;"><span> decode(((Response) message).getResult()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 执行后续逻辑</span> |
| </span></span><span style="display:flex;"><span> handler.received(channel, message); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">decode</span>(Object message) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// Decodeable 接口目前有两个实现类,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 分别为 DecodeableRpcInvocation 和 DecodeableRpcResult</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (message <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> message <span style="color:#719e07">instanceof</span> Decodeable) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 执行解码逻辑</span> |
| </span></span><span style="display:flex;"><span> ((Decodeable) message).decode(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (log.isWarnEnabled()) { |
| </span></span><span style="display:flex;"><span> log.warn(<span style="color:#2aa198">&#34;Call Decodeable.decode failed: &#34;</span> <span style="color:#719e07">+</span> e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>DecodeHandler 主要是包含了一些解码逻辑。2.2.1 节分析请求解码时说过,请求解码可在 IO 线程上执行,也可在线程池中执行,这个取决于运行时配置。DecodeHandler 存在的意义就是保证请求或响应对象可在线程池中被解码。解码完毕后,完全解码后的 Request 对象会继续向后传递,下一站是 HeaderExchangeHandler。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">HeaderExchangeHandler</span> <span style="color:#268bd2">implements</span> ChannelHandlerDelegate { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ExchangeHandler handler; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">HeaderExchangeHandler</span>(ExchangeHandler handler) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (handler <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(<span style="color:#2aa198">&#34;handler == null&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.handler <span style="color:#719e07">=</span> handler; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">received</span>(Channel channel, Object message) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis()); |
| </span></span><span style="display:flex;"><span> ExchangeChannel exchangeChannel <span style="color:#719e07">=</span> HeaderExchangeChannel.getOrAddChannel(channel); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 处理请求对象</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (message <span style="color:#719e07">instanceof</span> Request) { |
| </span></span><span style="display:flex;"><span> Request request <span style="color:#719e07">=</span> (Request) message; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (request.isEvent()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 处理事件</span> |
| </span></span><span style="display:flex;"><span> handlerEvent(channel, request); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 处理普通的请求</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 双向通信</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (request.isTwoWay()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 向后调用服务,并得到调用结果</span> |
| </span></span><span style="display:flex;"><span> Response response <span style="color:#719e07">=</span> handleRequest(exchangeChannel, request); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将调用结果返回给服务消费端</span> |
| </span></span><span style="display:flex;"><span> channel.send(response); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果是单向通信,仅向后调用指定服务即可,无需返回调用结果</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> handler.received(exchangeChannel, request.getData()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 处理响应对象,服务消费方会执行此处逻辑,后面分析</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (message <span style="color:#719e07">instanceof</span> Response) { |
| </span></span><span style="display:flex;"><span> handleResponse(channel, (Response) message); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (message <span style="color:#719e07">instanceof</span> String) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// telnet 相关,忽略</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> handler.received(exchangeChannel, message); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> HeaderExchangeChannel.removeChannelIfDisconnected(channel); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Response <span style="color:#268bd2">handleRequest</span>(ExchangeChannel channel, Request req) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> Response res <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Response(req.getId(), req.getVersion()); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测请求是否合法,不合法则返回状态码为 BAD_REQUEST 的响应</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (req.isBroken()) { |
| </span></span><span style="display:flex;"><span> Object data <span style="color:#719e07">=</span> req.getData(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> String msg; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (data <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) |
| </span></span><span style="display:flex;"><span> msg <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> |
| </span></span><span style="display:flex;"><span> (data <span style="color:#719e07">instanceof</span> Throwable) msg <span style="color:#719e07">=</span> StringUtils.toString((Throwable) data); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> |
| </span></span><span style="display:flex;"><span> msg <span style="color:#719e07">=</span> data.toString(); |
| </span></span><span style="display:flex;"><span> res.setErrorMessage(<span style="color:#2aa198">&#34;Fail to decode request due to: &#34;</span> <span style="color:#719e07">+</span> msg); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 BAD_REQUEST 状态</span> |
| </span></span><span style="display:flex;"><span> res.setStatus(Response.BAD_REQUEST); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> res; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 data 字段值,也就是 RpcInvocation 对象</span> |
| </span></span><span style="display:flex;"><span> Object msg <span style="color:#719e07">=</span> req.getData(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 继续向下调用</span> |
| </span></span><span style="display:flex;"><span> Object result <span style="color:#719e07">=</span> handler.reply(channel, msg); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 OK 状态码</span> |
| </span></span><span style="display:flex;"><span> res.setStatus(Response.OK); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置调用结果</span> |
| </span></span><span style="display:flex;"><span> res.setResult(result); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 若调用过程出现异常,则设置 SERVICE_ERROR,表示服务端异常</span> |
| </span></span><span style="display:flex;"><span> res.setStatus(Response.SERVICE_ERROR); |
| </span></span><span style="display:flex;"><span> res.setErrorMessage(StringUtils.toString(e)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> res; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>到这里,我们看到了比较清晰的请求和响应逻辑。对于双向通信,HeaderExchangeHandler 首先向后进行调用,得到调用结果。然后将调用结果封装到 Response 对象中,最后再将该对象返回给服务消费方。如果请求不合法,或者调用失败,则将错误信息封装到 Response 对象中,并返回给服务消费方。接下来我们继续向后分析,把剩余的调用过程分析完。下面分析定义在 DubboProtocol 类中的匿名类对象逻辑,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">DubboProtocol</span> <span style="color:#268bd2">extends</span> AbstractProtocol { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> String NAME <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;dubbo&#34;</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> ExchangeHandler requestHandler <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ExchangeHandlerAdapter() { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Object <span style="color:#268bd2">reply</span>(ExchangeChannel channel, Object message) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (message <span style="color:#719e07">instanceof</span> Invocation) { |
| </span></span><span style="display:flex;"><span> Invocation inv <span style="color:#719e07">=</span> (Invocation) message; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 Invoker 实例</span> |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;?&gt;</span> invoker <span style="color:#719e07">=</span> getInvoker(channel, inv); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 回调相关,忽略</span> |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress()); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过 Invoker 调用具体的服务</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invoker.invoke(inv); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RemotingException(channel, <span style="color:#2aa198">&#34;Unsupported request: ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 忽略其他方法</span> |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;?&gt;</span> getInvoker(Channel channel, Invocation inv) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 忽略回调和本地存根相关逻辑</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ...</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> port <span style="color:#719e07">=</span> channel.getLocalAddress().getPort(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 计算 service key,格式为 groupName/serviceName:serviceVersion:port。比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// dubbo/com.alibaba.dubbo.demo.DemoService:1.0.0:20880</span> |
| </span></span><span style="display:flex;"><span> String serviceKey <span style="color:#719e07">=</span> serviceKey(port, path, inv.getAttachments().get(Constants.VERSION_KEY), inv.getAttachments().get(Constants.GROUP_KEY)); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从 exporterMap 查找与 serviceKey 相对应的 DubboExporter 对象,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 服务导出过程中会将 &lt;serviceKey, DubboExporter&gt; 映射关系存储到 exporterMap 集合中</span> |
| </span></span><span style="display:flex;"><span> DubboExporter<span style="color:#719e07">&lt;?&gt;</span> exporter <span style="color:#719e07">=</span> (DubboExporter<span style="color:#719e07">&lt;?&gt;</span>) exporterMap.get(serviceKey); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (exporter <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RemotingException(channel, <span style="color:#2aa198">&#34;Not found exported service ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 Invoker 对象,并返回</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> exporter.getInvoker(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 忽略其他方法</span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上逻辑用于获取与指定服务对应的 Invoker 实例,并通过 Invoker 的 invoke 方法调用服务逻辑。invoke 方法定义在 AbstractProxyInvoker 中,代码如下。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">abstract</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">AbstractProxyInvoker</span><span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">implements</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Result <span style="color:#268bd2">invoke</span>(Invocation invocation) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 doInvoke 执行后续的调用,并将调用结果封装到 RpcResult 中,并</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments())); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (InvocationTargetException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> RpcResult(e.getTargetException()); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(<span style="color:#2aa198">&#34;Failed to invoke remote proxy method ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">abstract</span> Object <span style="color:#268bd2">doInvoke</span>(T proxy, String methodName, Class<span style="color:#719e07">&lt;?&gt;[]</span> parameterTypes, Object<span style="color:#719e07">[]</span> arguments) <span style="color:#268bd2">throws</span> Throwable; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,doInvoke 是一个抽象方法,这个需要由具体的 Invoker 实例实现。Invoker 实例是在运行时通过 JavassistProxyFactory 创建的,创建逻辑如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">JavassistProxyFactory</span> <span style="color:#268bd2">extends</span> AbstractProxyFactory { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略其他方法</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">getInvoker</span>(T proxy, Class<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> type, URL url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> Wrapper wrapper <span style="color:#719e07">=</span> Wrapper.getWrapper(proxy.getClass().getName().indexOf(<span style="color:#2aa198">&#39;$&#39;</span>) <span style="color:#719e07">&lt;</span> 0 <span style="color:#719e07">?</span> proxy.getClass() : type); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建匿名类对象</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> AbstractProxyInvoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>(proxy, type, url) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> Object <span style="color:#268bd2">doInvoke</span>(T proxy, String methodName, |
| </span></span><span style="display:flex;"><span> Class<span style="color:#719e07">&lt;?&gt;[]</span> parameterTypes, |
| </span></span><span style="display:flex;"><span> Object<span style="color:#719e07">[]</span> arguments) <span style="color:#268bd2">throws</span> Throwable { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 invokeMethod 方法进行后续的调用</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> }; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>Wrapper 是一个抽象类,其中 invokeMethod 是一个抽象方法。Dubbo 会在运行时通过 Javassist 框架为 Wrapper 生成实现类,并实现 invokeMethod 方法,该方法最终会根据调用信息调用具体的服务。以 DemoServiceImpl 为例,Javassist 为其生成的代理类如下。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#586e75">/** Wrapper0 是在运行时生成的,大家可使用 Arthas 进行反编译 */</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">Wrapper0</span> <span style="color:#268bd2">extends</span> Wrapper <span style="color:#268bd2">implements</span> ClassGenerator.DC { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> String<span style="color:#719e07">[]</span> pns; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> Map pts; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> String<span style="color:#719e07">[]</span> mns; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> String<span style="color:#719e07">[]</span> dmns; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> Class<span style="color:#719e07">[]</span> mts0; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略其他方法</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Object <span style="color:#268bd2">invokeMethod</span>(Object object, String string, Class<span style="color:#719e07">[]</span> arrclass, Object<span style="color:#719e07">[]</span> arrobject) <span style="color:#268bd2">throws</span> InvocationTargetException { |
| </span></span><span style="display:flex;"><span> DemoService demoService; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 类型转换</span> |
| </span></span><span style="display:flex;"><span> demoService <span style="color:#719e07">=</span> (DemoService)object; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">catch</span> (Throwable throwable) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IllegalArgumentException(throwable); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据方法名调用指定的方法</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#2aa198">&#34;sayHello&#34;</span>.equals(string) <span style="color:#719e07">&amp;&amp;</span> arrclass.length <span style="color:#719e07">==</span> 1) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> demoService.sayHello((String)arrobject<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">catch</span> (Throwable throwable) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> InvocationTargetException(throwable); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> NoSuchMethodException(<span style="color:#719e07">new</span> StringBuffer().append(<span style="color:#2aa198">&#34;Not found method \&#34;&#34;</span>).append(string).append(<span style="color:#2aa198">&#34;\&#34; in class com.alibaba.dubbo.demo.DemoService.&#34;</span>).toString()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>到这里,整个服务调用过程就分析完了。最后把调用过程贴出来,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>ChannelEventRunnable#run() |
| </span></span><span style="display:flex;"><span> —&gt; DecodeHandler#received(Channel, Object) |
| </span></span><span style="display:flex;"><span> —&gt; HeaderExchangeHandler#received(Channel, Object) |
| </span></span><span style="display:flex;"><span> —&gt; HeaderExchangeHandler#handleRequest(ExchangeChannel, Request) |
| </span></span><span style="display:flex;"><span> —&gt; DubboProtocol.requestHandler#reply(ExchangeChannel, Object) |
| </span></span><span style="display:flex;"><span> —&gt; Filter#invoke(Invoker, Invocation) |
| </span></span><span style="display:flex;"><span> —&gt; AbstractProxyInvoker#invoke(Invocation) |
| </span></span><span style="display:flex;"><span> —&gt; Wrapper0#invokeMethod(Object, String, Class[], Object[]) |
| </span></span><span style="display:flex;"><span> —&gt; DemoServiceImpl#sayHello(String) |
| </span></span></code></pre></div><h3 id="24-服务提供方返回调用结果">2.4 服务提供方返回调用结果</h3> |
| <p>服务提供方调用指定服务后,会将调用结果封装到 Response 对象中,并将该对象返回给服务消费方。服务提供方也是通过 NettyChannel 的 send 方法将 Response 对象返回,这个方法在 2.2.1 节分析过,这里就不在重复分析了。本节我们仅需关注 Response 对象的编码过程即可,这里仍然省略一些中间调用,直接分析具体的编码逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">ExchangeCodec</span> <span style="color:#268bd2">extends</span> TelnetCodec { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">encode</span>(Channel channel, ChannelBuffer buffer, Object msg) <span style="color:#268bd2">throws</span> IOException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (msg <span style="color:#719e07">instanceof</span> Request) { |
| </span></span><span style="display:flex;"><span> encodeRequest(channel, buffer, (Request) msg); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (msg <span style="color:#719e07">instanceof</span> Response) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对响应对象进行编码</span> |
| </span></span><span style="display:flex;"><span> encodeResponse(channel, buffer, (Response) msg); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">super</span>.encode(channel, buffer, msg); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">encodeResponse</span>(Channel channel, ChannelBuffer buffer, Response res) <span style="color:#268bd2">throws</span> IOException { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> savedWriteIndex <span style="color:#719e07">=</span> buffer.writerIndex(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> Serialization serialization <span style="color:#719e07">=</span> getSerialization(channel); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建消息头字节数组</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">byte</span><span style="color:#719e07">[]</span> header <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> <span style="color:#dc322f">byte</span><span style="color:#719e07">[</span>HEADER_LENGTH<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置魔数</span> |
| </span></span><span style="display:flex;"><span> Bytes.short2bytes(MAGIC, header); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置序列化器编号</span> |
| </span></span><span style="display:flex;"><span> header<span style="color:#719e07">[</span>2<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> serialization.getContentTypeId(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (res.isHeartbeat()) header<span style="color:#719e07">[</span>2<span style="color:#719e07">]</span> <span style="color:#719e07">|=</span> FLAG_EVENT; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取响应状态</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">byte</span> status <span style="color:#719e07">=</span> res.getStatus(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置响应状态</span> |
| </span></span><span style="display:flex;"><span> header<span style="color:#719e07">[</span>3<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> status; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置请求编号</span> |
| </span></span><span style="display:flex;"><span> Bytes.long2bytes(res.getId(), header, 4); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 更新 writerIndex,为消息头预留 16 个字节的空间</span> |
| </span></span><span style="display:flex;"><span> buffer.writerIndex(savedWriteIndex <span style="color:#719e07">+</span> HEADER_LENGTH); |
| </span></span><span style="display:flex;"><span> ChannelBufferOutputStream bos <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ChannelBufferOutputStream(buffer); |
| </span></span><span style="display:flex;"><span> ObjectOutput out <span style="color:#719e07">=</span> serialization.serialize(channel.getUrl(), bos); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (status <span style="color:#719e07">==</span> Response.OK) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (res.isHeartbeat()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对心跳响应结果进行序列化,已废弃</span> |
| </span></span><span style="display:flex;"><span> encodeHeartbeatData(channel, out, res.getResult()); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对调用结果进行序列化</span> |
| </span></span><span style="display:flex;"><span> encodeResponseData(channel, out, res.getResult(), res.getVersion()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对错误信息进行序列化</span> |
| </span></span><span style="display:flex;"><span> out.writeUTF(res.getErrorMessage()) |
| </span></span><span style="display:flex;"><span> }; |
| </span></span><span style="display:flex;"><span> out.flushBuffer(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (out <span style="color:#719e07">instanceof</span> Cleanable) { |
| </span></span><span style="display:flex;"><span> ((Cleanable) out).cleanup(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> bos.flush(); |
| </span></span><span style="display:flex;"><span> bos.close(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取写入的字节数,也就是消息体长度</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> len <span style="color:#719e07">=</span> bos.writtenBytes(); |
| </span></span><span style="display:flex;"><span> checkPayload(channel, len); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将消息体长度写入到消息头中</span> |
| </span></span><span style="display:flex;"><span> Bytes.int2bytes(len, header, 12); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 buffer 指针移动到 savedWriteIndex,为写消息头做准备</span> |
| </span></span><span style="display:flex;"><span> buffer.writerIndex(savedWriteIndex); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从 savedWriteIndex 下标处写入消息头</span> |
| </span></span><span style="display:flex;"><span> buffer.writeBytes(header); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置新的 writerIndex,writerIndex = 原写下标 + 消息头长度 + 消息体长度</span> |
| </span></span><span style="display:flex;"><span> buffer.writerIndex(savedWriteIndex <span style="color:#719e07">+</span> HEADER_LENGTH <span style="color:#719e07">+</span> len); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 异常处理逻辑不是很难理解,但是代码略多,这里忽略了</span> |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">DubboCodec</span> <span style="color:#268bd2">extends</span> ExchangeCodec <span style="color:#268bd2">implements</span> Codec2 { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">encodeResponseData</span>(Channel channel, ObjectOutput out, Object data, String version) <span style="color:#268bd2">throws</span> IOException { |
| </span></span><span style="display:flex;"><span> Result result <span style="color:#719e07">=</span> (Result) data; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测当前协议版本是否支持带有 attachment 集合的 Response 对象</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> attach <span style="color:#719e07">=</span> Version.isSupportResponseAttachment(version); |
| </span></span><span style="display:flex;"><span> Throwable th <span style="color:#719e07">=</span> result.getException(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 异常信息为空</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (th <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> Object ret <span style="color:#719e07">=</span> result.getValue(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用结果为空</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ret <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 序列化响应类型</span> |
| </span></span><span style="display:flex;"><span> out.writeByte(attach <span style="color:#719e07">?</span> RESPONSE_NULL_VALUE_WITH_ATTACHMENTS : RESPONSE_NULL_VALUE); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用结果非空</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 序列化响应类型</span> |
| </span></span><span style="display:flex;"><span> out.writeByte(attach <span style="color:#719e07">?</span> RESPONSE_VALUE_WITH_ATTACHMENTS : RESPONSE_VALUE); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 序列化调用结果</span> |
| </span></span><span style="display:flex;"><span> out.writeObject(ret); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 异常信息非空</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 序列化响应类型</span> |
| </span></span><span style="display:flex;"><span> out.writeByte(attach <span style="color:#719e07">?</span> RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : RESPONSE_WITH_EXCEPTION); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 序列化异常对象</span> |
| </span></span><span style="display:flex;"><span> out.writeObject(th); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (attach) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 记录 Dubbo 协议版本</span> |
| </span></span><span style="display:flex;"><span> result.getAttachments().put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion()); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 序列化 attachments 集合</span> |
| </span></span><span style="display:flex;"><span> out.writeObject(result.getAttachments()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上就是 Response 对象编码的过程,和前面分析的 Request 对象编码过程很相似。如果大家能看 Request 对象的编码逻辑,那么这里的 Response 对象的编码逻辑也不难理解,就不多说了。接下来我们再来分析双向通信的最后一环 —— 服务消费方接收调用结果。</p> |
| <h3 id="25-服务消费方接收调用结果">2.5 服务消费方接收调用结果</h3> |
| <p>服务消费方在收到响应数据后,首先要做的事情是对响应数据进行解码,得到 Response 对象。然后再将该对象传递给下一个入站处理器,这个入站处理器就是 NettyHandler。接下来 NettyHandler 会将这个对象继续向下传递,最后 AllChannelHandler 的 received 方法会收到这个对象,并将这个对象派发到线程池中。这个过程和服务提供方接收请求的过程是一样的,因此这里就不重复分析了。本节我们重点分析两个方面的内容,一是响应数据的解码过程,二是 Dubbo 如何将调用结果传递给用户线程的。下面先来分析响应数据的解码过程。</p> |
| <h4 id="251-响应数据解码">2.5.1 响应数据解码</h4> |
| <p>响应数据解码逻辑主要的逻辑封装在 DubboCodec 中,我们直接分析这个类的代码。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">DubboCodec</span> <span style="color:#268bd2">extends</span> ExchangeCodec <span style="color:#268bd2">implements</span> Codec2 { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> Object <span style="color:#268bd2">decodeBody</span>(Channel channel, InputStream is, <span style="color:#dc322f">byte</span><span style="color:#719e07">[]</span> header) <span style="color:#268bd2">throws</span> IOException { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">byte</span> flag <span style="color:#719e07">=</span> header<span style="color:#719e07">[</span>2<span style="color:#719e07">]</span>, proto <span style="color:#719e07">=</span> (<span style="color:#dc322f">byte</span>) (flag <span style="color:#719e07">&amp;</span> SERIALIZATION_MASK); |
| </span></span><span style="display:flex;"><span> Serialization s <span style="color:#719e07">=</span> CodecSupport.getSerialization(channel.getUrl(), proto); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取请求编号</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">long</span> id <span style="color:#719e07">=</span> Bytes.bytes2long(header, 4); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测消息类型,若下面的条件成立,表明消息类型为 Response</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((flag <span style="color:#719e07">&amp;</span> FLAG_REQUEST) <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 Response 对象</span> |
| </span></span><span style="display:flex;"><span> Response res <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Response(id); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测事件标志位</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((flag <span style="color:#719e07">&amp;</span> FLAG_EVENT) <span style="color:#719e07">!=</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置心跳事件</span> |
| </span></span><span style="display:flex;"><span> res.setEvent(Response.HEARTBEAT_EVENT); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取响应状态</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">byte</span> status <span style="color:#719e07">=</span> header<span style="color:#719e07">[</span>3<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置响应状态</span> |
| </span></span><span style="display:flex;"><span> res.setStatus(status); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果响应状态为 OK,表明调用过程正常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (status <span style="color:#719e07">==</span> Response.OK) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> Object data; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (res.isHeartbeat()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 反序列化心跳数据,已废弃</span> |
| </span></span><span style="display:flex;"><span> data <span style="color:#719e07">=</span> decodeHeartbeatData(channel, deserialize(s, channel.getUrl(), is)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (res.isEvent()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 反序列化事件数据</span> |
| </span></span><span style="display:flex;"><span> data <span style="color:#719e07">=</span> decodeEventData(channel, deserialize(s, channel.getUrl(), is)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> DecodeableRpcResult result; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据 url 参数决定是否在 IO 线程上执行解码逻辑</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (channel.getUrl().getParameter( |
| </span></span><span style="display:flex;"><span> Constants.DECODE_IN_IO_THREAD_KEY, |
| </span></span><span style="display:flex;"><span> Constants.DEFAULT_DECODE_IN_IO_THREAD)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 DecodeableRpcResult 对象</span> |
| </span></span><span style="display:flex;"><span> result <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> DecodeableRpcResult(channel, res, is, |
| </span></span><span style="display:flex;"><span> (Invocation) getRequestData(id), proto); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 进行后续的解码工作</span> |
| </span></span><span style="display:flex;"><span> result.decode(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 DecodeableRpcResult 对象</span> |
| </span></span><span style="display:flex;"><span> result <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> DecodeableRpcResult(channel, res, |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">new</span> UnsafeByteArrayInputStream(readMessageData(is)), |
| </span></span><span style="display:flex;"><span> (Invocation) getRequestData(id), proto); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> data <span style="color:#719e07">=</span> result; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 DecodeableRpcResult 对象到 Response 对象中</span> |
| </span></span><span style="display:flex;"><span> res.setResult(data); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 解码过程中出现了错误,此时设置 CLIENT_ERROR 状态码到 Response 对象中</span> |
| </span></span><span style="display:flex;"><span> res.setStatus(Response.CLIENT_ERROR); |
| </span></span><span style="display:flex;"><span> res.setErrorMessage(StringUtils.toString(t)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 响应状态非 OK,表明调用过程出现了异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 反序列化异常信息,并设置到 Response 对象中</span> |
| </span></span><span style="display:flex;"><span> res.setErrorMessage(deserialize(s, channel.getUrl(), is).readUTF()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> res; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对请求数据进行解码,前面已分析过,此处忽略</span> |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上就是响应数据的解码过程,上面逻辑看起来是不是似曾相识。对的,我们在前面章节分析过 DubboCodec 的 decodeBody 方法中关于请求数据的解码过程,该过程和响应数据的解码过程很相似。下面,我们继续分析调用结果的反序列化过程,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">DecodeableRpcResult</span> <span style="color:#268bd2">extends</span> RpcResult <span style="color:#268bd2">implements</span> Codec, Decodeable { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> Invocation invocation; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">decode</span>() <span style="color:#268bd2">throws</span> Exception { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>hasDecoded <span style="color:#719e07">&amp;&amp;</span> channel <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> inputStream <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 执行反序列化操作</span> |
| </span></span><span style="display:flex;"><span> decode(channel, inputStream); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 反序列化失败,设置 CLIENT_ERROR 状态到 Response 对象中</span> |
| </span></span><span style="display:flex;"><span> response.setStatus(Response.CLIENT_ERROR); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置异常信息</span> |
| </span></span><span style="display:flex;"><span> response.setErrorMessage(StringUtils.toString(e)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> hasDecoded <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Object <span style="color:#268bd2">decode</span>(Channel channel, InputStream input) <span style="color:#268bd2">throws</span> IOException { |
| </span></span><span style="display:flex;"><span> ObjectInput in <span style="color:#719e07">=</span> CodecSupport.getSerialization(channel.getUrl(), serializationType) |
| </span></span><span style="display:flex;"><span> .deserialize(channel.getUrl(), input); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 反序列化响应类型</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">byte</span> flag <span style="color:#719e07">=</span> in.readByte(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">switch</span> (flag) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">case</span> DubboCodec.RESPONSE_NULL_VALUE: |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">case</span> DubboCodec.RESPONSE_VALUE: |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ...</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">case</span> DubboCodec.RESPONSE_WITH_EXCEPTION: |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ...</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回值为空,且携带了 attachments 集合</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">case</span> DubboCodec.RESPONSE_NULL_VALUE_WITH_ATTACHMENTS: |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 反序列化 attachments 集合,并存储起来 </span> |
| </span></span><span style="display:flex;"><span> setAttachments((Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span>) in.readObject(Map.class)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (ClassNotFoundException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IOException(StringUtils.toString(<span style="color:#2aa198">&#34;Read response data failed.&#34;</span>, e)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回值不为空,且携带了 attachments 集合</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">case</span> DubboCodec.RESPONSE_VALUE_WITH_ATTACHMENTS: |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取返回值类型</span> |
| </span></span><span style="display:flex;"><span> Type<span style="color:#719e07">[]</span> returnType <span style="color:#719e07">=</span> RpcUtils.getReturnTypes(invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 反序列化调用结果,并保存起来</span> |
| </span></span><span style="display:flex;"><span> setValue(returnType <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> returnType.length <span style="color:#719e07">==</span> 0 <span style="color:#719e07">?</span> in.readObject() : |
| </span></span><span style="display:flex;"><span> (returnType.length <span style="color:#719e07">==</span> 1 <span style="color:#719e07">?</span> in.readObject((Class<span style="color:#719e07">&lt;?&gt;</span>) returnType<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>) |
| </span></span><span style="display:flex;"><span> : in.readObject((Class<span style="color:#719e07">&lt;?&gt;</span>) returnType<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>, returnType<span style="color:#719e07">[</span>1<span style="color:#719e07">]</span>))); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 反序列化 attachments 集合,并存储起来</span> |
| </span></span><span style="display:flex;"><span> setAttachments((Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span>) in.readObject(Map.class)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (ClassNotFoundException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IOException(StringUtils.toString(<span style="color:#2aa198">&#34;Read response data failed.&#34;</span>, e)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 异常对象不为空,且携带了 attachments 集合</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">case</span> DubboCodec.RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS: |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 反序列化异常对象</span> |
| </span></span><span style="display:flex;"><span> Object obj <span style="color:#719e07">=</span> in.readObject(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (obj <span style="color:#719e07">instanceof</span> Throwable <span style="color:#719e07">==</span> <span style="color:#cb4b16">false</span>) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IOException(<span style="color:#2aa198">&#34;Response data error, expect Throwable, but get &#34;</span> <span style="color:#719e07">+</span> obj); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置异常对象</span> |
| </span></span><span style="display:flex;"><span> setException((Throwable) obj); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 反序列化 attachments 集合,并存储起来</span> |
| </span></span><span style="display:flex;"><span> setAttachments((Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span>) in.readObject(Map.class)); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (ClassNotFoundException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IOException(StringUtils.toString(<span style="color:#2aa198">&#34;Read response data failed.&#34;</span>, e)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">default</span>: |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> IOException(<span style="color:#2aa198">&#34;Unknown result flag, expect &#39;0&#39; &#39;1&#39; &#39;2&#39;, get &#34;</span> <span style="color:#719e07">+</span> flag); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (in <span style="color:#719e07">instanceof</span> Cleanable) { |
| </span></span><span style="display:flex;"><span> ((Cleanable) in).cleanup(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">this</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>本篇文章所分析的源码版本为 2.6.4,该版本下的 Response 支持 attachments 集合,所以上面仅对部分 case 分支进行了注释。其他 case 分支的逻辑比被注释分支的逻辑更为简单,这里就忽略了。我们所使用的测试服务接口 DemoService 包含了一个具有返回值的方法,正常调用下,线程会进入 RESPONSE_VALUE_WITH_ATTACHMENTS 分支中。然后线程会从 invocation 变量(大家探索一下 invocation 变量的由来)中获取返回值类型,接着对调用结果进行反序列化,并将序列化后的结果存储起来。最后对 attachments 集合进行反序列化,并存到指定字段中。到此,关于响应数据的解码过程就分析完了。接下来,我们再来探索一下响应对象 Response 的去向。</p> |
| <h4 id="252-向用户线程传递调用结果">2.5.2 向用户线程传递调用结果</h4> |
| <p>响应数据解码完成后,Dubbo 会将响应对象派发到线程池上。要注意的是,线程池中的线程并非用户的调用线程,所以要想办法将响应对象从线程池线程传递到用户线程上。我们在 2.1 节分析过用户线程在发送完请求后的动作,即调用 DefaultFuture 的 get 方法等待响应对象的到来。当响应对象到来后,用户线程会被唤醒,并通过<strong>调用编号</strong>获取属于自己的响应对象。下面我们来看一下整个过程对应的代码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">HeaderExchangeHandler</span> <span style="color:#268bd2">implements</span> ChannelHandlerDelegate { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">received</span>(Channel channel, Object message) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis()); |
| </span></span><span style="display:flex;"><span> ExchangeChannel exchangeChannel <span style="color:#719e07">=</span> HeaderExchangeChannel.getOrAddChannel(channel); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (message <span style="color:#719e07">instanceof</span> Request) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 处理请求,前面已分析过,省略</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (message <span style="color:#719e07">instanceof</span> Response) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 处理响应</span> |
| </span></span><span style="display:flex;"><span> handleResponse(channel, (Response) message); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (message <span style="color:#719e07">instanceof</span> String) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// telnet 相关,忽略</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> handler.received(exchangeChannel, message); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> HeaderExchangeChannel.removeChannelIfDisconnected(channel); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">static</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">handleResponse</span>(Channel channel, Response response) <span style="color:#268bd2">throws</span> RemotingException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (response <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>response.isHeartbeat()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 继续向下调用</span> |
| </span></span><span style="display:flex;"><span> DefaultFuture.received(channel, response); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">DefaultFuture</span> <span style="color:#268bd2">implements</span> ResponseFuture { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Lock lock <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ReentrantLock(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Condition done <span style="color:#719e07">=</span> lock.newCondition(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">volatile</span> Response response; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">received</span>(Channel channel, Response response) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据调用编号从 FUTURES 集合中查找指定的 DefaultFuture 对象</span> |
| </span></span><span style="display:flex;"><span> DefaultFuture future <span style="color:#719e07">=</span> FUTURES.remove(response.getId()); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (future <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 继续向下调用</span> |
| </span></span><span style="display:flex;"><span> future.doReceived(response); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> logger.warn(<span style="color:#2aa198">&#34;The timeout response finally returned at ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> CHANNELS.remove(response.getId()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">doReceived</span>(Response res) { |
| </span></span><span style="display:flex;"><span> lock.lock(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 保存响应对象</span> |
| </span></span><span style="display:flex;"><span> response <span style="color:#719e07">=</span> res; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (done <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 唤醒用户线程</span> |
| </span></span><span style="display:flex;"><span> done.signal(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> lock.unlock(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (callback <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> invokeCallback(callback); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上逻辑是将响应对象保存到相应的 DefaultFuture 实例中,然后再唤醒用户线程,随后用户线程即可从 DefaultFuture 实例中获取到相应结果。</p> |
| <p>本篇文章在多个地方都强调过调用编号很重要,但一直没有解释原因,这里简单说明一下。一般情况下,服务消费方会并发调用多个服务,每个用户线程发送请求后,会调用不同 DefaultFuture 对象的 get 方法进行等待。 一段时间后,服务消费方的线程池会收到多个响应对象。这个时候要考虑一个问题,如何将每个响应对象传递给相应的 DefaultFuture 对象,且不出错。答案是通过调用编号。DefaultFuture 被创建时,会要求传入一个 Request 对象。此时 DefaultFuture 可从 Request 对象中获取调用编号,并将 &lt;调用编号, DefaultFuture 对象&gt; 映射关系存入到静态 Map 中,即 FUTURES。线程池中的线程在收到 Response 对象后,会根据 Response 对象中的调用编号到 FUTURES 集合中取出相应的 DefaultFuture 对象,然后再将 Response 对象设置到 DefaultFuture 对象中。最后再唤醒用户线程,这样用户线程即可从 DefaultFuture 对象中获取调用结果了。整个过程大致如下图:</p> |
| <p><img src="https://dubbo.apache.org/imgs/dev/request-id-application.jpg" alt="img"></p> |
| <h2 id="3-总结">3. 总结</h2> |
| <p>本篇文章主要对 Dubbo 中的几种服务调用方式,以及从双向通信的角度对整个通信过程进行了详细的分析。按照通信顺序,通信过程包括服务消费方发送请求,服务提供方接收请求,服务提供方返回响应数据,服务消费方接收响应数据等过程。理解这些过程需要大家对网络编程,尤其是 Netty 有一定的了解。限于篇幅原因,本篇文章无法将服务调用的所有内容都一一进行分析。对于本篇文章未讲到或未详细分析的内容,比如服务降级、过滤器链、以及序列化等。大家若感兴趣,可自行进行分析。并将分析整理成文,分享给社区。</p> |
| <p>本篇文章就到这里了,感谢阅读。</p></description></item><item><title>Docsv2.7: 服务目录</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/directory/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/directory/</guid><description> |
| <h2 id="1-简介">1. 简介</h2> |
| <p>本篇文章,将开始分析 Dubbo 集群容错方面的源码。集群容错源码包含四个部分,分别是服务目录 Directory、服务路由 Router、集群 Cluster 和负载均衡 LoadBalance。这几个部分的源码逻辑相对比较独立,我们将会分四篇文章进行分析。本篇文章作为集群容错的开篇文章,将和大家一起分析服务目录相关的源码。在进行深入分析之前,我们先来了解一下服务目录是什么。服务目录中存储了一些和服务提供者有关的信息,通过服务目录,服务消费者可获取到服务提供者的信息,比如 ip、端口、服务协议等。通过这些信息,服务消费者就可通过 Netty 等客户端进行远程调用。在一个服务集群中,服务提供者数量并不是一成不变的,如果集群中新增了一台机器,相应地在服务目录中就要新增一条服务提供者记录。或者,如果服务提供者的配置修改了,服务目录中的记录也要做相应的更新。如果这样说,服务目录和注册中心的功能不就雷同了吗?确实如此,这里这么说是为了方便大家理解。实际上服务目录在获取注册中心的服务配置信息后,会为每条配置信息生成一个 Invoker 对象,并把这个 Invoker 对象存储起来,这个 Invoker 才是服务目录最终持有的对象。Invoker 有什么用呢?看名字就知道了,这是一个具有远程调用功能的对象。讲到这大家应该知道了什么是服务目录了,它可以看做是 Invoker 集合,且这个集合中的元素会随注册中心的变化而进行动态调整。</p> |
| <p>关于服务目录这里就先介绍这些,大家先有个大致印象。接下来我们通过继承体系图来了解一下服务目录的家族成员都有哪些。</p> |
| <h2 id="2-继承体系">2. 继承体系</h2> |
| <p>服务目录目前内置的实现有两个,分别为 StaticDirectory 和 RegistryDirectory,它们均是 AbstractDirectory 的子类。AbstractDirectory 实现了 Directory 接口,这个接口包含了一个重要的方法定义,即 list(Invocation),用于列举 Invoker。下面我们来看一下他们的继承体系图。</p> |
| <p><img src="https://dubbo.apache.org/imgs/dev/directory-inherit-hierarchy.png" alt="img"></p> |
| <p>如上,Directory 继承自 Node 接口,Node 这个接口继承者比较多,像 Registry、Monitor、Invoker 等均继承了这个接口。这个接口包含了一个获取配置信息的方法 getUrl,实现该接口的类可以向外提供配置信息。另外,大家注意看 RegistryDirectory 实现了 NotifyListener 接口,当注册中心节点信息发生变化后,RegistryDirectory 可以通过此接口方法得到变更信息,并根据变更信息动态调整内部 Invoker 列表。</p> |
| <h2 id="3-源码分析">3. 源码分析</h2> |
| <p>本章将分析 AbstractDirectory 和它两个子类的源码。AbstractDirectory 封装了 Invoker 列举流程,具体的列举逻辑则由子类实现,这是典型的模板模式。所以,接下来我们先来看一下 AbstractDirectory 的源码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> <span style="color:#268bd2">list</span>(Invocation invocation) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (destroyed) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(<span style="color:#2aa198">&#34;Directory already destroyed...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 doList 方法列举 Invoker,doList 是模板方法,由子类实现</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers <span style="color:#719e07">=</span> doList(invocation); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取路由 Router 列表</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Router<span style="color:#719e07">&gt;</span> localRouters <span style="color:#719e07">=</span> <span style="color:#719e07">this</span>.routers; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (localRouters <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>localRouters.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Router router : localRouters) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 runtime 参数,并根据参数决定是否进行路由</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (router.getUrl() <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> router.getUrl().getParameter(Constants.RUNTIME_KEY, <span style="color:#cb4b16">false</span>)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 进行服务路由</span> |
| </span></span><span style="display:flex;"><span> invokers <span style="color:#719e07">=</span> router.route(invokers, getConsumerUrl(), invocation); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#2aa198">&#34;Failed to execute router: ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers; |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75">// 模板方法,由子类实现</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">protected</span> <span style="color:#268bd2">abstract</span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> <span style="color:#268bd2">doList</span>(Invocation invocation) <span style="color:#268bd2">throws</span> RpcException; |
| </span></span></code></pre></div><p>上面就是 AbstractDirectory 的 list 方法源码,这个方法封装了 Invoker 的列举过程。如下:</p> |
| <ol> |
| <li>调用 doList 获取 Invoker 列表</li> |
| <li>根据 Router 的 getUrl 返回值为空与否,以及 runtime 参数决定是否进行服务路由</li> |
| </ol> |
| <p>以上步骤中,doList 是模板方法,需由子类实现。Router 的 runtime 参数这里简单说明一下,这个参数决定了是否在每次调用服务时都执行路由规则。如果 runtime 为 true,那么每次调用服务前,都需要进行服务路由。这个对性能造成影响,配置时需要注意。</p> |
| <p>关于 AbstractDirectory 就分析这么多,下面开始分析子类的源码。</p> |
| <h3 id="31-staticdirectory">3.1 StaticDirectory</h3> |
| <p>StaticDirectory 即静态服务目录,顾名思义,它内部存放的 Invoker 是不会变动的。所以,理论上它和不可变 List 的功能很相似。下面我们来看一下这个类的实现。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">StaticDirectory</span><span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">extends</span> AbstractDirectory<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// Invoker 列表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略构造方法</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Class<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">getInterface</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取接口类</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers.get(0).getInterface(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测服务目录是否可用</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">boolean</span> <span style="color:#268bd2">isAvailable</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (isDestroyed()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker : invokers) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invoker.isAvailable()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 只要有一个 Invoker 是可用的,就认为当前目录是可用的</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">destroy</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (isDestroyed()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用父类销毁逻辑</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">super</span>.destroy(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 Invoker 列表,并执行相应的销毁逻辑</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker : invokers) { |
| </span></span><span style="display:flex;"><span> invoker.destroy(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> invokers.clear(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> <span style="color:#268bd2">doList</span>(Invocation invocation) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 列举 Inovker,也就是直接返回 invokers 成员变量</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上就是 StaticDirectory 的代码逻辑,很简单,就不多说了。下面来看看 RegistryDirectory,这个类的逻辑比较复杂。</p> |
| <h3 id="32-registrydirectory">3.2 RegistryDirectory</h3> |
| <p>RegistryDirectory 是一种动态服务目录,实现了 NotifyListener 接口。当注册中心服务配置发生变化后,RegistryDirectory 可收到与当前服务相关的变化。收到变更通知后,RegistryDirectory 可根据配置变更信息刷新 Invoker 列表。RegistryDirectory 中有几个比较重要的逻辑,第一是 Invoker 的列举逻辑,第二是接收服务配置变更的逻辑,第三是 Invoker 列表的刷新逻辑。接下来按顺序对这三块逻辑进行分析。</p> |
| <h4 id="321-列举-invoker">3.2.1 列举 Invoker</h4> |
| <p>Invoker 列举逻辑封装在 doList 方法中,相关代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> <span style="color:#268bd2">doList</span>(Invocation invocation) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (forbidden) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 服务提供者关闭或禁用了服务,此时抛出 No provider 异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(RpcException.FORBIDDEN_EXCEPTION, |
| </span></span><span style="display:flex;"><span> <span style="color:#2aa198">&#34;No provider available from registry ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 Invoker 本地缓存</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;&gt;</span> localMethodInvokerMap <span style="color:#719e07">=</span> <span style="color:#719e07">this</span>.methodInvokerMap; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (localMethodInvokerMap <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> localMethodInvokerMap.size() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取方法名和参数列表</span> |
| </span></span><span style="display:flex;"><span> String methodName <span style="color:#719e07">=</span> RpcUtils.getMethodName(invocation); |
| </span></span><span style="display:flex;"><span> Object<span style="color:#719e07">[]</span> args <span style="color:#719e07">=</span> RpcUtils.getArguments(invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测参数列表的第一个参数是否为 String 或 enum 类型</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (args <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> args.length <span style="color:#719e07">&gt;</span> 0 <span style="color:#719e07">&amp;&amp;</span> args<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span> <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> (args<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span> <span style="color:#719e07">instanceof</span> String <span style="color:#719e07">||</span> args<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>.getClass().isEnum())) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过 方法名 + 第一个参数名称 查询 Invoker 列表,具体的使用场景暂时没想到</span> |
| </span></span><span style="display:flex;"><span> invokers <span style="color:#719e07">=</span> localMethodInvokerMap.get(methodName <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.&#34;</span> <span style="color:#719e07">+</span> args<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过方法名获取 Invoker 列表</span> |
| </span></span><span style="display:flex;"><span> invokers <span style="color:#719e07">=</span> localMethodInvokerMap.get(methodName); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过星号 * 获取 Invoker 列表</span> |
| </span></span><span style="display:flex;"><span> invokers <span style="color:#719e07">=</span> localMethodInvokerMap.get(Constants.ANY_VALUE); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 冗余逻辑,pull request #2861 移除了下面的 if 分支代码</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> Iterator<span style="color:#719e07">&lt;</span>List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;&gt;</span> iterator <span style="color:#719e07">=</span> localMethodInvokerMap.values().iterator(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (iterator.hasNext()) { |
| </span></span><span style="display:flex;"><span> invokers <span style="color:#719e07">=</span> iterator.next(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回 Invoker 列表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">?</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span>(0) : invokers; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上代码进行多次尝试,以期从 localMethodInvokerMap 中获取到 Invoker 列表。一般情况下,普通的调用可通过方法名获取到对应的 Invoker 列表,泛化调用可通过 <code>*</code> 获取到 Invoker 列表。localMethodInvokerMap 源自 RegistryDirectory 类的成员变量 methodInvokerMap。doList 方法可以看做是对 methodInvokerMap 变量的读操作,至于对 methodInvokerMap 变量的写操作,下一节进行分析。</p> |
| <h4 id="322-接收服务变更通知">3.2.2 接收服务变更通知</h4> |
| <p>RegistryDirectory 是一个动态服务目录,会随注册中心配置的变化进行动态调整。因此 RegistryDirectory 实现了 NotifyListener 接口,通过这个接口获取注册中心变更通知。下面我们来看一下具体的逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">synchronized</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">notify</span>(List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> urls) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 定义三个集合,分别用于存放服务提供者 url,路由 url,配置器 url</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> invokerUrls <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> routerUrls <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> configuratorUrls <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (URL url : urls) { |
| </span></span><span style="display:flex;"><span> String protocol <span style="color:#719e07">=</span> url.getProtocol(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 category 参数</span> |
| </span></span><span style="display:flex;"><span> String category <span style="color:#719e07">=</span> url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据 category 参数将 url 分别放到不同的列表中</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (Constants.ROUTERS_CATEGORY.equals(category) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">||</span> Constants.ROUTE_PROTOCOL.equals(protocol)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加路由器 url</span> |
| </span></span><span style="display:flex;"><span> routerUrls.add(url); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (Constants.CONFIGURATORS_CATEGORY.equals(category) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">||</span> Constants.OVERRIDE_PROTOCOL.equals(protocol)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加配置器 url</span> |
| </span></span><span style="display:flex;"><span> configuratorUrls.add(url); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (Constants.PROVIDERS_CATEGORY.equals(category)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加服务提供者 url</span> |
| </span></span><span style="display:flex;"><span> invokerUrls.add(url); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 忽略不支持的 category</span> |
| </span></span><span style="display:flex;"><span> logger.warn(<span style="color:#2aa198">&#34;Unsupported category ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (configuratorUrls <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>configuratorUrls.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 url 转成 Configurator</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.configurators <span style="color:#719e07">=</span> toConfigurators(configuratorUrls); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (routerUrls <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>routerUrls.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 url 转成 Router</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Router<span style="color:#719e07">&gt;</span> routers <span style="color:#719e07">=</span> toRouters(routerUrls); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (routers <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> setRouters(routers); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Configurator<span style="color:#719e07">&gt;</span> localConfigurators <span style="color:#719e07">=</span> <span style="color:#719e07">this</span>.configurators; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.overrideDirectoryUrl <span style="color:#719e07">=</span> directoryUrl; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (localConfigurators <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>localConfigurators.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Configurator configurator : localConfigurators) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 配置 overrideDirectoryUrl</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.overrideDirectoryUrl <span style="color:#719e07">=</span> configurator.configure(overrideDirectoryUrl); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 刷新 Invoker 列表</span> |
| </span></span><span style="display:flex;"><span> refreshInvoker(invokerUrls); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,notify 方法首先是根据 url 的 category 参数对 url 进行分门别类存储,然后通过 toRouters 和 toConfigurators 将 url 列表转成 Router 和 Configurator 列表。最后调用 refreshInvoker 方法刷新 Invoker 列表。这里的 toRouters 和 toConfigurators 方法逻辑不复杂,大家自行分析。接下来,我们把重点放在 refreshInvoker 方法上。</p> |
| <h4 id="323-刷新-invoker-列表">3.2.3 刷新 Invoker 列表</h4> |
| <p>refreshInvoker 方法是保证 RegistryDirectory 随注册中心变化而变化的关键所在。这一块逻辑比较多,接下来一一进行分析。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">refreshInvoker</span>(List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> invokerUrls) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// invokerUrls 仅有一个元素,且 url 协议头为 empty,此时表示禁用所有服务</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokerUrls <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> invokerUrls.size() <span style="color:#719e07">==</span> 1 <span style="color:#719e07">&amp;&amp;</span> invokerUrls.get(0) <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 forbidden 为 true</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.forbidden <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.methodInvokerMap <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 销毁所有 Invoker</span> |
| </span></span><span style="display:flex;"><span> destroyAllInvokers(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.forbidden <span style="color:#719e07">=</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> oldUrlInvokerMap <span style="color:#719e07">=</span> <span style="color:#719e07">this</span>.urlInvokerMap; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokerUrls.isEmpty() <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">this</span>.cachedInvokerUrls <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加缓存 url 到 invokerUrls 中</span> |
| </span></span><span style="display:flex;"><span> invokerUrls.addAll(<span style="color:#719e07">this</span>.cachedInvokerUrls); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.cachedInvokerUrls <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashSet<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 缓存 invokerUrls</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.cachedInvokerUrls.addAll(invokerUrls); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokerUrls.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 url 转成 Invoker</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> newUrlInvokerMap <span style="color:#719e07">=</span> toInvokers(invokerUrls); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 newUrlInvokerMap 转成方法名到 Invoker 列表的映射</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;&gt;</span> newMethodInvokerMap <span style="color:#719e07">=</span> toMethodInvokers(newUrlInvokerMap); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 转换出错,直接打印异常,并返回</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (newUrlInvokerMap <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> newUrlInvokerMap.size() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;urls to invokers error ...&#34;</span>)); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 合并多个组的 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.methodInvokerMap <span style="color:#719e07">=</span> multiGroup <span style="color:#719e07">?</span> toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.urlInvokerMap <span style="color:#719e07">=</span> newUrlInvokerMap; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 销毁无用 Invoker</span> |
| </span></span><span style="display:flex;"><span> destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> logger.warn(<span style="color:#2aa198">&#34;destroyUnusedInvokers error. &#34;</span>, e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>refreshInvoker 方法首先会根据入参 invokerUrls 的数量和协议头判断是否禁用所有的服务,如果禁用,则将 forbidden 设为 true,并销毁所有的 Invoker。若不禁用,则将 url 转成 Invoker,得到 &lt;url, Invoker&gt; 的映射关系。然后进一步进行转换,得到 &lt;methodName, Invoker 列表&gt; 映射关系。之后进行多组 Invoker 合并操作,并将合并结果赋值给 methodInvokerMap。methodInvokerMap 变量在 doList 方法中会被用到,doList 会对该变量进行读操作,在这里是写操作。当新的 Invoker 列表生成后,还要一个重要的工作要做,就是销毁无用的 Invoker,避免服务消费者调用已下线的服务的服务。</p> |
| <p>接下来对 refreshInvoker 方法中涉及到的调用一一进行分析。按照顺序,先来分析 url 到 Invoker 的转换过程。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> Map<span style="color:#719e07">&lt;</span>String, Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> <span style="color:#268bd2">toInvokers</span>(List<span style="color:#719e07">&lt;</span>URL<span style="color:#719e07">&gt;</span> urls) { |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> newUrlInvokerMap <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (urls <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> urls.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> newUrlInvokerMap; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> Set<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span> keys <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashSet<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取服务消费端配置的协议</span> |
| </span></span><span style="display:flex;"><span> String queryProtocols <span style="color:#719e07">=</span> <span style="color:#719e07">this</span>.queryMap.get(Constants.PROTOCOL_KEY); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (URL providerUrl : urls) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (queryProtocols <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> queryProtocols.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> accept <span style="color:#719e07">=</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> String<span style="color:#719e07">[]</span> acceptProtocols <span style="color:#719e07">=</span> queryProtocols.split(<span style="color:#2aa198">&#34;,&#34;</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测服务提供者协议是否被服务消费者所支持</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (String acceptProtocol : acceptProtocols) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (providerUrl.getProtocol().equals(acceptProtocol)) { |
| </span></span><span style="display:flex;"><span> accept <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">break</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>accept) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 若服务提供者协议头不被消费者所支持,则忽略当前 providerUrl</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">continue</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 忽略 empty 协议</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">continue</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过 SPI 检测服务端协议是否被消费端支持,不支持则抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) { |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#719e07">new</span> IllegalStateException(<span style="color:#2aa198">&#34;Unsupported protocol...&#34;</span>)); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">continue</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 合并 url</span> |
| </span></span><span style="display:flex;"><span> URL url <span style="color:#719e07">=</span> mergeUrl(providerUrl); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> String key <span style="color:#719e07">=</span> url.toFullString(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (keys.contains(key)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 忽略重复 url</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">continue</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> keys.add(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将本地 Invoker 缓存赋值给 localUrlInvokerMap</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> localUrlInvokerMap <span style="color:#719e07">=</span> <span style="color:#719e07">this</span>.urlInvokerMap; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取与 url 对应的 Invoker</span> |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker <span style="color:#719e07">=</span> localUrlInvokerMap <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">?</span> <span style="color:#cb4b16">null</span> : localUrlInvokerMap.get(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 缓存未命中</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invoker <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> enabled <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url.hasParameter(Constants.DISABLED_KEY)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 disable 配置,取反,然后赋值给 enable 变量</span> |
| </span></span><span style="display:flex;"><span> enabled <span style="color:#719e07">=</span> <span style="color:#719e07">!</span>url.getParameter(Constants.DISABLED_KEY, <span style="color:#cb4b16">false</span>); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 enable 配置,并赋值给 enable 变量</span> |
| </span></span><span style="display:flex;"><span> enabled <span style="color:#719e07">=</span> url.getParameter(Constants.ENABLED_KEY, <span style="color:#cb4b16">true</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (enabled) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 refer 获取 Invoker</span> |
| </span></span><span style="display:flex;"><span> invoker <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> InvokerDelegate<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>(protocol.refer(serviceType, url), url, providerUrl); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#2aa198">&#34;Failed to refer invoker for interface...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invoker <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 缓存 Invoker 实例</span> |
| </span></span><span style="display:flex;"><span> newUrlInvokerMap.put(key, invoker); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 缓存命中</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 invoker 存储到 newUrlInvokerMap 中</span> |
| </span></span><span style="display:flex;"><span> newUrlInvokerMap.put(key, invoker); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> keys.clear(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> newUrlInvokerMap; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>toInvokers 方法一开始会对服务提供者 url 进行检测,若服务消费端的配置不支持服务端的协议,或服务端 url 协议头为 empty 时,toInvokers 均会忽略服务提供方 url。必要的检测做完后,紧接着是合并 url,然后访问缓存,尝试获取与 url 对应的 invoker。如果缓存命中,直接将 Invoker 存入 newUrlInvokerMap 中即可。如果未命中,则需新建 Invoker。</p> |
| <p>toInvokers 方法返回的是 &lt;url, Invoker&gt; 映射关系表,接下来还要对这个结果进行进一步处理,得到方法名到 Invoker 列表的映射关系。这个过程由 toMethodInvokers 方法完成,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> Map<span style="color:#719e07">&lt;</span>String, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;&gt;</span> <span style="color:#268bd2">toMethodInvokers</span>(Map<span style="color:#719e07">&lt;</span>String, Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokersMap) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 方法名 -&gt; Invoker 列表</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;&gt;</span> newMethodInvokerMap <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokersList <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokersMap <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> invokersMap.size() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker : invokersMap.values()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 methods 参数</span> |
| </span></span><span style="display:flex;"><span> String parameter <span style="color:#719e07">=</span> invoker.getUrl().getParameter(Constants.METHODS_KEY); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (parameter <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> parameter.length() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 切分 methods 参数值,得到方法名数组</span> |
| </span></span><span style="display:flex;"><span> String<span style="color:#719e07">[]</span> methods <span style="color:#719e07">=</span> Constants.COMMA_SPLIT_PATTERN.split(parameter); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (methods <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> methods.length <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (String method : methods) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 方法名不为 *</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (method <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> method.length() <span style="color:#719e07">&gt;</span> 0 |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>Constants.ANY_VALUE.equals(method)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据方法名获取 Invoker 列表</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> methodInvokers <span style="color:#719e07">=</span> newMethodInvokerMap.get(method); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (methodInvokers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> methodInvokers <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> newMethodInvokerMap.put(method, methodInvokers); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 存储 Invoker 到列表中</span> |
| </span></span><span style="display:flex;"><span> methodInvokers.add(invoker); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> invokersList.add(invoker); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 进行服务级别路由,参考 pull request #749</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> newInvokersList <span style="color:#719e07">=</span> route(invokersList, <span style="color:#cb4b16">null</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 存储 &lt;*, newInvokersList&gt; 映射关系</span> |
| </span></span><span style="display:flex;"><span> newMethodInvokerMap.put(Constants.ANY_VALUE, newInvokersList); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (serviceMethods <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> serviceMethods.length <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (String method : serviceMethods) { |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> methodInvokers <span style="color:#719e07">=</span> newMethodInvokerMap.get(method); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (methodInvokers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> methodInvokers.isEmpty()) { |
| </span></span><span style="display:flex;"><span> methodInvokers <span style="color:#719e07">=</span> newInvokersList; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 进行方法级别路由</span> |
| </span></span><span style="display:flex;"><span> newMethodInvokerMap.put(method, route(methodInvokers, method)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 排序,转成不可变列表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (String method : <span style="color:#719e07">new</span> HashSet<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span>(newMethodInvokerMap.keySet())) { |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> methodInvokers <span style="color:#719e07">=</span> newMethodInvokerMap.get(method); |
| </span></span><span style="display:flex;"><span> Collections.sort(methodInvokers, InvokerComparator.getComparator()); |
| </span></span><span style="display:flex;"><span> newMethodInvokerMap.put(method, Collections.unmodifiableList(methodInvokers)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> Collections.unmodifiableMap(newMethodInvokerMap); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面方法主要做了三件事情, 第一是对入参进行遍历,然后从 Invoker 的 url 成员变量中获取 methods 参数,并切分成数组。随后以方法名为键,Invoker 列表为值,将映射关系存储到 newMethodInvokerMap 中。第二是分别基于类和方法对 Invoker 列表进行路由操作。第三是对 Invoker 列表进行排序,并转成不可变列表。关于 toMethodInvokers 方法就先分析到这,我们继续向下分析,这次要分析的多组服务的合并逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> Map<span style="color:#719e07">&lt;</span>String, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;&gt;</span> <span style="color:#268bd2">toMergeMethodInvokerMap</span>(Map<span style="color:#719e07">&lt;</span>String, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;&gt;</span> methodMap) { |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;&gt;</span> result <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历入参</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Map.Entry<span style="color:#719e07">&lt;</span>String, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;&gt;</span> entry : methodMap.entrySet()) { |
| </span></span><span style="display:flex;"><span> String method <span style="color:#719e07">=</span> entry.getKey(); |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers <span style="color:#719e07">=</span> entry.getValue(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// group -&gt; Invoker 列表</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;&gt;</span> groupMap <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>String, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 Invoker 列表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker : invokers) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取分组配置</span> |
| </span></span><span style="display:flex;"><span> String group <span style="color:#719e07">=</span> invoker.getUrl().getParameter(Constants.GROUP_KEY, <span style="color:#2aa198">&#34;&#34;</span>); |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> groupInvokers <span style="color:#719e07">=</span> groupMap.get(group); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (groupInvokers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> groupInvokers <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 缓存 &lt;group, List&lt;Invoker&gt;&gt; 到 groupMap 中</span> |
| </span></span><span style="display:flex;"><span> groupMap.put(group, groupInvokers); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 存储 invoker 到 groupInvokers</span> |
| </span></span><span style="display:flex;"><span> groupInvokers.add(invoker); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (groupMap.size() <span style="color:#719e07">==</span> 1) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 groupMap 中仅包含一组键值对,此时直接取出该键值对的值即可</span> |
| </span></span><span style="display:flex;"><span> result.put(method, groupMap.values().iterator().next()); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// groupMap.size() &gt; 1 成立,表示 groupMap 中包含多组键值对,比如:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// {</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &#34;dubbo&#34;: [invoker1, invoker2, invoker3, ...],</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &#34;hello&#34;: [invoker4, invoker5, invoker6, ...]</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// }</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (groupMap.size() <span style="color:#719e07">&gt;</span> 1) { |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> groupInvokers <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> groupList : groupMap.values()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过集群类合并每个分组对应的 Invoker 列表</span> |
| </span></span><span style="display:flex;"><span> groupInvokers.add(cluster.join(<span style="color:#719e07">new</span> StaticDirectory<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>(groupList))); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 缓存结果</span> |
| </span></span><span style="display:flex;"><span> result.put(method, groupInvokers); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> result.put(method, invokers); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> result; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面方法首先是生成 group 到 Invoker 列表的映射关系表,若关系表中的映射关系数量大于1,表示有多组服务。此时通过集群类合并每组 Invoker,并将合并结果存储到 groupInvokers 中。之后将方法名与 groupInvokers 存到到 result 中,并返回,整个逻辑结束。</p> |
| <p>接下来我们再来看一下 Invoker 列表刷新逻辑的最后一个动作 — 删除无用 Invoker。如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">destroyUnusedInvokers</span>(Map<span style="color:#719e07">&lt;</span>String, Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> oldUrlInvokerMap, Map<span style="color:#719e07">&lt;</span>String, Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> newUrlInvokerMap) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (newUrlInvokerMap <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> newUrlInvokerMap.size() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> destroyAllInvokers(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span> deleted <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (oldUrlInvokerMap <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取新生成的 Invoker 列表</span> |
| </span></span><span style="display:flex;"><span> Collection<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> newInvokers <span style="color:#719e07">=</span> newUrlInvokerMap.values(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历老的 &lt;url, Invoker&gt; 映射表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Map.Entry<span style="color:#719e07">&lt;</span>String, Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> entry : oldUrlInvokerMap.entrySet()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 newInvokers 中是否包含老的 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>newInvokers.contains(entry.getValue())) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (deleted <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> deleted <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 若不包含,则将老的 Invoker 对应的 url 存入 deleted 列表中</span> |
| </span></span><span style="display:flex;"><span> deleted.add(entry.getKey()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (deleted <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 deleted 集合,并到老的 &lt;url, Invoker&gt; 映射关系表查出 Invoker,销毁之</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (String url : deleted) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (url <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从 oldUrlInvokerMap 中移除 url 对应的 Invoker</span> |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker <span style="color:#719e07">=</span> oldUrlInvokerMap.remove(url); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invoker <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 销毁 Invoker</span> |
| </span></span><span style="display:flex;"><span> invoker.destroy(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> logger.warn(<span style="color:#2aa198">&#34;destroy invoker...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>destroyUnusedInvokers 方法的主要逻辑是通过 newUrlInvokerMap 找出待删除 Invoker 对应的 url,并将 url 存入到 deleted 列表中。然后再遍历 deleted 列表,并从 oldUrlInvokerMap 中移除相应的 Invoker,销毁之。整个逻辑大致如此,不是很难理解。</p> |
| <p>到此关于 Invoker 列表的刷新逻辑就分析了,这里对整个过程进行简单总结。如下:</p> |
| <ol> |
| <li>检测入参是否仅包含一个 url,且 url 协议头为 empty</li> |
| <li>若第一步检测结果为 true,表示禁用所有服务,此时销毁所有的 Invoker</li> |
| <li>若第一步检测结果为 false,此时将入参转为 Invoker 列表</li> |
| <li>对上一步逻辑生成的结果进行进一步处理,得到方法名到 Invoker 的映射关系表</li> |
| <li>合并多组 Invoker</li> |
| <li>销毁无用 Invoker</li> |
| </ol> |
| <p>Invoker 的刷新逻辑还是比较复杂的,大家在看的过程中多写点 demo 进行调试,以加深理解。</p> |
| <h2 id="4-总结">4. 总结</h2> |
| <p>本篇文章对 Dubbo 服务目录进行了较为详细的分析,篇幅主要集中在 RegistryDirectory 的源码分析上。从代码量上可以看出,想让本地服务目录和注册中心保持一致还是需要做很多事情的,并不简单。服务目录是 Dubbo 集群容错的一部分,也是比较基础的部分,所以大家应尽量搞懂。</p></description></item><item><title>Docsv2.7: 集群</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/cluster/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/cluster/</guid><description> |
| <h2 id="1简介">1.简介</h2> |
| <p>为了避免单点故障,现在的应用通常至少会部署在两台服务器上。对于一些负载比较高的服务,会部署更多的服务器。这样,在同一环境下的服务提供者数量会大于1。对于服务消费者来说,同一环境下出现了多个服务提供者。这时会出现一个问题,服务消费者需要决定选择哪个服务提供者进行调用。另外服务调用失败时的处理措施也是需要考虑的,是重试呢,还是抛出异常,亦或是只打印异常等。为了处理这些问题,Dubbo 定义了集群接口 Cluster 以及 Cluster Invoker。集群 Cluster 用途是将多个服务提供者合并为一个 Cluster Invoker,并将这个 Invoker 暴露给服务消费者。这样一来,服务消费者只需通过这个 Invoker 进行远程调用即可,至于具体调用哪个服务提供者,以及调用失败后如何处理等问题,现在都交给集群模块去处理。集群模块是服务提供者和服务消费者的中间层,为服务消费者屏蔽了服务提供者的情况,这样服务消费者就可以专心处理远程调用相关事宜。比如发请求,接受服务提供者返回的数据等。这就是集群的作用。</p> |
| <p>Dubbo 提供了多种集群实现,包含但不限于 Failover Cluster、Failfast Cluster 和 Failsafe Cluster 等。每种集群实现类的用途不同,接下来会一一进行分析。</p> |
| <h2 id="2-集群容错">2. 集群容错</h2> |
| <p>在对集群相关代码进行分析之前,这里有必要先来介绍一下集群容错的所有组件。包含 Cluster、Cluster Invoker、Directory、Router 和 LoadBalance 等。</p> |
| <p><img src="https://dubbo.apache.org/imgs/dev/cluster.jpg" alt="img"></p> |
| <p>集群工作过程可分为两个阶段,第一个阶段是在服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例,即上图中的 merge 操作。第二个阶段是在服务消费者进行远程调用时。以 FailoverClusterInvoker 为例,该类型 Cluster Invoker 首先会调用 Directory 的 list 方法列举 Invoker 列表(可将 Invoker 简单理解为服务提供者)。Directory 的用途是保存 Invoker,可简单类比为 List&lt;Invoker&gt;。其实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Invoker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删 Invoker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。当 FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表后,它会通过 LoadBalance 从 Invoker 列表中选择一个 Invoker。最后 FailoverClusterInvoker 会将参数传给 LoadBalance 选择出的 Invoker 实例的 invoke 方法,进行真正的远程调用。</p> |
| <p>以上就是集群工作的整个流程,这里并没介绍集群是如何容错的。Dubbo 主要提供了这样几种容错方式:</p> |
| <ul> |
| <li>Failover Cluster - 失败自动切换</li> |
| <li>Failfast Cluster - 快速失败</li> |
| <li>Failsafe Cluster - 失败安全</li> |
| <li>Failback Cluster - 失败自动恢复</li> |
| <li>Forking Cluster - 并行调用多个服务提供者</li> |
| </ul> |
| <p>下面开始分析源码。</p> |
| <h2 id="3源码分析">3.源码分析</h2> |
| <h3 id="31-cluster-实现类分析">3.1 Cluster 实现类分析</h3> |
| <p>我们在上一章看到了两个概念,分别是集群接口 Cluster 和 Cluster Invoker,这两者是不同的。Cluster 是接口,而 Cluster Invoker 是一种 Invoker。服务提供者的选择逻辑,以及远程调用失败后的的处理逻辑均是封装在 Cluster Invoker 中。那么 Cluster 接口和相关实现类有什么用呢?用途比较简单,仅用于生成 Cluster Invoker。下面我们来看一下源码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">FailoverCluster</span> <span style="color:#268bd2">implements</span> Cluster { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">final</span> <span style="color:#268bd2">static</span> String NAME <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;failover&#34;</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">join</span>(Directory<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> directory) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建并返回 FailoverClusterInvoker 对象</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> FailoverClusterInvoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>(directory); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,FailoverCluster 总共就包含这几行代码,用于创建 FailoverClusterInvoker 对象,很简单。下面再看一个。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">FailbackCluster</span> <span style="color:#268bd2">implements</span> Cluster { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">final</span> <span style="color:#268bd2">static</span> String NAME <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;failback&#34;</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">join</span>(Directory<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> directory) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建并返回 FailbackClusterInvoker 对象</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> FailbackClusterInvoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>(directory); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,FailbackCluster 的逻辑也是很简单,无需解释了。所以接下来,我们把重点放在各种 Cluster Invoker 上</p> |
| <h3 id="32-cluster-invoker-分析">3.2 Cluster Invoker 分析</h3> |
| <p>我们首先从各种 Cluster Invoker 的父类 AbstractClusterInvoker 源码开始说起。前面说过,集群工作过程可分为两个阶段,第一个阶段是在服务消费者初始化期间,这个在<a href="../refer-service">服务引用</a>那篇文章中分析过,就不赘述。第二个阶段是在服务消费者进行远程调用时,此时 AbstractClusterInvoker 的 invoke 方法会被调用。列举 Invoker,负载均衡等操作均会在此阶段被执行。因此下面先来看一下 invoke 方法的逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> Result <span style="color:#268bd2">invoke</span>(<span style="color:#268bd2">final</span> Invocation invocation) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> checkWhetherDestroyed(); |
| </span></span><span style="display:flex;"><span> LoadBalance loadbalance <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 绑定 attachments 到 invocation 中.</span> |
| </span></span><span style="display:flex;"><span> Map<span style="color:#719e07">&lt;</span>String, String<span style="color:#719e07">&gt;</span> contextAttachments <span style="color:#719e07">=</span> RpcContext.getContext().getAttachments(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (contextAttachments <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> contextAttachments.size() <span style="color:#719e07">!=</span> 0) { |
| </span></span><span style="display:flex;"><span> ((RpcInvocation) invocation).addAttachments(contextAttachments); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 列举 Invoker</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers <span style="color:#719e07">=</span> list(invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokers <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>invokers.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 加载 LoadBalance</span> |
| </span></span><span style="display:flex;"><span> loadbalance <span style="color:#719e07">=</span> ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl() |
| </span></span><span style="display:flex;"><span> .getMethodParameter(RpcUtils.getMethodName(invocation), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 doInvoke 进行后续操作</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> doInvoke(invocation, invokers, loadbalance); |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75">// 抽象方法,由子类实现</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">protected</span> <span style="color:#268bd2">abstract</span> Result <span style="color:#268bd2">doInvoke</span>(Invocation invocation, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, |
| </span></span><span style="display:flex;"><span> LoadBalance loadbalance) <span style="color:#268bd2">throws</span> RpcException; |
| </span></span></code></pre></div><p>AbstractClusterInvoker 的 invoke 方法主要用于列举 Invoker,以及加载 LoadBalance。最后再调用模板方法 doInvoke 进行后续操作。下面我们来看一下 Invoker 列举方法 list(Invocation) 的逻辑,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">protected</span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> <span style="color:#268bd2">list</span>(Invocation invocation) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 Directory 的 list 方法列举 Invoker</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers <span style="color:#719e07">=</span> directory.list(invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,AbstractClusterInvoker 中的 list 方法做的事情很简单,只是简单的调用了 Directory 的 list 方法,没有其他更多的逻辑了。Directory 即相关实现类在前文已经分析过,这里就不多说了。接下来,我们把目光转移到 AbstractClusterInvoker 的各种实现类上,来看一下这些实现类是如何实现 doInvoke 方法逻辑的。</p> |
| <h4 id="321-failoverclusterinvoker">3.2.1 FailoverClusterInvoker</h4> |
| <p>FailoverClusterInvoker 在调用失败时,会自动切换 Invoker 进行重试。默认配置下,Dubbo 会使用这个类作为缺省 Cluster Invoker。下面来看一下该类的逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">FailoverClusterInvoker</span><span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">extends</span> AbstractClusterInvoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略部分代码</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Result <span style="color:#268bd2">doInvoke</span>(Invocation invocation, <span style="color:#268bd2">final</span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, LoadBalance loadbalance) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> copyinvokers <span style="color:#719e07">=</span> invokers; |
| </span></span><span style="display:flex;"><span> checkInvokers(copyinvokers, invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取重试次数</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> len <span style="color:#719e07">=</span> getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) <span style="color:#719e07">+</span> 1; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (len <span style="color:#719e07">&lt;=</span> 0) { |
| </span></span><span style="display:flex;"><span> len <span style="color:#719e07">=</span> 1; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> RpcException le <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invoked <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span>(copyinvokers.size()); |
| </span></span><span style="display:flex;"><span> Set<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span> providers <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> HashSet<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;</span>(len); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 循环调用,失败重试</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> len; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (i <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> checkWhetherDestroyed(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 在进行重试前重新列举 Invoker,这样做的好处是,如果某个服务挂了,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过调用 list 可得到最新可用的 Invoker 列表</span> |
| </span></span><span style="display:flex;"><span> copyinvokers <span style="color:#719e07">=</span> list(invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 copyinvokers 进行判空检查</span> |
| </span></span><span style="display:flex;"><span> checkInvokers(copyinvokers, invocation); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过负载均衡选择 Invoker</span> |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker <span style="color:#719e07">=</span> select(loadbalance, invocation, copyinvokers, invoked); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加到 invoker 到 invoked 列表中</span> |
| </span></span><span style="display:flex;"><span> invoked.add(invoker); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 invoked 到 RPC 上下文中</span> |
| </span></span><span style="display:flex;"><span> RpcContext.getContext().setInvokers((List) invoked); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用目标 Invoker 的 invoke 方法</span> |
| </span></span><span style="display:flex;"><span> Result result <span style="color:#719e07">=</span> invoker.invoke(invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> result; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (RpcException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (e.isBiz()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> e; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> le <span style="color:#719e07">=</span> e; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> le <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> RpcException(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> providers.add(invoker.getUrl().getAddress()); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 若重试失败,则抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(..., <span style="color:#2aa198">&#34;Failed to invoke the method ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,FailoverClusterInvoker 的 doInvoke 方法首先是获取重试次数,然后根据重试次数进行循环调用,失败后进行重试。在 for 循环内,首先是通过负载均衡组件选择一个 Invoker,然后再通过这个 Invoker 的 invoke 方法进行远程调用。如果失败了,记录下异常,并进行重试。重试时会再次调用父类的 list 方法列举 Invoker。整个流程大致如此,不是很难理解。下面我们看一下 select 方法的逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">protected</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">select</span>(LoadBalance loadbalance, Invocation invocation, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> selected) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> invokers.isEmpty()) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取调用方法名</span> |
| </span></span><span style="display:flex;"><span> String methodName <span style="color:#719e07">=</span> invocation <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">?</span> <span style="color:#2aa198">&#34;&#34;</span> : invocation.getMethodName(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 sticky 配置,sticky 表示粘滞连接。所谓粘滞连接是指让服务消费者尽可能的</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用同一个服务提供者,除非该提供者挂了再进行切换</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> sticky <span style="color:#719e07">=</span> invokers.get(0).getUrl().getMethodParameter(methodName, Constants.CLUSTER_STICKY_KEY, Constants.DEFAULT_CLUSTER_STICKY); |
| </span></span><span style="display:flex;"><span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 invokers 列表是否包含 stickyInvoker,如果不包含,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 说明 stickyInvoker 代表的服务提供者挂了,此时需要将其置空</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (stickyInvoker <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>invokers.contains(stickyInvoker)) { |
| </span></span><span style="display:flex;"><span> stickyInvoker <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 在 sticky 为 true,且 stickyInvoker != null 的情况下。如果 selected 包含 </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// stickyInvoker,表明 stickyInvoker 对应的服务提供者可能因网络原因未能成功提供服务。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 但是该提供者并没挂,此时 invokers 列表中仍存在该服务提供者对应的 Invoker。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (sticky <span style="color:#719e07">&amp;&amp;</span> stickyInvoker <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> (selected <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> <span style="color:#719e07">!</span>selected.contains(stickyInvoker))) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// availablecheck 表示是否开启了可用性检查,如果开启了,则调用 stickyInvoker 的 </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// isAvailable 方法进行检查,如果检查通过,则直接返回 stickyInvoker。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (availablecheck <span style="color:#719e07">&amp;&amp;</span> stickyInvoker.isAvailable()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> stickyInvoker; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果线程走到当前代码处,说明前面的 stickyInvoker 为空,或者不可用。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 此时继续调用 doSelect 选择 Invoker</span> |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker <span style="color:#719e07">=</span> doSelect(loadbalance, invocation, invokers, selected); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 sticky 为 true,则将负载均衡组件选出的 Invoker 赋值给 stickyInvoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (sticky) { |
| </span></span><span style="display:flex;"><span> stickyInvoker <span style="color:#719e07">=</span> invoker; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invoker; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,select 方法的主要逻辑集中在了对粘滞连接特性的支持上。首先是获取 sticky 配置,然后再检测 invokers 列表中是否包含 stickyInvoker,如果不包含,则认为该 stickyInvoker 不可用,此时将其置空。这里的 invokers 列表可以看做是<strong>存活着的服务提供者</strong>列表,如果这个列表不包含 stickyInvoker,那自然而然的认为 stickyInvoker 挂了,所以置空。如果 stickyInvoker 存在于 invokers 列表中,此时要进行下一项检测 — 检测 selected 中是否包含 stickyInvoker。如果包含的话,说明 stickyInvoker 在此之前没有成功提供服务(但其仍然处于存活状态)。此时我们认为这个服务不可靠,不应该在重试期间内再次被调用,因此这个时候不会返回该 stickyInvoker。如果 selected 不包含 stickyInvoker,此时还需要进行可用性检测,比如检测服务提供者网络连通性等。当可用性检测通过,才可返回 stickyInvoker,否则调用 doSelect 方法选择 Invoker。如果 sticky 为 true,此时会将 doSelect 方法选出的 Invoker 赋值给 stickyInvoker。</p> |
| <p>以上就是 select 方法的逻辑,这段逻辑看起来不是很复杂,但是信息量比较大。不搞懂 invokers 和 selected 两个入参的含义,以及粘滞连接特性,这段代码是不容易看懂的。所以大家在阅读这段代码时,不要忽略了对背景知识的理解。关于 select 方法先分析这么多,继续向下分析。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">doSelect</span>(LoadBalance loadbalance, Invocation invocation, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> selected) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> invokers.isEmpty()) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokers.size() <span style="color:#719e07">==</span> 1) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers.get(0); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (loadbalance <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 loadbalance 为空,这里通过 SPI 加载 Loadbalance,默认为 RandomLoadBalance</span> |
| </span></span><span style="display:flex;"><span> loadbalance <span style="color:#719e07">=</span> ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过负载均衡组件选择 Invoker</span> |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker <span style="color:#719e07">=</span> loadbalance.select(invokers, getUrl(), invocation); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 selected 包含负载均衡选择出的 Invoker,或者该 Invoker 无法经过可用性检查,此时进行重选</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((selected <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> selected.contains(invoker)) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">||</span> (<span style="color:#719e07">!</span>invoker.isAvailable() <span style="color:#719e07">&amp;&amp;</span> getUrl() <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> availablecheck)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 进行重选</span> |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> rinvoker <span style="color:#719e07">=</span> reselect(loadbalance, invocation, invokers, selected, availablecheck); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (rinvoker <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 rinvoker 不为空,则将其赋值给 invoker</span> |
| </span></span><span style="display:flex;"><span> invoker <span style="color:#719e07">=</span> rinvoker; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// rinvoker 为空,定位 invoker 在 invokers 中的位置</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> index <span style="color:#719e07">=</span> invokers.indexOf(invoker); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 index + 1 位置处的 Invoker,以下代码等价于:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// invoker = invokers.get((index + 1) % invokers.size());</span> |
| </span></span><span style="display:flex;"><span> invoker <span style="color:#719e07">=</span> index <span style="color:#719e07">&lt;</span> invokers.size() <span style="color:#719e07">-</span> 1 <span style="color:#719e07">?</span> invokers.get(index <span style="color:#719e07">+</span> 1) : invokers.get(0); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Exception e) { |
| </span></span><span style="display:flex;"><span> logger.warn(<span style="color:#2aa198">&#34;... may because invokers list dynamic change, ignore.&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#2aa198">&#34;cluster reselect fail reason is : ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invoker; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>doSelect 主要做了两件事,第一是通过负载均衡组件选择 Invoker。第二是,如果选出来的 Invoker 不稳定,或不可用,此时需要调用 reselect 方法进行重选。若 reselect 选出来的 Invoker 为空,此时定位 invoker 在 invokers 列表中的位置 index,然后获取 index + 1 处的 invoker,这也可以看做是重选逻辑的一部分。下面我们来看一下 reselect 方法的逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">reselect</span>(LoadBalance loadbalance, Invocation invocation, |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> selected, <span style="color:#dc322f">boolean</span> availablecheck) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> reselectInvokers <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span>(invokers.size() <span style="color:#719e07">&gt;</span> 1 <span style="color:#719e07">?</span> (invokers.size() <span style="color:#719e07">-</span> 1) : invokers.size()); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 下面的 if-else 分支逻辑有些冗余,pull request #2826 对这段代码进行了简化,可以参考一下</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 根据 availablecheck 进行不同的处理</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (availablecheck) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 invokers 列表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker : invokers) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测可用性</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invoker.isAvailable()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 selected 列表不包含当前 invoker,则将其添加到 reselectInvokers 中</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (selected <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> <span style="color:#719e07">!</span>selected.contains(invoker)) { |
| </span></span><span style="display:flex;"><span> reselectInvokers.add(invoker); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// reselectInvokers 不为空,此时通过负载均衡组件进行选择</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>reselectInvokers.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> loadbalance.select(reselectInvokers, getUrl(), invocation); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 不检查 Invoker 可用性</span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker : invokers) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 selected 列表不包含当前 invoker,则将其添加到 reselectInvokers 中</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (selected <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> <span style="color:#719e07">!</span>selected.contains(invoker)) { |
| </span></span><span style="display:flex;"><span> reselectInvokers.add(invoker); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>reselectInvokers.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 通过负载均衡组件进行选择</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> loadbalance.select(reselectInvokers, getUrl(), invocation); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 若线程走到此处,说明 reselectInvokers 集合为空,此时不会调用负载均衡组件进行筛选。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 这里从 selected 列表中查找可用的 Invoker,并将其添加到 reselectInvokers 集合中</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (selected <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker : selected) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> ((invoker.isAvailable()) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>reselectInvokers.contains(invoker)) { |
| </span></span><span style="display:flex;"><span> reselectInvokers.add(invoker); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>reselectInvokers.isEmpty()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 再次进行选择,并返回选择结果</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> loadbalance.select(reselectInvokers, getUrl(), invocation); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>reselect 方法总结下来其实只做了两件事情,第一是查找可用的 Invoker,并将其添加到 reselectInvokers 集合中。第二,如果 reselectInvokers 不为空,则通过负载均衡组件再次进行选择。其中第一件事情又可进行细分,一开始,reselect 从 invokers 列表中查找有效可用的 Invoker,若未能找到,此时再到 selected 列表中继续查找。关于 reselect 方法就先分析到这,继续分析其他的 Cluster Invoker。</p> |
| <h4 id="322-failbackclusterinvoker">3.2.2 FailbackClusterInvoker</h4> |
| <p>FailbackClusterInvoker 会在调用失败后,返回一个空结果给服务消费者。并通过定时任务对失败的调用进行重传,适合执行消息通知等操作。下面来看一下它的实现逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">FailbackClusterInvoker</span><span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">extends</span> AbstractClusterInvoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">long</span> RETRY_FAILED_PERIOD <span style="color:#719e07">=</span> 5 <span style="color:#719e07">*</span> 1000; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ScheduledExecutorService scheduledExecutorService <span style="color:#719e07">=</span> Executors.newScheduledThreadPool(2, |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">new</span> NamedInternalThreadFactory(<span style="color:#2aa198">&#34;failback-cluster-timer&#34;</span>, <span style="color:#cb4b16">true</span>)); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ConcurrentMap<span style="color:#719e07">&lt;</span>Invocation, AbstractClusterInvoker<span style="color:#719e07">&lt;?&gt;&gt;</span> failed <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;</span>Invocation, AbstractClusterInvoker<span style="color:#719e07">&lt;?&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">volatile</span> ScheduledFuture<span style="color:#719e07">&lt;?&gt;</span> retryFuture; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> Result <span style="color:#268bd2">doInvoke</span>(Invocation invocation, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, LoadBalance loadbalance) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> checkInvokers(invokers, invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 选择 Invoker</span> |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker <span style="color:#719e07">=</span> select(loadbalance, invocation, invokers, <span style="color:#cb4b16">null</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 进行调用</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invoker.invoke(invocation); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果调用过程中发生异常,此时仅打印错误日志,不抛出异常</span> |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#2aa198">&#34;Failback to invoke method ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 记录调用信息</span> |
| </span></span><span style="display:flex;"><span> addFailed(invocation, <span style="color:#719e07">this</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回一个空结果给服务消费者</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> RpcResult(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">addFailed</span>(Invocation invocation, AbstractClusterInvoker<span style="color:#719e07">&lt;?&gt;</span> router) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (retryFuture <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">synchronized</span> (<span style="color:#719e07">this</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (retryFuture <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建定时任务,每隔5秒执行一次</span> |
| </span></span><span style="display:flex;"><span> retryFuture <span style="color:#719e07">=</span> scheduledExecutorService.scheduleWithFixedDelay(<span style="color:#719e07">new</span> Runnable() { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">run</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对失败的调用进行重试</span> |
| </span></span><span style="display:flex;"><span> retryFailed(); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable t) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果发生异常,仅打印异常日志,不抛出</span> |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#2aa198">&#34;Unexpected error occur at collect statistic&#34;</span>, t); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> }, RETRY_FAILED_PERIOD, RETRY_FAILED_PERIOD, TimeUnit.MILLISECONDS); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 添加 invocation 和 invoker 到 failed 中</span> |
| </span></span><span style="display:flex;"><span> failed.put(invocation, router); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">retryFailed</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (failed.size() <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 failed,对失败的调用进行重试</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Map.Entry<span style="color:#719e07">&lt;</span>Invocation, AbstractClusterInvoker<span style="color:#719e07">&lt;?&gt;&gt;</span> entry : <span style="color:#719e07">new</span> HashMap<span style="color:#719e07">&lt;</span>Invocation, AbstractClusterInvoker<span style="color:#719e07">&lt;?&gt;&gt;</span>(failed).entrySet()) { |
| </span></span><span style="display:flex;"><span> Invocation invocation <span style="color:#719e07">=</span> entry.getKey(); |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;?&gt;</span> invoker <span style="color:#719e07">=</span> entry.getValue(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 再次进行调用</span> |
| </span></span><span style="display:flex;"><span> invoker.invoke(invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用成功后,从 failed 中移除 invoker</span> |
| </span></span><span style="display:flex;"><span> failed.remove(invocation); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 仅打印异常,不抛出</span> |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#2aa198">&#34;Failed retry to invoke method ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>这个类主要由3个方法组成,首先是 doInvoker,该方法负责初次的远程调用。若远程调用失败,则通过 addFailed 方法将调用信息存入到 failed 中,等待定时重试。addFailed 在开始阶段会根据 retryFuture 为空与否,来决定是否开启定时任务。retryFailed 方法则是包含了失败重试的逻辑,该方法会对 failed 进行遍历,然后依次对 Invoker 进行调用。调用成功则将 Invoker 从 failed 中移除,调用失败则忽略失败原因。</p> |
| <p>以上就是 FailbackClusterInvoker 的执行逻辑,不是很复杂,继续往下看。</p> |
| <h4 id="323-failfastclusterinvoker">3.2.3 FailfastClusterInvoker</h4> |
| <p>FailfastClusterInvoker 只会发起一次调用,失败后立即抛出异常。通常用于非幂等性的写操作,比如新增记录。源码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">FailfastClusterInvoker</span><span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">extends</span> AbstractClusterInvoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Result <span style="color:#268bd2">doInvoke</span>(Invocation invocation, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, LoadBalance loadbalance) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> checkInvokers(invokers, invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 选择 Invoker</span> |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker <span style="color:#719e07">=</span> select(loadbalance, invocation, invokers, <span style="color:#cb4b16">null</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invoker.invoke(invocation); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (e <span style="color:#719e07">instanceof</span> RpcException <span style="color:#719e07">&amp;&amp;</span> ((RpcException) e).isBiz()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> (RpcException) e; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(..., <span style="color:#2aa198">&#34;Failfast invoke providers ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,首先是通过 select 方法选择 Invoker,然后进行远程调用。如果调用失败,则立即抛出异常。FailfastClusterInvoker 就先分析到这,下面分析 FailsafeClusterInvoker。</p> |
| <h4 id="324-failsafeclusterinvoker">3.2.4 FailsafeClusterInvoker</h4> |
| <p>FailsafeClusterInvoker 是一种失败安全的 Cluster Invoker。所谓的失败安全是指,当调用过程中出现异常时,FailsafeClusterInvoker 仅会打印异常,而不会抛出异常。适用于写入审计日志等操作。下面分析源码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">FailsafeClusterInvoker</span><span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">extends</span> AbstractClusterInvoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Result <span style="color:#268bd2">doInvoke</span>(Invocation invocation, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, LoadBalance loadbalance) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> checkInvokers(invokers, invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 选择 Invoker</span> |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker <span style="color:#719e07">=</span> select(loadbalance, invocation, invokers, <span style="color:#cb4b16">null</span>); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 进行远程调用</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invoker.invoke(invocation); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 打印错误日志,但不抛出</span> |
| </span></span><span style="display:flex;"><span> logger.error(<span style="color:#2aa198">&#34;Failsafe ignore exception: &#34;</span> <span style="color:#719e07">+</span> e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回空结果忽略错误</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#719e07">new</span> RpcResult(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>FailsafeClusterInvoker 的逻辑和 FailfastClusterInvoker 的逻辑一样简单,无需过多说明。继续向下分析。</p> |
| <h4 id="325-forkingclusterinvoker">3.2.5 ForkingClusterInvoker</h4> |
| <p>ForkingClusterInvoker 会在运行时通过线程池创建多个线程,并发调用多个服务提供者。只要有一个服务提供者成功返回了结果,doInvoke 方法就会立即结束运行。ForkingClusterInvoker 的应用场景是在一些对实时性要求比较高<strong>读操作</strong>(注意是读操作,并行写操作可能不安全)下使用,但这将会耗费更多的资源。下面来看该类的实现。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">ForkingClusterInvoker</span><span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">extends</span> AbstractClusterInvoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ExecutorService executor <span style="color:#719e07">=</span> Executors.newCachedThreadPool( |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">new</span> NamedInternalThreadFactory(<span style="color:#2aa198">&#34;forking-cluster-timer&#34;</span>, <span style="color:#cb4b16">true</span>)); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Result <span style="color:#268bd2">doInvoke</span>(<span style="color:#268bd2">final</span> Invocation invocation, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, LoadBalance loadbalance) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> checkInvokers(invokers, invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> selected; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 forks 配置</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">int</span> forks <span style="color:#719e07">=</span> getUrl().getParameter(Constants.FORKS_KEY, Constants.DEFAULT_FORKS); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取超时配置</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">int</span> timeout <span style="color:#719e07">=</span> getUrl().getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 forks 配置不合理,则直接将 invokers 赋值给 selected</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (forks <span style="color:#719e07">&lt;=</span> 0 <span style="color:#719e07">||</span> forks <span style="color:#719e07">&gt;=</span> invokers.size()) { |
| </span></span><span style="display:flex;"><span> selected <span style="color:#719e07">=</span> invokers; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> { |
| </span></span><span style="display:flex;"><span> selected <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 循环选出 forks 个 Invoker,并添加到 selected 中</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> forks; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 选择 Invoker</span> |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker <span style="color:#719e07">=</span> select(loadbalance, invocation, invokers, selected); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>selected.contains(invoker)) { |
| </span></span><span style="display:flex;"><span> selected.add(invoker); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ----------------------✨ 分割线1 ✨---------------------- //</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> RpcContext.getContext().setInvokers((List) selected); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> AtomicInteger count <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> AtomicInteger(); |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> BlockingQueue<span style="color:#719e07">&lt;</span>Object<span style="color:#719e07">&gt;</span> ref <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> LinkedBlockingQueue<span style="color:#719e07">&lt;</span>Object<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 selected 列表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#268bd2">final</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker : selected) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 为每个 Invoker 创建一个执行线程</span> |
| </span></span><span style="display:flex;"><span> executor.execute(<span style="color:#719e07">new</span> Runnable() { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">run</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 进行远程调用</span> |
| </span></span><span style="display:flex;"><span> Result result <span style="color:#719e07">=</span> invoker.invoke(invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将结果存到阻塞队列中</span> |
| </span></span><span style="display:flex;"><span> ref.offer(result); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> value <span style="color:#719e07">=</span> count.incrementAndGet(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 仅在 value 大于等于 selected.size() 时,才将异常对象</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 放入阻塞队列中,请大家思考一下为什么要这样做。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (value <span style="color:#719e07">&gt;=</span> selected.size()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将异常对象存入到阻塞队列中</span> |
| </span></span><span style="display:flex;"><span> ref.offer(e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> }); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// ----------------------✨ 分割线2 ✨---------------------- //</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从阻塞队列中取出远程调用结果</span> |
| </span></span><span style="display:flex;"><span> Object ret <span style="color:#719e07">=</span> ref.poll(timeout, TimeUnit.MILLISECONDS); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果结果类型为 Throwable,则抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (ret <span style="color:#719e07">instanceof</span> Throwable) { |
| </span></span><span style="display:flex;"><span> Throwable e <span style="color:#719e07">=</span> (Throwable) ret; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(..., <span style="color:#2aa198">&#34;Failed to forking invoke provider ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回结果</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> (Result) ret; |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (InterruptedException e) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RpcException(<span style="color:#2aa198">&#34;Failed to forking invoke provider ...&#34;</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> RpcContext.getContext().clearAttachments(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>ForkingClusterInvoker 的 doInvoker 方法比较长,这里通过两个分割线将整个方法划分为三个逻辑块。从方法开始到分割线1之间的代码主要是用于选出 forks 个 Invoker,为接下来的并发调用提供输入。分割线1和分割线2之间的逻辑通过线程池并发调用多个 Invoker,并将结果存储在阻塞队列中。分割线2到方法结尾之间的逻辑主要用于从阻塞队列中获取返回结果,并对返回结果类型进行判断。如果为异常类型,则直接抛出,否则返回。</p> |
| <p>以上就是ForkingClusterInvoker 的 doInvoker 方法大致过程。我们在分割线1和分割线2之间的代码上留了一个问题,问题是这样的:为什么要在<code>value &gt;= selected.size()</code>的情况下,才将异常对象添加到阻塞队列中?这里来解答一下。原因是这样的,在并行调用多个服务提供者的情况下,只要有一个服务提供者能够成功返回结果,而其他全部失败。此时 ForkingClusterInvoker 仍应该返回成功的结果,而非抛出异常。在<code>value &gt;= selected.size()</code>时将异常对象放入阻塞队列中,可以保证异常对象不会出现在正常结果的前面,这样可从阻塞队列中优先取出正常的结果。</p> |
| <p>关于 ForkingClusterInvoker 就先分析到这,接下来分析最后一个 Cluster Invoker。</p> |
| <h4 id="326-broadcastclusterinvoker">3.2.6 BroadcastClusterInvoker</h4> |
| <p>本章的最后,我们再来看一下 BroadcastClusterInvoker。BroadcastClusterInvoker 会逐个调用每个服务提供者,如果其中一台报错,在循环调用结束后,BroadcastClusterInvoker 会抛出异常。该类通常用于通知所有提供者更新缓存或日志等本地资源信息。源码如下。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">BroadcastClusterInvoker</span><span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">extends</span> AbstractClusterInvoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Result <span style="color:#268bd2">doInvoke</span>(<span style="color:#268bd2">final</span> Invocation invocation, List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, LoadBalance loadbalance) <span style="color:#268bd2">throws</span> RpcException { |
| </span></span><span style="display:flex;"><span> checkInvokers(invokers, invocation); |
| </span></span><span style="display:flex;"><span> RpcContext.getContext().setInvokers((List) invokers); |
| </span></span><span style="display:flex;"><span> RpcException exception <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> Result result <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 Invoker 列表,逐个调用</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker : invokers) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 进行远程调用</span> |
| </span></span><span style="display:flex;"><span> result <span style="color:#719e07">=</span> invoker.invoke(invocation); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (RpcException e) { |
| </span></span><span style="display:flex;"><span> exception <span style="color:#719e07">=</span> e; |
| </span></span><span style="display:flex;"><span> logger.warn(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">catch</span> (Throwable e) { |
| </span></span><span style="display:flex;"><span> exception <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> RpcException(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> logger.warn(e.getMessage(), e); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// exception 不为空,则抛出异常</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (exception <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> exception; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> result; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上就是 BroadcastClusterInvoker 的代码,比较简单,就不多说了。</p> |
| <h2 id="4总结">4.总结</h2> |
| <p>本篇文章详细分析了集群容错的几种实现方式。集群容错对于 Dubbo 框架来说,是很重要的逻辑。集群模块处于服务提供者和消费者之间,对于服务消费者来说,集群可向其屏蔽服务提供者集群的情况,使其能够专心进行远程调用。除此之外,通过集群模块,我们还可以对服务之间的调用链路进行编排优化,治理服务。总的来说,对于 Dubbo 而言,集群容错相关逻辑是非常重要的。想要对 Dubbo 有比较深的理解,集群容错是必须要掌握的。</p> |
| <p>关于集群模块就先分析到这,感谢阅读。</p></description></item><item><title>Docsv2.7: 负载均衡</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/loadbalance/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/dev/source/loadbalance/</guid><description> |
| <h2 id="1简介">1.简介</h2> |
| <p>LoadBalance 中文意思为负载均衡,它的职责是将网络请求,或者其他形式的负载“均摊”到不同的机器上。避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况。通过负载均衡,可以让每台服务器获取到适合自己处理能力的负载。在为高负载服务器分流的同时,还可以避免资源浪费,一举两得。负载均衡可分为软件负载均衡和硬件负载均衡。在我们日常开发中,一般很难接触到硬件负载均衡。但软件负载均衡还是可以接触到的,比如 Nginx。在 Dubbo 中,也有负载均衡的概念和相应的实现。Dubbo 需要对服务消费者的调用请求进行分配,避免少数服务提供者负载过大。服务提供者负载过大,会导致部分请求超时。因此将负载均衡到每个服务提供者上,是非常必要的。Dubbo 提供了4种负载均衡实现,分别是基于权重随机算法的 RandomLoadBalance、基于最少活跃调用数算法的 LeastActiveLoadBalance、基于 hash 一致性的 ConsistentHashLoadBalance,以及基于加权轮询算法的 RoundRobinLoadBalance。这几个负载均衡算法代码不是很长,但是想看懂也不是很容易,需要大家对这几个算法的原理有一定了解才行。如果不是很了解,也没不用太担心。我们会在分析每个算法的源码之前,对算法原理进行简单的讲解,帮助大家建立初步的印象。</p> |
| <p>本系列文章在编写之初是基于 Dubbo 2.6.4 的,近期,Dubbo 2.6.5 发布了,其中就有针对对负载均衡部分的优化。因此我们在分析完 2.6.4 版本后的源码后,会另外分析 2.6.5 更新的部分。其他的就不多说了,进入正题吧。</p> |
| <h2 id="2源码分析">2.源码分析</h2> |
| <p>在 Dubbo 中,所有负载均衡实现类均继承自 AbstractLoadBalance,该类实现了 LoadBalance 接口,并封装了一些公共的逻辑。所以在分析负载均衡实现之前,先来看一下 AbstractLoadBalance 的逻辑。首先来看一下负载均衡的入口方法 select,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">select</span>(List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, URL url, Invocation invocation) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokers <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> invokers.isEmpty()) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 invokers 列表中仅有一个 Invoker,直接返回即可,无需进行负载均衡</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (invokers.size() <span style="color:#719e07">==</span> 1) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers.get(0); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 doSelect 方法进行负载均衡,该方法为抽象方法,由子类实现</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> doSelect(invokers, url, invocation); |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">protected</span> <span style="color:#268bd2">abstract</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">doSelect</span>(List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, URL url, Invocation invocation); |
| </span></span></code></pre></div><p>select 方法的逻辑比较简单,首先会检测 invokers 集合的合法性,然后再检测 invokers 集合元素数量。如果只包含一个 Invoker,直接返回该 Inovker 即可。如果包含多个 Invoker,此时需要通过负载均衡算法选择一个 Invoker。具体的负载均衡算法由子类实现,接下来章节会对这些子类一一进行详细分析。</p> |
| <p>AbstractLoadBalance 除了实现了 LoadBalance 接口方法,还封装了一些公共逻辑,比如服务提供者权重计算逻辑。具体实现如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">protected</span> <span style="color:#dc322f">int</span> <span style="color:#268bd2">getWeight</span>(Invoker<span style="color:#719e07">&lt;?&gt;</span> invoker, Invocation invocation) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 从 url 中获取权重 weight 配置值</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> weight <span style="color:#719e07">=</span> invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (weight <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取服务提供者启动时间戳</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">long</span> timestamp <span style="color:#719e07">=</span> invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (timestamp <span style="color:#719e07">&gt;</span> 0L) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 计算服务提供者运行时长</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> uptime <span style="color:#719e07">=</span> (<span style="color:#dc322f">int</span>) (System.currentTimeMillis() <span style="color:#719e07">-</span> timestamp); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取服务预热时间,默认为10分钟</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> warmup <span style="color:#719e07">=</span> invoker.getUrl().getParameter(Constants.WARMUP_KEY, Constants.DEFAULT_WARMUP); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果服务运行时间小于预热时间,则重新计算服务权重,即降权</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (uptime <span style="color:#719e07">&gt;</span> 0 <span style="color:#719e07">&amp;&amp;</span> uptime <span style="color:#719e07">&lt;</span> warmup) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 重新计算服务权重</span> |
| </span></span><span style="display:flex;"><span> weight <span style="color:#719e07">=</span> calculateWarmupWeight(uptime, warmup, weight); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> weight; |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">static</span> <span style="color:#dc322f">int</span> <span style="color:#268bd2">calculateWarmupWeight</span>(<span style="color:#dc322f">int</span> uptime, <span style="color:#dc322f">int</span> warmup, <span style="color:#dc322f">int</span> weight) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 计算权重,下面代码逻辑上形似于 (uptime / warmup) * weight。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 随着服务运行时间 uptime 增大,权重计算值 ww 会慢慢接近配置值 weight</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> ww <span style="color:#719e07">=</span> (<span style="color:#dc322f">int</span>) ((<span style="color:#dc322f">float</span>) uptime <span style="color:#719e07">/</span> ((<span style="color:#dc322f">float</span>) warmup <span style="color:#719e07">/</span> (<span style="color:#dc322f">float</span>) weight)); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> ww <span style="color:#719e07">&lt;</span> 1 <span style="color:#719e07">?</span> 1 : (ww <span style="color:#719e07">&gt;</span> weight <span style="color:#719e07">?</span> weight : ww); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面是权重的计算过程,该过程主要用于保证当服务运行时长小于服务预热时间时,对服务进行降权,避免让服务在启动之初就处于高负载状态。服务预热是一个优化手段,与此类似的还有 JVM 预热。主要目的是让服务启动后“低功率”运行一段时间,使其效率慢慢提升至最佳状态。</p> |
| <p>关于 AbstractLoadBalance 就先分析到这,接下来分析各个实现类的代码。首先,我们从 Dubbo 缺省的实现类 RandomLoadBalance 看起。</p> |
| <h3 id="21-randomloadbalance">2.1 RandomLoadBalance</h3> |
| <p>RandomLoadBalance 是加权随机算法的具体实现,它的算法思想很简单。假设我们有一组服务器 servers = [A, B, C],他们对应的权重为 weights = [5, 3, 2],权重总和为10。现在把这些权重值平铺在一维坐标值上,[0, 5) 区间属于服务器 A,[5, 8) 区间属于服务器 B,[8, 10) 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 [0, 10) 之间的随机数,然后计算这个随机数会落到哪个区间上。比如数字3会落到服务器 A 对应的区间上,此时返回服务器 A 即可。权重越大的机器,在坐标轴上对应的区间范围就越大,因此随机数生成器生成的数字就会有更大的概率落到此区间内。只要随机数生成器产生的随机数分布性很好,在经过多次选择后,每个服务器被选中的次数比例接近其权重比例。比如,经过一万次选择后,服务器 A 被选中的次数大约为5000次,服务器 B 被选中的次数约为3000次,服务器 C 被选中的次数约为2000次。</p> |
| <p>以上就是 RandomLoadBalance 背后的算法思想,比较简单。下面开始分析源码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">RandomLoadBalance</span> <span style="color:#268bd2">extends</span> AbstractLoadBalance { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> String NAME <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;random&#34;</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Random random <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Random(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">doSelect</span>(List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, URL url, Invocation invocation) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> length <span style="color:#719e07">=</span> invokers.size(); |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> totalWeight <span style="color:#719e07">=</span> 0; |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> sameWeight <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 下面这个循环有两个作用,第一是计算总权重 totalWeight,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 第二是检测每个服务提供者的权重是否相同</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> weight <span style="color:#719e07">=</span> getWeight(invokers.get(i), invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 累加权重</span> |
| </span></span><span style="display:flex;"><span> totalWeight <span style="color:#719e07">+=</span> weight; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测当前服务提供者的权重与上一个服务提供者的权重是否相同,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 不相同的话,则将 sameWeight 置为 false。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (sameWeight <span style="color:#719e07">&amp;&amp;</span> i <span style="color:#719e07">&gt;</span> 0 |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> weight <span style="color:#719e07">!=</span> getWeight(invokers.get(i <span style="color:#719e07">-</span> 1), invocation)) { |
| </span></span><span style="display:flex;"><span> sameWeight <span style="color:#719e07">=</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 下面的 if 分支主要用于获取随机数,并计算随机数落在哪个区间上</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (totalWeight <span style="color:#719e07">&gt;</span> 0 <span style="color:#719e07">&amp;&amp;</span> <span style="color:#719e07">!</span>sameWeight) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 随机获取一个 [0, totalWeight) 区间内的数字</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> offset <span style="color:#719e07">=</span> random.nextInt(totalWeight); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 循环让 offset 数减去服务提供者权重值,当 offset 小于0时,返回相应的 Invoker。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 举例说明一下,我们有 servers = [A, B, C],weights = [5, 3, 2],offset = 7。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 第一次循环,offset - 5 = 2 &gt; 0,即 offset &gt; 5,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 表明其不会落在服务器 A 对应的区间上。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 第二次循环,offset - 3 = -1 &lt; 0,即 5 &lt; offset &lt; 8,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 表明其会落在服务器 B 对应的区间上</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 让随机值 offset 减去权重值</span> |
| </span></span><span style="display:flex;"><span> offset <span style="color:#719e07">-=</span> getWeight(invokers.get(i), invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (offset <span style="color:#719e07">&lt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回相应的 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers.get(i); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果所有服务提供者权重值相同,此时直接随机返回一个即可</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers.get(random.nextInt(length)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>RandomLoadBalance 的算法思想比较简单,在经过多次请求后,能够将调用请求按照权重值进行“均匀”分配。当然 RandomLoadBalance 也存在一定的缺点,当调用次数比较少时,Random 产生的随机数可能会比较集中,此时多数请求会落到同一台服务器上。这个缺点并不是很严重,多数情况下可以忽略。RandomLoadBalance 是一个简单,高效的负载均衡实现,因此 Dubbo 选择它作为缺省实现。</p> |
| <p>关于 RandomLoadBalance 就先到这了,接下来分析 LeastActiveLoadBalance。</p> |
| <h3 id="22-leastactiveloadbalance">2.2 LeastActiveLoadBalance</h3> |
| <p>LeastActiveLoadBalance 翻译过来是最小活跃数负载均衡。活跃调用数越小,表明该服务提供者效率越高,单位时间内可处理更多的请求。此时应优先将请求分配给该服务提供者。在具体实现中,每个服务提供者对应一个活跃数 active。初始情况下,所有服务提供者活跃数均为0。每收到一个请求,活跃数加1,完成请求后则将活跃数减1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求、这就是最小活跃数负载均衡算法的基本思想。除了最小活跃数,LeastActiveLoadBalance 在实现上还引入了权重值。所以准确的来说,LeastActiveLoadBalance 是基于加权最小活跃数算法实现的。举个例子说明一下,在一个服务提供者集群中,有两个性能优异的服务提供者。某一时刻它们的活跃数相同,此时 Dubbo 会根据它们的权重去分配请求,权重越大,获取到新请求的概率就越大。如果两个服务提供者权重相同,此时随机选择一个即可。关于 LeastActiveLoadBalance 的背景知识就先介绍到这里,下面开始分析源码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">LeastActiveLoadBalance</span> <span style="color:#268bd2">extends</span> AbstractLoadBalance { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> String NAME <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;leastactive&#34;</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> Random random <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> Random(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">doSelect</span>(List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, URL url, Invocation invocation) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> length <span style="color:#719e07">=</span> invokers.size(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 最小的活跃数</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> leastActive <span style="color:#719e07">=</span> <span style="color:#719e07">-</span>1; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 具有相同“最小活跃数”的服务者提供者(以下用 Invoker 代称)数量</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> leastCount <span style="color:#719e07">=</span> 0; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// leastIndexs 用于记录具有相同“最小活跃数”的 Invoker 在 invokers 列表中的下标信息</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span><span style="color:#719e07">[]</span> leastIndexs <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> <span style="color:#dc322f">int</span><span style="color:#719e07">[</span>length<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> totalWeight <span style="color:#719e07">=</span> 0; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 第一个最小活跃数的 Invoker 权重值,用于与其他具有相同最小活跃数的 Invoker 的权重进行对比,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 以检测是否“所有具有相同最小活跃数的 Invoker 的权重”均相等</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> firstWeight <span style="color:#719e07">=</span> 0; |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">boolean</span> sameWeight <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 invokers 列表</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker <span style="color:#719e07">=</span> invokers.get(i); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 Invoker 对应的活跃数</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> active <span style="color:#719e07">=</span> RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取权重 - ⭐️</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> weight <span style="color:#719e07">=</span> invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 发现更小的活跃数,重新开始</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (leastActive <span style="color:#719e07">==</span> <span style="color:#719e07">-</span>1 <span style="color:#719e07">||</span> active <span style="color:#719e07">&lt;</span> leastActive) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 使用当前活跃数 active 更新最小活跃数 leastActive</span> |
| </span></span><span style="display:flex;"><span> leastActive <span style="color:#719e07">=</span> active; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 更新 leastCount 为 1</span> |
| </span></span><span style="display:flex;"><span> leastCount <span style="color:#719e07">=</span> 1; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 记录当前下标值到 leastIndexs 中</span> |
| </span></span><span style="display:flex;"><span> leastIndexs<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> i; |
| </span></span><span style="display:flex;"><span> totalWeight <span style="color:#719e07">=</span> weight; |
| </span></span><span style="display:flex;"><span> firstWeight <span style="color:#719e07">=</span> weight; |
| </span></span><span style="display:flex;"><span> sameWeight <span style="color:#719e07">=</span> <span style="color:#cb4b16">true</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 当前 Invoker 的活跃数 active 与最小活跃数 leastActive 相同 </span> |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">else</span> <span style="color:#719e07">if</span> (active <span style="color:#719e07">==</span> leastActive) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 在 leastIndexs 中记录下当前 Invoker 在 invokers 集合中的下标</span> |
| </span></span><span style="display:flex;"><span> leastIndexs<span style="color:#719e07">[</span>leastCount<span style="color:#719e07">++]</span> <span style="color:#719e07">=</span> i; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 累加权重</span> |
| </span></span><span style="display:flex;"><span> totalWeight <span style="color:#719e07">+=</span> weight; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测当前 Invoker 的权重与 firstWeight 是否相等,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 不相等则将 sameWeight 置为 false</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (sameWeight <span style="color:#719e07">&amp;&amp;</span> i <span style="color:#719e07">&gt;</span> 0 |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">&amp;&amp;</span> weight <span style="color:#719e07">!=</span> firstWeight) { |
| </span></span><span style="display:flex;"><span> sameWeight <span style="color:#719e07">=</span> <span style="color:#cb4b16">false</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 当只有一个 Invoker 具有最小活跃数,此时直接返回该 Invoker 即可</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (leastCount <span style="color:#719e07">==</span> 1) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers.get(leastIndexs<span style="color:#719e07">[</span>0<span style="color:#719e07">]</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 有多个 Invoker 具有相同的最小活跃数,但它们之间的权重不同</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>sameWeight <span style="color:#719e07">&amp;&amp;</span> totalWeight <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 随机生成一个 [0, totalWeight) 之间的数字</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> offsetWeight <span style="color:#719e07">=</span> random.nextInt(totalWeight); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 循环让随机数减去具有最小活跃数的 Invoker 的权重值,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 当 offset 小于等于0时,返回相应的 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> leastCount; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> leastIndex <span style="color:#719e07">=</span> leastIndexs<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取权重值,并让随机数减去权重值 - ⭐️</span> |
| </span></span><span style="display:flex;"><span> offsetWeight <span style="color:#719e07">-=</span> getWeight(invokers.get(leastIndex), invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (offsetWeight <span style="color:#719e07">&lt;=</span> 0) |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers.get(leastIndex); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果权重相同或权重为0时,随机返回一个 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers.get(leastIndexs<span style="color:#719e07">[</span>random.nextInt(leastCount)<span style="color:#719e07">]</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码的逻辑比较多,我们在代码中写了大量的注释,有帮助大家理解代码逻辑。下面简单总结一下以上代码所做的事情,如下:</p> |
| <ol> |
| <li>遍历 invokers 列表,寻找活跃数最小的 Invoker</li> |
| <li>如果有多个 Invoker 具有相同的最小活跃数,此时记录下这些 Invoker 在 invokers 集合中的下标,并累加它们的权重,比较它们的权重值是否相等</li> |
| <li>如果只有一个 Invoker 具有最小的活跃数,此时直接返回该 Invoker 即可</li> |
| <li>如果有多个 Invoker 具有最小活跃数,且它们的权重不相等,此时处理方式和 RandomLoadBalance 一致</li> |
| <li>如果有多个 Invoker 具有最小活跃数,但它们的权重相等,此时随机返回一个即可</li> |
| </ol> |
| <p>以上就是 LeastActiveLoadBalance 大致的实现逻辑,大家在阅读的源码的过程中要注意区分活跃数与权重这两个概念,不要混为一谈。</p> |
| <p>以上分析是基于 Dubbo 2.6.4 版本进行的,由于近期 Dubbo 2.6.5 发布了,并对 LeastActiveLoadBalance 进行了一些修改,下面简单来介绍一下修改内容。回到上面的源码中,我们在上面的代码中标注了两个黄色的五角星⭐️。两处标记对应的代码分别如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#dc322f">int</span> weight <span style="color:#719e07">=</span> invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); |
| </span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>offsetWeight <span style="color:#719e07">-=</span> getWeight(invokers.get(leastIndex), invocation); |
| </span></span></code></pre></div><p>问题出在服务预热阶段,第一行代码直接从 url 中取权重值,未被降权过。第二行代码获取到的是经过降权后的权重。第一行代码获取到的权重值最终会被累加到权重总和 totalWeight 中,这个时候会导致一个问题。offsetWeight 是一个在 [0, totalWeight) 范围内的随机数,而它所减去的是经过降权的权重。很有可能在经过 leastCount 次运算后,offsetWeight 仍然是大于0的,导致无法选中 Invoker。这个问题对应的 issue 为 <a href="https://github.com/apache/dubbo/issues/904">#904</a>,并在 pull request <a href="https://github.com/apache/dubbo/pull/2172">#2172</a> 中被修复。具体的修复逻辑是将标注一处的代码修改为:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#586e75">// afterWarmup 等价于上面的 weight 变量,这样命名是为了强调该变量经过了 warmup 降权处理</span> |
| </span></span><span style="display:flex;"><span><span style="color:#dc322f">int</span> afterWarmup <span style="color:#719e07">=</span> getWeight(invoker, invocation); |
| </span></span></code></pre></div><p>另外,2.6.4 版本中的 LeastActiveLoadBalance 还有一个缺陷,即当一组 Invoker 具有相同的最小活跃数,且其中一个 Invoker 的权重值为1,此时这个 Invoker 无法被选中。缺陷代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#dc322f">int</span> offsetWeight <span style="color:#719e07">=</span> random.nextInt(totalWeight); |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> leastCount; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> leastIndex <span style="color:#719e07">=</span> leastIndexs<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> offsetWeight <span style="color:#719e07">-=</span> getWeight(invokers.get(leastIndex), invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (offsetWeight <span style="color:#719e07">&lt;=</span> 0) <span style="color:#586e75">// ❌</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers.get(leastIndex); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>问题出在了<code>offsetWeight &lt;= 0</code>上,举例说明,假设有一组 Invoker 的权重为 5、2、1,offsetWeight 最大值为 7。假设 offsetWeight = 7,你会发现,当 for 循环进行第二次遍历后 offsetWeight = 7 - 5 - 2 = 0,提前返回了。此时,此时权重为1的 Invoker 就没有机会被选中了。该问题在 Dubbo 2.6.5 中被修复了,修改后的代码如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#dc322f">int</span> offsetWeight <span style="color:#719e07">=</span> random.nextInt(totalWeight) <span style="color:#719e07">+</span> 1; |
| </span></span></code></pre></div><p>以上就是 Dubbo 2.6.5 对 LeastActiveLoadBalance 的更新,内容不是很多,先分析到这。接下来分析基于一致性 hash 思想的 ConsistentHashLoadBalance。</p> |
| <h3 id="23-consistenthashloadbalance">2.3 ConsistentHashLoadBalance</h3> |
| <p>一致性 hash 算法由麻省理工学院的 Karger 及其合作者于1997年提出的,算法提出之初是用于大规模缓存系统的负载均衡。它的工作过程是这样的,首先根据 ip 或者其他的信息为缓存节点生成一个 hash,并将这个 hash 投射到 [0, 2<sup>32</sup> - 1] 的圆环上。当有查询或写入请求时,则为缓存项的 key 生成一个 hash 值。然后查找第一个大于或等于该 hash 值的缓存节点,并到这个节点中查询或写入缓存项。如果当前节点挂了,则在下一次查询或写入缓存时,为缓存项查找另一个大于其 hash 值的缓存节点即可。大致效果如下图所示,每个缓存节点在圆环上占据一个位置。如果缓存项的 key 的 hash 值小于缓存节点 hash 值,则到该缓存节点中存储或读取缓存项。比如下面绿色点对应的缓存项将会被存储到 cache-2 节点中。由于 cache-3 挂了,原本应该存到该节点中的缓存项最终会存储到 cache-4 节点中。</p> |
| <p><img src="https://dubbo.apache.org/imgs/dev/consistent-hash.jpg" alt="img"></p> |
| <p>下面来看看一致性 hash 在 Dubbo 中的应用。我们把上图的缓存节点替换成 Dubbo 的服务提供者,于是得到了下图:</p> |
| <p><img src="https://dubbo.apache.org/imgs/dev/consistent-hash-invoker.jpg" alt="img"></p> |
| <p>这里相同颜色的节点均属于同一个服务提供者,比如 Invoker1-1,Invoker1-2,……, Invoker1-160。这样做的目的是通过引入虚拟节点,让 Invoker 在圆环上分散开来,避免数据倾斜问题。所谓数据倾斜是指,由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。比如:</p> |
| <p><img src="https://dubbo.apache.org/imgs/dev/consistent-hash-data-incline.jpg" alt="img"></p> |
| <p>如上,由于 Invoker-1 和 Invoker-2 在圆环上分布不均,导致系统中75%的请求都会落到 Invoker-1 上,只有 25% 的请求会落到 Invoker-2 上。解决这个问题办法是引入虚拟节点,通过虚拟节点均衡各个节点的请求量。</p> |
| <p>到这里背景知识就普及完了,接下来开始分析源码。我们先从 ConsistentHashLoadBalance 的 doSelect 方法开始看起,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">ConsistentHashLoadBalance</span> <span style="color:#268bd2">extends</span> AbstractLoadBalance { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ConcurrentMap<span style="color:#719e07">&lt;</span>String, ConsistentHashSelector<span style="color:#719e07">&lt;?&gt;&gt;</span> selectors <span style="color:#719e07">=</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;</span>String, ConsistentHashSelector<span style="color:#719e07">&lt;?&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">doSelect</span>(List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, URL url, Invocation invocation) { |
| </span></span><span style="display:flex;"><span> String methodName <span style="color:#719e07">=</span> RpcUtils.getMethodName(invocation); |
| </span></span><span style="display:flex;"><span> String key <span style="color:#719e07">=</span> invokers.get(0).getUrl().getServiceKey() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.&#34;</span> <span style="color:#719e07">+</span> methodName; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 invokers 原始的 hashcode</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> identityHashCode <span style="color:#719e07">=</span> System.identityHashCode(invokers); |
| </span></span><span style="display:flex;"><span> ConsistentHashSelector<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> selector <span style="color:#719e07">=</span> (ConsistentHashSelector<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>) selectors.get(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 invokers 是一个新的 List 对象,意味着服务提供者数量发生了变化,可能新增也可能减少了。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 此时 selector.identityHashCode != identityHashCode 条件成立</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (selector <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">||</span> selector.identityHashCode <span style="color:#719e07">!=</span> identityHashCode) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建新的 ConsistentHashSelector</span> |
| </span></span><span style="display:flex;"><span> selectors.put(key, <span style="color:#719e07">new</span> ConsistentHashSelector<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>(invokers, methodName, identityHashCode)); |
| </span></span><span style="display:flex;"><span> selector <span style="color:#719e07">=</span> (ConsistentHashSelector<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>) selectors.get(key); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 调用 ConsistentHashSelector 的 select 方法选择 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> selector.select(invocation); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">ConsistentHashSelector</span><span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> {...} |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,doSelect 方法主要做了一些前置工作,比如检测 invokers 列表是不是变动过,以及创建 ConsistentHashSelector。这些工作做完后,接下来开始调用 ConsistentHashSelector 的 select 方法执行负载均衡逻辑。在分析 select 方法之前,我们先来看一下一致性 hash 选择器 ConsistentHashSelector 的初始化过程,如下:</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">ConsistentHashSelector</span><span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 使用 TreeMap 存储 Invoker 虚拟节点</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> TreeMap<span style="color:#719e07">&lt;</span>Long, Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> virtualInvokers; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">int</span> replicaNumber; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">int</span> identityHashCode; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> <span style="color:#dc322f">int</span><span style="color:#719e07">[]</span> argumentIndex; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> ConsistentHashSelector(List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, String methodName, <span style="color:#dc322f">int</span> identityHashCode) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.virtualInvokers <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> TreeMap<span style="color:#719e07">&lt;</span>Long, Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.identityHashCode <span style="color:#719e07">=</span> identityHashCode; |
| </span></span><span style="display:flex;"><span> URL url <span style="color:#719e07">=</span> invokers.get(0).getUrl(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取虚拟节点数,默认为160</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.replicaNumber <span style="color:#719e07">=</span> url.getMethodParameter(methodName, <span style="color:#2aa198">&#34;hash.nodes&#34;</span>, 160); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取参与 hash 计算的参数下标值,默认对第一个参数进行 hash 运算</span> |
| </span></span><span style="display:flex;"><span> String<span style="color:#719e07">[]</span> index <span style="color:#719e07">=</span> Constants.COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, <span style="color:#2aa198">&#34;hash.arguments&#34;</span>, <span style="color:#2aa198">&#34;0&#34;</span>)); |
| </span></span><span style="display:flex;"><span> argumentIndex <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> <span style="color:#dc322f">int</span><span style="color:#719e07">[</span>index.length<span style="color:#719e07">]</span>; |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> index.length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> argumentIndex<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span> <span style="color:#719e07">=</span> Integer.parseInt(index<span style="color:#719e07">[</span>i<span style="color:#719e07">]</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker : invokers) { |
| </span></span><span style="display:flex;"><span> String address <span style="color:#719e07">=</span> invoker.getUrl().getAddress(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> replicaNumber <span style="color:#719e07">/</span> 4; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 address + i 进行 md5 运算,得到一个长度为16的字节数组</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">byte</span><span style="color:#719e07">[]</span> digest <span style="color:#719e07">=</span> md5(address <span style="color:#719e07">+</span> i); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 digest 部分字节进行4次 hash 运算,得到四个不同的 long 型正整数</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> h <span style="color:#719e07">=</span> 0; h <span style="color:#719e07">&lt;</span> 4; h<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// h = 0 时,取 digest 中下标为 0 ~ 3 的4个字节进行位运算</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// h = 1 时,取 digest 中下标为 4 ~ 7 的4个字节进行位运算</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// h = 2, h = 3 时过程同上</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">long</span> m <span style="color:#719e07">=</span> hash(digest, h); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 hash 到 invoker 的映射关系存储到 virtualInvokers 中,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// virtualInvokers 需要提供高效的查询操作,因此选用 TreeMap 作为存储结构</span> |
| </span></span><span style="display:flex;"><span> virtualInvokers.put(m, invoker); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>ConsistentHashSelector 的构造方法执行了一系列的初始化逻辑,比如从配置中获取虚拟节点数以及参与 hash 计算的参数下标,默认情况下只使用第一个参数进行 hash。需要特别说明的是,ConsistentHashLoadBalance 的负载均衡逻辑只受参数值影响,具有相同参数值的请求将会被分配给同一个服务提供者。ConsistentHashLoadBalance 不 关系权重,因此使用时需要注意一下。</p> |
| <p>在获取虚拟节点数和参数下标配置后,接下来要做的事情是计算虚拟节点 hash 值,并将虚拟节点存储到 TreeMap 中。到此,ConsistentHashSelector 初始化工作就完成了。接下来,我们来看看 select 方法的逻辑。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">select</span>(Invocation invocation) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将参数转为 key</span> |
| </span></span><span style="display:flex;"><span> String key <span style="color:#719e07">=</span> toKey(invocation.getArguments()); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对参数 key 进行 md5 运算</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">byte</span><span style="color:#719e07">[]</span> digest <span style="color:#719e07">=</span> md5(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 取 digest 数组的前四个字节进行 hash 运算,再将 hash 值传给 selectForKey 方法,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 寻找合适的 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> selectForKey(hash(digest, 0)); |
| </span></span><span style="display:flex;"><span>} |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">private</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">selectForKey</span>(<span style="color:#dc322f">long</span> hash) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 到 TreeMap 中查找第一个节点值大于或等于当前 hash 的 Invoker</span> |
| </span></span><span style="display:flex;"><span> Map.Entry<span style="color:#719e07">&lt;</span>Long, Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> entry <span style="color:#719e07">=</span> virtualInvokers.tailMap(hash, <span style="color:#cb4b16">true</span>).firstEntry(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 hash 大于 Invoker 在圆环上最大的位置,此时 entry = null,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 需要将 TreeMap 的头节点赋值给 entry</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (entry <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> entry <span style="color:#719e07">=</span> virtualInvokers.firstEntry(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> entry.getValue(); |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,选择的过程相对比较简单了。首先是对参数进行 md5 以及 hash 运算,得到一个 hash 值。然后再拿这个值到 TreeMap 中查找目标 Invoker 即可。</p> |
| <p>到此关于 ConsistentHashLoadBalance 就分析完了。在阅读 ConsistentHashLoadBalance 源码之前,大家一定要先补充背景知识,不然很难看懂代码逻辑。</p> |
| <h3 id="24-roundrobinloadbalance">2.4 RoundRobinLoadBalance</h3> |
| <p>本节,我们来看一下 Dubbo 中加权轮询负载均衡的实现 RoundRobinLoadBalance。在详细分析源码前,我们先来了解一下什么是加权轮询。这里从最简单的轮询开始讲起,所谓轮询是指将请求轮流分配给每台服务器。举个例子,我们有三台服务器 A、B、C。我们将第一个请求分配给服务器 A,第二个请求分配给服务器 B,第三个请求分配给服务器 C,第四个请求再次分配给服务器 A。这个过程就叫做轮询。轮询是一种无状态负载均衡算法,实现简单,适用于每台服务器性能相近的场景下。但现实情况下,我们并不能保证每台服务器性能均相近。如果我们将等量的请求分配给性能较差的服务器,这显然是不合理的。因此,这个时候我们需要对轮询过程进行加权,以调控每台服务器的负载。经过加权后,每台服务器能够得到的请求数比例,接近或等于他们的权重比。比如服务器 A、B、C 权重比为 5:2:1。那么在8次请求中,服务器 A 将收到其中的5次请求,服务器 B 会收到其中的2次请求,服务器 C 则收到其中的1次请求。</p> |
| <p>以上就是加权轮询的算法思想,搞懂了这个思想,接下来我们就可以分析源码了。我们先来看一下 2.6.4 版本的 RoundRobinLoadBalance。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">RoundRobinLoadBalance</span> <span style="color:#268bd2">extends</span> AbstractLoadBalance { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> String NAME <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;roundrobin&#34;</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ConcurrentMap<span style="color:#719e07">&lt;</span>String, AtomicPositiveInteger<span style="color:#719e07">&gt;</span> sequences <span style="color:#719e07">=</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;</span>String, AtomicPositiveInteger<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">doSelect</span>(List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, URL url, Invocation invocation) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// key = 全限定类名 + &#34;.&#34; + 方法名,比如 com.xxx.DemoService.sayHello</span> |
| </span></span><span style="display:flex;"><span> String key <span style="color:#719e07">=</span> invokers.get(0).getUrl().getServiceKey() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.&#34;</span> <span style="color:#719e07">+</span> invocation.getMethodName(); |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> length <span style="color:#719e07">=</span> invokers.size(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 最大权重</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> maxWeight <span style="color:#719e07">=</span> 0; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 最小权重</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> minWeight <span style="color:#719e07">=</span> Integer.MAX_VALUE; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> LinkedHashMap<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>, IntegerWrapper<span style="color:#719e07">&gt;</span> invokerToWeightMap <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> LinkedHashMap<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>, IntegerWrapper<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 权重总和</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> weightSum <span style="color:#719e07">=</span> 0; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 下面这个循环主要用于查找最大和最小权重,计算权重总和等</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> weight <span style="color:#719e07">=</span> getWeight(invokers.get(i), invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取最大和最小权重</span> |
| </span></span><span style="display:flex;"><span> maxWeight <span style="color:#719e07">=</span> Math.max(maxWeight, weight); |
| </span></span><span style="display:flex;"><span> minWeight <span style="color:#719e07">=</span> Math.min(minWeight, weight); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (weight <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 weight 封装到 IntegerWrapper 中</span> |
| </span></span><span style="display:flex;"><span> invokerToWeightMap.put(invokers.get(i), <span style="color:#719e07">new</span> IntegerWrapper(weight)); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 累加权重</span> |
| </span></span><span style="display:flex;"><span> weightSum <span style="color:#719e07">+=</span> weight; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 查找 key 对应的对应 AtomicPositiveInteger 实例,为空则创建。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 这里可以把 AtomicPositiveInteger 看成一个黑盒,大家只要知道</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// AtomicPositiveInteger 用于记录服务的调用编号即可。至于细节,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 大家如果感兴趣,可以自行分析</span> |
| </span></span><span style="display:flex;"><span> AtomicPositiveInteger sequence <span style="color:#719e07">=</span> sequences.get(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (sequence <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> sequences.putIfAbsent(key, <span style="color:#719e07">new</span> AtomicPositiveInteger()); |
| </span></span><span style="display:flex;"><span> sequence <span style="color:#719e07">=</span> sequences.get(key); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取当前的调用编号</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> currentSequence <span style="color:#719e07">=</span> sequence.getAndIncrement(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果最小权重小于最大权重,表明服务提供者之间的权重是不相等的</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (maxWeight <span style="color:#719e07">&gt;</span> 0 <span style="color:#719e07">&amp;&amp;</span> minWeight <span style="color:#719e07">&lt;</span> maxWeight) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 使用调用编号对权重总和进行取余操作</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> mod <span style="color:#719e07">=</span> currentSequence <span style="color:#719e07">%</span> weightSum; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 进行 maxWeight 次遍历</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> maxWeight; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历 invokerToWeightMap</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Map.Entry<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span>, IntegerWrapper<span style="color:#719e07">&gt;</span> each : invokerToWeightMap.entrySet()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> k <span style="color:#719e07">=</span> each.getKey(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取权重包装类 IntegerWrapper</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> IntegerWrapper v <span style="color:#719e07">=</span> each.getValue(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 如果 mod = 0,且权重大于0,此时返回相应的 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (mod <span style="color:#719e07">==</span> 0 <span style="color:#719e07">&amp;&amp;</span> v.getValue() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> k; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// mod != 0,且权重大于0,此时对权重和 mod 分别进行自减操作</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (v.getValue() <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> v.decrement(); |
| </span></span><span style="display:flex;"><span> mod<span style="color:#719e07">--</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 服务提供者之间的权重相等,此时通过轮询选择 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers.get(currentSequence <span style="color:#719e07">%</span> length); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// IntegerWrapper 是一个 int 包装类,主要包含了一个自减方法。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">IntegerWrapper</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">int</span> value; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">decrement</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.value<span style="color:#719e07">--</span>; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 省略部分代码</span> |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>如上,RoundRobinLoadBalance 的每行代码都不是很难理解,但是将它们组合在一起之后,就不是很好理解了。所以下面我们举例进行说明,假设我们有三台服务器 servers = [A, B, C],对应的权重为 weights = [2, 5, 1]。接下来对上面的逻辑进行简单的模拟。</p> |
| <p>mod = 0:满足条件,此时直接返回服务器 A</p> |
| <p>mod = 1:需要进行一次递减操作才能满足条件,此时返回服务器 B</p> |
| <p>mod = 2:需要进行两次递减操作才能满足条件,此时返回服务器 C</p> |
| <p>mod = 3:需要进行三次递减操作才能满足条件,经过递减后,服务器权重为 [1, 4, 0],此时返回服务器 A</p> |
| <p>mod = 4:需要进行四次递减操作才能满足条件,经过递减后,服务器权重为 [0, 4, 0],此时返回服务器 B</p> |
| <p>mod = 5:需要进行五次递减操作才能满足条件,经过递减后,服务器权重为 [0, 3, 0],此时返回服务器 B</p> |
| <p>mod = 6:需要进行六次递减操作才能满足条件,经过递减后,服务器权重为 [0, 2, 0],此时返回服务器 B</p> |
| <p>mod = 7:需要进行七次递减操作才能满足条件,经过递减后,服务器权重为 [0, 1, 0],此时返回服务器 B</p> |
| <p>经过8次调用后,我们得到的负载均衡结果为 [A, B, C, A, B, B, B, B],次数比 A:B:C = 2:5:1,等于权重比。当 sequence = 8 时,mod = 0,此时重头再来。从上面的模拟过程可以看出,当 mod &gt;= 3 后,服务器 C 就不会被选中了,因为它的权重被减为0了。当 mod &gt;= 4 后,服务器 A 的权重被减为0,此后 A 就不会再被选中。</p> |
| <p>以上是 2.6.4 版本的 RoundRobinLoadBalance 分析过程,2.6.4 版本的 RoundRobinLoadBalance 在某些情况下存在着比较严重的性能问题,该问题最初是在 <a href="https://github.com/apache/dubbo/issues/2578">issue #2578</a> 中被反馈出来。问题出在了 Invoker 的返回时机上,RoundRobinLoadBalance 需要在<code>mod == 0 &amp;&amp; v.getValue() &gt; 0</code> 条件成立的情况下才会被返回相应的 Invoker。假如 mod 很大,比如 10000,50000,甚至更大时,doSelect 方法需要进行很多次计算才能将 mod 减为0。由此可知,doSelect 的效率与 mod 有关,时间复杂度为 O(mod)。mod 又受最大权重 maxWeight 的影响,因此当某个服务提供者配置了非常大的权重,此时 RoundRobinLoadBalance 会产生比较严重的性能问题。这个问题被反馈后,社区很快做了回应。并对 RoundRobinLoadBalance 的代码进行了重构,将时间复杂度优化至了常量级别。这个优化可以说很好了,下面我们来学习一下优化后的代码。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">RoundRobinLoadBalance</span> <span style="color:#268bd2">extends</span> AbstractLoadBalance { |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> String NAME <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;roundrobin&#34;</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ConcurrentMap<span style="color:#719e07">&lt;</span>String, AtomicPositiveInteger<span style="color:#719e07">&gt;</span> sequences <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;</span>String, AtomicPositiveInteger<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">final</span> ConcurrentMap<span style="color:#719e07">&lt;</span>String, AtomicPositiveInteger<span style="color:#719e07">&gt;</span> indexSeqs <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;</span>String, AtomicPositiveInteger<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">doSelect</span>(List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, URL url, Invocation invocation) { |
| </span></span><span style="display:flex;"><span> String key <span style="color:#719e07">=</span> invokers.get(0).getUrl().getServiceKey() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.&#34;</span> <span style="color:#719e07">+</span> invocation.getMethodName(); |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> length <span style="color:#719e07">=</span> invokers.size(); |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> maxWeight <span style="color:#719e07">=</span> 0; |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> minWeight <span style="color:#719e07">=</span> Integer.MAX_VALUE; |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">final</span> List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokerToWeightList <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ArrayList<span style="color:#719e07">&lt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 查找最大和最小权重</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (<span style="color:#dc322f">int</span> i <span style="color:#719e07">=</span> 0; i <span style="color:#719e07">&lt;</span> length; i<span style="color:#719e07">++</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> weight <span style="color:#719e07">=</span> getWeight(invokers.get(i), invocation); |
| </span></span><span style="display:flex;"><span> maxWeight <span style="color:#719e07">=</span> Math.max(maxWeight, weight); |
| </span></span><span style="display:flex;"><span> minWeight <span style="color:#719e07">=</span> Math.min(minWeight, weight); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (weight <span style="color:#719e07">&gt;</span> 0) { |
| </span></span><span style="display:flex;"><span> invokerToWeightList.add(invokers.get(i)); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取当前服务对应的调用序列对象 AtomicPositiveInteger</span> |
| </span></span><span style="display:flex;"><span> AtomicPositiveInteger sequence <span style="color:#719e07">=</span> sequences.get(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (sequence <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 AtomicPositiveInteger,默认值为0</span> |
| </span></span><span style="display:flex;"><span> sequences.putIfAbsent(key, <span style="color:#719e07">new</span> AtomicPositiveInteger()); |
| </span></span><span style="display:flex;"><span> sequence <span style="color:#719e07">=</span> sequences.get(key); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取下标序列对象 AtomicPositiveInteger</span> |
| </span></span><span style="display:flex;"><span> AtomicPositiveInteger indexSeq <span style="color:#719e07">=</span> indexSeqs.get(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (indexSeq <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 创建 AtomicPositiveInteger,默认值为 -1</span> |
| </span></span><span style="display:flex;"><span> indexSeqs.putIfAbsent(key, <span style="color:#719e07">new</span> AtomicPositiveInteger(<span style="color:#719e07">-</span>1)); |
| </span></span><span style="display:flex;"><span> indexSeq <span style="color:#719e07">=</span> indexSeqs.get(key); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (maxWeight <span style="color:#719e07">&gt;</span> 0 <span style="color:#719e07">&amp;&amp;</span> minWeight <span style="color:#719e07">&lt;</span> maxWeight) { |
| </span></span><span style="display:flex;"><span> length <span style="color:#719e07">=</span> invokerToWeightList.size(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">while</span> (<span style="color:#cb4b16">true</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> index <span style="color:#719e07">=</span> indexSeq.incrementAndGet() <span style="color:#719e07">%</span> length; |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> currentWeight <span style="color:#719e07">=</span> sequence.get() <span style="color:#719e07">%</span> maxWeight; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 每循环一轮(index = 0),重新计算 currentWeight</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (index <span style="color:#719e07">==</span> 0) { |
| </span></span><span style="display:flex;"><span> currentWeight <span style="color:#719e07">=</span> sequence.incrementAndGet() <span style="color:#719e07">%</span> maxWeight; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测 Invoker 的权重是否大于 currentWeight,大于则返回</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (getWeight(invokerToWeightList.get(index), invocation) <span style="color:#719e07">&gt;</span> currentWeight) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokerToWeightList.get(index); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 所有 Invoker 权重相等,此时进行普通的轮询即可</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers.get(sequence.incrementAndGet() <span style="color:#719e07">%</span> length); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>上面代码的逻辑是这样的,每进行一轮循环,重新计算 currentWeight。如果当前 Invoker 权重大于 currentWeight,则返回该 Invoker。下面举例说明,假设服务器 [A, B, C] 对应权重 [5, 2, 1]。</p> |
| <p>第一轮循环,currentWeight = 1,可返回 A 和 B</p> |
| <p>第二轮循环,currentWeight = 2,返回 A</p> |
| <p>第三轮循环,currentWeight = 3,返回 A</p> |
| <p>第四轮循环,currentWeight = 4,返回 A</p> |
| <p>第五轮循环,currentWeight = 0,返回 A, B, C</p> |
| <p>如上,这里的一轮循环是指 index 再次变为0所经历过的循环,这里可以把 index = 0 看做是一轮循环的开始。每一轮循环的次数与 Invoker 的数量有关,Invoker 数量通常不会太多,所以我们可以认为上面代码的时间复杂度为常数级。</p> |
| <p>重构后的 RoundRobinLoadBalance 看起来已经很不错了,但是在代码更新不久后,很快又被重构了。这次重构原因是新的 RoundRobinLoadBalance 在某些情况下选出的服务器序列不够均匀。比如,服务器 [A, B, C] 对应权重 [5, 1, 1]。进行7次负载均衡后,选择出来的序列为 [A, A, A, A, A, B, C]。前5个请求全部都落在了服务器 A上,这将会使服务器 A 短时间内接收大量的请求,压力陡增。而 B 和 C 此时无请求,处于空闲状态。而我们期望的结果是这样的 [A, A, B, A, C, A, A],不同服务器可以穿插获取请求。为了增加负载均衡结果的平滑性,社区再次对 RoundRobinLoadBalance 的实现进行了重构,这次重构参考自 Nginx 的平滑加权轮询负载均衡。每个服务器对应两个权重,分别为 weight 和 currentWeight。其中 weight 是固定的,currentWeight 会动态调整,初始值为0。当有新的请求进来时,遍历服务器列表,让它的 currentWeight 加上自身权重。遍历完成后,找到最大的 currentWeight,并将其减去权重总和,然后返回相应的服务器即可。</p> |
| <p>上面描述不是很好理解,下面还是举例进行说明。这里仍然使用服务器 [A, B, C] 对应权重 [5, 1, 1] 的例子说明,现在有7个请求依次进入负载均衡逻辑,选择过程如下:</p> |
| <table> |
| <thead> |
| <tr> |
| <th>请求编号</th> |
| <th>currentWeight 数组</th> |
| <th>选择结果</th> |
| <th>减去权重总和后的 currentWeight 数组</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>1</td> |
| <td>[5, 1, 1]</td> |
| <td>A</td> |
| <td>[-2, 1, 1]</td> |
| </tr> |
| <tr> |
| <td>2</td> |
| <td>[3, 2, 2]</td> |
| <td>A</td> |
| <td>[-4, 2, 2]</td> |
| </tr> |
| <tr> |
| <td>3</td> |
| <td>[1, 3, 3]</td> |
| <td>B</td> |
| <td>[1, -4, 3]</td> |
| </tr> |
| <tr> |
| <td>4</td> |
| <td>[6, -3, 4]</td> |
| <td>A</td> |
| <td>[-1, -3, 4]</td> |
| </tr> |
| <tr> |
| <td>5</td> |
| <td>[4, -2, 5]</td> |
| <td>C</td> |
| <td>[4, -2, -2]</td> |
| </tr> |
| <tr> |
| <td>6</td> |
| <td>[9, -1, -1]</td> |
| <td>A</td> |
| <td>[2, -1, -1]</td> |
| </tr> |
| <tr> |
| <td>7</td> |
| <td>[7, 0, 0]</td> |
| <td>A</td> |
| <td>[0, 0, 0]</td> |
| </tr> |
| </tbody> |
| </table> |
| <p>如上,经过平滑性处理后,得到的服务器序列为 [A, A, B, A, C, A, A],相比之前的序列 [A, A, A, A, A, B, C],分布性要好一些。初始情况下 currentWeight = [0, 0, 0],第7个请求处理完后,currentWeight 再次变为 [0, 0, 0]。</p> |
| <p>以上就是平滑加权轮询的计算过程,接下来,我们来看看 Dubbo-2.6.5 是如何实现上面的计算过程的。</p> |
| <div class="highlight"><pre tabindex="0" style="color:#93a1a1;background-color:#002b36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">RoundRobinLoadBalance</span> <span style="color:#268bd2">extends</span> AbstractLoadBalance { |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">final</span> String NAME <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;roundrobin&#34;</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#268bd2">static</span> <span style="color:#dc322f">int</span> RECYCLE_PERIOD <span style="color:#719e07">=</span> 60000; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#268bd2">static</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">WeightedRoundRobin</span> { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 服务提供者权重</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">int</span> weight; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 当前权重</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> AtomicLong current <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> AtomicLong(0); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 最后一次更新时间</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> <span style="color:#dc322f">long</span> lastUpdate; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">setWeight</span>(<span style="color:#dc322f">int</span> weight) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span>.weight <span style="color:#719e07">=</span> weight; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 初始情况下,current = 0</span> |
| </span></span><span style="display:flex;"><span> current.set(0); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">long</span> <span style="color:#268bd2">increaseCurrent</span>() { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// current = current + weight;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> current.addAndGet(weight); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">sel</span>(<span style="color:#dc322f">int</span> total) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// current = current - total;</span> |
| </span></span><span style="display:flex;"><span> current.addAndGet(<span style="color:#719e07">-</span>1 <span style="color:#719e07">*</span> total); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 嵌套 Map 结构,存储的数据结构示例如下:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// {</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &#34;UserService.query&#34;: {</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &#34;url1&#34;: WeightedRoundRobin@123, </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &#34;url2&#34;: WeightedRoundRobin@456, </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// },</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &#34;UserService.update&#34;: {</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &#34;url1&#34;: WeightedRoundRobin@123, </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// &#34;url2&#34;: WeightedRoundRobin@456,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// }</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// }</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 最外层为服务类名 + 方法名,第二层为 url 到 WeightedRoundRobin 的映射关系。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 这里我们可以将 url 看成是服务提供者的 id</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> ConcurrentMap<span style="color:#719e07">&lt;</span>String, ConcurrentMap<span style="color:#719e07">&lt;</span>String, WeightedRoundRobin<span style="color:#719e07">&gt;&gt;</span> methodWeightMap <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;</span>String, ConcurrentMap<span style="color:#719e07">&lt;</span>String, WeightedRoundRobin<span style="color:#719e07">&gt;&gt;</span>(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 原子更新锁</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> AtomicBoolean updateLock <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> AtomicBoolean(); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Override</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> <span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">doSelect</span>(List<span style="color:#719e07">&lt;</span>Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;&gt;</span> invokers, URL url, Invocation invocation) { |
| </span></span><span style="display:flex;"><span> String key <span style="color:#719e07">=</span> invokers.get(0).getUrl().getServiceKey() <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;.&#34;</span> <span style="color:#719e07">+</span> invocation.getMethodName(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取 url 到 WeightedRoundRobin 映射表,如果为空,则创建一个新的</span> |
| </span></span><span style="display:flex;"><span> ConcurrentMap<span style="color:#719e07">&lt;</span>String, WeightedRoundRobin<span style="color:#719e07">&gt;</span> map <span style="color:#719e07">=</span> methodWeightMap.get(key); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (map <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> methodWeightMap.putIfAbsent(key, <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;</span>String, WeightedRoundRobin<span style="color:#719e07">&gt;</span>()); |
| </span></span><span style="display:flex;"><span> map <span style="color:#719e07">=</span> methodWeightMap.get(key); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> totalWeight <span style="color:#719e07">=</span> 0; |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">long</span> maxCurrent <span style="color:#719e07">=</span> Long.MIN_VALUE; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 获取当前时间</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">long</span> now <span style="color:#719e07">=</span> System.currentTimeMillis(); |
| </span></span><span style="display:flex;"><span> Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> selectedInvoker <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> WeightedRoundRobin selectedWRR <span style="color:#719e07">=</span> <span style="color:#cb4b16">null</span>; |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 下面这个循环主要做了这样几件事情:</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 1. 遍历 Invoker 列表,检测当前 Invoker 是否有</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 相应的 WeightedRoundRobin,没有则创建</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 2. 检测 Invoker 权重是否发生了变化,若变化了,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 则更新 WeightedRoundRobin 的 weight 字段</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 3. 让 current 字段加上自身权重,等价于 current += weight</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 4. 设置 lastUpdate 字段,即 lastUpdate = now</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 5. 寻找具有最大 current 的 Invoker,以及 Invoker 对应的 WeightedRoundRobin,</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 暂存起来,留作后用</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 6. 计算权重总和</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> (Invoker<span style="color:#719e07">&lt;</span>T<span style="color:#719e07">&gt;</span> invoker : invokers) { |
| </span></span><span style="display:flex;"><span> String identifyString <span style="color:#719e07">=</span> invoker.getUrl().toIdentityString(); |
| </span></span><span style="display:flex;"><span> WeightedRoundRobin weightedRoundRobin <span style="color:#719e07">=</span> map.get(identifyString); |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">int</span> weight <span style="color:#719e07">=</span> getWeight(invoker, invocation); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (weight <span style="color:#719e07">&lt;</span> 0) { |
| </span></span><span style="display:flex;"><span> weight <span style="color:#719e07">=</span> 0; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 检测当前 Invoker 是否有对应的 WeightedRoundRobin,没有则创建</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (weightedRoundRobin <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> weightedRoundRobin <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> WeightedRoundRobin(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 Invoker 权重</span> |
| </span></span><span style="display:flex;"><span> weightedRoundRobin.setWeight(weight); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 存储 url 唯一标识 identifyString 到 weightedRoundRobin 的映射关系</span> |
| </span></span><span style="display:flex;"><span> map.putIfAbsent(identifyString, weightedRoundRobin); |
| </span></span><span style="display:flex;"><span> weightedRoundRobin <span style="color:#719e07">=</span> map.get(identifyString); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// Invoker 权重不等于 WeightedRoundRobin 中保存的权重,说明权重变化了,此时进行更新</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (weight <span style="color:#719e07">!=</span> weightedRoundRobin.getWeight()) { |
| </span></span><span style="display:flex;"><span> weightedRoundRobin.setWeight(weight); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 让 current 加上自身权重,等价于 current += weight</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">long</span> cur <span style="color:#719e07">=</span> weightedRoundRobin.increaseCurrent(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 设置 lastUpdate,表示近期更新过</span> |
| </span></span><span style="display:flex;"><span> weightedRoundRobin.setLastUpdate(now); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 找出最大的 current </span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (cur <span style="color:#719e07">&gt;</span> maxCurrent) { |
| </span></span><span style="display:flex;"><span> maxCurrent <span style="color:#719e07">=</span> cur; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将具有最大 current 权重的 Invoker 赋值给 selectedInvoker</span> |
| </span></span><span style="display:flex;"><span> selectedInvoker <span style="color:#719e07">=</span> invoker; |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 将 Invoker 对应的 weightedRoundRobin 赋值给 selectedWRR,留作后用</span> |
| </span></span><span style="display:flex;"><span> selectedWRR <span style="color:#719e07">=</span> weightedRoundRobin; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 计算权重总和</span> |
| </span></span><span style="display:flex;"><span> totalWeight <span style="color:#719e07">+=</span> weight; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 对 &lt;identifyString, WeightedRoundRobin&gt; 进行检查,过滤掉长时间未被更新的节点。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 该节点可能挂了,invokers 中不包含该节点,所以该节点的 lastUpdate 长时间无法被更新。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 若未更新时长超过阈值后,就会被移除掉,默认阈值为60秒。</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (<span style="color:#719e07">!</span>updateLock.get() <span style="color:#719e07">&amp;&amp;</span> invokers.size() <span style="color:#719e07">!=</span> map.size()) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (updateLock.compareAndSet(<span style="color:#cb4b16">false</span>, <span style="color:#cb4b16">true</span>)) { |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">try</span> { |
| </span></span><span style="display:flex;"><span> ConcurrentMap<span style="color:#719e07">&lt;</span>String, WeightedRoundRobin<span style="color:#719e07">&gt;</span> newMap <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ConcurrentHashMap<span style="color:#719e07">&lt;</span>String, WeightedRoundRobin<span style="color:#719e07">&gt;</span>(); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 拷贝</span> |
| </span></span><span style="display:flex;"><span> newMap.putAll(map); |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 遍历修改,即移除过期记录</span> |
| </span></span><span style="display:flex;"><span> Iterator<span style="color:#719e07">&lt;</span>Entry<span style="color:#719e07">&lt;</span>String, WeightedRoundRobin<span style="color:#719e07">&gt;&gt;</span> it <span style="color:#719e07">=</span> newMap.entrySet().iterator(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">while</span> (it.hasNext()) { |
| </span></span><span style="display:flex;"><span> Entry<span style="color:#719e07">&lt;</span>String, WeightedRoundRobin<span style="color:#719e07">&gt;</span> item <span style="color:#719e07">=</span> it.next(); |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (now <span style="color:#719e07">-</span> item.getValue().getLastUpdate() <span style="color:#719e07">&gt;</span> RECYCLE_PERIOD) { |
| </span></span><span style="display:flex;"><span> it.remove(); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 更新引用</span> |
| </span></span><span style="display:flex;"><span> methodWeightMap.put(key, newMap); |
| </span></span><span style="display:flex;"><span> } <span style="color:#719e07">finally</span> { |
| </span></span><span style="display:flex;"><span> updateLock.set(<span style="color:#cb4b16">false</span>); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> (selectedInvoker <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span>) { |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 让 current 减去权重总和,等价于 current -= totalWeight</span> |
| </span></span><span style="display:flex;"><span> selectedWRR.sel(totalWeight); |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 返回具有最大 current 的 Invoker</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> selectedInvoker; |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// should not happen here</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> invokers.get(0); |
| </span></span><span style="display:flex;"><span> } |
| </span></span><span style="display:flex;"><span>} |
| </span></span></code></pre></div><p>以上就是 Dubbo-2.6.5 版本的 RoundRobinLoadBalance,大家如果能够理解平滑加权轮询算法的计算过程,再配合代码中注释,理解上面的代码应该不难。</p> |
| <h2 id="3总结">3.总结</h2> |
| <p>本篇文章对 Dubbo 中的几种负载均衡实现进行了详细的分析,内容比较多,大家慢慢消化。理解负载均衡代码逻辑的关键之处在于对背景知识的理解,因此大家在阅读源码前,务必先了解每种负载均衡对应的背景知识。</p></description></item></channel></rss> |