| <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Apache Dubbo – 用户文档</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/</link><description>Recent content in 用户文档 on Apache Dubbo</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><atom:link href="https://dubbo.apache.org/zh-cn/docsv2.7/user/index.xml" rel="self" type="application/rss+xml"/><item><title>Docsv2.7: 入门介绍</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/preface/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/preface/</guid><description/></item><item><title>Docsv2.7: 快速开始</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/quick-start/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/quick-start/</guid><description> |
| <p>Dubbo 采用全 Spring 配置方式,透明化接入应用,对应用没有任何 API 侵入,只需用 Spring 加载 Dubbo 的配置即可,Dubbo 基于 <a href="https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/xsd-configuration.html">Spring 的 Schema 扩展</a> 进行加载。</p> |
| <p>如果不想使用 Spring 配置,可以通过 <a href="../configuration/api">API 的方式</a> 进行调用。</p> |
| <h2 id="服务提供者">服务提供者</h2> |
| <p>完整安装步骤,请参见:<a href="../../admin/install/provider-demo">示例提供者安装</a></p> |
| <h3 id="定义服务接口">定义服务接口</h3> |
| <p>DemoService.java <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</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.demo<span style="color:#719e07">;</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">interface</span> <span style="color:#268bd2">DemoService</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> String <span style="color:#268bd2">sayHello</span><span style="color:#719e07">(</span>String name<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><h3 id="在服务提供方实现接口">在服务提供方实现接口</h3> |
| <p>DemoServiceImpl.java <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>:</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:#719e07">package</span> org.apache.dubbo.demo.provider<span style="color:#719e07">;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">import</span> org.apache.dubbo.demo.DemoService<span style="color:#719e07">;</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">DemoServiceImpl</span> <span style="color:#268bd2">implements</span> DemoService <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> String <span style="color:#268bd2">sayHello</span><span style="color:#719e07">(</span>String name<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> <span style="color:#2aa198">&#34;Hello &#34;</span> <span style="color:#719e07">+</span> name<span style="color:#719e07">;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><h3 id="用-spring-配置声明暴露服务">用 Spring 配置声明暴露服务</h3> |
| <p>provider.xml:</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:#719e07">&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34;?&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;beans</span> xmlns=<span style="color:#2aa198">&#34;http://www.springframework.org/schema/beans&#34;</span> |
| </span></span><span style="display:flex;"><span> xmlns:xsi=<span style="color:#2aa198">&#34;http://www.w3.org/2001/XMLSchema-instance&#34;</span> |
| </span></span><span style="display:flex;"><span> xmlns:dubbo=<span style="color:#2aa198">&#34;http://dubbo.apache.org/schema/dubbo&#34;</span> |
| </span></span><span style="display:flex;"><span> xsi:schemaLocation=<span style="color:#2aa198">&#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd&#34;</span><span style="color:#268bd2">&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 提供方应用信息,用于计算依赖关系 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:application</span> name=<span style="color:#2aa198">&#34;hello-world-app&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 使用multicast广播注册中心暴露服务地址 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:registry</span> address=<span style="color:#2aa198">&#34;multicast://224.5.6.7:1234&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 用dubbo协议在20880端口暴露服务 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:protocol</span> name=<span style="color:#2aa198">&#34;dubbo&#34;</span> port=<span style="color:#2aa198">&#34;20880&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 声明需要暴露的服务接口 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:service</span> interface=<span style="color:#2aa198">&#34;org.apache.dubbo.demo.DemoService&#34;</span> ref=<span style="color:#2aa198">&#34;demoService&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 和本地bean一样实现服务 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;bean</span> id=<span style="color:#2aa198">&#34;demoService&#34;</span> class=<span style="color:#2aa198">&#34;org.apache.dubbo.demo.provider.DemoServiceImpl&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/beans&gt;</span> |
| </span></span></code></pre></div><h3 id="加载-spring-配置">加载 Spring 配置</h3> |
| <p>Provider.java:</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">import</span> org.springframework.context.support.ClassPathXmlApplicationContext<span style="color:#719e07">;</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">Provider</span> <span style="color:#719e07">{</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">main</span><span style="color:#719e07">(</span>String<span style="color:#719e07">[]</span> args<span style="color:#719e07">)</span> <span style="color:#268bd2">throws</span> Exception <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> ClassPathXmlApplicationContext context <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ClassPathXmlApplicationContext<span style="color:#719e07">(</span><span style="color:#719e07">new</span> String<span style="color:#719e07">[]{</span><span style="color:#2aa198">&#34;META-INF/spring/dubbo-demo-provider.xml&#34;</span><span style="color:#719e07">});</span> |
| </span></span><span style="display:flex;"><span> context<span style="color:#719e07">.</span>start<span style="color:#719e07">();</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>in<span style="color:#719e07">.</span>read<span style="color:#719e07">();</span> <span style="color:#586e75">// 按任意键退出 |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"></span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><h2 id="服务消费者">服务消费者</h2> |
| <p>完整安装步骤,请参见:<a href="../../admin/install/consumer-demo">示例消费者安装</a></p> |
| <h3 id="通过-spring-配置引用远程服务">通过 Spring 配置引用远程服务</h3> |
| <p>consumer.xml:</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:#719e07">&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34;?&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;beans</span> xmlns=<span style="color:#2aa198">&#34;http://www.springframework.org/schema/beans&#34;</span> |
| </span></span><span style="display:flex;"><span> xmlns:xsi=<span style="color:#2aa198">&#34;http://www.w3.org/2001/XMLSchema-instance&#34;</span> |
| </span></span><span style="display:flex;"><span> xmlns:dubbo=<span style="color:#2aa198">&#34;http://dubbo.apache.org/schema/dubbo&#34;</span> |
| </span></span><span style="display:flex;"><span> xsi:schemaLocation=<span style="color:#2aa198">&#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd&#34;</span><span style="color:#268bd2">&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:application</span> name=<span style="color:#2aa198">&#34;consumer-of-helloworld-app&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 使用multicast广播注册中心暴露发现服务地址 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:registry</span> address=<span style="color:#2aa198">&#34;multicast://224.5.6.7:1234&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 生成远程服务代理,可以和本地bean一样使用demoService --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:reference</span> id=<span style="color:#2aa198">&#34;demoService&#34;</span> interface=<span style="color:#2aa198">&#34;org.apache.dubbo.demo.DemoService&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/beans&gt;</span> |
| </span></span></code></pre></div><h3 id="加载spring配置并调用远程服务">加载Spring配置,并调用远程服务</h3> |
| <p>Consumer.java <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>:</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">import</span> org.springframework.context.support.ClassPathXmlApplicationContext<span style="color:#719e07">;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">import</span> org.apache.dubbo.demo.DemoService<span style="color:#719e07">;</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">Consumer</span> <span style="color:#719e07">{</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">main</span><span style="color:#719e07">(</span>String<span style="color:#719e07">[]</span> args<span style="color:#719e07">)</span> <span style="color:#268bd2">throws</span> Exception <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> ClassPathXmlApplicationContext context <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ClassPathXmlApplicationContext<span style="color:#719e07">(</span><span style="color:#719e07">new</span> String<span style="color:#719e07">[]</span> <span style="color:#719e07">{</span><span style="color:#2aa198">&#34;META-INF/spring/dubbo-demo-consumer.xml&#34;</span><span style="color:#719e07">});</span> |
| </span></span><span style="display:flex;"><span> context<span style="color:#719e07">.</span>start<span style="color:#719e07">();</span> |
| </span></span><span style="display:flex;"><span> DemoService demoService <span style="color:#719e07">=</span> <span style="color:#719e07">(</span>DemoService<span style="color:#719e07">)</span>context<span style="color:#719e07">.</span>getBean<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;demoService&#34;</span><span style="color:#719e07">);</span> <span style="color:#586e75">// 获取远程服务代理 |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"></span> String hello <span style="color:#719e07">=</span> demoService<span style="color:#719e07">.</span>sayHello<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;world&#34;</span><span style="color:#719e07">);</span> <span style="color:#586e75">// 执行远程方法 |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"></span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span> hello <span style="color:#719e07">);</span> <span style="color:#586e75">// 显示调用结果 |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"></span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><div class="footnotes" role="doc-endnotes"> |
| <hr> |
| <ol> |
| <li id="fn:1"> |
| <p>该接口需单独打包,在服务提供方和消费方共享&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p> |
| </li> |
| <li id="fn:2"> |
| <p>对服务消费方隐藏实现&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p> |
| </li> |
| <li id="fn:3"> |
| <p>也可以使用 IoC 注入&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p> |
| </li> |
| </ol> |
| </div></description></item><item><title>Docsv2.7: 基本依赖</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/dependencies/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/dependencies/</guid><description> |
| <h3 id="必须依赖">必须依赖</h3> |
| <p>JDK 1.6+ <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p> |
| <h3 id="缺省依赖">缺省依赖</h3> |
| <p>通过 <code>mvn dependency:tree &gt; dep.log</code> 命令分析,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>[INFO] +- com.alibaba:dubbo:jar:2.5.9-SNAPSHOT:compile |
| </span></span><span style="display:flex;"><span>[INFO] | +- org.springframework:spring-context:jar:4.3.10.RELEASE:compile |
| </span></span><span style="display:flex;"><span>[INFO] | +- org.javassist:javassist:jar:3.21.0-GA:compile |
| </span></span><span style="display:flex;"><span>[INFO] | \- org.jboss.netty:netty:jar:3.2.5.Final:compile |
| </span></span></code></pre></div><p>这里所有依赖都是按照 Dubbo 缺省配置选的,这些缺省值是基于稳定性和性能考虑的。</p> |
| <ul> |
| <li>javassist.jar <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>: 如果 <code>&lt;dubbo:provider proxy=&quot;jdk&quot; /&gt;</code> 或 <code>&lt;dubbo:consumer proxy=&quot;jdk&quot; /&gt;</code>,以及 <code>&lt;dubbo:application compiler=&quot;jdk&quot; /&gt;</code>,则不需要。</li> |
| <li>spring-context.jar <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>: 如果用 <code>ServiceConfig</code> 和 <code>ReferenceConfig</code> 的 API 调用,则不需要。</li> |
| <li>netty.jar <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>: 如果 <code>&lt;dubbo:protocol server=&quot;mina&quot;/&gt;</code> 或 <code>&lt;dubbo:protocol server=&quot;grizzly&quot;/&gt;</code>,则换成 mina.jar 或 grizzly.jar。如果 <code>&lt;protocol name=&quot;rmi&quot;/&gt;</code>,则不需要。</li> |
| </ul> |
| <h3 id="可选依赖">可选依赖</h3> |
| <p>以下依赖,在主动配置使用相应实现策略时用到,需自行加入依赖。</p> |
| <ul> |
| <li>netty-all 4.0.35.Final</li> |
| <li>mina: 1.1.7</li> |
| <li>grizzly: 2.1.4</li> |
| <li>httpclient: 4.5.3</li> |
| <li>hessian_lite: 3.2.1-fixed</li> |
| <li>fastjson: 1.2.31</li> |
| <li>zookeeper: 3.4.9</li> |
| <li>jedis: 2.9.0</li> |
| <li>xmemcached: 1.3.6</li> |
| <li>hessian: 4.0.38</li> |
| <li>jetty: 6.1.26</li> |
| <li>hibernate-validator: 5.4.1.Final</li> |
| <li>zkclient: 0.2</li> |
| <li>curator: 2.12.0</li> |
| <li>cxf: 3.0.14</li> |
| <li>thrift: 0.8.0</li> |
| <li>servlet: 3.0 <sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></li> |
| <li>validation-api: 1.1.0.GA <sup id="fnref1:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></li> |
| <li>jcache: 1.0.0 <sup id="fnref2:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></li> |
| <li>javax.el: 3.0.1-b08 <sup id="fnref3:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></li> |
| <li>kryo: 4.0.1</li> |
| <li>kryo-serializers: 0.42</li> |
| <li>fst: 2.48-jdk-6</li> |
| <li>resteasy: 3.0.19.Final</li> |
| <li>tomcat-embed-core: 8.0.11</li> |
| <li>slf4j: 1.7.25</li> |
| <li>log4j: 1.2.16</li> |
| </ul> |
| <div class="footnotes" role="doc-endnotes"> |
| <hr> |
| <ol> |
| <li id="fn:1"> |
| <p>理论上 Dubbo 可以只依赖 JDK,不依赖于任何三方库运行,只需配置使用 JDK 相关实现策略&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p> |
| </li> |
| <li id="fn:2"> |
| <p>字节码生成&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p> |
| </li> |
| <li id="fn:3"> |
| <p>配置解析&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p> |
| </li> |
| <li id="fn:4"> |
| <p>网络传输&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p> |
| </li> |
| <li id="fn:5"> |
| <p>JEE&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref2:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref3:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p> |
| </li> |
| </ol> |
| </div></description></item><item><title>Docsv2.7: 成熟度</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/maturity/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/maturity/</guid><description> |
| <h3 id="功能成熟度">功能成熟度</h3> |
| <table> |
| <thead> |
| <tr> |
| <th>Feature</th> |
| <th>Maturity</th> |
| <th>Strength</th> |
| <th>Problem</th> |
| <th>Advise</th> |
| <th>User</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>并发控制</td> |
| <td>Tested</td> |
| <td>并发控制</td> |
| <td></td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>连接控制</td> |
| <td>Tested</td> |
| <td>连接数控制</td> |
| <td></td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>直连提供者</td> |
| <td>Tested</td> |
| <td>点对点直连服务提供方,用于测试</td> |
| <td></td> |
| <td>测试环境使用</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>分组聚合</td> |
| <td>Tested</td> |
| <td>分组聚合返回值,用于菜单聚合等服务</td> |
| <td>特殊场景使用</td> |
| <td>可用于生产环境</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>参数验证</td> |
| <td>Tested</td> |
| <td>参数验证,JSR303验证框架集成</td> |
| <td>对性能有影响</td> |
| <td>试用</td> |
| <td>LaiWang</td> |
| </tr> |
| <tr> |
| <td>结果缓存</td> |
| <td>Tested</td> |
| <td>结果缓存,用于加速请求</td> |
| <td></td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>泛化引用</td> |
| <td>Stable</td> |
| <td>泛化调用,无需业务接口类进行远程调用,用于测试平台,开放网关桥接等</td> |
| <td></td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>泛化实现</td> |
| <td>Stable</td> |
| <td>泛化实现,无需业务接口类实现任意接口,用于Mock平台</td> |
| <td></td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>回声测试</td> |
| <td>Tested</td> |
| <td>回声测试</td> |
| <td></td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>隐式传参</td> |
| <td>Stable</td> |
| <td>附加参数</td> |
| <td></td> |
| <td>可用于生产环境</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>异步调用</td> |
| <td>Tested</td> |
| <td>不可靠异步调用</td> |
| <td></td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>本地调用</td> |
| <td>Tested</td> |
| <td>本地调用</td> |
| <td></td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>参数回调</td> |
| <td>Tested</td> |
| <td>参数回调</td> |
| <td>特殊场景使用</td> |
| <td>试用</td> |
| <td>Registry</td> |
| </tr> |
| <tr> |
| <td>事件通知</td> |
| <td>Tested</td> |
| <td>事件通知,在远程调用执行前后触发</td> |
| <td></td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>本地存根</td> |
| <td>Stable</td> |
| <td>在客户端执行部分逻辑</td> |
| <td></td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>本地伪装</td> |
| <td>Stable</td> |
| <td>伪造返回结果,可在失败时执行,或直接执行,用于服务降级</td> |
| <td>需注册中心支持</td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>延迟暴露</td> |
| <td>Stable</td> |
| <td>延迟暴露服务,用于等待应用加载warmup数据,或等待spring加载完成</td> |
| <td></td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>延迟连接</td> |
| <td>Tested</td> |
| <td>延迟建立连接,调用时建立</td> |
| <td></td> |
| <td>试用</td> |
| <td>Registry</td> |
| </tr> |
| <tr> |
| <td>粘滞连接</td> |
| <td>Tested</td> |
| <td>粘滞连接,总是向同一个提供方发起请求,除非此提供方挂掉,再切换到另一台</td> |
| <td></td> |
| <td>试用</td> |
| <td>Registry</td> |
| </tr> |
| <tr> |
| <td>令牌验证</td> |
| <td>Tested</td> |
| <td>令牌验证,用于服务授权</td> |
| <td>需注册中心支持</td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>路由规则</td> |
| <td>Tested</td> |
| <td>动态决定调用关系</td> |
| <td>需注册中心支持</td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>配置规则</td> |
| <td>Tested</td> |
| <td>动态下发配置,实现功能的开关</td> |
| <td>需注册中心支持</td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>访问日志</td> |
| <td>Tested</td> |
| <td>访问日志,用于记录调用信息</td> |
| <td>本地存储,影响性能,受磁盘大小限制</td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>分布式事务</td> |
| <td>Research</td> |
| <td>JTA/XA三阶段提交事务</td> |
| <td>不稳定</td> |
| <td>不可用</td> |
| <td></td> |
| </tr> |
| </tbody> |
| </table> |
| <h3 id="策略成熟度">策略成熟度</h3> |
| <table> |
| <thead> |
| <tr> |
| <th>Feature</th> |
| <th>Maturity</th> |
| <th>Strength</th> |
| <th>Problem</th> |
| <th>Advise</th> |
| <th>User</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>Zookeeper注册中心</td> |
| <td>Stable</td> |
| <td>支持基于网络的集群方式,有广泛周边开源产品,建议使用dubbo-2.3.3以上版本(推荐使用)</td> |
| <td>依赖于Zookeeper的稳定性</td> |
| <td>可用于生产环境</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>Redis注册中心</td> |
| <td>Stable</td> |
| <td>支持基于客户端双写的集群方式,性能高</td> |
| <td>要求服务器时间同步,用于检查心跳过期脏数据</td> |
| <td>可用于生产环境</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>Multicast注册中心</td> |
| <td>Tested</td> |
| <td>去中心化,不需要安装注册中心</td> |
| <td>依赖于网络拓扑和路由,跨机房有风险</td> |
| <td>小规模应用或开发测试环境</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>Simple注册中心</td> |
| <td>Tested</td> |
| <td>Dogfooding,注册中心本身也是一个标准的RPC服务</td> |
| <td>没有集群支持,可能单点故障</td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| </tbody> |
| </table> |
| <table> |
| <thead> |
| <tr> |
| <th>Feature</th> |
| <th>Maturity</th> |
| <th>Strength</th> |
| <th>Problem</th> |
| <th>Advise</th> |
| <th>User</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>Simple监控中心</td> |
| <td>Stable</td> |
| <td>支持JFreeChart统计报表</td> |
| <td>没有集群支持,可能单点故障,但故障后不影响RPC运行</td> |
| <td>可用于生产环境</td> |
| <td></td> |
| </tr> |
| </tbody> |
| </table> |
| <table> |
| <thead> |
| <tr> |
| <th>Feature</th> |
| <th>Maturity</th> |
| <th>Strength</th> |
| <th>Problem</th> |
| <th>Advise</th> |
| <th>User</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>Dubbo协议</td> |
| <td>Stable</td> |
| <td>采用NIO复用单一长连接,并使用线程池并发处理请求,减少握手和加大并发效率,性能较好(推荐使用)</td> |
| <td>在大文件传输时,单一连接会成为瓶颈</td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>Rmi协议</td> |
| <td>Stable</td> |
| <td>可与原生RMI互操作,基于TCP协议</td> |
| <td>偶尔会连接失败,需重建Stub</td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>Hessian协议</td> |
| <td>Stable</td> |
| <td>可与原生Hessian互操作,基于HTTP协议</td> |
| <td>需hessian.jar支持,http短连接的开销大</td> |
| <td>可用于生产环境</td> |
| <td></td> |
| </tr> |
| </tbody> |
| </table> |
| <table> |
| <thead> |
| <tr> |
| <th>Feature</th> |
| <th>Maturity</th> |
| <th>Strength</th> |
| <th>Problem</th> |
| <th>Advise</th> |
| <th>User</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>Netty Transporter</td> |
| <td>Stable</td> |
| <td>JBoss的NIO框架,性能较好(推荐使用)</td> |
| <td>一次请求派发两种事件,需屏蔽无用事件</td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>Mina Transporter</td> |
| <td>Stable</td> |
| <td>老牌NIO框架,稳定</td> |
| <td>待发送消息队列派发不及时,大压力下,会出现FullGC</td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>Grizzly Transporter</td> |
| <td>Tested</td> |
| <td>Sun的NIO框架,应用于GlassFish服务器中</td> |
| <td>线程池不可扩展,Filter不能拦截下一Filter</td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| </tbody> |
| </table> |
| <table> |
| <thead> |
| <tr> |
| <th>Feature</th> |
| <th>Maturity</th> |
| <th>Strength</th> |
| <th>Problem</th> |
| <th>Advise</th> |
| <th>User</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>Hessian Serialization</td> |
| <td>Stable</td> |
| <td>性能较好,多语言支持(推荐使用)</td> |
| <td>Hessian的各版本兼容性不好,可能和应用使用的Hessian冲突,Dubbo内嵌了hessian3.2.1的源码</td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>Dubbo Serialization</td> |
| <td>Tested</td> |
| <td>通过不传送POJO的类元信息,在大量POJO传输时,性能较好</td> |
| <td>当参数对象增加字段时,需外部文件声明</td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>Json Serialization</td> |
| <td>Tested</td> |
| <td>纯文本,可跨语言解析,缺省采用FastJson解析</td> |
| <td>性能较差</td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>Java Serialization</td> |
| <td>Stable</td> |
| <td>Java原生支持</td> |
| <td>性能较差</td> |
| <td>可用于生产环境</td> |
| <td></td> |
| </tr> |
| </tbody> |
| </table> |
| <table> |
| <thead> |
| <tr> |
| <th>Feature</th> |
| <th>Maturity</th> |
| <th>Strength</th> |
| <th>Problem</th> |
| <th>Advise</th> |
| <th>User</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>Javassist ProxyFactory</td> |
| <td>Stable</td> |
| <td>通过字节码生成代替反射,性能比较好(推荐使用)</td> |
| <td>依赖于javassist.jar包,占用JVM的Perm内存,Perm可能要设大一些:java -XX:PermSize=128m</td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>Jdk ProxyFactory</td> |
| <td>Stable</td> |
| <td>JDK原生支持</td> |
| <td>性能较差</td> |
| <td>可用于生产环境</td> |
| <td></td> |
| </tr> |
| </tbody> |
| </table> |
| <table> |
| <thead> |
| <tr> |
| <th>Feature</th> |
| <th>Maturity</th> |
| <th>Strength</th> |
| <th>Problem</th> |
| <th>Advise</th> |
| <th>User</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>Failover Cluster</td> |
| <td>Stable</td> |
| <td>失败自动切换,当出现失败,重试其它服务器,通常用于读操作(推荐使用)</td> |
| <td>重试会带来更长延迟</td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>Failfast Cluster</td> |
| <td>Stable</td> |
| <td>快速失败,只发起一次调用,失败立即报错,通常用于非幂等性的写操作</td> |
| <td>如果有机器正在重启,可能会出现调用失败</td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>Failsafe Cluster</td> |
| <td>Stable</td> |
| <td>失败安全,出现异常时,直接忽略,通常用于写入审计日志等操作</td> |
| <td>调用信息丢失</td> |
| <td>可用于生产环境</td> |
| <td>Monitor</td> |
| </tr> |
| <tr> |
| <td>Failback Cluster</td> |
| <td>Tested</td> |
| <td>失败自动恢复,后台记录失败请求,定时重发,通常用于消息通知操作</td> |
| <td>不可靠,重启丢失</td> |
| <td>可用于生产环境</td> |
| <td>Registry</td> |
| </tr> |
| <tr> |
| <td>Forking Cluster</td> |
| <td>Tested</td> |
| <td>并行调用多个服务器,只要一个成功即返回,通常用于实时性要求较高的读操作</td> |
| <td>需要浪费更多服务资源</td> |
| <td>可用于生产环境</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>Broadcast Cluster</td> |
| <td>Tested</td> |
| <td>广播调用所有提供者,逐个调用,任意一台报错则报错,通常用于更新提供方本地状态</td> |
| <td>速度慢,任意一台报错则报错</td> |
| <td>可用于生产环境</td> |
| <td></td> |
| </tr> |
| </tbody> |
| </table> |
| <table> |
| <thead> |
| <tr> |
| <th>Feature</th> |
| <th>Maturity</th> |
| <th>Strength</th> |
| <th>Problem</th> |
| <th>Advise</th> |
| <th>User</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>Random LoadBalance</td> |
| <td>Stable</td> |
| <td>随机,按权重设置随机概率(推荐使用)</td> |
| <td>在一个截面上碰撞的概率高,重试时,可能出现瞬间压力不均</td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>RoundRobin LoadBalance</td> |
| <td>Stable</td> |
| <td>轮询,按公约后的权重设置轮询比率</td> |
| <td>存在慢的机器累积请求问题,极端情况可能产生雪崩</td> |
| <td>可用于生产环境</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>LeastActive LoadBalance</td> |
| <td>Stable</td> |
| <td>最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差,使慢的机器收到更少请求</td> |
| <td>不支持权重,在容量规划时,不能通过权重把压力导向一台机器压测容量</td> |
| <td>可用于生产环境</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>ConsistentHash LoadBalance</td> |
| <td>Stable</td> |
| <td>一致性Hash,相同参数的请求总是发到同一提供者,当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动</td> |
| <td>压力分摊不均</td> |
| <td>可用于生产环境</td> |
| <td></td> |
| </tr> |
| </tbody> |
| </table> |
| <table> |
| <thead> |
| <tr> |
| <th>Feature</th> |
| <th>Maturity</th> |
| <th>Strength</th> |
| <th>Problem</th> |
| <th>Advise</th> |
| <th>User</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>条件路由规则</td> |
| <td>Stable</td> |
| <td>基于条件表达式的路由规则,功能简单易用</td> |
| <td>有些复杂多分支条件情况,规则很难描述</td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>脚本路由规则</td> |
| <td>Tested</td> |
| <td>基于脚本引擎的路由规则,功能强大</td> |
| <td>没有运行沙箱,脚本能力过于强大,可能成为后门</td> |
| <td>试用</td> |
| <td></td> |
| </tr> |
| </tbody> |
| </table> |
| <table> |
| <thead> |
| <tr> |
| <th>Feature</th> |
| <th>Maturity</th> |
| <th>Strength</th> |
| <th>Problem</th> |
| <th>Advise</th> |
| <th>User</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>Spring Container</td> |
| <td>Stable</td> |
| <td>自动加载META-INF/spring目录下的所有Spring配置</td> |
| <td></td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>Jetty Container</td> |
| <td>Stable</td> |
| <td>启动一个内嵌Jetty,用于汇报状态</td> |
| <td>大量访问页面时,会影响服务器的线程和内存</td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| <tr> |
| <td>Log4j Container</td> |
| <td>Stable</td> |
| <td>自动配置log4j的配置,在多进程启动时,自动给日志文件按进程分目录</td> |
| <td>用户不能控制log4j的配置,不灵活</td> |
| <td>可用于生产环境</td> |
| <td>Alibaba</td> |
| </tr> |
| </tbody> |
| </table></description></item><item><title>Docsv2.7: 配置手册</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/configuration/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/configuration/</guid><description/></item><item><title>Docsv2.7: 用法示例</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/examples/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/examples/</guid><description> |
| <div class="alert alert-primary" role="alert"> |
| <h4 class="alert-heading">Tips</h4> |
| 想完整的运行起来,请参见:<a href="../quick-start">快速启动</a>,这里只列出各种场景的配置方式。 |
| 以下示例全部使用基于 Spring 的 <a href="../configuration/xml">Xml配置</a>作为参考,如果不想使用 Spring,而希望通过 API 的方式进行调用,请参见:<a href="../configuration/api">API配置</a> |
| </div></description></item><item><title>Docsv2.7: 参考手册</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/references/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/references/</guid><description/></item><item><title>Docsv2.7: 版本升级</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/versions/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/versions/</guid><description/></item><item><title>Docsv2.7: 服务化最佳实践</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/best-practice/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/best-practice/</guid><description> |
| <h2 id="分包">分包</h2> |
| <p>建议将服务接口、服务模型、服务异常等均放在 API 包中,因为服务模型和异常也是 API 的一部分,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)。</p> |
| <p>如果需要,也可以考虑在 API 包中放置一份 Spring 的引用配置,这样使用方只需在 Spring 加载过程中引用此配置即可。配置建议放在模块的包目录下,以免冲突,如:<code>com/alibaba/china/xxx/dubbo-reference.xml</code>。</p> |
| <h2 id="粒度">粒度</h2> |
| <p>服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo 暂未提供分布式事务支持。</p> |
| <p>服务接口建议以业务场景为单位划分,并对相近业务做抽象,防止接口数量爆炸。</p> |
| <p>不建议使用过于抽象的通用接口,如:<code>Map query(Map)</code>,这样的接口没有明确语义,会给后期维护带来不便。</p> |
| <h2 id="版本">版本</h2> |
| <p>每个接口都应定义版本号,为后续不兼容升级提供可能,如: <code>&lt;dubbo:service interface=&quot;com.xxx.XxxService&quot; version=&quot;1.0&quot; /&gt;</code>。</p> |
| <p>建议使用两位版本号,因为第三位版本号通常表示兼容升级,只有不兼容时才需要变更服务版本。</p> |
| <p>当不兼容时,先升级一半提供者为新版本,再将消费者全部升为新版本,然后将剩下的一半提供者升为新版本。</p> |
| <h2 id="兼容性">兼容性</h2> |
| <p>服务接口增加方法,或服务模型增加字段,可向后兼容,删除方法或删除字段,将不兼容,枚举类型新增字段也不兼容,需通过变更版本号升级。</p> |
| <p>各协议的兼容性不同,参见:<a href="../references/protocol/">服务协议</a></p> |
| <h2 id="枚举值">枚举值</h2> |
| <p>如果是完备集,可以用 <code>Enum</code>,比如:<code>ENABLE</code>, <code>DISABLE</code>。</p> |
| <p>如果是业务种类,以后明显会有类型增加,不建议用 <code>Enum</code>,可以用 <code>String</code> 代替。</p> |
| <p>如果是在返回值中用了 <code>Enum</code>,并新增了 <code>Enum</code> 值,建议先升级服务消费方,这样服务提供方不会返回新值。</p> |
| <p>如果是在传入参数中用了 <code>Enum</code>,并新增了 <code>Enum</code> 值,建议先升级服务提供方,这样服务消费方不会传入新值。</p> |
| <h2 id="序列化">序列化</h2> |
| <p>服务参数及返回值建议使用 POJO 对象,即通过 <code>setter</code>, <code>getter</code> 方法表示属性的对象。</p> |
| <p>服务参数及返回值不建议使用接口,因为数据模型抽象的意义不大,并且序列化需要接口实现类的元信息,并不能起到隐藏实现的意图。</p> |
| <p>服务参数及返回值都必须是<a href="https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_value">传值调用</a>,而不能是<a href="https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_reference">传引用调用</a>,消费方和提供方的参数或返回值引用并不是同一个,只是值相同,Dubbo 不支持引用远程对象。</p> |
| <h2 id="异常">异常</h2> |
| <p>建议使用异常汇报错误,而不是返回错误码,异常信息能携带更多信息,并且语义更友好。</p> |
| <p>如果担心性能问题,在必要时,可以通过 override 掉异常类的 <code>fillInStackTrace()</code> 方法为空方法,使其不拷贝栈信息。</p> |
| <p>查询方法不建议抛出 checked 异常,否则调用方在查询时将过多的 <code>try...catch</code>,并且不能进行有效处理。</p> |
| <p>服务提供方不应将 DAO 或 SQL 等异常抛给消费方,应在服务实现中对消费方不关心的异常进行包装,否则可能出现消费方无法反序列化相应异常。</p> |
| <h2 id="调用">调用</h2> |
| <p>不要只是因为是 Dubbo 调用,而把调用 <code>try...catch</code> 起来。<code>try...catch</code> 应该加上合适的回滚边界上。</p> |
| <p>Provider 端需要对输入参数进行校验。如有性能上的考虑,服务实现者可以考虑在 API 包上加上服务 Stub 类来完成检验。</p></description></item><item><title>Docsv2.7: 推荐用法</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/recommend/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/recommend/</guid><description> |
| <h2 id="在-provider-端尽量多配置-consumer-端属性">在 Provider 端尽量多配置 Consumer 端属性</h2> |
| <p>原因如下:</p> |
| <ul> |
| <li>作为服务的提供方,比服务消费方更清楚服务的性能参数,如调用的超时时间、合理的重试次数等</li> |
| <li>在 Provider 端配置后,Consumer 端不配置则会使用 Provider 端的配置,即 Provider 端的配置可以作为 Consumer 的缺省值 <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。否则,Consumer 会使用 Consumer 端的全局设置,这对于 Provider 是不可控的,并且往往是不合理的</li> |
| </ul> |
| <p>Provider 端尽量多配置 Consumer 端的属性,让 Provider 的实现者一开始就思考 Provider 端的服务特点和服务质量等问题。</p> |
| <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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:service</span> interface=<span style="color:#2aa198">&#34;com.alibaba.hello.api.HelloService&#34;</span> version=<span style="color:#2aa198">&#34;1.0.0&#34;</span> ref=<span style="color:#2aa198">&#34;helloService&#34;</span> |
| </span></span><span style="display:flex;"><span> timeout=<span style="color:#2aa198">&#34;300&#34;</span> retries=<span style="color:#2aa198">&#34;2&#34;</span> loadbalance=<span style="color:#2aa198">&#34;random&#34;</span> actives=<span style="color:#2aa198">&#34;0&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:service</span> interface=<span style="color:#2aa198">&#34;com.alibaba.hello.api.WorldService&#34;</span> version=<span style="color:#2aa198">&#34;1.0.0&#34;</span> ref=<span style="color:#2aa198">&#34;helloService&#34;</span> |
| </span></span><span style="display:flex;"><span> timeout=<span style="color:#2aa198">&#34;300&#34;</span> retries=<span style="color:#2aa198">&#34;2&#34;</span> loadbalance=<span style="color:#2aa198">&#34;random&#34;</span> actives=<span style="color:#2aa198">&#34;0&#34;</span> <span style="color:#268bd2">&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:method</span> name=<span style="color:#2aa198">&#34;findAllPerson&#34;</span> timeout=<span style="color:#2aa198">&#34;10000&#34;</span> retries=<span style="color:#2aa198">&#34;9&#34;</span> loadbalance=<span style="color:#2aa198">&#34;leastactive&#34;</span> actives=<span style="color:#2aa198">&#34;5&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:service/&gt;</span> |
| </span></span></code></pre></div><p>建议在 Provider 端配置的 Consumer 端属性有:</p> |
| <ol> |
| <li><code>timeout</code>:方法调用的超时时间</li> |
| <li><code>retries</code>:失败重试次数,缺省是 2 <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></li> |
| <li><code>loadbalance</code>:负载均衡算法 <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>,缺省是随机 <code>random</code>。还可以配置轮询 <code>roundrobin</code>、最不活跃优先 <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> <code>leastactive</code> 和一致性哈希 <code>consistenthash</code> 等</li> |
| <li><code>actives</code>:消费者端的最大并发调用限制,即当 Consumer 对一个服务的并发调用到上限后,新调用会阻塞直到超时,在方法上配置 <code>dubbo:method</code> 则针对该方法进行并发限制,在接口上配置 <code>dubbo:service</code>,则针对该服务进行并发限制</li> |
| </ol> |
| <p>详细配置说明请参考:<a href="../references/xml/">Dubbo配置参考手册</a></p> |
| <h2 id="在-provider-端配置合理的-provider-端属性">在 Provider 端配置合理的 Provider 端属性</h2> |
| <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:protocol</span> threads=<span style="color:#2aa198">&#34;200&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:service</span> interface=<span style="color:#2aa198">&#34;com.alibaba.hello.api.HelloService&#34;</span> version=<span style="color:#2aa198">&#34;1.0.0&#34;</span> ref=<span style="color:#2aa198">&#34;helloService&#34;</span> |
| </span></span><span style="display:flex;"><span> executes=<span style="color:#2aa198">&#34;200&#34;</span> <span style="color:#268bd2">&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:method</span> name=<span style="color:#2aa198">&#34;findAllPerson&#34;</span> executes=<span style="color:#2aa198">&#34;50&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/dubbo:service&gt;</span> |
| </span></span></code></pre></div><p>建议在 Provider 端配置的 Provider 端属性有:</p> |
| <ol> |
| <li><code>threads</code>:服务线程池大小</li> |
| <li><code>executes</code>:一个服务提供者并行执行请求上限,即当 Provider 对一个服务的并发调用达到上限后,新调用会阻塞,此时 Consumer 可能会超时。在方法上配置 <code>dubbo:method</code> 则针对该方法进行并发限制,在接口上配置 <code>dubbo:service</code>,则针对该服务进行并发限制</li> |
| </ol> |
| <h2 id="配置管理信息">配置管理信息</h2> |
| <p>目前有负责人信息和组织信息用于区分站点。以便于在发现问题时找到服务对应负责人,建议至少配置两个人以便备份。负责人和组织信息可以在运维平台 (Dubbo Ops) 上看到。</p> |
| <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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:application</span> owner=<span style="color:#2aa198">”ding.lid,william.liangf”</span> organization=<span style="color:#2aa198">”intl”</span> <span style="color:#268bd2">/&gt;</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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:service</span> owner=<span style="color:#2aa198">”ding.lid,william.liangf”</span> <span style="color:#268bd2">/&gt;</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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:reference</span> owner=<span style="color:#2aa198">”ding.lid,william.liangf”</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>若没有配置服务层面的负责人,则默认使用 <code>dubbo:application</code> 设置的负责人。</p> |
| <h2 id="配置-dubbo-缓存文件">配置 Dubbo 缓存文件</h2> |
| <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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:registry</span> file=<span style="color:#2aa198">”${user.home}/output/dubbo.cache”</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>注意:</p> |
| <ol> |
| <li>可以根据需要调整缓存文件的路径,保证这个文件不会在发布过程中被清除;</li> |
| <li>如果有多个应用进程,请注意不要使用同一个文件,避免内容被覆盖;</li> |
| </ol> |
| <p>该文件会缓存注册中心列表和服务提供者列表。配置缓存文件后,应用重启过程中,若注册中心不可用,应用会从该缓存文件读取服务提供者列表,进一步保证应用可靠性。</p> |
| <h2 id="监控配置">监控配置</h2> |
| <ol> |
| <li> |
| <p>使用固定端口暴露服务,而不要使用随机端口</p> |
| <p>这样在注册中心推送有延迟的情况下,消费者通过缓存列表也能调用到原地址,保证调用成功。</p> |
| </li> |
| <li> |
| <p>使用 Dubbo Admin 监控注册中心上的服务提供方</p> |
| <p>使用 <a href="https://github.com/apache/dubbo-admin">Dubbo Admin</a> 监控服务在注册中心上的状态,确保注册中心上有该服务的存在。</p> |
| </li> |
| <li> |
| <p>服务提供方可使用 Dubbo Qos 的 telnet 或 shell 监控项</p> |
| <p>监控服务提供者端口状态:<code>echo status | nc -i 1 20880 | grep OK | wc -l</code>,其中的 20880 为服务端口</p> |
| </li> |
| <li> |
| <p>服务消费方可通过将服务强制转型为 EchoService,并调用 <code>$echo()</code> 测试该服务的提供者是可用</p> |
| <p>如 <code>assertEqauls(“OK”, ((EchoService)memberService).$echo(“OK”));</code></p> |
| </li> |
| </ol> |
| <h2 id="不要使用-dubboproperties-文件配置推荐使用对应-xml-配置">不要使用 dubbo.properties 文件配置,推荐使用对应 XML 配置</h2> |
| <p>Dubbo 中所有的配置项都可以配置在 Spring 配置文件中,并且可以针对单个服务配置。</p> |
| <p>如完全不配置则使用 Dubbo 缺省值,详情请参考 <a href="../references/xml/">Dubbo配置参考手册</a> 中的说明。</p> |
| <h3 id="dubboproperties-中属性名与-xml-的对应关系">dubbo.properties 中属性名与 XML 的对应关系</h3> |
| <ol> |
| <li> |
| <p>应用名 <code>dubbo.application.name</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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:application</span> name=<span style="color:#2aa198">&#34;myalibaba&#34;</span> <span style="color:#268bd2">&gt;</span> |
| </span></span></code></pre></div></li> |
| <li> |
| <p>注册中心地址 <code>dubbo.registry.address</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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:registry</span> address=<span style="color:#2aa198">&#34;11.22.33.44:9090&#34;</span> <span style="color:#268bd2">&gt;</span> |
| </span></span></code></pre></div></li> |
| <li> |
| <p>调用超时 <code>dubbo.service.*.timeout</code></p> |
| <p>可以在多个配置项设置超时 <code>timeout</code>,由上至下覆盖(即上面的优先)<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>,其它的参数(<code>retries</code>、<code>loadbalance</code>、<code>actives</code>等)的覆盖策略与 <code>timeout</code> 相同。示例如下:</p> |
| <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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:service</span> interface=<span style="color:#2aa198">&#34;com.alibaba.xxx.XxxService&#34;</span> <span style="color:#268bd2">&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:method</span> name=<span style="color:#2aa198">&#34;findPerson&#34;</span> timeout=<span style="color:#2aa198">&#34;1000&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/dubbo:service&gt;</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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:service</span> interface=<span style="color:#2aa198">&#34;com.alibaba.xxx.XxxService&#34;</span> timeout=<span style="color:#2aa198">&#34;200&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div></li> |
| <li> |
| <p>服务提供者协议 <code>dubbo.service.protocol</code>、服务的监听端口 <code>dubbo.service.server.port</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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:protocol</span> name=<span style="color:#2aa198">&#34;dubbo&#34;</span> port=<span style="color:#2aa198">&#34;20880&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div></li> |
| <li> |
| <p>服务线程池大小 <code>dubbo.service.max.thread.threads.size</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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:protocol</span> threads=<span style="color:#2aa198">&#34;100&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div></li> |
| <li> |
| <p>消费者启动时,没有提供者是否抛异常 <code>alibaba.intl.commons.dubbo.service.allow.no.provider</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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:reference</span> interface=<span style="color:#2aa198">&#34;com.alibaba.xxx.XxxService&#34;</span> check=<span style="color:#2aa198">&#34;false&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div></li> |
| </ol> |
| <div class="footnotes" role="doc-endnotes"> |
| <hr> |
| <ol> |
| <li id="fn:1"> |
| <p>配置的覆盖规则:1) 方法级别配置优于接口级别,即小 Scope 优先 2) Consumer 端配置优于 Provider 端配置,优于全局配置,最后是 Dubbo 硬编码的配置值(<a href="../configuration/properties">Dubbo 配置参考手册</a>)&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p> |
| </li> |
| <li id="fn:2"> |
| <p>表示加上第一次调用,会调用 3 次&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p> |
| </li> |
| <li id="fn:3"> |
| <p>有多个 Provider 时,如何挑选 Provider 调用&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p> |
| </li> |
| <li id="fn:4"> |
| <p>指从 Consumer 端并发调用最好的 Provider,可以减少对响应慢的 Provider 的调用,因为响应慢更容易累积并发调用&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p> |
| </li> |
| <li id="fn:5"> |
| <p><code>timeout</code> 可以在多处设置,配置项及覆盖规则请参考: <a href="../references/xml/">Dubbo 配置参考手册</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p> |
| </li> |
| </ol> |
| </div></description></item><item><title>Docsv2.7: 容量规划</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/capacity-plan/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/capacity-plan/</guid><description> |
| <p>以下数据供参考:</p> |
| <h2 id="使用-dubbo-的会员服务项目">使用 Dubbo 的会员服务项目</h2> |
| <ul> |
| <li>每天接收 4 亿次远程调用</li> |
| <li>使用 12 台网站标配机器提供服务(8 核 CPU,8G 内存)</li> |
| <li>平均负载在 1 以下(对于 8 核 CPU 负载很低)</li> |
| <li>平均响应时间 2.3 到 2.5 毫秒,网络开销约占 1.5 到 1.6 毫秒(和数据包大小有关)</li> |
| </ul> |
| <h2 id="使用-dubbo-的产品授权服务项目">使用 Dubbo 的产品授权服务项目</h2> |
| <ul> |
| <li>每天接收 3 亿次远程调用</li> |
| <li>使用 8 台网站标配机器提供服务(8 核CPU,8G 内存)</li> |
| <li>平均负载在 1 以下(对于 8 核 CPU 负载很低)</li> |
| <li>平均响应时间 1.4 到 2.8 毫秒,网络开销约占 1.0 到 1.1 毫秒(和数据包大小有关)</li> |
| </ul></description></item><item><title>Docsv2.7: 性能测试报告</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/perf-test/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/perf-test/</guid><description> |
| <h2 id="测试说明">测试说明</h2> |
| <ol> |
| <li>本次性能测试,测试了 dubbo 2.0 所有支持的协议在不同大小和数据类型下的表现,并与 dubbo 1.0 进行了对比。</li> |
| <li>整体性能相比 1.0 有了提升,平均提升 10%,使用 dubbo 2.0 新增的 dubbo 序列化还能获得 10%~50% 的性能提升,详见下面的性能数据。</li> |
| <li>稳定性测试中由于将底层通信框架从 mina 换成 netty,old 区对象的增长大大减少,50 小时运行,增长不到 200m,无 fullgc。</li> |
| <li>存在的问题:在 50k 数据的时候 2.0 性能不如 1.0,怀疑可能是缓冲区设置的问题,下版本会进一步确认。</li> |
| </ol> |
| <h2 id="测试环境">测试环境</h2> |
| <h3 id="硬件部署与参数调整">硬件部署与参数调整</h3> |
| <table> |
| <thead> |
| <tr> |
| <th>机型</th> |
| <th>CPU</th> |
| <th>内存</th> |
| <th>网络</th> |
| <th>磁盘</th> |
| <th>内核</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>Tecal BH620</td> |
| <td>model name : Intel(R) Xeon(R) CPU E5520 @ 2.27GHz cache size : 8192 KB processor_count : 16</td> |
| <td>Total System Memory: 6G Hardware Memory Info: Size: 4096MB</td> |
| <td>eth0: Link is up at 1000 Mbps, full duplex. peth0: Link is up at 1000 Mbps, full duplex.</td> |
| <td>/dev/sda: 597.9 GB</td> |
| <td>2.6.18-128.el5xen x86_64</td> |
| </tr> |
| </tbody> |
| </table> |
| <h3 id="软件架构">软件架构</h3> |
| <table> |
| <thead> |
| <tr> |
| <th>软件名称及版本</th> |
| <th>关键参数</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>java version &ldquo;1.6.0_18&rdquo; Java(TM) SE Runtime Environment (build 1.6.0_18-b07) Java HotSpot(TM) 64-Bit Server VM (build 16.0-b13, mixed mode)</td> |
| <td>-server -Xmx2g -Xms2g -Xmn256m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70</td> |
| </tr> |
| <tr> |
| <td>jboss-4.0.5.GA</td> |
| <td></td> |
| </tr> |
| <tr> |
| <td>httpd-2.0.61</td> |
| <td>KeepAlive On MaxKeepAliveRequests 100000 KeepAliveTimeout 180 MaxRequestsPerChild 1000000 <IfModule worker.c> StartServers 5 MaxClients 1024 MinSpareThreads 25 MaxSpareThreads 75 ThreadsPerChild 64 ThreadLimit 128 ServerLimit 16 </IfModule></td> |
| </tr> |
| </tbody> |
| </table> |
| <h2 id="测试目的">测试目的</h2> |
| <h3 id="期望性能指标量化">期望性能指标(量化)</h3> |
| <table> |
| <thead> |
| <tr> |
| <th>场景名称</th> |
| <th>对应指标名称</th> |
| <th>期望值范围</th> |
| <th>实际值</th> |
| <th>是否满足期望(是/否)</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>1k数据</td> |
| <td>响应时间</td> |
| <td>0.9ms</td> |
| <td>0.79ms</td> |
| <td>是</td> |
| </tr> |
| <tr> |
| <td>1k数据</td> |
| <td>TPS</td> |
| <td>10000</td> |
| <td>11994</td> |
| <td>是</td> |
| </tr> |
| </tbody> |
| </table> |
| <h3 id="期望运行状况非量化可选">期望运行状况(非量化,可选)</h3> |
| <ul> |
| <li>2.0 性能不低于 1.0, 2.0 和 1.0 互调用的性能无明显下降。 除了 50k string 其余皆通过</li> |
| <li>JVM 内存运行稳定,无 OOM,堆内存中无不合理的大对象的占用。通过</li> |
| <li>CPU、内存、网络、磁盘、文件句柄占用平稳。通过</li> |
| <li>无频繁线程锁,线程数平稳。通过</li> |
| <li>业务线程负载均衡。通过</li> |
| </ul> |
| <h2 id="测试脚本">测试脚本</h2> |
| <ol start="0"> |
| <li> |
| <p>性能测试场景(10 并发)</p> |
| <ul> |
| <li>传入 1k String,不做任何处理,原样返回</li> |
| <li>传入 50k String,不做任何处理,原样返回</li> |
| <li>传入 200k String,不做任何处理,原样返回</li> |
| <li>传入 1k POJO(嵌套的复杂 person 对象),不做任何处理,原样返回</li> |
| </ul> |
| <p>上述场景在 dubbo 1.0, dubbo 2.0(hessian2序列化), dubbo 2.0(dubbo序列化), rmi, hessian 3.2.0, http(json序列化) 进行 10 分钟的性能测试。主要考察序列化和网络 IO 的性能,因此服务端无任何业务逻辑。取 10 并发是考虑到 http 协议在高并发下对 CPU 的使用率较高可能会先打到瓶颈。</p> |
| </li> |
| <li> |
| <p>并发场景(20 并发) |
| 传入 1k String,在服务器段循环 1w 次,每次重新生成一个随机数然后进行拼装。考察业务线程是否能够分配到每个 CPU 上。</p> |
| </li> |
| <li> |
| <p>稳定性场景(20 并发) |
| 同时调用 1 个参数为 String(5k)方法,1 个参数为 person 对象的方法,1 个参数为 map(值为 3 个 person)的方法,持续运行 50 小时。</p> |
| </li> |
| <li> |
| <p>高压力场景(20 并发) |
| 在稳定性场景的基础上,将提供者和消费者布置成均为 2 台(一台机器 2 个实例),且 String 的参数从 20byte 到 200k,每隔 10 分钟随机变换。</p> |
| </li> |
| </ol> |
| <h2 id="测试结果">测试结果</h2> |
| <h3 id="场景名称pojo-场景">场景名称:POJO 场景</h3> |
| <table> |
| <thead> |
| <tr> |
| <th>TPS成功平均值</th> |
| <th>响应时间成功平均值(ms)</th> |
| <th></th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>dubbo1 (hessian2序列化+mina)</td> |
| <td>10813.5</td> |
| <td>0.9</td> |
| </tr> |
| <tr> |
| <td>dubbo2 (hessian2序列化+netty)</td> |
| <td>11994</td> |
| <td>0.79</td> |
| </tr> |
| <tr> |
| <td>dubbo2 (dubbo序列化+netty)</td> |
| <td>13620</td> |
| <td>0.67</td> |
| </tr> |
| <tr> |
| <td>rmi</td> |
| <td>2461.79</td> |
| <td>4</td> |
| </tr> |
| <tr> |
| <td>hessian</td> |
| <td>2417.7</td> |
| <td>4.1</td> |
| </tr> |
| <tr> |
| <td>http(json序列化)</td> |
| <td>8179.08</td> |
| <td>1.15</td> |
| </tr> |
| <tr> |
| <td>2.0和1.0默认对比百分比</td> |
| <td>10.92</td> |
| <td>-12.22</td> |
| </tr> |
| <tr> |
| <td>dubbo序列化相比hessian2序列化百分比</td> |
| <td>13.56</td> |
| <td>-15.19</td> |
| </tr> |
| </tbody> |
| </table> |
| <p>POJO TPS</p> |
| <p><img src="https://dubbo.apache.org/imgs/user/pojotps.png" alt="pojotps.png"></p> |
| <p>POJO Response</p> |
| <p><img src="https://dubbo.apache.org/imgs/user/pojores.png" alt="pojores.png"></p> |
| <h3 id="场景名称1k-string-场景">场景名称:1k string 场景</h3> |
| <table> |
| <thead> |
| <tr> |
| <th>TPS成功平均值</th> |
| <th>响应时间成功平均值(ms)</th> |
| <th></th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>dubbo1(hessian2序列化+mina)</td> |
| <td>11940</td> |
| <td>0.8</td> |
| </tr> |
| <tr> |
| <td>dubbo2 (hessian2序列化+netty)</td> |
| <td>14402</td> |
| <td>0.64</td> |
| </tr> |
| <tr> |
| <td>dubbo2 (dubbo序列化+netty)</td> |
| <td>15096</td> |
| <td>0.6</td> |
| </tr> |
| <tr> |
| <td>rmi</td> |
| <td>11136.02</td> |
| <td>0.81</td> |
| </tr> |
| <tr> |
| <td>hessian</td> |
| <td>11426.83</td> |
| <td>0.79</td> |
| </tr> |
| <tr> |
| <td>http(json序列化)</td> |
| <td>8919.27</td> |
| <td>1.04</td> |
| </tr> |
| <tr> |
| <td>2.0和1.0默认对比百分比</td> |
| <td>20.62</td> |
| <td>-20.00</td> |
| </tr> |
| <tr> |
| <td>dubbo序列化相比hessian2序列化百分比</td> |
| <td>4.82</td> |
| <td>-6.25</td> |
| </tr> |
| </tbody> |
| </table> |
| <p>1k TPS</p> |
| <p><img src="https://dubbo.apache.org/imgs/user/1ktps.png" alt="1ktps.png"></p> |
| <p>1k Response</p> |
| <p><img src="https://dubbo.apache.org/imgs/user/1kres.png" alt="1kres.png"></p> |
| <h3 id="场景名称50k-string-场景">场景名称:50k string 场景</h3> |
| <table> |
| <thead> |
| <tr> |
| <th>TPS成功平均值</th> |
| <th>响应时间成功平均值(ms)</th> |
| <th></th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>dubbo1(hessian2序列化+mina</td> |
| <td>1962.7</td> |
| <td>5.1</td> |
| </tr> |
| <tr> |
| <td>dubbo2 (hessian2序列化+netty)</td> |
| <td>1293</td> |
| <td>5.03</td> |
| </tr> |
| <tr> |
| <td>dubbo2 (dubbo序列化+netty)</td> |
| <td>1966</td> |
| <td>7.68</td> |
| </tr> |
| <tr> |
| <td>rmi</td> |
| <td>3349.88</td> |
| <td>2.9</td> |
| </tr> |
| <tr> |
| <td>hessian</td> |
| <td>1925.33</td> |
| <td>5.13</td> |
| </tr> |
| <tr> |
| <td>http(json序列化)</td> |
| <td>3247.1</td> |
| <td>3</td> |
| </tr> |
| <tr> |
| <td>2.0和1.0默认对比百分比</td> |
| <td>-34.12</td> |
| <td>-1.37</td> |
| </tr> |
| <tr> |
| <td>dubbo序列化相比hessian2序列化百分比</td> |
| <td>52.05</td> |
| <td>52.68</td> |
| </tr> |
| </tbody> |
| </table> |
| <p>50K TPS</p> |
| <p><img src="https://dubbo.apache.org/imgs/user/50ktps.png" alt="50ktps.png"></p> |
| <p>50K Response</p> |
| <p><img src="https://dubbo.apache.org/imgs/user/50kres.png" alt="50kres.png"></p> |
| <h3 id="场景名称200k-string-场景">场景名称:200k string 场景</h3> |
| <table> |
| <thead> |
| <tr> |
| <th>TPS成功平均值</th> |
| <th>响应时间成功平均值(ms)</th> |
| <th></th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>dubbo1(hessian2序列化+mina)</td> |
| <td>324.2</td> |
| <td>30.8</td> |
| </tr> |
| <tr> |
| <td>dubbo2 (hessian2序列化+netty)</td> |
| <td>362.92</td> |
| <td>27.49</td> |
| </tr> |
| <tr> |
| <td>dubbo2 (dubbo序列化+netty)</td> |
| <td>569.5</td> |
| <td>17.51</td> |
| </tr> |
| <tr> |
| <td>rmi</td> |
| <td>1031.28</td> |
| <td>9.61</td> |
| </tr> |
| <tr> |
| <td>hessian</td> |
| <td>628.06</td> |
| <td>15.83</td> |
| </tr> |
| <tr> |
| <td>http(json序列化)</td> |
| <td>1011.97</td> |
| <td>9.79</td> |
| </tr> |
| <tr> |
| <td>2.0和1.0默认对比百分比</td> |
| <td>11.94</td> |
| <td>-10.75</td> |
| </tr> |
| <tr> |
| <td>dubbo序列化相比hessian2序列化百分比</td> |
| <td>56.92</td> |
| <td>-36.30</td> |
| </tr> |
| </tbody> |
| </table> |
| <p>200K TPS</p> |
| <p><img src="https://dubbo.apache.org/imgs/user/200ktps.png" alt="200ktps.png"></p> |
| <p><strong>200K Response</strong></p> |
| <p><img src="https://dubbo.apache.org/imgs/user/200kres.png" alt="200kres.png"></p> |
| <h2 id="测试分析">测试分析</h2> |
| <h3 id="性能分析评估">性能分析评估</h3> |
| <p>Dubbo 2.0 的性能测试结论为通过,从性能、内存占用和稳定性上都有了提高和改进。由于将内存管理从 mina 换成netty,大大减少了 1.0 版本在高并发大数据下的内存大锯齿。</p> |
| <h3 id="性能对比分析新旧环境不同数据量级等">性能对比分析(新旧环境、不同数据量级等)</h3> |
| <p>Dubbo 2.0 相比较Dubbo 1.0(默认使用的都是 hessian2 序列化)性能均有提升(除了50k String),详见第五章的性能数据。</p> |
| <p>出于兼容性考虑默认的序列化方式和 1.0 保持一致使用 hessian2,如对性能有更高要求可以使用 dubbo 序列化,由其是在处理复杂对象时,在大数据量下能获得 50% 的提升(但此时已不建议使用 Dubbo 协议)。</p> |
| <p>Dubbo 的设计目的是为了满足高并发小数据量的 rpc 调用,在大数据量下的性能表现并不好,建议使用 rmi 或 http 协议。</p> |
| <h3 id="测试局限性分析可选">测试局限性分析(可选)</h3> |
| <p>本次性能测试考察的是 dubbo 本身的性能,实际使用过程中的性能有待应用来验证。</p> |
| <p>由于 dubbo 本身的性能占用都在毫秒级,占的基数很小,性能提升可能对应用整体的性能变化不大。</p> |
| <p>由于邮件篇幅所限没有列出所有的监控图,如需获得可在大力神平台上查询。</p></description></item><item><title>Docsv2.7: 测试覆盖率报告</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/coveragence/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/coveragence/</guid><description> |
| <ul> |
| <li>v2.0 Codecov报表 , 从 2017-12-29 后开始统计 |
| 测试覆盖率为 : <a href="https://codecov.io/gh/apache/dubbo"><img src="https://codecov.io/gh/apache/dubbo/branch/master/graph/badge.svg" alt="codecov"></a> , 可以从 <a href="https://codecov.io/gh/apache/dubbo">https://codecov.io/gh/apache/dubbo</a> 页面得到覆盖率报表</li> |
| <li>v1.0 基于 <code>2.0.12</code> 版本,统计于 2012-02-03</li> |
| </ul> |
| <p><img src="https://dubbo.apache.org/imgs/user/code-quality1.jpg" alt="//imgs/user/code-quality1.jpg"></p> |
| <p><img src="https://dubbo.apache.org/imgs/user/code-quality5.jpg" alt="//imgs/user/code-quality5.jpg"></p> |
| <p><img src="https://dubbo.apache.org/imgs/user/code-coverage.jpg" alt="//imgs/user/code-coverage.jpg"></p> |
| <p><img src="https://dubbo.apache.org/imgs/user/code-tendency.jpg" alt="//imgs/user/code-tendency.jpg"></p> |
| <p><img src="https://dubbo.apache.org/imgs/user/code-dependency.jpg" alt="//imgs/user/code-dependency.jpg"></p></description></item><item><title>Docsv2.7: 基准测试工具包</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/benchmark-tool/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/benchmark-tool/</guid><description> |
| <ul> |
| <li>下载源码: git clone <a href="https://github.com/apache/dubbo.git">https://github.com/apache/dubbo.git</a></li> |
| <li>编译benchmark: cd dubbo/dubbo-test/dubbo-test-benchmark; mvn clean install</li> |
| <li>解压 benchmark压缩包: dubbo/dubbo-test/dubbo-test-benchmark/target/dubbo-test-benchmark-2.6.2-SNAPSHOT.tar.gz</li> |
| </ul> |
| <p>阅读ReadMe.txt(内容如下,请以压缩包内的为准)</p> |
| <ul> |
| <li> |
| <p>新建一个benchmark工程,如demo.benchmark</p> |
| </li> |
| <li> |
| <p>导入自己服务的接口api包和dubbo.benchmark.jar(解压dubbo.benchmark.tar.gz,在lib目录下)</p> |
| </li> |
| <li> |
| <p>新建一个类,实现AbstractClientRunnable</p> |
| <ul> |
| <li>实现父类的构造函数</li> |
| <li>实现invoke方法,通过serviceFactory创建本地接口代理,并实现自己的业务逻辑,如下</li> |
| </ul> |
| <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">invoke</span><span style="color:#719e07">(</span>ServiceFactory serviceFactory<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> DemoService demoService <span style="color:#719e07">=</span> <span style="color:#719e07">(</span>DemoService<span style="color:#719e07">)</span> serviceFactory<span style="color:#719e07">.</span>get<span style="color:#719e07">(</span>DemoService<span style="color:#719e07">.</span>class<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> demoService<span style="color:#719e07">.</span>sendRequest<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;hello&#34;</span><span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div></li> |
| <li> |
| <p>将自己的benchmark工程打成jar包,如demo.benchmark.jar</p> |
| </li> |
| <li> |
| <p>将demo.benchmark.jar 和服务的api包放到dubbo.benchmark/lib目录下</p> |
| </li> |
| <li> |
| <p>配置dubbo.properties</p> |
| </li> |
| <li> |
| <p>运行run.bat(windows)或run.sh(linux)</p> |
| </li> |
| </ul> |
| <p>如想测试dubbo的不同版本,直接替换lib下的dubbo的jar包即可。</p></description></item><item><title>Docsv2.7: 开发 REST 应用</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/rest/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/rest/</guid><description> |
| <div class="pageinfo pageinfo-primary"> |
| <p>作者:沈理</p> |
| <p>文档版权:<a href="http://www.apache.org/licenses/LICENSE-2.0">Apache 2.0许可证 署名-禁止演绎</a></p> |
| <p>本文篇幅较长,因为REST本身涉及面较多。另外,本文参照 Spring 等的文档风格,不仅仅局限于框架用法的阐述,同时也努力呈现框架的设计理念和优良应用的架构思想。 |
| 对于想粗略了解 dubbo 和 REST 的人,只需浏览 概述 至 标准Java REST API:JAX-RS简介 几节即可。</p> |
| </div> |
| <h2 id="目录">目录</h2> |
| <ul> |
| <li>概述</li> |
| <li>REST的优点</li> |
| <li>应用场景</li> |
| <li>快速入门</li> |
| <li>标准Java REST API:JAX-RS简介</li> |
| <li>REST服务提供端详解 |
| <ul> |
| <li>HTTP POST/GET的实现</li> |
| <li>Annotation放在接口类还是实现类</li> |
| <li>JSON、XML等多数据格式的支持</li> |
| <li>中文字符支持</li> |
| <li>XML数据格式的额外要求</li> |
| <li>定制序列化</li> |
| <li>配置REST Server的实现</li> |
| <li>获取上下文(Context)信息</li> |
| <li>配置端口号和Context Path</li> |
| <li>配置线程数和IO线程数</li> |
| <li>配置长连接</li> |
| <li>配置最大的HTTP连接数</li> |
| <li>配置每个消费端的超时时间和HTTP连接数</li> |
| <li>GZIP数据压缩</li> |
| <li>用Annotation取代部分Spring XML配置</li> |
| <li>添加自定义的Filter、Interceptor等</li> |
| <li>添加自定义的Exception处理</li> |
| <li>配置HTTP日志输出</li> |
| <li>输入参数的校验</li> |
| <li>是否应该透明发布REST服务</li> |
| <li>Dubbo的REST提供端在被调用时使用header</li> |
| </ul> |
| </li> |
| <li>REST服务消费端详解 |
| <ul> |
| <li>场景1:非dubbo的消费端调用dubbo的REST服务</li> |
| <li>场景2:dubbo消费端调用dubbo的REST服务</li> |
| <li>场景3:dubbo的消费端调用非dubbo的REST服务</li> |
| <li>Dubbo的消费端在调用REST服务时配置自定义header</li> |
| </ul> |
| </li> |
| <li>Dubbo中JAX-RS的限制</li> |
| <li>REST常见问题解答(REST FAQ) |
| <ul> |
| <li>Dubbo REST的服务能和Dubbo注册中心、监控中心集成吗?</li> |
| <li>Dubbo REST中如何实现负载均衡和容错(failover)?</li> |
| <li>JAX-RS中重载的方法能够映射到同一URL地址吗?</li> |
| <li>JAX-RS中作POST的方法能够接收多个参数吗?</li> |
| </ul> |
| </li> |
| <li>Dubbo当前体系可能的不足之处(与REST相关的) |
| <ul> |
| <li>RpcContext的侵入性</li> |
| <li>Protocol配置的局限性</li> |
| <li>XML命名不符合spring规范</li> |
| </ul> |
| </li> |
| <li>REST最佳实践</li> |
| <li>性能基准测试 |
| <ul> |
| <li>测试环境</li> |
| <li>测试脚本</li> |
| <li>测试结果</li> |
| </ul> |
| </li> |
| <li>扩展讨论 |
| <ul> |
| <li>REST与Thrift、Protobuf等的对比</li> |
| <li>REST与传统WebServices的对比</li> |
| <li>JAX-RS与Spring MVC的对比</li> |
| </ul> |
| </li> |
| <li>未来</li> |
| </ul> |
| <h2 id="概述">概述</h2> |
| <p>dubbo支持多种远程调用方式,例如dubbo RPC(二进制序列化 + tcp协议)、http invoker(二进制序列化 + http协议,至少在开源版本没发现对文本序列化的支持)、hessian(二进制序列化 + http协议)、WebServices (文本序列化 + http协议)等等,但缺乏对当今特别流行的REST风格远程调用(文本序列化 + http协议)的支持。</p> |
| <p>有鉴于此,我们基于标准的Java REST API——JAX-RS 2.0(Java API for RESTful Web Services的简写),为dubbo提供了接近透明的REST调用支持。由于完全兼容Java标准API,所以为dubbo开发的所有REST服务,未来脱离dubbo或者任何特定的REST底层实现一般也可以正常运行。</p> |
| <p>特别值得指出的是,我们并不需要完全严格遵守REST的原始定义和架构风格。即使著名的Twitter REST API也会根据情况做适度调整,而不是机械的遵守原始的REST风格。</p> |
| <blockquote> |
| <p>附注:我们将这个功能称之为REST风格的远程调用,即RESTful Remoting(抽象的远程处理或者调用),而不是叫RESTful RPC(具体的远程“过程”调用),是因为REST和RPC本身可以被认为是两种不同的风格。在dubbo的REST实现中,可以说有两个面向,其一是提供或消费正常的REST服务,其二是将REST作为dubbo RPC体系中一种协议实现,而RESTful Remoting同时涵盖了这两个面向。</p> |
| </blockquote> |
| <h2 id="rest的优点">REST的优点</h2> |
| <p>以下摘自维基百科:</p> |
| <ul> |
| <li>可更高效利用缓存来提高响应速度</li> |
| <li>通讯本身的无状态性可以让不同的服务器的处理一系列请求中的不同请求,提高服务器的扩展性</li> |
| <li>浏览器即可作为客户端,简化软件需求</li> |
| <li>相对于其他叠加在HTTP协议之上的机制,REST的软件依赖性更小</li> |
| <li>不需要额外的资源发现机制</li> |
| <li>在软件技术演进中的长期的兼容性更好</li> |
| </ul> |
| <p>这里我还想特别补充REST的显著优点:基于简单的文本格式消息和通用的HTTP协议,使它具备极广的适用性,几乎所有语言和平台都对它提供支持,同时其学习和使用的门槛也较低。</p> |
| <h2 id="应用场景">应用场景</h2> |
| <p>正是由于REST在适用性方面的优点,所以在dubbo中支持REST,可以为当今多数主流的远程调用场景都带来(显著)好处:</p> |
| <ol> |
| <li> |
| <p>显著简化企业内部的异构系统之间的(跨语言)调用。此处主要针对这种场景:dubbo的系统做服务提供端,其他语言的系统(也包括某些不基于dubbo的java系统)做服务消费端,两者通过HTTP和文本消息进行通信。即使相比Thrift、ProtoBuf等二进制跨语言调用方案,REST也有自己独特的优势(详见后面讨论)</p> |
| </li> |
| <li> |
| <p>显著简化对外Open API(开放平台)的开发。既可以用dubbo来开发专门的Open API应用,也可以将原内部使用的dubbo service直接“透明”发布为对外的Open REST API(当然dubbo本身未来最好可以较透明的提供诸如权限控制、频次控制、计费等诸多功能)</p> |
| </li> |
| <li> |
| <p>显著简化手机(平板)APP或者PC桌面客户端开发。类似于2,既可以用dubbo来开发专门针对无线或者桌面的服务器端,也可以将原内部使用的dubbo service直接”透明“的暴露给手机APP或桌面程序。当然在有些项目中,手机或桌面程序也可以直接访问以上场景2中所述的Open API。</p> |
| </li> |
| <li> |
| <p>显著简化浏览器AJAX应用的开发。类似于2,既可以用dubbo来开发专门的AJAX服务器端,也可以将原内部使用的dubbo service直接”透明“的暴露给浏览器中JavaScript。当然,很多AJAX应用更适合与web框架协同工作,所以直接访问dubbo service在很多web项目中未必是一种非常优雅的架构。</p> |
| </li> |
| <li> |
| <p>为企业内部的dubbo系统之间(即服务提供端和消费端都是基于dubbo的系统)提供一种基于文本的、易读的远程调用方式。</p> |
| </li> |
| <li> |
| <p>一定程度简化dubbo系统对其它异构系统的调用。可以用类似dubbo的简便方式“透明”的调用非dubbo系统提供的REST服务(不管服务提供端是在企业内部还是外部)</p> |
| </li> |
| </ol> |
| <p>需要指出的是,我认为1~3是dubbo的REST调用最有价值的三种应用场景,并且我们为dubbo添加REST调用,其最主要到目的也是面向服务的提供端,即开发REST服务来提供给非dubbo的(异构)消费端。</p> |
| <p>归纳起来,所有应用场景如下图所示: |
| <img src="https://dubbo.apache.org/imgs/user/rest.jpg" alt="rest"></p> |
| <p>借用Java过去最流行的宣传语,为dubbo添加REST调用后,可以实现服务的”一次编写,到处访问“,理论上可以面向全世界开放,从而真正实现比较理想化的面向服务架构(SOA)。</p> |
| <p>当然,传统的WebServices(WSDL/SOAP)也基本同样能满足以上场景(除了场景4)的要求(甚至还能满足那些需要企业级特性的场景),但由于其复杂性等问题,现在已经越来越少被实际采用了。</p> |
| <h2 id="快速入门">快速入门</h2> |
| <p>在dubbo中开发一个REST风格的服务会比较简单,下面以一个注册用户的简单服务为例说明。</p> |
| <p>这个服务要实现的功能是提供如下URL(注:这个URL不是完全符合REST的风格,但是更简单实用):</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>http://localhost:8080/users/register |
| </span></span></code></pre></div><p>而任何客户端都可以将包含用户信息的JSON字符串POST到以上URL来完成用户注册。</p> |
| <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">interface</span> <span style="color:#268bd2">UserService</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">registerUser</span><span style="color:#719e07">(</span>User user<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</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">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;users&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">UserServiceImpl</span> <span style="color:#268bd2">implements</span> UserService <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@POST</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;register&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Consumes</span><span style="color:#719e07">({</span>MediaType<span style="color:#719e07">.</span>APPLICATION_JSON<span style="color:#719e07">})</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">registerUser</span><span style="color:#719e07">(</span>User user<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// save the user... |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"></span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>上面的服务实现代码非常简单,但是由于REST服务是要被发布到特定HTTP URL,供任意语言客户端甚至浏览器来访问,所以这里要额外添加了几个JAX-RS的标准annotation来做相关的配置:</p> |
| <p>@Path(&ldquo;users&rdquo;):指定访问UserService的URL相对路径是/users,即http://localhost:8080/users</p> |
| <p>@Path(&ldquo;register&rdquo;):指定访问registerUser()方法的URL相对路径是/register,再结合上一个@Path为UserService指定的路径,则调用UserService.register()的完整路径为http://localhost:8080/users/register</p> |
| <p>@POST:指定访问registerUser()用HTTP POST方法</p> |
| <p>@Consumes({MediaType.APPLICATION_JSON}):指定registerUser()接收JSON格式的数据。REST框架会自动将JSON数据反序列化为User对象</p> |
| <p>最后,在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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#586e75">&lt;!-- 用rest协议在8080端口暴露服务 --&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> port=<span style="color:#2aa198">&#34;8080&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75">&lt;!-- 声明需要暴露的服务接口 --&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:service</span> interface=<span style="color:#2aa198">&#34;xxx.UserService&#34;</span> ref=<span style="color:#2aa198">&#34;userService&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span><span style="color:#586e75">&lt;!-- 和本地bean一样实现服务 --&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;bean</span> id=<span style="color:#2aa198">&#34;userService&#34;</span> class=<span style="color:#2aa198">&#34;xxx.UserServiceImpl&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><h2 id="标准java-rest-apijax-rs简介">标准Java REST API:JAX-RS简介</h2> |
| <p>JAX-RS是标准的Java REST API,得到了业界的广泛支持和应用,其著名的开源实现就有很多,包括Oracle的Jersey,RedHat的RestEasy,Apache的CXF和Wink,以及restlet等等。另外,所有支持JavaEE 6.0以上规范的商用JavaEE应用服务器都对JAX-RS提供了支持。因此,JAX-RS是一种已经非常成熟的解决方案,并且采用它没有任何所谓vendor lock-in的问题。</p> |
| <p>JAX-RS在网上的资料非常丰富,例如下面的入门教程:</p> |
| <ul> |
| <li>Oracle官方的tutorial:https://www.oracle.com/technical-resources/articles/java/jax-rs.html</li> |
| <li>IBM developerWorks中国站文章:http://www.ibm.com/developerworks/cn/java/j-lo-jaxrs/</li> |
| </ul> |
| <p>更多的资料请自行google或者百度一下。就学习JAX-RS来说,一般主要掌握其各种annotation的用法即可。</p> |
| <blockquote> |
| <p>注意:dubbo是基于JAX-RS 2.0版本的,有时候需要注意一下资料或REST实现所涉及的版本。</p> |
| </blockquote> |
| <h2 id="rest服务提供端详解">REST服务提供端详解</h2> |
| <p>下面我们扩充“快速入门”中的UserService,进一步展示在dubbo中REST服务提供端的开发要点。</p> |
| <h3 id="http-postget的实现">HTTP POST/GET的实现</h3> |
| <p>REST服务中虽然建议使用HTTP协议中四种标准方法POST、DELETE、PUT、GET来分别实现常见的“增删改查”,但实际中,我们一般情况直接用POST来实现“增改”,GET来实现“删查”即可(DELETE和PUT甚至会被一些防火墙阻挡)。</p> |
| <p>前面已经简单演示了POST的实现,在此,我们为UserService添加一个获取注册用户资料的功能,来演示GET的实现。</p> |
| <p>这个功能就是要实现客户端通过访问如下不同URL来获取不同ID的用户资料:</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>http://localhost:8080/users/1001 |
| </span></span><span style="display:flex;"><span>http://localhost:8080/users/1002 |
| </span></span><span style="display:flex;"><span>http://localhost:8080/users/1003 |
| </span></span></code></pre></div><p>当然,也可以通过其他形式的URL来访问不同ID的用户资料,例如:</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>http://localhost:8080/users/load?id=1001 |
| </span></span></code></pre></div><p>JAX-RS本身可以支持所有这些形式。但是上面那种在URL路径中包含查询参数的形式(http://localhost:8080/users/1001) 更符合REST的一般习惯,所以更推荐大家来使用。下面我们就为UserService添加一个getUser()方法来实现这种形式的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">@GET</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;{id : \\d+}&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">@Produces</span><span style="color:#719e07">({</span>MediaType<span style="color:#719e07">.</span>APPLICATION_JSON<span style="color:#719e07">})</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> User <span style="color:#268bd2">getUser</span><span style="color:#719e07">(</span><span style="color:#268bd2">@PathParam</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;id&#34;</span><span style="color:#719e07">)</span> Long id<span style="color:#719e07">)</span> <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"></span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>@GET:指定用HTTP GET方法访问</p> |
| <p>@Path(&quot;{id : \d+}&quot;):根据上面的功能需求,访问getUser()的URL应当是“http://localhost:8080/users/ + 任意数字&quot;,并且这个数字要被做为参数传入getUser()方法。 这里的annotation配置中,@Path中间的{id: xxx}指定URL相对路径中包含了名为id参数,而它的值也将被自动传递给下面用@PathParam(&ldquo;id&rdquo;)修饰的方法参数id。{id:后面紧跟的\d+是一个正则表达式,指定了id参数必须是数字。</p> |
| <p>@Produces({MediaType.APPLICATION_JSON}):指定getUser()输出JSON格式的数据。框架会自动将User对象序列化为JSON数据。</p> |
| <h3 id="annotation放在接口类还是实现类">Annotation放在接口类还是实现类</h3> |
| <p>在Dubbo中开发REST服务主要都是通过JAX-RS的annotation来完成配置的,在上面的示例中,我们都是将annotation放在服务的实现类中。但其实,我们完全也可以将annotation放到服务的接口上,这两种方式是完全等价的,例如:</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">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;users&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">interface</span> <span style="color:#268bd2">UserService</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@GET</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;{id : \\d+}&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Produces</span><span style="color:#719e07">({</span>MediaType<span style="color:#719e07">.</span>APPLICATION_JSON<span style="color:#719e07">})</span> |
| </span></span><span style="display:flex;"><span> User <span style="color:#268bd2">getUser</span><span style="color:#719e07">(</span><span style="color:#268bd2">@PathParam</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;id&#34;</span><span style="color:#719e07">)</span> Long id<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>在一般应用中,我们建议将annotation放到服务实现类,这样annotation和java实现代码位置更接近,更便于开发和维护。另外更重要的是,我们一般倾向于避免对接口的污染,保持接口的纯净性和广泛适用性。</p> |
| <p>但是,如后文所述,如果我们要用dubbo直接开发的消费端来访问此服务,则annotation必须放到接口上。</p> |
| <p>如果接口和实现类都同时添加了annotation,则实现类的annotation配置会生效,接口上的annotation被直接忽略。</p> |
| <h3 id="jsonxml等多数据格式的支持">JSON、XML等多数据格式的支持</h3> |
| <p>在dubbo中开发的REST服务可以同时支持传输多种格式的数据,以给客户端提供最大的灵活性。其中我们目前对最常用的JSON和XML格式特别添加了额外的功能。</p> |
| <p>比如,我们要让上例中的getUser()方法支持分别返回JSON和XML格式的数据,只需要在annotation中同时包含两种格式即可:</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">@Produces</span><span style="color:#719e07">({</span>MediaType<span style="color:#719e07">.</span>APPLICATION_JSON<span style="color:#719e07">,</span> MediaType<span style="color:#719e07">.</span>TEXT_XML<span style="color:#719e07">})</span> |
| </span></span><span style="display:flex;"><span>User <span style="color:#268bd2">getUser</span><span style="color:#719e07">(</span><span style="color:#268bd2">@PathParam</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;id&#34;</span><span style="color:#719e07">)</span> Long id<span style="color:#719e07">);</span> |
| </span></span></code></pre></div><p>或者也可以直接用字符串(还支持通配符)表示MediaType:</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">@Produces</span><span style="color:#719e07">({</span><span style="color:#2aa198">&#34;application/json&#34;</span><span style="color:#719e07">,</span> <span style="color:#2aa198">&#34;text/xml&#34;</span><span style="color:#719e07">})</span> |
| </span></span><span style="display:flex;"><span>User <span style="color:#268bd2">getUser</span><span style="color:#719e07">(</span><span style="color:#268bd2">@PathParam</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;id&#34;</span><span style="color:#719e07">)</span> Long id<span style="color:#719e07">);</span> |
| </span></span></code></pre></div><p>如果所有方法都支持同样类型的输入输出数据格式,则我们无需在每个方法上做配置,只需要在服务类上添加annotation即可:</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">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;users&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">@Consumes</span><span style="color:#719e07">({</span>MediaType<span style="color:#719e07">.</span>APPLICATION_JSON<span style="color:#719e07">,</span> MediaType<span style="color:#719e07">.</span>TEXT_XML<span style="color:#719e07">})</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">@Produces</span><span style="color:#719e07">({</span>MediaType<span style="color:#719e07">.</span>APPLICATION_JSON<span style="color:#719e07">,</span> MediaType<span style="color:#719e07">.</span>TEXT_XML<span style="color:#719e07">})</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">UserServiceImpl</span> <span style="color:#268bd2">implements</span> UserService <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"></span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>在一个REST服务同时对多种数据格式支持的情况下,根据JAX-RS标准,一般是通过HTTP中的MIME header(content-type和accept)来指定当前想用的是哪种格式的数据。</p> |
| <p>但是在dubbo中,我们还自动支持目前业界普遍使用的方式,即用一个URL后缀(.json和.xml)来指定想用的数据格式。例如,在添加上述annotation后,直接访问http://localhost:8888/users/1001.json则表示用json格式,直接访问http://localhost:8888/users/1002.xml则表示用xml格式,比用HTTP Header更简单直观。Twitter、微博等的REST API都是采用这种方式。</p> |
| <p>如果你既不加HTTP header,也不加后缀,则dubbo的REST会优先启用在以上annotation定义中排位最靠前的那种数据格式。</p> |
| <blockquote> |
| <p>注意:这里要支持XML格式数据,在annotation中既可以用MediaType.TEXT_XML,也可以用MediaType.APPLICATION_XML,但是TEXT_XML是更常用的,并且如果要利用上述的URL后缀方式来指定数据格式,只能配置为TEXT_XML才能生效。</p> |
| </blockquote> |
| <h3 id="中文字符支持">中文字符支持</h3> |
| <p>为了在dubbo REST中正常输出中文字符,和通常的Java web应用一样,我们需要将HTTP响应的contentType设置为UTF-8编码。</p> |
| <p>基于JAX-RS的标准用法,我们只需要做如下annotation配置即可:</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">@Produces</span><span style="color:#719e07">({</span><span style="color:#2aa198">&#34;application/json; charset=UTF-8&#34;</span><span style="color:#719e07">,</span> <span style="color:#2aa198">&#34;text/xml; charset=UTF-8&#34;</span><span style="color:#719e07">})</span> |
| </span></span><span style="display:flex;"><span>User <span style="color:#268bd2">getUser</span><span style="color:#719e07">(</span><span style="color:#268bd2">@PathParam</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;id&#34;</span><span style="color:#719e07">)</span> Long id<span style="color:#719e07">);</span> |
| </span></span></code></pre></div><p>为了方便用户,我们在dubbo REST中直接添加了一个支持类,来定义以上的常量,可以直接使用,减少出错的可能性。</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">@Produces</span><span style="color:#719e07">({</span>ContentType<span style="color:#719e07">.</span>APPLICATION_JSON_UTF_8<span style="color:#719e07">,</span> ContentType<span style="color:#719e07">.</span>TEXT_XML_UTF_8<span style="color:#719e07">})</span> |
| </span></span><span style="display:flex;"><span>User <span style="color:#268bd2">getUser</span><span style="color:#719e07">(</span><span style="color:#268bd2">@PathParam</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;id&#34;</span><span style="color:#719e07">)</span> Long id<span style="color:#719e07">);</span> |
| </span></span></code></pre></div><h3 id="xml数据格式的额外要求">XML数据格式的额外要求</h3> |
| <p>由于JAX-RS的实现一般都用标准的JAXB(Java API for XML Binding)来序列化和反序列化XML格式数据,所以我们需要为每一个要用XML传输的对象添加一个类级别的JAXB annotation,否则序列化将报错。例如为getUser()中返回的User添加如下:</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">@XmlRootElement</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">User</span> <span style="color:#268bd2">implements</span> Serializable <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"></span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>此外,如果service方法中的返回值是Java的 primitive类型(如int,long,float,double等),最好为它们添加一层wrapper对象,因为JAXB不能直接序列化primitive类型。</p> |
| <p>例如,我们想让前述的registerUser()方法返回服务器端为用户生成的ID号:</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">long</span> <span style="color:#268bd2">registerUser</span><span style="color:#719e07">(</span>User user<span style="color:#719e07">);</span> |
| </span></span></code></pre></div><p>由于primitive类型不被JAXB序列化支持,所以添加一个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">@XmlRootElement</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">RegistrationResult</span> <span style="color:#268bd2">implements</span> Serializable <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> Long id<span style="color:#719e07">;</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">RegistrationResult</span><span style="color:#719e07">()</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</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">RegistrationResult</span><span style="color:#719e07">(</span>Long id<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span><span style="color:#719e07">.</span>id <span style="color:#719e07">=</span> id<span style="color:#719e07">;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Long <span style="color:#268bd2">getId</span><span style="color:#719e07">()</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> id<span style="color:#719e07">;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</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">setId</span><span style="color:#719e07">(</span>Long id<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">this</span><span style="color:#719e07">.</span>id <span style="color:#719e07">=</span> id<span style="color:#719e07">;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>并修改service方法:</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>RegistrationResult <span style="color:#268bd2">registerUser</span><span style="color:#719e07">(</span>User user<span style="color:#719e07">);</span> |
| </span></span></code></pre></div><p>这样不但能够解决XML序列化的问题,而且使得返回的数据都符合XML和JSON的规范。例如,在JSON中,返回的将是如下形式:</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-javascript" data-lang="javascript"><span style="display:flex;"><span>{<span style="color:#2aa198">&#34;id&#34;</span><span style="color:#719e07">:</span> <span style="color:#2aa198">1001</span>} |
| </span></span></code></pre></div><p>如果不加wrapper,JSON返回值将直接是</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>1001 |
| </span></span></code></pre></div><p>而在XML中,加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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;registrationResult&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;id&gt;</span>1002<span style="color:#268bd2">&lt;/id&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/registrationResult&gt;</span> |
| </span></span></code></pre></div><p>这种wrapper对象其实利用所谓Data Transfer Object(DTO)模式,采用DTO还能对传输数据做更多有用的定制。</p> |
| <h3 id="定制序列化">定制序列化</h3> |
| <p>如上所述,REST的底层实现会在service的对象和JSON/XML数据格式之间自动做序列化/反序列化。但有些场景下,如果觉得这种自动转换不满足要求,可以对其做定制。</p> |
| <p>Dubbo中的REST实现是用JAXB做XML序列化,用Jackson做JSON序列化,所以在对象上添加JAXB或Jackson的annotation即可以定制映射。</p> |
| <p>例如,定制对象属性映射到XML元素的名字:</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">@XmlRootElement</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">@XmlAccessorType</span><span style="color:#719e07">(</span>XmlAccessType<span style="color:#719e07">.</span>FIELD<span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">User</span> <span style="color:#268bd2">implements</span> Serializable <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@XmlElement</span><span style="color:#719e07">(</span>name<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;username&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> String name<span style="color:#719e07">;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>定制对象属性映射到JSON字段的名字:</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">User</span> <span style="color:#268bd2">implements</span> Serializable <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@JsonProperty</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;username&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> String name<span style="color:#719e07">;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>更多资料请参考JAXB和Jackson的官方文档,或自行google。</p> |
| <h3 id="配置rest-server的实现">配置REST Server的实现</h3> |
| <p>目前在dubbo中,我们支持5种嵌入式rest server的实现,并同时支持采用外部应用服务器来做rest server的实现。rest server的实现是通过如下server这个XML属性来选择的:</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> server=<span style="color:#2aa198">&#34;jetty&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>以上配置选用了嵌入式的jetty来做rest server,同时,如果不配置server属性,rest协议默认也是选用jetty。jetty是非常成熟的java servlet容器,并和dubbo已经有较好的集成(目前5种嵌入式server中只有jetty和后面所述的tomcat、tjws,与dubbo监控系统等完成了无缝的集成),所以,如果你的dubbo系统是单独启动的进程,你可以直接默认采用jetty即可。</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> server=<span style="color:#2aa198">&#34;tomcat&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>以上配置选用了嵌入式的tomcat来做rest server。在嵌入式tomcat上,REST的性能比jetty上要好得多(参见后面的基准测试),建议在需要高性能的场景下采用tomcat。</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> server=<span style="color:#2aa198">&#34;netty&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>以上配置选用嵌入式的netty来做rest server。(TODO more contents to add)</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> server=<span style="color:#2aa198">&#34;tjws&#34;</span><span style="color:#268bd2">/&gt;</span> (tjws is now deprecated) |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> server=<span style="color:#2aa198">&#34;sunhttp&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>以上配置选用嵌入式的tjws或Sun HTTP server来做rest server。这两个server实现非常轻量级,非常方便在集成测试中快速启动使用,当然也可以在负荷不高的生产环境中使用。 注:tjws目前已经被deprecated掉了,因为它不能很好的和servlet 3.1 API工作。</p> |
| <p>如果你的dubbo系统不是单独启动的进程,而是部署到了Java应用服务器中,则建议你采用以下配置:</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> server=<span style="color:#2aa198">&#34;servlet&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>通过将server设置为servlet,dubbo将采用外部应用服务器的servlet容器来做rest server。同时,还要在dubbo系统的web.xml中添加如下配置:</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;web-app&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;context-param&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;param-name&gt;</span>contextConfigLocation<span style="color:#268bd2">&lt;/param-name&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;param-value&gt;</span>/WEB-INF/classes/META-INF/spring/dubbo-demo-provider.xml<span style="color:#268bd2">&lt;/param-value&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;/context-param&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;listener&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;listener-class&gt;</span>org.apache.dubbo.remoting.http.servlet.BootstrapListener<span style="color:#268bd2">&lt;/listener-class&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;/listener&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;listener&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;listener-class&gt;</span>org.springframework.web.context.ContextLoaderListener<span style="color:#268bd2">&lt;/listener-class&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;/listener&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;servlet&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;servlet-name&gt;</span>dispatcher<span style="color:#268bd2">&lt;/servlet-name&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;servlet-class&gt;</span>org.apache.dubbo.remoting.http.servlet.DispatcherServlet<span style="color:#268bd2">&lt;/servlet-class&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;load-on-startup&gt;</span>1<span style="color:#268bd2">&lt;/load-on-startup&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;/servlet&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;servlet-mapping&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;servlet-name&gt;</span>dispatcher<span style="color:#268bd2">&lt;/servlet-name&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;url-pattern&gt;</span>/*<span style="color:#268bd2">&lt;/url-pattern&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;/servlet-mapping&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/web-app&gt;</span> |
| </span></span></code></pre></div><p>即必须将dubbo的BootstrapListener和DispatherServlet添加到web.xml,以完成dubbo的REST功能与外部servlet容器的集成。</p> |
| <blockquote> |
| <p>注意:如果你是用spring的ContextLoaderListener来加载spring,则必须保证BootstrapListener配置在ContextLoaderListener之前,否则dubbo初始化会出错。</p> |
| </blockquote> |
| <p>其实,这种场景下你依然可以坚持用嵌入式server,但外部应用服务器的servlet容器往往比嵌入式server更加强大(特别是如果你是部署到更健壮更可伸缩的WebLogic,WebSphere等),另外有时也便于在应用服务器做统一管理、监控等等。</p> |
| <h3 id="获取上下文context信息">获取上下文(Context)信息</h3> |
| <p>在远程调用中,值得获取的上下文信息可能有很多种,这里特别以获取客户端IP为例。</p> |
| <p>在dubbo的REST中,我们有两种方式获取客户端IP。</p> |
| <p>第一种方式,用JAX-RS标准的@Context annotation:</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> User <span style="color:#268bd2">getUser</span><span style="color:#719e07">(</span><span style="color:#268bd2">@PathParam</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;id&#34;</span><span style="color:#719e07">)</span> Long id<span style="color:#719e07">,</span> <span style="color:#268bd2">@Context</span> HttpServletRequest request<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;Client address is &#34;</span> <span style="color:#719e07">+</span> request<span style="color:#719e07">.</span>getRemoteAddr<span style="color:#719e07">());</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>用Context修饰getUser()的一个方法参数后,就可以将当前的HttpServletRequest注入进来,然后直接调用servlet api获取IP。</p> |
| <blockquote> |
| <p>注意:这种方式只能在设置server=&ldquo;tjws&quot;或者server=&ldquo;tomcat&quot;或者server=&ldquo;jetty&quot;或者server=&ldquo;servlet&quot;的时候才能工作,因为只有这几种REST server的实现才提供了servlet容器。另外,标准的JAX-RS还支持用@Context修饰service类的一个实例字段来获取HttpServletRequest,但在dubbo中我们没有对此作出支持。</p> |
| </blockquote> |
| <p>第二种方式,用dubbo中常用的RpcContext:</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> User <span style="color:#268bd2">getUser</span><span style="color:#719e07">(</span><span style="color:#268bd2">@PathParam</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;id&#34;</span><span style="color:#719e07">)</span> Long id<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;Client address is &#34;</span> <span style="color:#719e07">+</span> RpcContext<span style="color:#719e07">.</span>getContext<span style="color:#719e07">().</span>getRemoteAddressString<span style="color:#719e07">());</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><blockquote> |
| <p>注意:这种方式只能在设置server=&ldquo;jetty&quot;或者server=&ldquo;tomcat&quot;或者server=&ldquo;servlet&quot;或者server=&ldquo;tjws&quot;的时候才能工作。另外,目前dubbo的RpcContext是一种比较有侵入性的用法,未来我们很可能会做出重构。</p> |
| </blockquote> |
| <p>如果你想保持你的项目对JAX-RS的兼容性,未来脱离dubbo也可以运行,请选择第一种方式。如果你想要更优雅的服务接口定义,请选用第二种方式。</p> |
| <p>此外,在最新的dubbo rest中,还支持通过RpcContext来获取HttpServletRequest和HttpServletResponse,以提供更大的灵活性来方便用户实现某些复杂功能,比如在dubbo标准的filter中访问HTTP Header。用法示例如下:</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">if</span> <span style="color:#719e07">(</span>RpcContext<span style="color:#719e07">.</span>getContext<span style="color:#719e07">().</span>getRequest<span style="color:#719e07">()</span> <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> RpcContext<span style="color:#719e07">.</span>getContext<span style="color:#719e07">().</span>getRequest<span style="color:#719e07">()</span> <span style="color:#719e07">instanceof</span> HttpServletRequest<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;Client address is &#34;</span> <span style="color:#719e07">+</span> <span style="color:#719e07">((</span>HttpServletRequest<span style="color:#719e07">)</span> RpcContext<span style="color:#719e07">.</span>getContext<span style="color:#719e07">().</span>getRequest<span style="color:#719e07">()).</span>getRemoteAddr<span style="color:#719e07">());</span> |
| </span></span><span style="display:flex;"><span><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> <span style="color:#719e07">(</span>RpcContext<span style="color:#719e07">.</span>getContext<span style="color:#719e07">().</span>getResponse<span style="color:#719e07">()</span> <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">&amp;&amp;</span> RpcContext<span style="color:#719e07">.</span>getContext<span style="color:#719e07">().</span>getResponse<span style="color:#719e07">()</span> <span style="color:#719e07">instanceof</span> HttpServletResponse<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;Response object from RpcContext: &#34;</span> <span style="color:#719e07">+</span> RpcContext<span style="color:#719e07">.</span>getContext<span style="color:#719e07">().</span>getResponse<span style="color:#719e07">());</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><blockquote> |
| <p>注意:为了保持协议的中立性,RpcContext.getRequest()和RpcContext.getResponse()返回的仅仅是一个Object类,而且可能为null。所以,你必须自己做null和类型的检查。</p> |
| </blockquote> |
| <blockquote> |
| <p>注意:只有在设置server=&ldquo;jetty&quot;或者server=&ldquo;tomcat&quot;或者server=&ldquo;servlet&quot;的时候,你才能通过以上方法正确的得到HttpServletRequest和HttpServletResponse,因为只有这几种server实现了servlet容器。</p> |
| </blockquote> |
| <p>为了简化编程,在此你也可以用泛型的方式来直接获取特定类型的request/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:#719e07">if</span> <span style="color:#719e07">(</span>RpcContext<span style="color:#719e07">.</span>getContext<span style="color:#719e07">().</span>getRequest<span style="color:#719e07">(</span>HttpServletRequest<span style="color:#719e07">.</span>class<span style="color:#719e07">)</span> <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span><span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;Client address is &#34;</span> <span style="color:#719e07">+</span> RpcContext<span style="color:#719e07">.</span>getContext<span style="color:#719e07">().</span>getRequest<span style="color:#719e07">(</span>HttpServletRequest<span style="color:#719e07">.</span>class<span style="color:#719e07">).</span>getRemoteAddr<span style="color:#719e07">());</span> |
| </span></span><span style="display:flex;"><span><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> <span style="color:#719e07">(</span>RpcContext<span style="color:#719e07">.</span>getContext<span style="color:#719e07">().</span>getResponse<span style="color:#719e07">(</span>HttpServletResponse<span style="color:#719e07">.</span>class<span style="color:#719e07">)</span> <span style="color:#719e07">!=</span> <span style="color:#cb4b16">null</span><span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;Response object from RpcContext: &#34;</span> <span style="color:#719e07">+</span> RpcContext<span style="color:#719e07">.</span>getContext<span style="color:#719e07">().</span>getResponse<span style="color:#719e07">(</span>HttpServletResponse<span style="color:#719e07">.</span>class<span style="color:#719e07">));</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>如果request/response不符合指定的类型,这里也会返回null。</p> |
| <h3 id="配置端口号和context-path">配置端口号和Context Path</h3> |
| <p>dubbo中的rest协议默认将采用80端口,如果想修改端口,直接配置:</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> port=<span style="color:#2aa198">&#34;8888&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>另外,如前所述,我们可以用@Path来配置单个rest服务的URL相对路径。但其实,我们还可以设置一个所有rest服务都适用的基础相对路径,即java web应用中常说的context path。</p> |
| <p>只需要添加如下contextpath属性即可:</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> port=<span style="color:#2aa198">&#34;8888&#34;</span> contextpath=<span style="color:#2aa198">&#34;services&#34;</span><span style="color:#268bd2">/&gt;</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">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;users&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">UserServiceImpl</span> <span style="color:#268bd2">implements</span> UserService <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@POST</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;register&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Consumes</span><span style="color:#719e07">({</span>MediaType<span style="color:#719e07">.</span>APPLICATION_JSON<span style="color:#719e07">})</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">registerUser</span><span style="color:#719e07">(</span>User user<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// save the user... |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"></span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>现在registerUser()的完整访问路径为:</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>http://localhost:8888/services/users/register |
| </span></span></code></pre></div><p>注意:如果你是选用外部应用服务器做rest server,即配置:</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> port=<span style="color:#2aa198">&#34;8888&#34;</span> contextpath=<span style="color:#2aa198">&#34;services&#34;</span> server=<span style="color:#2aa198">&#34;servlet&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>则必须保证这里设置的port、contextpath,与外部应用服务器的端口、DispatcherServlet的上下文路径(即webapp path加上servlet url pattern)保持一致。例如,对于部署为tomcat ROOT路径的应用,这里的contextpath必须与web.xml中DispacherServlet的<code>&lt;url-pattern/&gt;</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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;servlet-mapping&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;servlet-name&gt;</span>dispatcher<span style="color:#268bd2">&lt;/servlet-name&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;url-pattern&gt;</span>/services/*<span style="color:#268bd2">&lt;/url-pattern&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/servlet-mapping&gt;</span> |
| </span></span></code></pre></div><h3 id="配置线程数和io线程数">配置线程数和IO线程数</h3> |
| <p>可以为rest服务配置线程池大小:</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> threads=<span style="color:#2aa198">&#34;500&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><blockquote> |
| <p>注意:目前线程池的设置只有当server=&ldquo;netty&quot;或者server=&ldquo;jetty&quot;或者server=&ldquo;tomcat&quot;的时候才能生效。另外,如果server=&ldquo;servlet&rdquo;,由于这时候启用的是外部应用服务器做rest server,不受dubbo控制,所以这里的线程池设置也无效。</p> |
| </blockquote> |
| <p>如果是选用netty server,还可以配置Netty的IO worker线程数:</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> iothreads=<span style="color:#2aa198">&#34;5&#34;</span> threads=<span style="color:#2aa198">&#34;100&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><h3 id="配置长连接">配置长连接</h3> |
| <p>Dubbo中的rest服务默认都是采用http长连接来访问,如果想切换为短连接,直接配置:</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> keepalive=<span style="color:#2aa198">&#34;false&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><blockquote> |
| <p>注意:这个配置目前只对server=&ldquo;netty&quot;和server=&ldquo;tomcat&quot;才能生效。</p> |
| </blockquote> |
| <h3 id="配置最大的http连接数">配置最大的HTTP连接数</h3> |
| <p>可以配置服务器提供端所能同时接收的最大HTTP连接数,防止REST server被过多连接撑爆,以作为一种最基本的自我保护机制:</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> accepts=<span style="color:#2aa198">&#34;500&#34;</span> server=<span style="color:#2aa198">&#34;tomcat/</span><span style="color:#268bd2">&gt;</span> |
| </span></span></code></pre></div><blockquote> |
| <p>注意:这个配置目前只对server=&ldquo;tomcat&quot;才能生效。</p> |
| </blockquote> |
| <h3 id="配置每个消费端的超时时间和http连接数">配置每个消费端的超时时间和HTTP连接数</h3> |
| <p>如果rest服务的消费端也是dubbo系统,可以像其他dubbo RPC机制一样,配置消费端调用此rest服务的最大超时时间以及每个消费端所能启动的最大HTTP连接数。</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:service</span> interface=<span style="color:#2aa198">&#34;xxx&#34;</span> ref=<span style="color:#2aa198">&#34;xxx&#34;</span> protocol=<span style="color:#2aa198">&#34;rest&#34;</span> timeout=<span style="color:#2aa198">&#34;2000&#34;</span> connections=<span style="color:#2aa198">&#34;10&#34;</span><span style="color:#268bd2">/&gt;</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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:reference</span> id=<span style="color:#2aa198">&#34;xxx&#34;</span> interface=<span style="color:#2aa198">&#34;xxx&#34;</span> timeout=<span style="color:#2aa198">&#34;2000&#34;</span> connections=<span style="color:#2aa198">&#34;10&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>但是,通常我们建议配置在服务提供端提供此类配置。按照dubbo官方文档的说法:“Provider上尽量多配置Consumer端的属性,让Provider实现者一开始就思考Provider服务特点、服务质量的问题。”</p> |
| <blockquote> |
| <p>注意:如果dubbo的REST服务是发布给非dubbo的客户端使用,则这里<code>&lt;dubbo:service/&gt;</code>上的配置完全无效,因为这种客户端不受dubbo控制。</p> |
| </blockquote> |
| <h3 id="gzip数据压缩">GZIP数据压缩</h3> |
| <p>Dubbo的REST支持用GZIP压缩请求和响应的数据,以减少网络传输时间和带宽占用,但这种方式会也增加CPU开销。</p> |
| <p>TODO more contents to add</p> |
| <h3 id="用annotation取代部分spring-xml配置">用Annotation取代部分Spring XML配置</h3> |
| <p>以上所有的讨论都是基于dubbo在spring中的xml配置。但是,dubbo/spring本身也支持用annotation来作配置,所以我们也可以按dubbo官方文档中的步骤,把相关annotation加到REST服务的实现中,取代一些xml配置,例如:</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">@Service</span><span style="color:#719e07">(</span>protocol <span style="color:#719e07">=</span> <span style="color:#2aa198">&#34;rest&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;users&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">UserServiceImpl</span> <span style="color:#268bd2">implements</span> UserService <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Autowired</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> UserRepository userRepository<span style="color:#719e07">;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@POST</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;register&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Consumes</span><span style="color:#719e07">({</span>MediaType<span style="color:#719e07">.</span>APPLICATION_JSON<span style="color:#719e07">})</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> <span style="color:#dc322f">void</span> <span style="color:#268bd2">registerUser</span><span style="color:#719e07">(</span>User user<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// save the user |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"></span> userRepository<span style="color:#719e07">.</span>save<span style="color:#719e07">(</span>user<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>annotation的配置更简单更精确,经常也更便于维护(当然现代IDE都可以在xml中支持比如类名重构,所以就这里的特定用例而言,xml的维护性也很好)。而xml对代码的侵入性更小一些,尤其有利于动态修改配置,特别是比如你要针对单个服务配置连接超时时间、每客户端最大连接数、集群策略、权重等等。另外,特别对复杂应用或者模块来说,xml提供了一个中心点来涵盖的所有组件和配置,更一目了然,一般更便于项目长时期的维护。</p> |
| <p>当然,选择哪种配置方式没有绝对的优劣,和个人的偏好也不无关系。</p> |
| <h3 id="添加自定义的filterinterceptor等">添加自定义的Filter、Interceptor等</h3> |
| <p>Dubbo的REST也支持JAX-RS标准的Filter和Interceptor,以方便对REST的请求与响应过程做定制化的拦截处理。</p> |
| <p>其中,Filter主要用于访问和设置HTTP请求和响应的参数、URI等等。例如,设置HTTP响应的cache header:</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">CacheControlFilter</span> <span style="color:#268bd2">implements</span> ContainerResponseFilter <span style="color:#719e07">{</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">filter</span><span style="color:#719e07">(</span>ContainerRequestContext req<span style="color:#719e07">,</span> ContainerResponseContext res<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> <span style="color:#719e07">(</span>req<span style="color:#719e07">.</span>getMethod<span style="color:#719e07">().</span>equals<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;GET&#34;</span><span style="color:#719e07">))</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> res<span style="color:#719e07">.</span>getHeaders<span style="color:#719e07">().</span>add<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;Cache-Control&#34;</span><span style="color:#719e07">,</span> <span style="color:#2aa198">&#34;someValue&#34;</span><span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>Interceptor主要用于访问和修改输入与输出字节流,例如,手动添加GZIP压缩:</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">GZIPWriterInterceptor</span> <span style="color:#268bd2">implements</span> WriterInterceptor <span style="color:#719e07">{</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">aroundWriteTo</span><span style="color:#719e07">(</span>WriterInterceptorContext context<span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">throws</span> IOException<span style="color:#719e07">,</span> WebApplicationException <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> OutputStream outputStream <span style="color:#719e07">=</span> context<span style="color:#719e07">.</span>getOutputStream<span style="color:#719e07">();</span> |
| </span></span><span style="display:flex;"><span> context<span style="color:#719e07">.</span>setOutputStream<span style="color:#719e07">(</span><span style="color:#719e07">new</span> GZIPOutputStream<span style="color:#719e07">(</span>outputStream<span style="color:#719e07">));</span> |
| </span></span><span style="display:flex;"><span> context<span style="color:#719e07">.</span>proceed<span style="color:#719e07">();</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>在标准JAX-RS应用中,我们一般是为Filter和Interceptor添加@Provider annotation,然后JAX-RS runtime会自动发现并启用它们。而在dubbo中,我们是通过添加XML配置的方式来注册Filter和Interceptor:</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> port=<span style="color:#2aa198">&#34;8888&#34;</span> extension=<span style="color:#2aa198">&#34;xxx.TraceInterceptor, xxx.TraceFilter&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>在此,我们可以将Filter、Interceptor和DynamicFeature这三种类型的对象都添加到<code>extension</code>属性上,多个之间用逗号分隔。(DynamicFeature是另一个接口,可以方便我们更动态的启用Filter和Interceptor,感兴趣请自行google。)</p> |
| <p>当然,dubbo自身也支持Filter的概念,但我们这里讨论的Filter和Interceptor更加接近协议实现的底层,相比dubbo的filter,可以做更底层的定制化。</p> |
| <blockquote> |
| <p>注:这里的XML属性叫extension,而不是叫interceptor或者filter,是因为除了Interceptor和Filter,未来我们还会添加更多的扩展类型。</p> |
| </blockquote> |
| <p>如果REST的消费端也是dubbo系统(参见下文的讨论),则也可以用类似方式为消费端配置Interceptor和Filter。但注意,JAX-RS中消费端的Filter和提供端的Filter是两种不同的接口。例如前面例子中服务端是ContainerResponseFilter接口,而消费端对应的是ClientResponseFilter:</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">LoggingFilter</span> <span style="color:#268bd2">implements</span> ClientResponseFilter <span style="color:#719e07">{</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">filter</span><span style="color:#719e07">(</span>ClientRequestContext reqCtx<span style="color:#719e07">,</span> ClientResponseContext resCtx<span style="color:#719e07">)</span> <span style="color:#268bd2">throws</span> IOException <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;status: &#34;</span> <span style="color:#719e07">+</span> resCtx<span style="color:#719e07">.</span>getStatus<span style="color:#719e07">());</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;date: &#34;</span> <span style="color:#719e07">+</span> resCtx<span style="color:#719e07">.</span>getDate<span style="color:#719e07">());</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;last-modified: &#34;</span> <span style="color:#719e07">+</span> resCtx<span style="color:#719e07">.</span>getLastModified<span style="color:#719e07">());</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;location: &#34;</span> <span style="color:#719e07">+</span> resCtx<span style="color:#719e07">.</span>getLocation<span style="color:#719e07">());</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;headers:&#34;</span><span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> <span style="color:#719e07">(</span>Entry<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">,</span> List<span style="color:#719e07">&lt;</span>String<span style="color:#719e07">&gt;&gt;</span> header <span style="color:#719e07">:</span> resCtx<span style="color:#719e07">.</span>getHeaders<span style="color:#719e07">().</span>entrySet<span style="color:#719e07">())</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>print<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;\t&#34;</span> <span style="color:#719e07">+</span> header<span style="color:#719e07">.</span>getKey<span style="color:#719e07">()</span> <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34; :&#34;</span><span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> <span style="color:#719e07">(</span>String value <span style="color:#719e07">:</span> header<span style="color:#719e07">.</span>getValue<span style="color:#719e07">())</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>print<span style="color:#719e07">(</span>value <span style="color:#719e07">+</span> <span style="color:#2aa198">&#34;, &#34;</span><span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>print<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;\n&#34;</span><span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;media-type: &#34;</span> <span style="color:#719e07">+</span> resCtx<span style="color:#719e07">.</span>getMediaType<span style="color:#719e07">().</span>getType<span style="color:#719e07">());</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><h3 id="添加自定义的exception处理">添加自定义的Exception处理</h3> |
| <p>Dubbo的REST也支持JAX-RS标准的ExceptionMapper,可以用来定制特定exception发生后应该返回的HTTP响应。</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">CustomExceptionMapper</span> <span style="color:#268bd2">implements</span> ExceptionMapper<span style="color:#719e07">&lt;</span>NotFoundException<span style="color:#719e07">&gt;</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Response <span style="color:#268bd2">toResponse</span><span style="color:#719e07">(</span>NotFoundException e<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> Response<span style="color:#719e07">.</span>status<span style="color:#719e07">(</span>Response<span style="color:#719e07">.</span>Status<span style="color:#719e07">.</span>NOT_FOUND<span style="color:#719e07">).</span>entity<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;Oops! the requested resource is not found!&#34;</span><span style="color:#719e07">).</span>type<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;text/plain&#34;</span><span style="color:#719e07">).</span>build<span style="color:#719e07">();</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>和Interceptor、Filter类似,将其添加到XML配置文件中即可启用:</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> port=<span style="color:#2aa198">&#34;8888&#34;</span> extension=<span style="color:#2aa198">&#34;xxx.CustomExceptionMapper&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><h3 id="配置http日志输出">配置HTTP日志输出</h3> |
| <p>Dubbo rest支持输出所有HTTP请求/响应中的header字段和body消息体。</p> |
| <p>在XML配置中添加如下自带的REST filter:</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> port=<span style="color:#2aa198">&#34;8888&#34;</span> extension=<span style="color:#2aa198">&#34;org.apache.dubbo.rpc.protocol.rest.support.LoggingFilter&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p><strong>然后在logging配置中至少为org.apache.dubbo.rpc.protocol.rest.support打开INFO级别日志输出</strong>,例如,在log4j.xml中配置:</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;logger</span> name=<span style="color:#2aa198">&#34;org.apache.dubbo.rpc.protocol.rest.support&#34;</span><span style="color:#268bd2">&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;level</span> value=<span style="color:#2aa198">&#34;INFO&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;appender-ref</span> ref=<span style="color:#2aa198">&#34;CONSOLE&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/logger&gt;</span> |
| </span></span></code></pre></div><p>当然,你也可以直接在ROOT logger打开INFO级别日志输出:</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;root&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;level</span> value=<span style="color:#2aa198">&#34;INFO&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;appender-ref</span> ref=<span style="color:#2aa198">&#34;CONSOLE&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/root&gt;</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>The HTTP headers are: |
| </span></span><span style="display:flex;"><span>accept: application/json;charset=UTF-8 |
| </span></span><span style="display:flex;"><span>accept-encoding: gzip, deflate |
| </span></span><span style="display:flex;"><span>connection: Keep-Alive |
| </span></span><span style="display:flex;"><span>content-length: 22 |
| </span></span><span style="display:flex;"><span>content-type: application/json |
| </span></span><span style="display:flex;"><span>host: 192.168.1.100:8888 |
| </span></span><span style="display:flex;"><span>user-agent: Apache-HttpClient/4.2.1 (java 1.5) |
| </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-fallback" data-lang="fallback"><span style="display:flex;"><span>The contents of request body is: |
| </span></span><span style="display:flex;"><span>{&#34;id&#34;:1,&#34;name&#34;:&#34;dang&#34;} |
| </span></span></code></pre></div><p>打开HTTP日志输出后,除了正常日志输出的性能开销外,也会在比如HTTP请求解析时产生额外的开销,因为需要建立额外的内存缓冲区来为日志的输出做数据准备。</p> |
| <h3 id="输入参数的校验">输入参数的校验</h3> |
| <p>dubbo的rest支持采用Java标准的bean validation annotation(JSR 303)来做输入校验http://beanvalidation.org/</p> |
| <p>为了和其他dubbo远程调用协议保持一致,在rest中作校验的annotation必须放在服务的接口上,例如:</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">UserService</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> User <span style="color:#268bd2">getUser</span><span style="color:#719e07">(</span><span style="color:#268bd2">@Min</span><span style="color:#719e07">(</span>value<span style="color:#719e07">=</span>1L<span style="color:#719e07">,</span> message<span style="color:#719e07">=</span><span style="color:#2aa198">&#34;User ID must be greater than 1&#34;</span><span style="color:#719e07">)</span> Long id<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>当然,在很多其他的bean validation的应用场景都是将annotation放到实现类而不是接口上。把annotation放在接口上至少有一个好处是,dubbo的客户端可以共享这个接口的信息,dubbo甚至不需要做远程调用,在本地就可以完成输入校验。</p> |
| <p>然后按照dubbo的标准方式在XML配置中打开验证:</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:service</span> interface=<span style="color:#2aa198">xxx.UserService&#34;</span> ref=<span style="color:#2aa198">&#34;userService&#34;</span> protocol=<span style="color:#2aa198">&#34;rest&#34;</span> validation=<span style="color:#2aa198">&#34;true&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>在dubbo的其他很多远程调用协议中,如果输入验证出错,是直接将<code>RpcException</code>抛向客户端,而在rest中由于客户端经常是非dubbo,甚至非java的系统,所以不便直接抛出Java异常。因此,目前我们将校验错误以XML的格式返回:</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;violationReport&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;constraintViolations&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;path&gt;</span>getUserArgument0<span style="color:#268bd2">&lt;/path&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;message&gt;</span>User ID must be greater than 1<span style="color:#268bd2">&lt;/message&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;value&gt;</span>0<span style="color:#268bd2">&lt;/value&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;/constraintViolations&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/violationReport&gt;</span> |
| </span></span></code></pre></div><p>稍后也会支持其他数据格式的返回值。至于如何对验证错误消息作国际化处理,直接参考bean validation的相关文档即可。</p> |
| <p>如果你认为默认的校验错误返回格式不符合你的要求,可以如上面章节所述,添加自定义的ExceptionMapper来自由的定制错误返回格式。需要注意的是,这个ExceptionMapper必须用泛型声明来捕获dubbo的RpcException,才能成功覆盖dubbo rest默认的异常处理策略。为了简化操作,其实这里最简单的方式是直接继承dubbo rest的RpcExceptionMapper,并覆盖其中处理校验异常的方法即可:</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">MyValidationExceptionMapper</span> <span style="color:#268bd2">extends</span> RpcExceptionMapper <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">protected</span> Response <span style="color:#268bd2">handleConstraintViolationException</span><span style="color:#719e07">(</span>ConstraintViolationException cve<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> ViolationReport report <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> ViolationReport<span style="color:#719e07">();</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">for</span> <span style="color:#719e07">(</span>ConstraintViolation cv <span style="color:#719e07">:</span> cve<span style="color:#719e07">.</span>getConstraintViolations<span style="color:#719e07">())</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> report<span style="color:#719e07">.</span>addConstraintViolation<span style="color:#719e07">(</span><span style="color:#719e07">new</span> RestConstraintViolation<span style="color:#719e07">(</span> |
| </span></span><span style="display:flex;"><span> cv<span style="color:#719e07">.</span>getPropertyPath<span style="color:#719e07">().</span>toString<span style="color:#719e07">(),</span> |
| </span></span><span style="display:flex;"><span> cv<span style="color:#719e07">.</span>getMessage<span style="color:#719e07">(),</span> |
| </span></span><span style="display:flex;"><span> cv<span style="color:#719e07">.</span>getInvalidValue<span style="color:#719e07">()</span> <span style="color:#719e07">==</span> <span style="color:#cb4b16">null</span> <span style="color:#719e07">?</span> <span style="color:#2aa198">&#34;null&#34;</span> <span style="color:#719e07">:</span> cv<span style="color:#719e07">.</span>getInvalidValue<span style="color:#719e07">().</span>toString<span style="color:#719e07">()));</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">// 采用json输出代替xml输出 |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"></span> <span style="color:#719e07">return</span> Response<span style="color:#719e07">.</span>status<span style="color:#719e07">(</span>Response<span style="color:#719e07">.</span>Status<span style="color:#719e07">.</span>INTERNAL_SERVER_ERROR<span style="color:#719e07">).</span>entity<span style="color:#719e07">(</span>report<span style="color:#719e07">).</span>type<span style="color:#719e07">(</span>ContentType<span style="color:#719e07">.</span>APPLICATION_JSON_UTF_8<span style="color:#719e07">).</span>build<span style="color:#719e07">();</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>然后将这个ExceptionMapper添加到XML配置中即可:</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:protocol</span> name=<span style="color:#2aa198">&#34;rest&#34;</span> port=<span style="color:#2aa198">&#34;8888&#34;</span> extension=<span style="color:#2aa198">&#34;xxx.MyValidationExceptionMapper&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><h3 id="是否应该透明发布rest服务">是否应该透明发布REST服务</h3> |
| <p>Dubbo的REST调用和dubbo中其它某些RPC不同的是,需要在服务代码中添加JAX-RS的annotation(以及JAXB、Jackson的annotation),如果你觉得这些annotation一定程度“污染”了你的服务代码,你可以考虑编写额外的Facade和DTO类,在Facade和DTO上添加annotation,而Facade将调用转发给真正的服务实现类。当然事实上,直接在服务代码中添加annotation基本没有任何负面作用,而且这本身是Java EE的标准用法,另外JAX-RS和JAXB的annotation是属于java标准,比我们经常使用的spring、dubbo等等annotation更没有vendor lock-in的问题,所以一般没有必要因此而引入额外对象。</p> |
| <p>另外,如果你想用前述的@Context annotation,通过方法参数注入HttpServletRequest(如<code>public User getUser(@PathParam(&quot;id&quot;) Long id, @Context HttpServletRequest request)</code>),这时候由于改变了服务的方法签名,并且HttpServletRequest是REST特有的参数,<strong>所以如果你的服务要支持多种RPC机制的话</strong>,则引入额外的Facade类是比较适当的。</p> |
| <p>当然,在没有添加REST调用之前,你的服务代码可能本身已经就充当了Facade和DTO的角色(至于为什么有些场景需要这些角色,有兴趣可参考<a href="http://www.infoq.com/cn/articles/micro-soa-1">微观SOA:服务设计原则及其实践方式</a>)。这种情况下,在添加REST之后,如果你再额外添加与REST相关的Facade和DTO,就相当于对原有代码对再一次包装,即形成如下调用链:</p> |
| <p><code>RestFacade/RestDTO -&gt; Facade/DTO -&gt; Service</code></p> |
| <p>这种体系比较繁琐,数据转换之类的工作量也不小,所以一般应尽量避免如此。</p> |
| <h3 id="dubbo的提供端在调用rest服务时使用header">dubbo的提供端在调用REST服务时使用header</h3> |
| <p>Dubbo通过RpcContextFilter将header取出分解之后设置到RpcContext的attachments,所以在提供端可以直接从RpcContext的attachments中获取到消费端设置的header信息:</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> RpcContext.getContext().getAttachment(key1) |
| </span></span><span style="display:flex;"><span> RpcContext.getContext().getAttachment(key2) |
| </span></span></code></pre></div><h2 id="rest服务消费端详解">REST服务消费端详解</h2> |
| <p>这里我们用三种场景来分别讨论:</p> |
| <ol> |
| <li>非dubbo的消费端调用dubbo的REST服务(non-dubbo &ndash;&gt; dubbo)</li> |
| <li>dubbo消费端调用dubbo的REST服务 (dubbo &ndash;&gt; dubbo)</li> |
| <li>dubbo的消费端调用非dubbo的REST服务 (dubbo &ndash;&gt; non-dubbo)</li> |
| </ol> |
| <h3 id="场景1非dubbo的消费端调用dubbo的rest服务">场景1:非dubbo的消费端调用dubbo的REST服务</h3> |
| <p>这种场景的客户端与dubbo本身无关,直接选用相应语言和框架中合适的方式即可。</p> |
| <p>如果是还是java的客户端(但没用dubbo),可以考虑直接使用标准的JAX-RS Client API或者特定REST实现的Client API来调用REST服务。下面是用JAX-RS Client API来访问上述的UserService的registerUser():</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>User user <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> User<span style="color:#719e07">();</span> |
| </span></span><span style="display:flex;"><span>user<span style="color:#719e07">.</span>setName<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;Larry&#34;</span><span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span>Client client <span style="color:#719e07">=</span> ClientBuilder<span style="color:#719e07">.</span>newClient<span style="color:#719e07">();</span> |
| </span></span><span style="display:flex;"><span>WebTarget target <span style="color:#719e07">=</span> client<span style="color:#719e07">.</span>target<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;http://localhost:8080/services/users/register.json&#34;</span><span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span>Response response <span style="color:#719e07">=</span> target<span style="color:#719e07">.</span>request<span style="color:#719e07">().</span>post<span style="color:#719e07">(</span>Entity<span style="color:#719e07">.</span>entity<span style="color:#719e07">(</span>user<span style="color:#719e07">,</span> MediaType<span style="color:#719e07">.</span>APPLICATION_JSON_TYPE<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 style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">if</span> <span style="color:#719e07">(</span>response<span style="color:#719e07">.</span>getStatus<span style="color:#719e07">()</span> <span style="color:#719e07">!=</span> 200<span style="color:#719e07">)</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">throw</span> <span style="color:#719e07">new</span> RuntimeException<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;Failed with HTTP error code : &#34;</span> <span style="color:#719e07">+</span> response<span style="color:#719e07">.</span>getStatus<span style="color:#719e07">());</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span> System<span style="color:#719e07">.</span>out<span style="color:#719e07">.</span>println<span style="color:#719e07">(</span><span style="color:#2aa198">&#34;The generated id is &#34;</span> <span style="color:#719e07">+</span> response<span style="color:#719e07">.</span>readEntity<span style="color:#719e07">(</span>RegistrationResult<span style="color:#719e07">.</span>class<span style="color:#719e07">).</span>getId<span style="color:#719e07">());</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> <span style="color:#719e07">finally</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> response<span style="color:#719e07">.</span>close<span style="color:#719e07">();</span> |
| </span></span><span style="display:flex;"><span> client<span style="color:#719e07">.</span>close<span style="color:#719e07">();</span> <span style="color:#586e75">// 在真正开发中不要每次关闭client,比如HTTP长连接是由client持有的 |
| </span></span></span><span style="display:flex;"><span><span style="color:#586e75"></span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>上面代码片段中的User和RegistrationResult类都是消费端自己编写的,JAX-RS Client API会自动对它们做序列化/反序列化。</p> |
| <p>当然,在java中也可以直接用自己熟悉的比如HttpClient,FastJson,XStream等等各种不同技术来实现REST客户端,在此不再详述。</p> |
| <h3 id="场景2dubbo消费端调用dubbo的rest服务">场景2:dubbo消费端调用dubbo的REST服务</h3> |
| <p>这种场景下,和使用其他dubbo的远程调用方式一样,直接在服务提供端和服务消费端共享Java服务接口,并添加spring xml配置(当然也可以用spring/dubbo的annotation配置),即可透明的调用远程REST服务:</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:reference</span> id=<span style="color:#2aa198">&#34;userService&#34;</span> interface=<span style="color:#2aa198">&#34;xxx.UserService&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>如前所述,这种场景下必须把JAX-RS的annotation添加到服务接口上,这样在dubbo在消费端才能共享相应的REST配置信息,并据之做远程调用:</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">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;users&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">interface</span> <span style="color:#268bd2">UserService</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@GET</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;{id : \\d+}&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Produces</span><span style="color:#719e07">({</span>MediaType<span style="color:#719e07">.</span>APPLICATION_JSON<span style="color:#719e07">,</span> MediaType<span style="color:#719e07">.</span>APPLICATION_XML<span style="color:#719e07">})</span> |
| </span></span><span style="display:flex;"><span> User <span style="color:#268bd2">getUser</span><span style="color:#719e07">(</span><span style="color:#268bd2">@PathParam</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;id&#34;</span><span style="color:#719e07">)</span> Long id<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>如果服务接口的annotation中配置了多种数据格式,这里由于两端都是dubbo系统,REST的大量细节被屏蔽了,所以不存在用前述URL后缀之类选择数据格式的可能。目前在这种情况下,排名最靠前的数据格式将直接被使用。</p> |
| <p>因此,我们建议你在定义annotation的时候最好把最合适的数据格式放到前面,比如以上我们是把json放在xml前面,因为json的传输性能优于xml。</p> |
| <h3 id="场景3dubbo的消费端调用非dubbo的rest服务">场景3:dubbo的消费端调用非dubbo的REST服务</h3> |
| <p>这种场景下,可以直接用场景1中描述的Java的方式来调用REST服务。但其实也可以采用场景2中描述的方式,即更透明的调用REST服务,即使这个服务并不是dubbo提供的。</p> |
| <p>如果用场景2的方式,由于这里REST服务并非dubbo提供,一般也就没有前述的共享的Java服务接口,所以在此我们需要根据外部REST服务的情况,自己来编写Java接口以及相应参数类,并添加JAX-RS、JAXB、Jackson等的annotation,dubbo的REST底层实现会据此去自动生成请求消息,自动解析响应消息等等,从而透明的做远程调用。或者这种方式也可以理解为,我们尝试用JAX-RS的方式去仿造实现一遍外部的REST服务提供端,然后把写成服务接口放到客户端来直接使用,dubbo的REST底层实现就能像调用dubbo的REST服务一样调用其他REST服务。</p> |
| <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>http://api.foo.com/services/users/1001 |
| </span></span><span style="display:flex;"><span>http://api.foo.com/services/users/1002 |
| </span></span></code></pre></div><p>获取不同ID的用户资料,返回格式是JSON</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-javascript" data-lang="javascript"><span style="display:flex;"><span>{ |
| </span></span><span style="display:flex;"><span> <span style="color:#2aa198">&#34;id&#34;</span><span style="color:#719e07">:</span> <span style="color:#2aa198">1001</span>, |
| </span></span><span style="display:flex;"><span> <span style="color:#2aa198">&#34;name&#34;</span><span style="color:#719e07">:</span> <span style="color:#2aa198">&#34;Larry&#34;</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-java" data-lang="java"><span style="display:flex;"><span><span style="color:#268bd2">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;users&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">public</span> <span style="color:#268bd2">interface</span> <span style="color:#268bd2">UserService</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@GET</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Path</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;{id : \\d+}&#34;</span><span style="color:#719e07">)</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">@Produces</span><span style="color:#719e07">({</span>MediaType<span style="color:#719e07">.</span>APPLICATION_JSON<span style="color:#719e07">})</span> |
| </span></span><span style="display:flex;"><span> User <span style="color:#268bd2">getUser</span><span style="color:#719e07">(</span><span style="color:#268bd2">@PathParam</span><span style="color:#719e07">(</span><span style="color:#2aa198">&#34;id&#34;</span><span style="color:#719e07">)</span> Long id<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </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><span style="color:#268bd2">public</span> <span style="color:#268bd2">class</span> <span style="color:#268bd2">User</span> <span style="color:#268bd2">implements</span> Serializable <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> Long id<span style="color:#719e07">;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">private</span> String name<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:#586e75"></span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>对于spring中的配置,因为这里的REST服务不是dubbo提供的,所以无法使用dubbo的注册中心,直接配置外部REST服务的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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:reference</span> id=<span style="color:#2aa198">&#34;userService&#34;</span> interface=<span style="color:#2aa198">&#34;xxx.UserService&#34;</span> url=<span style="color:#2aa198">&#34;rest://api.foo.com/services/&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><blockquote> |
| <p>注意:这里协议必须用rest://而不是http://之类。如果外部的REST服务有context path,则在url中也必须添加上(除非你在每个服务接口的@Path annotation中都带上context path),例如上面的/services/。同时这里的services后面必须带上/,这样才能使dubbo正常工作。</p> |
| </blockquote> |
| <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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:reference</span> id=<span style="color:#2aa198">&#34;userService&#34;</span> interface=<span style="color:#2aa198">&#34;xxx.UserService&#34;</span> url=<span style="color:#2aa198">&#34;rest://api.foo.com/services/&#34;</span> timeout=<span style="color:#2aa198">&#34;2000&#34;</span> connections=<span style="color:#2aa198">&#34;10&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><h3 id="dubbo的消费端在调用rest服务时配置自定义header">dubbo的消费端在调用REST服务时配置自定义header</h3> |
| <p>Dubbo进行rest调用的时候,采用的是将RpcContext的attachment转换为header的方式,所以,dubbo消费端可以按以下方式进行自定义header的设置:</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> RpcContext.getContext().setAttachment(&#34;key1&#34;, &#34;value1&#34;); |
| </span></span><span style="display:flex;"><span> RpcContext.getContext().setAttachment(&#34;key2&#34;, &#34;value2&#34;); |
| </span></span></code></pre></div><p>即可设置如下格式的header:</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> key1=value1 |
| </span></span><span style="display:flex;"><span> key2=value2 |
| </span></span></code></pre></div><h2 id="dubbo中jax-rs的限制">Dubbo中JAX-RS的限制</h2> |
| <p>Dubbo中的REST开发是完全兼容标准JAX-RS的,但其支持的功能目前是完整JAX-RS的一个子集,部分因为它要受限于dubbo和spring的特定体系。</p> |
| <p>在dubbo中使用的JAX-RS的局限包括但不限于:</p> |
| <ol> |
| <li>服务实现只能是singleton的,不能支持per-request scope和per-lookup scope</li> |
| <li>不支持用@Context annotation对服务的实例字段注入 ServletConfig、ServletContext、HttpServletRequest、HttpServletResponse等等,但可以支持对服务方法参数的注入。但对某些特定REST server实现,(祥见前面的叙述),也不支持对服务方法参数的注入。</li> |
| </ol> |
| <h2 id="rest常见问题解答rest-faq">REST常见问题解答(REST FAQ)</h2> |
| <h3 id="dubbo-rest的服务能和dubbo注册中心监控中心集成吗">Dubbo REST的服务能和Dubbo注册中心、监控中心集成吗?</h3> |
| <p>可以的,而且是自动集成的,也就是你在dubbo中开发的所有REST服务都会自动注册到注册中心和监控中心,可以通过它们做管理。</p> |
| <p>但是,只有当REST的消费端也是基于dubbo的时候,注册中心中的许多服务治理操作才能完全起作用。而如果消费端是非dubbo的,自然不受注册中心管理,所以其中很多操作是不会对消费端起作用的。</p> |
| <h3 id="dubbo-rest中如何实现负载均衡和容错failover">Dubbo REST中如何实现负载均衡和容错(failover)?</h3> |
| <p>如果dubbo REST的消费端也是dubbo的,则Dubbo REST和其他dubbo远程调用协议基本完全一样,由dubbo框架透明的在消费端做load balance、failover等等。</p> |
| <p>如果dubbo REST的消费端是非dubbo的,甚至是非java的,则最好配置服务提供端的软负载均衡机制,目前可考虑用LVS、HAProxy、 Nginx等等对HTTP请求做负载均衡。</p> |
| <h3 id="jax-rs中重载的方法能够映射到同一url地址吗">JAX-RS中重载的方法能够映射到同一URL地址吗?</h3> |
| <p><a href="http://stackoverflow.com/questions/17196766/can-resteasy-choose-method-based-on-query-params">http://stackoverflow.com/questions/17196766/can-resteasy-choose-method-based-on-query-params</a></p> |
| <h3 id="jax-rs中作post的方法能够接收多个参数吗">JAX-RS中作POST的方法能够接收多个参数吗?</h3> |
| <p><a href="http://stackoverflow.com/questions/5553218/jax-rs-post-multiple-objects">http://stackoverflow.com/questions/5553218/jax-rs-post-multiple-objects</a></p> |
| <h2 id="dubbo当前体系的不足之处与rest相关的">Dubbo当前体系的不足之处(与REST相关的)</h2> |
| <p>我认为dubbo当前体系中显然也有不少不足之处,这里列出几个与REST有关的、并影响用户使用的问题(不包括内部实现的问题),供参考评论,为下一步重构作准备。</p> |
| <h3 id="rpccontext的侵入性">RpcContext的侵入性</h3> |
| <p>在前文,前面我们已经提到过RpcContext用法的侵入性,由于它是用单例的方式来访问上下文信息,这完全不符合spring应用的一般风格,不利于应用扩展和单元测试。未来我们可能用依赖注入方式注入一个接口,再用它去访问ThreadLocal中的上下文信息。</p> |
| <h3 id="protocol配置的局限性">Protocol配置的局限性</h3> |
| <p>dubbo支持多种远程调用方式,但所有调用方式都是用<code>&lt;dubbo:protocol/&gt;</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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:protocol</span> name=<span style="color:#2aa198">&#34;dubbo&#34;</span> port=<span style="color:#2aa198">&#34;9090&#34;</span> server=<span style="color:#2aa198">&#34;netty&#34;</span> client=<span style="color:#2aa198">&#34;netty&#34;</span> codec=<span style="color:#2aa198">&#34;dubbo&#34;</span> serialization=<span style="color:#2aa198">&#34;hessian2&#34;</span> |
| </span></span><span style="display:flex;"><span> charset=<span style="color:#2aa198">&#34;UTF-8&#34;</span> threadpool=<span style="color:#2aa198">&#34;fixed&#34;</span> threads=<span style="color:#2aa198">&#34;100&#34;</span> queues=<span style="color:#2aa198">&#34;0&#34;</span> iothreads=<span style="color:#2aa198">&#34;9&#34;</span> buffer=<span style="color:#2aa198">&#34;8192&#34;</span> accepts=<span style="color:#2aa198">&#34;1000&#34;</span> payload=<span style="color:#2aa198">&#34;8388608&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>其实,上面很多属性实际上dubbo RPC远程调用方式特有的,很多dubbo中的其它远程调用方式根本就不支持例如server, client, codec, iothreads, accepts, payload等等(当然,有的是条件所限不支持,有的是根本没有必要支持)。这给用户的使用徒增很多困惑,用户也并不知道有些属性(比如做性能调优)添加了实际上是不起作用的。</p> |
| <p>另一方面,各种远程调用方式往往有大量自己独特的配置需要,特别是我们逐步为每种远程调用方式都添加更丰富、更高级的功能,这就不可避免的扩展<code>&lt;protocol/&gt;</code>中的属性(例如目前我们在REST中已经添加了keepalive和extension两个属性),到最后会导致<code>&lt;protocol/&gt;</code>臃肿不堪,用户的使用也更加困惑。</p> |
| <p>当然,dubbo中有一种扩展<code>&lt;protocol/&gt;</code>的方式是用<code>&lt;dubbo:parameter/&gt;</code>,但这种方式显然很有局限性,而且用法复杂,缺乏schema校验。</p> |
| <p>所以,最好的方式是为每种远程调用方式设置自己的protocol元素,比如<code>&lt;protocol-dubbo/&gt;</code>,<code>&lt;protocol-rest/&gt;</code>等等,每种元素用XML schema规定自己的属性(当然属性在各种远程调用方式之间能通用是最好的)。</p> |
| <p>如此一来,例如前面提到过的extension配置也可以用更自由的方式,从而更清楚更可扩展(以下只是举例,当然也许有更好的方式):</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:protocol-rest</span> port=<span style="color:#2aa198">&#34;8080&#34;</span><span style="color:#268bd2">&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:extension&gt;</span>someInterceptor<span style="color:#268bd2">&lt;/dubbo:extension&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:extension&gt;</span>someFilter<span style="color:#268bd2">&lt;/dubbo:extension&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:extension&gt;</span>someDynamicFeature<span style="color:#268bd2">&lt;/dubbo:extension&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:extension&gt;</span>someEntityProvider<span style="color:#268bd2">&lt;/dubbo:extension&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/dubbo:protocol-rest&gt;</span> |
| </span></span></code></pre></div><h3 id="xml命名不符合spring规范">XML命名不符合spring规范</h3> |
| <p>dubbo的XML配置中大量命名都不符合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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:protocol</span> name=<span style="color:#2aa198">&#34;dubbo&#34;</span> port=<span style="color:#2aa198">&#34;9090&#34;</span> server=<span style="color:#2aa198">&#34;netty&#34;</span> client=<span style="color:#2aa198">&#34;netty&#34;</span> codec=<span style="color:#2aa198">&#34;dubbo&#34;</span> serialization=<span style="color:#2aa198">&#34;hessian2&#34;</span> |
| </span></span><span style="display:flex;"><span> charset=<span style="color:#2aa198">&#34;UTF-8&#34;</span> threadpool=<span style="color:#2aa198">&#34;fixed&#34;</span> threads=<span style="color:#2aa198">&#34;100&#34;</span> queues=<span style="color:#2aa198">&#34;0&#34;</span> iothreads=<span style="color:#2aa198">&#34;9&#34;</span> buffer=<span style="color:#2aa198">&#34;8192&#34;</span> accepts=<span style="color:#2aa198">&#34;1000&#34;</span> payload=<span style="color:#2aa198">&#34;8388608&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>上面threadpool应该改为thread-pool,iothreads应该改为io-threads,单词之间应该用&rdquo;-&ldquo;分隔。这虽然看起来是个小问题,但也涉及到了可读性,特别是可扩展性,因为有时候我们不可避免要用更多单词来描述XML元素和属性。</p> |
| <p>其实dubbo本身也是建议遵守spring到XML的命名规范。</p> |
| <h2 id="rest最佳实践">REST最佳实践</h2> |
| <p>TODO</p> |
| <h2 id="性能基准测试">性能基准测试</h2> |
| <h3 id="测试环境">测试环境</h3> |
| <p>粗略如下:</p> |
| <ul> |
| <li>两台独立服务器</li> |
| <li>4核Intel(R) Xeon(R) CPU E5-2603 0 @ 1.80GHz</li> |
| <li>8G内存</li> |
| <li>服务器之间网络通过百兆交换机</li> |
| <li>CentOS 5</li> |
| <li>JDK 7</li> |
| <li>Tomcat 7</li> |
| <li>JVM参数-server -Xms1g -Xmx1g -XX:PermSize=64M -XX:+UseConcMarkSweepGC</li> |
| </ul> |
| <h3 id="测试脚本">测试脚本</h3> |
| <p>和dubbo自身的基准测试保持接近:</p> |
| <p>10个并发客户端持续不断发出请求:</p> |
| <ul> |
| <li>传入嵌套复杂对象(但单个数据量很小),不做任何处理,原样返回</li> |
| <li>传入50K字符串,不做任何处理,原样返回(TODO:结果尚未列出)</li> |
| </ul> |
| <p>进行5分钟性能测试。(引用dubbo自身测试的考虑:“主要考察序列化和网络IO的性能,因此服务端无任何业务逻辑。取10并发是考虑到http协议在高并发下对CPU的使用率较高可能会先打到瓶颈。”)</p> |
| <h3 id="测试结果">测试结果</h3> |
| <p>下面的结果主要对比的是REST和dubbo RPC两种远程调用方式,并对它们作不同的配置,例如:</p> |
| <ul> |
| <li>“REST: Jetty + XML + GZIP”的意思是:测试REST,并采用jetty server,XML数据格式,启用GZIP压缩。</li> |
| <li>“Dubbo: hessian2”的意思是:测试dubbo RPC,并采用hessian2序列化方式。</li> |
| </ul> |
| <p>针对复杂对象的结果如下(响应时间越小越好,TPS越大越好):</p> |
| <table> |
| <thead> |
| <tr> |
| <th>远程调用方式</th> |
| <th>平均响应时间</th> |
| <th>平均TPS(每秒事务数)</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>REST: Jetty + JSON</td> |
| <td>7.806</td> |
| <td>1280</td> |
| </tr> |
| <tr> |
| <td>REST: Jetty + JSON + GZIP</td> |
| <td>TODO</td> |
| <td>TODO</td> |
| </tr> |
| <tr> |
| <td>REST: Jetty + XML</td> |
| <td>TODO</td> |
| <td>TODO</td> |
| </tr> |
| <tr> |
| <td>REST: Jetty + XML + GZIP</td> |
| <td>TODO</td> |
| <td>TODO</td> |
| </tr> |
| <tr> |
| <td>REST: Tomcat + JSON</td> |
| <td>2.082</td> |
| <td>4796</td> |
| </tr> |
| <tr> |
| <td>REST: Netty + JSON</td> |
| <td>2.182</td> |
| <td>4576</td> |
| </tr> |
| <tr> |
| <td>Dubbo: FST</td> |
| <td>1.211</td> |
| <td>8244</td> |
| </tr> |
| <tr> |
| <td>Dubbo: kyro</td> |
| <td>1.182</td> |
| <td>8444</td> |
| </tr> |
| <tr> |
| <td>Dubbo: dubbo serialization</td> |
| <td>1.43</td> |
| <td>6982</td> |
| </tr> |
| <tr> |
| <td>Dubbo: hessian2</td> |
| <td>1.49</td> |
| <td>6701</td> |
| </tr> |
| <tr> |
| <td>Dubbo: fastjson</td> |
| <td>1.572</td> |
| <td>6352</td> |
| </tr> |
| </tbody> |
| </table> |
| <p><img src="https://dubbo.apache.org/imgs/user/rt.png" alt="rt"></p> |
| <p><img src="https://dubbo.apache.org/imgs/user/tps.png" alt="tps"></p> |
| <p>仅就目前的结果,一点简单总结:</p> |
| <ul> |
| <li>dubbo RPC(特别是基于高效java序列化方式如kryo,fst)比REST的响应时间和吞吐量都有较显著优势,内网的dubbo系统之间优先选择dubbo RPC。</li> |
| <li>在REST的实现选择上,仅就性能而言,目前tomcat7和netty最优(当然目前使用的jetty和netty版本都较低)。tjws和sun http server在性能测试中表现极差,平均响应时间超过200ms,平均tps只有50左右(为了避免影响图片效果,没在上面列出)。</li> |
| <li>在REST中JSON数据格式性能优于XML(数据暂未在以上列出)。</li> |
| <li>在REST中启用GZIP对企业内网中的小数据量复杂对象帮助不大,性能反而有下降(数据暂未在以上列出)。</li> |
| </ul> |
| <h2 id="性能优化建议">性能优化建议</h2> |
| <p>如果将dubbo REST部署到外部Tomcat上,并配置server=&ldquo;servlet&rdquo;,即启用外部的tomcat来做为rest server的底层实现,则最好在tomcat上添加如下配置:</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;Connector</span> port=<span style="color:#2aa198">&#34;8080&#34;</span> protocol=<span style="color:#2aa198">&#34;org.apache.coyote.http11.Http11NioProtocol&#34;</span> |
| </span></span><span style="display:flex;"><span> connectionTimeout=<span style="color:#2aa198">&#34;20000&#34;</span> |
| </span></span><span style="display:flex;"><span> redirectPort=<span style="color:#2aa198">&#34;8443&#34;</span> |
| </span></span><span style="display:flex;"><span> minSpareThreads=<span style="color:#2aa198">&#34;20&#34;</span> |
| </span></span><span style="display:flex;"><span> enableLookups=<span style="color:#2aa198">&#34;false&#34;</span> |
| </span></span><span style="display:flex;"><span> maxThreads=<span style="color:#2aa198">&#34;100&#34;</span> |
| </span></span><span style="display:flex;"><span> maxKeepAliveRequests=<span style="color:#2aa198">&#34;-1&#34;</span> |
| </span></span><span style="display:flex;"><span> keepAliveTimeout=<span style="color:#2aa198">&#34;60000&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>特别是maxKeepAliveRequests=&quot;-1&rdquo;,这个配置主要是保证tomcat一直启用http长连接,以提高REST调用性能。但是请注意,如果REST消费端不是持续的调用REST服务,则一直启用长连接未必是最好的做法。另外,一直启用长连接的方式一般不适合针对普通webapp,更适合这种类似rpc的场景。所以为了高性能,在tomcat中,dubbo REST应用和普通web应用最好不要混合部署,而应该用单独的实例。</p> |
| <p>TODO more contents to add</p> |
| <h2 id="扩展讨论">扩展讨论</h2> |
| <h3 id="rest与thriftprotobuf等的对比">REST与Thrift、Protobuf等的对比</h3> |
| <p>TODO</p> |
| <h3 id="rest与传统webservices的对比">REST与传统WebServices的对比</h3> |
| <p>TODO</p> |
| <h3 id="jax-rs与spring-mvc的对比">JAX-RS与Spring MVC的对比</h3> |
| <p>初步看法,摘自http://www.infoq.com/cn/news/2014/10/dubbox-open-source?utm_source=infoq&amp;utm_medium=popular_links_homepage#theCommentsSection</p> |
| <blockquote> |
| <p>谢谢,对于jax-rs和spring mvc,其实我对spring mvc的rest支持还没有太深入的看过,说点初步想法,请大家指正:</p> |
| <p>spring mvc也支持annotation的配置,其实和jax-rs看起来是非常非常类似的。</p> |
| <p>我个人认为spring mvc相对更适合于面向web应用的restful服务,比如被AJAX调用,也可能输出HTML之类的,应用中还有页面跳转流程之类,spring mvc既可以做好正常的web页面请求也可以同时处理rest请求。但总的来说这个restful服务是在展现层或者叫web层之类实现的</p> |
| <p>而jax-rs相对更适合纯粹的服务化应用,也就是传统Java EE中所说的中间层服务,比如它可以把传统的EJB发布成restful服务。在spring应用中,也就把spring中充当service之类的bean直接发布成restful服务。总的来说这个restful服务是在业务、应用层或者facade层。而MVC层次和概念在这种做比如(后台)服务化的应用中通常是没有多大价值的。</p> |
| <p>当然jax-rs的有些实现比如jersey,也试图提供mvc支持,以更好的适应上面所说的web应用,但应该是不如spring mvc。</p> |
| <p>在dubbo应用中,我想很多人都比较喜欢直接将一个本地的spring service bean(或者叫manager之类的)完全透明的发布成远程服务,则这里用JAX-RS是更自然更直接的,不必额外的引入MVC概念。当然,先不讨论透明发布远程服务是不是最佳实践,要不要添加facade之类。</p> |
| <p>当然,我知道在dubbo不支持rest的情况下,很多朋友采用的架构是spring mvc restful调用dubbo (spring) service来发布restful服务的。这种方式我觉得也非常好,只是如果不修改spring mvc并将其与dubbo深度集成,restful服务不能像dubbo中的其他远程调用协议比如webservices、dubbo rpc、hessian等等那样,享受诸多高级的服务治理的功能,比如:注册到dubbo的服务注册中心,通过dubbo监控中心监控其调用次数、TPS、响应时间之类,通过dubbo的统一的配置方式控制其比如线程池大小、最大连接数等等,通过dubbo统一方式做服务流量控制、权限控制、频次控制。另外spring mvc仅仅负责服务端,而在消费端,通常是用spring restTemplate,如果restTemplate不和dubbo集成,有可能像dubbo服务客户端那样自动或者人工干预做服务降级。如果服务端消费端都是dubbo系统,通过spring的rest交互,如果spring rest不深度整合dubbo,则不能用dubbo统一的路由分流等功能。</p> |
| <p>当然,其实我个人认为这些东西不必要非此即彼的。我听说spring创始人rod johnson总是爱说一句话,the customer is always right,其实与其非要探讨哪种方式更好,不如同时支持两种方式就是了,所以原来在文档中也写过计划支持spring rest annoation,只是不知道具体可行性有多高。</p> |
| </blockquote> |
| <h2 id="未来">未来</h2> |
| <p>稍后可能要实现的功能:</p> |
| <ul> |
| <li>spring mvc的rest annotation支持</li> |
| <li>安全</li> |
| <li>OAuth</li> |
| <li>异步调用</li> |
| <li>完善gzip</li> |
| <li>最大payload限制</li> |
| </ul></description></item><item><title>Docsv2.7: 简单监控</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/simple-monitor/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/simple-monitor/</guid><description> |
| <div class="alert alert-warning" role="alert"> |
| <h4 class="alert-heading">Warning</h4> |
| 监控中心也是一个标准的 Dubbo 服务,可以通过注册中心发现,也可以直连。 |
| </div> |
| <ol> |
| <li> |
| <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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;beans</span> xmlns=<span style="color:#2aa198">&#34;http://www.springframework.org/schema/beans&#34;</span> |
| </span></span><span style="display:flex;"><span> xmlns:xsi=<span style="color:#2aa198">&#34;http://www.w3.org/2001/XMLSchema-instance&#34;</span> |
| </span></span><span style="display:flex;"><span> xmlns:dubbo=<span style="color:#2aa198">&#34;http://dubbo.apache.org/schema/dubbo&#34;</span> |
| </span></span><span style="display:flex;"><span> xsi:schemaLocation=<span style="color:#2aa198">&#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd&#34;</span><span style="color:#268bd2">&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 当前应用信息配置 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:application</span> name=<span style="color:#2aa198">&#34;simple-monitor&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 连接注册中心配置 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:registry</span> address=<span style="color:#2aa198">&#34;127.0.0.1:9090&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 暴露服务协议配置 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:protocol</span> port=<span style="color:#2aa198">&#34;7070&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 暴露服务配置 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:service</span> interface=<span style="color:#2aa198">&#34;org.apache.dubbo.monitor.MonitorService&#34;</span> ref=<span style="color:#2aa198">&#34;monitorService&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;bean</span> id=<span style="color:#2aa198">&#34;monitorService&#34;</span> class=<span style="color:#2aa198">&#34;org.apache.dubbo.monitor.simple.SimpleMonitorService&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/beans&gt;</span> |
| </span></span></code></pre></div></li> |
| <li> |
| <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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:monitor</span> protocol=<span style="color:#2aa198">&#34;registry&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>或者在 dubbo.properties 配置:</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-properties" data-lang="properties"><span style="display:flex;"><span>dubbo.monitor.protocol<span style="color:#719e07">=</span><span style="color:#2aa198">registry</span> |
| </span></span></code></pre></div></li> |
| <li> |
| <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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;beans</span> xmlns=<span style="color:#2aa198">&#34;http://www.springframework.org/schema/beans&#34;</span> |
| </span></span><span style="display:flex;"><span> xmlns:xsi=<span style="color:#2aa198">&#34;http://www.w3.org/2001/XMLSchema-instance&#34;</span> |
| </span></span><span style="display:flex;"><span> xmlns:dubbo=<span style="color:#2aa198">&#34;http://dubbo.apache.org/schema/dubbo&#34;</span> |
| </span></span><span style="display:flex;"><span> xsi:schemaLocation=<span style="color:#2aa198">&#34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd&#34;</span><span style="color:#268bd2">&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 当前应用信息配置 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:application</span> name=<span style="color:#2aa198">&#34;simple-monitor&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 暴露服务协议配置 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:protocol</span> port=<span style="color:#2aa198">&#34;7070&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#586e75">&lt;!-- 暴露服务配置 --&gt;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;dubbo:service</span> interface=<span style="color:#2aa198">&#34;org.apache.dubbo.monitor.MonitorService&#34;</span> ref=<span style="color:#2aa198">&#34;monitorService&#34;</span> registry=<span style="color:#2aa198">&#34;N/A&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">&lt;bean</span> id=<span style="color:#2aa198">&#34;monitorService&#34;</span> class=<span style="color:#2aa198">&#34;org.apache.dubbo.monitor.simple.SimpleMonitorService&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span><span style="display:flex;"><span><span style="color:#268bd2">&lt;/beans&gt;</span> |
| </span></span></code></pre></div></li> |
| <li> |
| <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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:monitor</span> address=<span style="color:#2aa198">&#34;dubbo://127.0.0.1:7070/org.apache.dubbo.monitor.MonitorService&#34;</span> <span style="color:#268bd2">/&gt;</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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:monitor</span> address=<span style="color:#2aa198">&#34;127.0.0.1:7070&#34;</span> <span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>或者在 dubbo.properties 中配置:</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-properties" data-lang="properties"><span style="display:flex;"><span>dubbo.monitor.address<span style="color:#719e07">=</span><span style="color:#2aa198">127.0.0.1:7070</span> |
| </span></span></code></pre></div></li> |
| </ol></description></item><item><title>Docsv2.7: Kryo 和 FST 序列化</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/serialization/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/serialization/</guid><description> |
| <h2 id="目录">目录</h2> |
| <ul> |
| <li>序列化漫谈</li> |
| <li>启用Kryo和FST</li> |
| <li>注册被序列化类</li> |
| <li>无参构造函数和Serializable接口</li> |
| <li>序列化性能分析与测试 |
| <ul> |
| <li>测试环境</li> |
| <li>测试脚本</li> |
| <li>Dubbo RPC中不同序列化生成字节大小比较</li> |
| <li>Dubbo RPC中不同序列化响应时间和吞吐量对比</li> |
| </ul> |
| </li> |
| <li>未来</li> |
| </ul> |
| <h2 id="序列化漫谈">序列化漫谈</h2> |
| <p>dubbo RPC是dubbo体系中最核心的一种高性能、高吞吐量的远程调用方式,我喜欢称之为多路复用的TCP长连接调用,简单的说:</p> |
| <ul> |
| <li>长连接:避免了每次调用新建TCP连接,提高了调用的响应速度</li> |
| <li>多路复用:单个TCP连接可交替传输多个请求和响应的消息,降低了连接的等待闲置时间,从而减少了同样并发数下的网络连接数,提高了系统吞吐量。</li> |
| </ul> |
| <p>dubbo RPC主要用于两个dubbo系统之间作远程调用,特别适合高并发、小数据的互联网场景。</p> |
| <p>而序列化对于远程调用的响应速度、吞吐量、网络带宽消耗等同样也起着至关重要的作用,是我们提升分布式系统性能的最关键因素之一。</p> |
| <p>在dubbo RPC中,同时支持多种序列化方式,例如:</p> |
| <ol> |
| <li>dubbo序列化:阿里尚未开发成熟的高效java序列化实现,阿里不建议在生产环境使用它</li> |
| <li>hessian2序列化:hessian是一种跨语言的高效二进制序列化方式。但这里实际不是原生的hessian2序列化,而是阿里修改过的hessian lite,它是dubbo RPC默认启用的序列化方式</li> |
| <li>json序列化:目前有两种实现,一种是采用的阿里的fastjson库,另一种是采用dubbo中自己实现的简单json库,但其实现都不是特别成熟,而且json这种文本序列化性能一般不如上面两种二进制序列化。</li> |
| <li>java序列化:主要是采用JDK自带的Java序列化实现,性能很不理想。</li> |
| </ol> |
| <p>在通常情况下,这四种主要序列化方式的性能从上到下依次递减。对于dubbo RPC这种追求高性能的远程调用方式来说,实际上只有1、2两种高效序列化方式比较般配,而第1个dubbo序列化由于还不成熟,所以实际只剩下2可用,所以dubbo RPC默认采用hessian2序列化。</p> |
| <p>但hessian是一个比较老的序列化实现了,而且它是跨语言的,所以不是单独针对java进行优化的。而dubbo RPC实际上完全是一种Java to Java的远程调用,其实没有必要采用跨语言的序列化方式(当然肯定也不排斥跨语言的序列化)。</p> |
| <p>最近几年,各种新的高效序列化方式层出不穷,不断刷新序列化性能的上限,最典型的包括:</p> |
| <ul> |
| <li>专门针对Java语言的:Kryo,FST等等</li> |
| <li>跨语言的:Protostuff,ProtoBuf,Thrift,Avro,MsgPack等等</li> |
| </ul> |
| <p>这些序列化方式的性能多数都显著优于hessian2(甚至包括尚未成熟的dubbo序列化)。</p> |
| <p>有鉴于此,我们为dubbo引入Kryo和FST这两种高效Java序列化实现,来逐步取代hessian2。</p> |
| <p>其中,Kryo是一种非常成熟的序列化实现,已经在Twitter、Groupon、Yahoo以及多个著名开源项目(如Hive、Storm)中广泛的使用。而FST是一种较新的序列化实现,目前还缺乏足够多的成熟使用案例,但我认为它还是非常有前途的。</p> |
| <p>在面向生产环境的应用中,我建议目前更优先选择Kryo。</p> |
| <h2 id="启用kryo和fst">启用Kryo和FST</h2> |
| <p>使用Kryo和FST非常简单,只需要在dubbo RPC的XML配置中添加一个属性即可:</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:protocol</span> name=<span style="color:#2aa198">&#34;dubbo&#34;</span> serialization=<span style="color:#2aa198">&#34;kryo&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </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-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#268bd2">&lt;dubbo:protocol</span> name=<span style="color:#2aa198">&#34;dubbo&#34;</span> serialization=<span style="color:#2aa198">&#34;fst&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><h2 id="注册被序列化类">注册被序列化类</h2> |
| <p>要让Kryo和FST完全发挥出高性能,最好将那些需要被序列化的类注册到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">SerializationOptimizerImpl</span> <span style="color:#268bd2">implements</span> SerializationOptimizer <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> |
| </span></span><span style="display:flex;"><span> <span style="color:#268bd2">public</span> Collection<span style="color:#719e07">&lt;</span>Class<span style="color:#719e07">&gt;</span> <span style="color:#268bd2">getSerializableClasses</span><span style="color:#719e07">()</span> <span style="color:#719e07">{</span> |
| </span></span><span style="display:flex;"><span> List<span style="color:#719e07">&lt;</span>Class<span style="color:#719e07">&gt;</span> classes <span style="color:#719e07">=</span> <span style="color:#719e07">new</span> LinkedList<span style="color:#719e07">&lt;</span>Class<span style="color:#719e07">&gt;();</span> |
| </span></span><span style="display:flex;"><span> classes<span style="color:#719e07">.</span>add<span style="color:#719e07">(</span>BidRequest<span style="color:#719e07">.</span>class<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> classes<span style="color:#719e07">.</span>add<span style="color:#719e07">(</span>BidResponse<span style="color:#719e07">.</span>class<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> classes<span style="color:#719e07">.</span>add<span style="color:#719e07">(</span>Device<span style="color:#719e07">.</span>class<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> classes<span style="color:#719e07">.</span>add<span style="color:#719e07">(</span>Geo<span style="color:#719e07">.</span>class<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> classes<span style="color:#719e07">.</span>add<span style="color:#719e07">(</span>Impression<span style="color:#719e07">.</span>class<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> classes<span style="color:#719e07">.</span>add<span style="color:#719e07">(</span>SeatBid<span style="color:#719e07">.</span>class<span style="color:#719e07">);</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">return</span> classes<span style="color:#719e07">;</span> |
| </span></span><span style="display:flex;"><span> <span style="color:#719e07">}</span> |
| </span></span><span style="display:flex;"><span><span style="color:#719e07">}</span> |
| </span></span></code></pre></div><p>然后在XML配置中添加:</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:protocol</span> name=<span style="color:#2aa198">&#34;dubbo&#34;</span> serialization=<span style="color:#2aa198">&#34;kryo&#34;</span> optimizer=<span style="color:#2aa198">&#34;org.apache.dubbo.demo.SerializationOptimizerImpl&#34;</span><span style="color:#268bd2">/&gt;</span> |
| </span></span></code></pre></div><p>在注册这些类后,序列化的性能可能被大大提升,特别针对小数量的嵌套对象的时候。</p> |
| <p>当然,在对一个类做序列化的时候,可能还级联引用到很多类,比如Java集合类。针对这种情况,我们已经自动将JDK中的常用类进行了注册,所以你不需要重复注册它们(当然你重复注册了也没有任何影响),包括:</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>GregorianCalendar |
| </span></span><span style="display:flex;"><span>InvocationHandler |
| </span></span><span style="display:flex;"><span>BigDecimal |
| </span></span><span style="display:flex;"><span>BigInteger |
| </span></span><span style="display:flex;"><span>Pattern |
| </span></span><span style="display:flex;"><span>BitSet |
| </span></span><span style="display:flex;"><span>URI |
| </span></span><span style="display:flex;"><span>UUID |
| </span></span><span style="display:flex;"><span>HashMap |
| </span></span><span style="display:flex;"><span>ArrayList |
| </span></span><span style="display:flex;"><span>LinkedList |
| </span></span><span style="display:flex;"><span>HashSet |
| </span></span><span style="display:flex;"><span>TreeSet |
| </span></span><span style="display:flex;"><span>Hashtable |
| </span></span><span style="display:flex;"><span>Date |
| </span></span><span style="display:flex;"><span>Calendar |
| </span></span><span style="display:flex;"><span>ConcurrentHashMap |
| </span></span><span style="display:flex;"><span>SimpleDateFormat |
| </span></span><span style="display:flex;"><span>Vector |
| </span></span><span style="display:flex;"><span>BitSet |
| </span></span><span style="display:flex;"><span>StringBuffer |
| </span></span><span style="display:flex;"><span>StringBuilder |
| </span></span><span style="display:flex;"><span>Object |
| </span></span><span style="display:flex;"><span>Object[] |
| </span></span><span style="display:flex;"><span>String[] |
| </span></span><span style="display:flex;"><span>byte[] |
| </span></span><span style="display:flex;"><span>char[] |
| </span></span><span style="display:flex;"><span>int[] |
| </span></span><span style="display:flex;"><span>float[] |
| </span></span><span style="display:flex;"><span>double[] |
| </span></span></code></pre></div><p>由于注册被序列化的类仅仅是出于性能优化的目的,所以即使你忘记注册某些类也没有关系。事实上,即使不注册任何类,Kryo和FST的性能依然普遍优于hessian和dubbo序列化。</p> |
| <blockquote> |
| <p>当然,有人可能会问为什么不用配置文件来注册这些类?这是因为要注册的类往往数量较多,导致配置文件冗长;而且在没有好的IDE支持的情况下,配置文件的编写和重构都比java类麻烦得多;最后,这些注册的类一般是不需要在项目编译打包后还需要做动态修改的。</p> |
| </blockquote> |
| <blockquote> |
| <p>另外,有人也会觉得手工注册被序列化的类是一种相对繁琐的工作,是不是可以用annotation来标注,然后系统来自动发现并注册。但这里annotation的局限是,它只能用来标注你可以修改的类,而很多序列化中引用的类很可能是你没法做修改的(比如第三方库或者JDK系统类或者其他项目的类)。另外,添加annotation毕竟稍微的“污染”了一下代码,使应用代码对框架增加了一点点的依赖性。</p> |
| </blockquote> |
| <blockquote> |
| <p>除了annotation,我们还可以考虑用其它方式来自动注册被序列化的类,例如扫描类路径,自动发现实现Serializable接口(甚至包括Externalizable)的类并将它们注册。当然,我们知道类路径上能找到Serializable类可能是非常多的,所以也可以考虑用package前缀之类来一定程度限定扫描范围。</p> |
| </blockquote> |
| <blockquote> |
| <p>当然,在自动注册机制中,特别需要考虑如何保证服务提供端和消费端都以同样的顺序(或者ID)来注册类,避免错位,毕竟两端可被发现然后注册的类的数量可能都是不一样的。</p> |
| </blockquote> |
| <h2 id="无参构造函数和serializable接口">无参构造函数和Serializable接口</h2> |
| <p>如果被序列化的类中不包含无参的构造函数,则在Kryo的序列化中,性能将会大打折扣,因为此时我们在底层将用Java的序列化来透明的取代Kryo序列化。所以,尽可能为每一个被序列化的类添加无参构造函数是一种最佳实践(当然一个java类如果不自定义构造函数,默认就有无参构造函数)。</p> |
| <p>另外,Kryo和FST本来都不需要被序列化的类实现Serializable接口,但我们还是建议每个被序列化类都去实现它,因为这样可以保持和Java序列化以及dubbo序列化的兼容性,另外也使我们未来采用上述某些自动注册机制带来可能。</p> |
| <h2 id="序列化性能分析与测试">序列化性能分析与测试</h2> |
| <p>本文我们主要讨论的是序列化,但在做性能分析和测试的时候我们并不单独处理每种序列化方式,而是把它们放到dubbo RPC中加以对比,因为这样更有现实意义。</p> |
| <h3 id="测试环境">测试环境</h3> |
| <p>粗略如下:</p> |
| <ul> |
| <li>两台独立服务器</li> |
| <li>4核Intel(R) Xeon(R) CPU E5-2603 0 @ 1.80GHz</li> |
| <li>8G内存</li> |
| <li>虚拟机之间网络通过百兆交换机</li> |
| <li>CentOS 5</li> |
| <li>JDK 7</li> |
| <li>Tomcat 7</li> |
| <li>JVM参数-server -Xms1g -Xmx1g -XX:PermSize=64M -XX:+UseConcMarkSweepGC</li> |
| </ul> |
| <p>当然这个测试环境较有局限,故当前测试结果未必有非常权威的代表性。</p> |
| <h3 id="测试脚本">测试脚本</h3> |
| <p>和dubbo自身的基准测试保持接近:</p> |
| <p>10个并发客户端持续不断发出请求:</p> |
| <ul> |
| <li>传入嵌套复杂对象(但单个数据量很小),不做任何处理,原样返回</li> |
| <li>传入50K字符串,不做任何处理,原样返回(TODO:结果尚未列出)</li> |
| </ul> |
| <p>进行5分钟性能测试。(引用dubbo自身测试的考虑:“主要考察序列化和网络IO的性能,因此服务端无任何业务逻辑。取10并发是考虑到rpc协议在高并发下对CPU的使用率较高可能会先打到瓶颈。”)</p> |
| <h3 id="dubbo-rpc中不同序列化生成字节大小比较">Dubbo RPC中不同序列化生成字节大小比较</h3> |
| <p>序列化生成字节数的大小是一个比较有确定性的指标,它决定了远程调用的网络传输时间和带宽占用。</p> |
| <p>针对复杂对象的结果如下(数值越小越好):</p> |
| <table> |
| <thead> |
| <tr> |
| <th>序列化实现</th> |
| <th>请求字节数</th> |
| <th>响应字节数</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>Kryo</td> |
| <td>272</td> |
| <td>90</td> |
| </tr> |
| <tr> |
| <td>FST</td> |
| <td>288</td> |
| <td>96</td> |
| </tr> |
| <tr> |
| <td>Dubbo Serialization</td> |
| <td>430</td> |
| <td>186</td> |
| </tr> |
| <tr> |
| <td>Hessian</td> |
| <td>546</td> |
| <td>329</td> |
| </tr> |
| <tr> |
| <td>FastJson</td> |
| <td>461</td> |
| <td>218</td> |
| </tr> |
| <tr> |
| <td>Json</td> |
| <td>657</td> |
| <td>409</td> |
| </tr> |
| <tr> |
| <td>Java Serialization</td> |
| <td>963</td> |
| <td>630</td> |
| </tr> |
| </tbody> |
| </table> |
| <h3 id="dubbo-rpc中不同序列化响应时间和吞吐量对比">Dubbo RPC中不同序列化响应时间和吞吐量对比</h3> |
| <table> |
| <thead> |
| <tr> |
| <th>远程调用方式</th> |
| <th>平均响应时间</th> |
| <th>平均TPS(每秒事务数)</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>REST: Jetty + JSON</td> |
| <td>7.806</td> |
| <td>1280</td> |
| </tr> |
| <tr> |
| <td>REST: Jetty + JSON + GZIP</td> |
| <td>TODO</td> |
| <td>TODO</td> |
| </tr> |
| <tr> |
| <td>REST: Jetty + XML</td> |
| <td>TODO</td> |
| <td>TODO</td> |
| </tr> |
| <tr> |
| <td>REST: Jetty + XML + GZIP</td> |
| <td>TODO</td> |
| <td>TODO</td> |
| </tr> |
| <tr> |
| <td>REST: Tomcat + JSON</td> |
| <td>2.082</td> |
| <td>4796</td> |
| </tr> |
| <tr> |
| <td>REST: Netty + JSON</td> |
| <td>2.182</td> |
| <td>4576</td> |
| </tr> |
| <tr> |
| <td>Dubbo: FST</td> |
| <td>1.211</td> |
| <td>8244</td> |
| </tr> |
| <tr> |
| <td>Dubbo: kyro</td> |
| <td>1.182</td> |
| <td>8444</td> |
| </tr> |
| <tr> |
| <td>Dubbo: dubbo serialization</td> |
| <td>1.43</td> |
| <td>6982</td> |
| </tr> |
| <tr> |
| <td>Dubbo: hessian2</td> |
| <td>1.49</td> |
| <td>6701</td> |
| </tr> |
| <tr> |
| <td>Dubbo: fastjson</td> |
| <td>1.572</td> |
| <td>6352</td> |
| </tr> |
| </tbody> |
| </table> |
| <p><img src="https://dubbo.apache.org/imgs/user/rt.png" alt="rt"></p> |
| <p><img src="https://dubbo.apache.org/imgs/user/tps.png" alt="tps"></p> |
| <h3 id="测试总结">测试总结</h3> |
| <p>就目前结果而言,我们可以看到不管从生成字节的大小,还是平均响应时间和平均TPS,Kryo和FST相比Dubbo RPC中原有的序列化方式都有非常显著的改进。</p> |
| <h2 id="未来">未来</h2> |
| <p>未来,当Kryo或者FST在dubbo中当应用足够成熟之后,我们很可能会将dubbo RPC的默认序列化从hessian2改为它们中间的某一个。</p></description></item><item><title>Docsv2.7: 其他语言支持</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/languages/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/languages/</guid><description/></item><item><title>Docsv2.7:</title><link>https://dubbo.apache.org/zh-cn/docsv2.7/user/new-features-in-a-glance/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://dubbo.apache.org/zh-cn/docsv2.7/user/new-features-in-a-glance/</guid><description> |
| <h1 id="dubbo-版本发布及新特性速览">Dubbo 版本发布及新特性速览</h1> |
| <h2 id="版本速览">版本速览</h2> |
| <p>Dubbo 社区目前主力维护的有 2.6.x 和 2.7.x 两大版本,其中,</p> |
| <ul> |
| <li>2.6.x 主要以 bugfix 和少量 enhancements 为主,因此能完全保证稳定性</li> |
| <li>2.7.x 作为社区的主要开发版本,得到持续更新并增加了大量新 feature 和优化,同时也带来了一些稳定性挑战</li> |
| </ul> |
| <h3 id="27x-版本">2.7.x 版本</h3> |
| <table> |
| <thead> |
| <tr> |
| <th></th> |
| <th>版本</th> |
| <th>重要功能</th> |
| <th>升级建议</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>1</td> |
| <td>2.7.6</td> |
| <td>bugfix of 2.7.5<br /> 服务鉴权</td> |
| <td><strong>推荐生产使用</strong></td> |
| </tr> |
| <tr> |
| <td>2</td> |
| <td>2.7.5</td> |
| <td>服务自省<br />HTTP/2(gRPC) <br />Protobuf <br />TLS<br />性能优化<br /><br /><a href="https://github.com/apache/dubbo/releases/tag/dubbo-2.7.5">https://github.com/apache/dubbo/releases/tag/dubbo-2.7.5</a></td> |
| <td>不建议大规模生产使用</td> |
| </tr> |
| <tr> |
| <td>3</td> |
| <td>2.7.4.1</td> |
| <td><a href="https://github.com/apache/dubbo/releases/tag/dubbo-2.7.4.1">bugfixes and enhancements of 2.7.3</a></td> |
| <td><strong>推荐生产使用</strong></td> |
| </tr> |
| <tr> |
| <td>4</td> |
| <td>2.7.3</td> |
| <td><a href="https://github.com/apache/dubbo/releases/tag/dubbo-2.7.3">bigfixes of and enhancements of 2.7.2</a></td> |
| <td><strong>推荐生产使用</strong></td> |
| </tr> |
| <tr> |
| <td>5</td> |
| <td>2.7.2</td> |
| <td><a href="https://github.com/apache/dubbo/releases/tag/dubbo-2.7.2">bigfixes of and enhancements of 2.7.1</a></td> |
| <td>不建议大规模生产使用</td> |
| </tr> |
| <tr> |
| <td>6</td> |
| <td>2.7.1</td> |
| <td><a href="https://github.com/apache/dubbo/releases/tag/dubbo-2.7.1">bigfixes of and enhancements of 2.7.0</a></td> |
| <td>不建议大规模生产使用</td> |
| </tr> |
| <tr> |
| <td>7</td> |
| <td>2.7.0</td> |
| <td>异步编程模型 - 消费端/提供端异步<br />服务治理规则增强<br />简化的注册模型<br />配置中心、元数据中心<br />package 重构<br /><br /><a href="https://github.com/apache/dubbo/releases/tag/dubbo-2.7.0">https://github.com/apache/dubbo/releases/tag/dubbo-2.7.0</a></td> |
| <td>beta 版本,2.6.x 重构后首个版本</td> |
| </tr> |
| </tbody> |
| </table> |
| <h3 id="26x-及之前版本">2.6.x 及之前版本</h3> |
| <table> |
| <thead> |
| <tr> |
| <th></th> |
| <th>版本</th> |
| <th>重要功能</th> |
| <th>升级建议</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>1</td> |
| <td>2.6.x</td> |
| <td>bugfix</td> |
| <td>建议持续升级最新版本,所有版本生产可用</td> |
| </tr> |
| <tr> |
| <td>2</td> |
| <td>2.5.x</td> |
| <td>停止维护</td> |
| <td>建议升级最新 2.6.x 版本</td> |
| </tr> |
| <tr> |
| <td>3</td> |
| <td>2.4.x 及之前</td> |
| <td>停止维护</td> |
| <td>建议升级最新 2.6.x 版本</td> |
| </tr> |
| </tbody> |
| </table> |
| <h2 id="值得关注的新特性">值得关注的新特性</h2> |
| <ul> |
| <li>Dubbo 云原生计划(敬请期待&hellip;)</li> |
| <li>Kubernetes Native Service Discovery(敬请期待&hellip;)</li> |
| <li><a href="../references/protocol/grpc/">gRPC (HTTP/2) 协议</a></li> |
| <li><a href="../examples/protobuf-idl/">使用 Protobuf 定义 Dubbo 服务</a></li> |
| <li><a href="../examples/tls/">TLS 安全传输</a></li> |
| <li>实例级服务发现</li> |
| <li><a href="../examples/auth/">服务鉴权</a></li> |
| <li>性能优化 |
| <ul> |
| <li><a href="https://dubbo.apache.org/zh-cn/blog/2020/05/18/dubbo-java-2.7.5-%E5%8A%9F%E8%83%BD%E8%A7%A3%E6%9E%90/">调用链路提升 30%</a></li> |
| <li><a href="../examples/consumer-threadpool/">消费端线程模型</a></li> |
| <li>地址推送链路</li> |
| </ul> |
| </li> |
| </ul> |
| <h2 id="热门文章列表">热门文章列表</h2> |
| <p><a href="https://dubbo.apache.org/zh-cn/blog/2020/05/11/%E4%BB%8E-2019-%E5%88%B0-2020apache-dubbo-%E5%B9%B4%E5%BA%A6%E5%9B%9E%E9%A1%BE%E4%B8%8E%E6%80%BB%E7%BB%93/">从 2019 到 2020,Apache Dubbo 年度总结</a><br> |
| <a href="https://dubbo.apache.org/zh-cn/blog/2020/05/18/dubbo-java-2.7.5-%E5%8A%9F%E8%83%BD%E8%A7%A3%E6%9E%90/">Dubbo 2.7.5 里程碑版本发布</a><br> |
| <a href="https://dubbo.apache.org/zh-cn/blog/2019/10/28/dubbo-%E5%9C%A8%E8%B7%A8%E8%AF%AD%E8%A8%80%E5%92%8C%E5%8D%8F%E8%AE%AE%E7%A9%BF%E9%80%8F%E6%80%A7%E6%96%B9%E5%90%91%E4%B8%8A%E7%9A%84%E6%8E%A2%E7%B4%A2%E6%94%AF%E6%8C%81-http/2-grpc-%E5%92%8C-protobuf/">Dubbo 在协议与多语言方向的探索:支持 gRPC、Protobuf</a></p></description></item></channel></rss> |