You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by il...@apache.org on 2019/04/09 06:51:10 UTC
[incubator-dubbo-website] branch asf-site updated: add blog for
dubbo stub and dubbo mock, and rebuild
This is an automated email from the ASF dual-hosted git repository.
iluo pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/incubator-dubbo-website.git
The following commit(s) were added to refs/heads/asf-site by this push:
new 36219a7 add blog for dubbo stub and dubbo mock, and rebuild
36219a7 is described below
commit 36219a7756ada5130845da074a1d29980ccdde8d
Author: Ian Luo <ia...@gmail.com>
AuthorDate: Tue Apr 9 14:50:40 2019 +0800
add blog for dubbo stub and dubbo mock, and rebuild
---
blog/zh-cn/dubbo-stub-mock.md | 313 ++++++++++++++++++++++++++++++++++++++
build/blog.js | 4 +-
en-us/blog/download.html | 9 ++
en-us/blog/download.json | 2 +-
img/blog/dubbo-mock-stub-flow.png | Bin 0 -> 20020 bytes
md_json/blog.json | 5 +
site_config/blog.js | 7 +
zh-cn/blog/download.html | 9 ++
zh-cn/blog/download.json | 2 +-
zh-cn/blog/dubbo-stub-mock.html | 238 +++++++++++++++++++++++++++++
zh-cn/blog/dubbo-stub-mock.json | 6 +
zh-cn/blog/index.html | 2 +-
12 files changed, 592 insertions(+), 5 deletions(-)
diff --git a/blog/zh-cn/dubbo-stub-mock.md b/blog/zh-cn/dubbo-stub-mock.md
new file mode 100644
index 0000000..fa7a3af
--- /dev/null
+++ b/blog/zh-cn/dubbo-stub-mock.md
@@ -0,0 +1,313 @@
+# 本地存根和本地伪装
+
+## 基本概念
+
+典型的 RPC 调用客户端是依赖并且只依赖接口编程来进行远程调用的。在真正发起远程调用之前,用户往往需要做一些预处理的工作,比如提前校验参数。在拿到返回调用结果之后,用户可能需要缓存结果,或者是在调用失败的时候构造容错数据,而不是简单的抛出异常。
+
+
+
+这个时候,用户可以编写出类似以下的代码来处理上面提出的这些场景:
+
+```java
+try {
+ preProcess();
+ return service.invoke(...);
+} catch (Throwable e) {
+ return mockValue;
+} finally {
+ postProcess();
+}
+```
+
+
+
+类似的,用户也可以通过面向切面编程 *AOP* 的高级技巧来解决上面的诉求,比如通过 *Spring AOP* 的方式可以通过类似下面的这段配置来完成。使用 *AOP* 的技巧相比上面的代码来说,避免了容错处理等与业务无关的代码对业务代码的侵入,使得业务处理主逻辑更简洁。
+
+```xml
+<bean id="demo-service-stub" class="org.apache.dubbo.demo.DemoServiceStub"/>
+<bean id="demo-service-mock" class="org.apache.dubbo.demo.DemoServiceMock"/>
+<aop:config>
+ <aop:aspect id="stub" ref="demo-service-stub">
+ <aop:pointcut id="stubPointcut" expression="execution(* org.apache.dubbo.samples.DemoService+.*(..))"/>
+ <aop:before method="preProcess" pointcut-ref="stubPointcut"/>
+ <aop:after-returning method="postProcess" pointcut-ref="stubPointcut"/>
+ </aop:aspect>
+ <aop:aspect id="mock" ref="demo-service-mock">
+ <aop:pointcut id="mockPointcut" expression="execution(* org.apache.dubbo.samples.DemoService+.*(..))"/>
+ <aop:after-throwing method="mock" pointcut-ref="mockPointcut"/>
+ </aop:aspect>
+</aop:config>
+```
+
+
+
+为了进一步的方便用户做 Dubbo 开发,框架提出了本地存根 *Stub* 和本地伪装 *Mock* 的概念。通过约定大于配置的理念,进一步的简化了配置,使用起来更加方便,并且不依赖额外的 *AOP* 框架就达到了 *AOP* 的效果。
+
+
+
+本地存根的工作方式与 *AOP* 的 **around** advice 类似,而本地伪装的工作方式等同于 *AOP* 中的 **after-throwing** advice,也就是说,只有当远程调用发生 *exception* 的时候才会执行本地伪装。本地存根和本地伪装的工作流程如下图所示:
+
+![dubbo-mock-stub-flow](../../img/blog/dubbo-mock-stub-flow.png)
+
+
+1. 服务消费者发起调用
+2. 如果服务消费者端存在本地存根 *Stub* 的话,会先执行本地存根
+3. 本地存根 Stub 持有远程服务的 *Proxy* 对象,*Stub* 在执行的时候,会先执行自己的逻辑 (*before*),然后通过 *Proxy* 发起远程调用,最后在返回过程之前也会执行自己的逻辑 (*after-returning*)
+4. 如果远程服务的 *Proxy* 对象在执行过程中抛出了 *exception*,会执行服务消费端的本地伪装 *Mock* 的逻辑 (*after-throwing*),返回容错数据,从而达到服务降级的目的
+
+
+
+## 开发一个本地存根 Stub
+
+本地存根 *Stub* 由用户来提供,并在服务消费方部署。完整的示例可以在这里 [^stub-samples] 获得。
+
+
+
+```java
+public class DemoServiceStub implements DemoService { // #1
+ private static Logger logger = LoggerFactory.getLogger(DemoServiceStub.class);
+
+ private final DemoService demoService;
+
+ public DemoServiceStub(DemoService demoService) { // #2
+ this.demoService = demoService;
+ }
+
+ @Override
+ public String sayHello(String name) { // #3
+ logger.info("before execute remote service, parameter: " + name); // #4
+ try {
+ String result = demoService.sayHello(name); // #5
+ logger.info("after execute remote service, result: " + result); // #6
+ return result;
+ } catch (Exception e) {
+ logger.warn("fail to execute service", e); // #7
+ return null;
+ }
+ }
+}
+```
+
+
+
+要和框架在一起工作,本地存根的实现需要遵循一些与框架事先做出的约定:
+
+1. 首先本地存根 *Stub* 是服务接口的一个实现
+2. 本地存根的实现需要提供一个拷贝构造方法,方便框架将远程调用的 *Proxy* 对象注入进来
+3. 同样的,本地存根需要提供服务接口中所有方法的实现。在本例中,需要实现 *sayHello* 方法
+4. 在真正发起远程调用之前,用户可以在本地执行一些操作。在本例中,在日志中记录传入的参数
+5. 通过框架传入的 *Proxy* 对象真正发起远程调用
+6. 在远程调用结束后,也可以加入本地代码的执行。在本例中,在日志中记录远程调用的返回结果
+7. 如果发生错误的时候,也可以做一些错误恢复的动作。在本例中,在日志中记录异常。当然,如果提供了本地伪装的话,*catch* 中的逻辑是可以省略掉的
+
+其中步骤 4、步骤 6、和步骤 7 共同构建了等同于面向切面编程中的概念,分别对应于 **before**、**after-returning**、以及 **after-throwing**。
+
+
+
+*DemoServiceStub* 运行在客户端,要使用本地存根的话,还需要在 *stub-consumer.xml* 中配置属性 *stub*。可以简单的通过指定 *stub="true"* 来告诉 Dubbo 框架使用本地存根,这个时候,本地存根的包名需要和服务接口的包名一致,类名必须在服务接口的类名后加上 **Stub** 的后缀。例如,当服务接口名是 *org.apache.dubbo.samples.stub.api.DemoService* 时,本地存根的全类名应该是 *org.apache.dubbo.samples.stub.api.DemoServiceStub*。
+
+
+
+```xml
+<dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.samples.stub.api.DemoService" stub="true"/>
+```
+
+
+
+如果不希望使用默认的命名规则,也可以直接通过 *stub* 属性来指定本地存根的全类名。
+
+
+
+```xml
+<dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.samples.stub.api.DemoService" stub="org.apache.dubbo.samples.stub.impl.DemoStub"/>
+```
+
+
+
+启动服务端 *StubProvider* 后,再运行客户端 *StubConsumer*,可以通过观察客户端的日志来验证本地存根的运行结果。
+
+
+
+```bash
+[09/04/19 11:52:21:021 CST] main INFO api.DemoServiceStub: before execute remote service, parameter: dubbo
+[09/04/19 11:52:21:021 CST] main INFO api.DemoServiceStub: after execute remote service, result: greeting dubbo
+[09/04/19 11:52:21:021 CST] main INFO stub.StubConsumer: result: greeting dubbo
+```
+
+
+
+## 开发一个本地伪装 Mock
+
+上面说了本地伪装通常用于在远程调用出错的情况下服务降级。完整的示例可以在这里 [^mock-samples] 获得。
+
+
+
+这里通过在服务提供方的代码中睡眠来模拟调用端超时,从而执行本地伪装来做容错处理。
+
+```java
+public class DemoServiceImpl implements DemoService {
+ public String sayHello(String name) {
+ try {
+ Thread.sleep(5000); // #1
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return "hello " + name; // #2
+ }
+}
+```
+
+1. Dubbo 默认的超时时间是 *1000 ms*,这里通过睡眠 *5000ms* 来达到触发超时异常的发生
+2. 由于超时的发生,这个结果并不会被返回给客户端,取而代之的是 *org.apache.dubbo.remoting.TimeoutException*
+
+
+
+在客户端提供本地伪装的实现。当远程调用发生错误的时候,返回给调用方的不是服务端的 "hello name",取而代之的是 "mock name"。
+
+
+
+```java
+public class DemoServiceMock implements DemoService {
+ private static Logger logger = LoggerFactory.getLogger(DemoServiceMock.class);
+
+ public String sayHello(String name) {
+ logger.warn("about to execute mock: " + DemoServiceMock.class.getSimpleName());
+ return "mock " + name;
+ }
+}
+```
+
+
+
+同样的,要使用本地伪装的话,还需要在 *mock-consumer.xml* 中配置属性 *mock*。可以简单的通过指定 *mock="true"* 来告诉 Dubbo 框架使用本地伪装,这个时候,本地伪装的包名需要和服务接口的包名一致,类名必须在服务接口的类名后加上 **Mock** 的后缀。例如,当服务接口名是 *org.apache.dubbo.samples.stub.api.DemoService* 时,本地存根的全类名应该是 *org.apache.dubbo.samples.stub.api.DemoServiceMock*。
+
+
+
+```xml
+ <dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.samples.mock.api.DemoService"
+ mock="true"/>
+```
+
+
+
+如果不希望使用默认的命名规则,也可以直接通过 *mock* 属性来指定本地伪装的全类名。
+
+
+
+```xml
+<dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.samples.mock.api.DemoService" stub="org.apache.dubbo.samples.mock.impl.DemoMock"/>
+```
+
+
+
+通过提供一个本地伪装的类,可以最大限度的控制出错之后的容错逻辑。有的时候,业务上并不需要这样灵活的机制,只有返回一个默认值的诉求,这个时候提供一个完整的本地伪装的实现就显得有点重了。或者线上出错的时候,应用并没有打包本地伪装,需要通过推送规则的方式临时对服务降级。Dubbo 框架为上面的这两种诉求都提供了快捷方式,帮助用户快速配置服务降级。
+
+
+
+启动服务端 *MockProvider* 之后,然后再执行 *MockConsumer* 就可以看到下面的结果:
+
+
+
+```bash
+Caused by: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response timeout by scan timer. start time: 2019-04-09 14:20:48.061, end time: 2019-04-09 14:20:49.077, client elapsed: 0 ms, server elapsed: 1015 ms, timeout: 1000 ms, request: Request [id=2, version=2.0.2, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], arguments=[world], attachments={path=org.apache.dubbo.samples.mock.api.DemoService, int [...]
+ at org.apache.dubbo.remoting.exchange.support.DefaultFuture.returnFromResponse(DefaultFuture.java:295)
+ at org.apache.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:191)
+ at org.apache.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:164)
+ at org.apache.dubbo.rpc.protocol.dubbo.DubboInvoker.doInvoke(DubboInvoker.java:108)
+ at org.apache.dubbo.rpc.protocol.AbstractInvoker.invoke(AbstractInvoker.java:157)
+ at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:88)
+ at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:73)
+ at org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:49)
+ at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:73)
+ at org.apache.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:54)
+ at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:73)
+ at org.apache.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:78)
+ at org.apache.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56)
+ at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:80)
+ ... 5 more
+[09/04/19 02:20:49:049 CST] main WARN api.DemoServiceMock: about to execute mock: DemoServiceMock
+[09/04/19 02:20:49:049 CST] main INFO mock.MockConsumer: result: mock world
+```
+
+
+
+
+
+下面通过规则推送为例展示这种快捷方式的用法,更多的用法请参考 Dubbo 官方用户手册 [^mock]。通过向配置中心推送指定服务的配置,就可以做到动态服务降级的目的。
+
+
+
+```yaml
+--- # 1
+configVersion: v2.7
+scope: service
+ key: org.apache.dubbo.samples.mock.api.DemoService #2
+ enabled: true
+ configs:
+ - addresses: [0.0.0.0]
+ side: consumer #3
+ parameters:
+ mock: return configured-mock-value #4
+ ...
+```
+
+1. 以 *Zookeeper* 为例,规则的路径是 /dubbo/config/org.apache.dubbo.samples.mock.api.DemoService/configurators
+2. 该规则作用在 *org.apache.dubbo.samples.mock.api.DemoService* 服务上
+3. 该规则作用在客户端
+4. 当错误发送时,对服务的调用返回默认值 *configured-mock-value*
+
+
+
+启动服务端 *MockProvider* 之后,再执行例子[^mock-samples]中的 *Configurator* 完成对服务降级规则的配置,然后再执行 *MockConsumer* 就可以看到下面的结果:
+
+
+
+```bash
+[09/04/19 02:19:01:001 CST] main INFO integration.AbstractConfiguratorListener: [DUBBO] Notification of overriding rule, change type is: MODIFIED, raw config content is:
+ ---
+configVersion: v2.7
+scope: service
+key: org.apache.dubbo.samples.mock.api.DemoService
+enabled: true
+configs:
+- addresses: [0.0.0.0]
+ side: consumer
+ parameters:
+ mock: return configured-mock-value
+...
+, dubbo version: 2.7.1, current host: 30.5.125.99
+
+...
+
+Caused by: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response timeout. start time: 2019-04-09 14:19:03.737, end time: 2019-04-09 14:19:04.741, client elapsed: 1 ms, server elapsed: 1002 ms, timeout: 1000 ms, request: Request [id=2, version=2.0.2, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], arguments=[world], attachments={path=org.apache.dubbo.samples.mock.api.DemoService, interface=org.apa [...]
+ at org.apache.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:188)
+ at org.apache.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:164)
+ at org.apache.dubbo.rpc.protocol.dubbo.DubboInvoker.doInvoke(DubboInvoker.java:108)
+ at org.apache.dubbo.rpc.protocol.AbstractInvoker.invoke(AbstractInvoker.java:157)
+ at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:88)
+ at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:73)
+ at org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:49)
+ at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:73)
+ at org.apache.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:54)
+ at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:73)
+ at org.apache.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:78)
+ at org.apache.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56)
+ at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:80)
+ ... 5 more
+[09/04/19 02:19:04:004 CST] main INFO mock.MockConsumer: result: configured-mock-value
+```
+
+
+
+## 总结
+
+本文介绍了 Dubbo 中本地存根和本地伪装的概念和用法。从本质来讲,本地存根和本地伪装等同于面向切面编程中的概念,通过诸如 Spring 框架提供的 *AOP* 编程可以达到同样的目的。通过本文如何开发一个本地存根和本地伪装的示例,读者可以直观的感受到通过框架提供的机制更加方便快捷。同时,对于很多简单的场景和动态配置推送的场景,框架提供了仅通过配置而无需编码的方式来满足,进一步提升了框架使用者的效率。
+
+
+
+[^stub-samples]: https://github.com/apache/incubator-dubbo-samples/tree/master/dubbo-samples-stub
+[^mock-samples]: https://github.com/apache/incubator-dubbo-samples/tree/master/dubbo-samples-mock
+[^mock]: http://dubbo.apache.org/zh-cn/docs/user/demos/local-mock.html
+
+
+
diff --git a/build/blog.js b/build/blog.js
index 6878fce..6e90671 100644
--- a/build/blog.js
+++ b/build/blog.js
@@ -1,6 +1,6 @@
-!function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="/build/",t(t.s=78) [...]
+!function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="/build/",t(t.s=78) [...]
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
-!function(){"use strict";function n(){for(var e=[],t=0;t<arguments.length;t++){var r=arguments[t];if(r){var o=typeof r;if("string"===o||"number"===o)e.push(r);else if(Array.isArray(r)&&r.length){var a=n.apply(null,r);a&&e.push(a)}else if("object"===o)for(var u in r)i.call(r,u)&&r[u]&&e.push(u)}}return e.join(" ")}var i={}.hasOwnProperty;void 0!==e&&e.exports?(n.default=n,e.exports=n):(r=[],void 0!==(o=function(){return n}.apply(t,r))&&(e.exports=o))}()},function(e,t,n){"use strict";funct [...]
\ No newline at end of file
+!function(){"use strict";function n(){for(var e=[],t=0;t<arguments.length;t++){var r=arguments[t];if(r){var o=typeof r;if("string"===o||"number"===o)e.push(r);else if(Array.isArray(r)&&r.length){var a=n.apply(null,r);a&&e.push(a)}else if("object"===o)for(var u in r)i.call(r,u)&&r[u]&&e.push(u)}}return e.join(" ")}var i={}.hasOwnProperty;void 0!==e&&e.exports?(n.default=n,e.exports=n):(r=[],void 0!==(o=function(){return n}.apply(t,r))&&(e.exports=o))}()},function(e,t,n){"use strict";funct [...]
\ No newline at end of file
diff --git a/en-us/blog/download.html b/en-us/blog/download.html
index 0b451df..22c3590 100644
--- a/en-us/blog/download.html
+++ b/en-us/blog/download.html
@@ -86,6 +86,15 @@
<blockquote>
<p>Release Notes: <a href="https://github.com/apache/incubator-dubbo-spring-boot-project/releases">https://github.com/apache/incubator-dubbo-spring-boot-project/releases</a></p>
</blockquote>
+<h3>2.7.1 (2019-04-09)</h3>
+<ul>
+<li><a href="https://www.apache.org/dyn/closer.cgi?path=incubator/dubbo/spring-boot-project/2.7.1/apache-dubbo-spring-boot-project-incubating-2.7.1-source-release.zip">source</a> |
+<a href="https://dist.apache.org/repos/dist/release/incubator/dubbo/spring-boot-project/2.7.1/apache-dubbo-spring-boot-project-incubating-2.7.1-source-release.zip.asc">asc</a> |
+<a href="https://dist.apache.org/repos/dist/release/incubator/dubbo/spring-boot-project/2.7.1/apache-dubbo-spring-boot-project-incubating-2.7.1-source-release.zip.sha512">sha512</a></li>
+<li><a href="https://www.apache.org/dyn/closer.cgi?path=incubator/dubbo/spring-boot-project/2.7.1/apache-dubbo-spring-boot-project-incubating-2.7.1-bin-release.zip">binary</a> |
+<a href="https://dist.apache.org/repos/dist/release/incubator/dubbo/spring-boot-project/2.7.1/apache-dubbo-spring-boot-project-incubating-2.7.1-bin-release.zip.asc">asc</a> |
+<a href="https://dist.apache.org/repos/dist/release/incubator/dubbo/spring-boot-project/2.7.1/apache-dubbo-spring-boot-project-incubating-2.7.1-bin-release.zip.sha512">sha512</a></li>
+</ul>
<h3>2.7.0 (2019-02-14)</h3>
<ul>
<li><a href="https://www.apache.org/dyn/closer.cgi?path=incubator/dubbo/spring-boot-project/2.7.0/apache-dubbo-spring-boot-project-incubating-2.7.0-source-release.zip">source</a> |
diff --git a/en-us/blog/download.json b/en-us/blog/download.json
index e60d8aa..c204c66 100644
--- a/en-us/blog/download.json
+++ b/en-us/blog/download.json
@@ -1,6 +1,6 @@
{
"filename": "download.md",
- "__html": "<h1>Downloads</h1>\n<h2>Verification</h2>\n<p>you can follow these <a href=\"https://www.apache.org/info/verification\">procedures</a> and the <a href=\"https://dist.apache.org/repos/dist/release/incubator/dubbo/KEYS\">KEYS</a> file to verify the download files</p>\n<h2><a href=\"https://github.com/apache/incubator-dubbo\">Dubbo-RPC</a></h2>\n<blockquote>\n<p>Release Notes: <a href=\"https://github.com/apache/incubator-dubbo/releases\">https://github.com/apache/incubator-dub [...]
+ "__html": "<h1>Downloads</h1>\n<h2>Verification</h2>\n<p>you can follow these <a href=\"https://www.apache.org/info/verification\">procedures</a> and the <a href=\"https://dist.apache.org/repos/dist/release/incubator/dubbo/KEYS\">KEYS</a> file to verify the download files</p>\n<h2><a href=\"https://github.com/apache/incubator-dubbo\">Dubbo-RPC</a></h2>\n<blockquote>\n<p>Release Notes: <a href=\"https://github.com/apache/incubator-dubbo/releases\">https://github.com/apache/incubator-dub [...]
"link": "/en-us/blog/download.html",
"meta": {}
}
\ No newline at end of file
diff --git a/img/blog/dubbo-mock-stub-flow.png b/img/blog/dubbo-mock-stub-flow.png
new file mode 100644
index 0000000..3d6c0e3
Binary files /dev/null and b/img/blog/dubbo-mock-stub-flow.png differ
diff --git a/md_json/blog.json b/md_json/blog.json
index c62baef..3b8158f 100644
--- a/md_json/blog.json
+++ b/md_json/blog.json
@@ -531,6 +531,11 @@
"meta": {}
},
{
+ "filename": "dubbo-stub-mock.md",
+ "link": "/zh-cn/blog/dubbo-stub-mock.html",
+ "meta": {}
+ },
+ {
"filename": "dubbo-zk.md",
"link": "/zh-cn/blog/dubbo-zk.html",
"meta": {
diff --git a/site_config/blog.js b/site_config/blog.js
index 06371e8..0355aea 100644
--- a/site_config/blog.js
+++ b/site_config/blog.js
@@ -164,6 +164,13 @@ export default {
postsTitle: '所有文章',
list: [
{
+ title: 'Dubbo 本地存根和本地伪装',
+ author: '@beiwei30',
+ dateStr: 'Apr 9th, 2019',
+ desc: '介绍本地伪装、本地存根的概念以及如何开发',
+ link: '/zh-cn/blog/dubbo-stub-mock.html',
+ },
+ {
title: 'Dubbo2.7 三大新特性详解',
author: '@lexburner',
dateStr: 'Mar 22nd, 2019',
diff --git a/zh-cn/blog/download.html b/zh-cn/blog/download.html
index 281c7aa..83d1996 100644
--- a/zh-cn/blog/download.html
+++ b/zh-cn/blog/download.html
@@ -87,6 +87,15 @@
<blockquote>
<p>发布说明:<a href="https://github.com/apache/incubator-dubbo-spring-boot-project/releases">https://github.com/apache/incubator-dubbo-spring-boot-project/releases</a></p>
</blockquote>
+<h3>2.7.1 (2019-04-09)</h3>
+<ul>
+<li><a href="https://www.apache.org/dyn/closer.cgi?path=incubator/dubbo/spring-boot-project/2.7.1/apache-dubbo-spring-boot-project-incubating-2.7.1-source-release.zip">source</a> |
+<a href="https://dist.apache.org/repos/dist/release/incubator/dubbo/spring-boot-project/2.7.1/apache-dubbo-spring-boot-project-incubating-2.7.1-source-release.zip.asc">asc</a> |
+<a href="https://dist.apache.org/repos/dist/release/incubator/dubbo/spring-boot-project/2.7.1/apache-dubbo-spring-boot-project-incubating-2.7.1-source-release.zip.sha512">sha512</a></li>
+<li><a href="https://www.apache.org/dyn/closer.cgi?path=incubator/dubbo/spring-boot-project/2.7.1/apache-dubbo-spring-boot-project-incubating-2.7.1-bin-release.zip">binary</a> |
+<a href="https://dist.apache.org/repos/dist/release/incubator/dubbo/spring-boot-project/2.7.1/apache-dubbo-spring-boot-project-incubating-2.7.1-bin-release.zip.asc">asc</a> |
+<a href="https://dist.apache.org/repos/dist/release/incubator/dubbo/spring-boot-project/2.7.1/apache-dubbo-spring-boot-project-incubating-2.7.1-bin-release.zip.sha512">sha512</a></li>
+</ul>
<h3>2.7.0 (2019-02-14)</h3>
<ul>
<li><a href="https://www.apache.org/dyn/closer.cgi?path=incubator/dubbo/spring-boot-project/2.7.0/apache-dubbo-spring-boot-project-incubating-2.7.0-source-release.zip">source</a> |
diff --git a/zh-cn/blog/download.json b/zh-cn/blog/download.json
index bdd679a..ee15d2c 100644
--- a/zh-cn/blog/download.json
+++ b/zh-cn/blog/download.json
@@ -1,6 +1,6 @@
{
"filename": "download.md",
- "__html": "<h1>下载中心</h1>\n<h2>验证</h2>\n<p>可以按照这里的<a href=\"https://www.apache.org/info/verification\">步骤</a>, 利用<a href=\"https://dist.apache.org/repos/dist/release/incubator/dubbo/KEYS\">KEYS</a>文件来验证下载。</p>\n<h2>版本与升级</h2>\n<p>请点击了解各<a href=\"http://dubbo.apache.org/zh-cn/docs/user/versions/index.html\">版本详情和升级注意事项</a></p>\n<blockquote>\n<p>发布说明:<a href=\"https://github.com/apache/incubator-dubbo/releases\">https://github.com/apache/incubator-dubbo/releases</a></p>\n</blockquote>\n<h [...]
+ "__html": "<h1>下载中心</h1>\n<h2>验证</h2>\n<p>可以按照这里的<a href=\"https://www.apache.org/info/verification\">步骤</a>, 利用<a href=\"https://dist.apache.org/repos/dist/release/incubator/dubbo/KEYS\">KEYS</a>文件来验证下载。</p>\n<h2>版本与升级</h2>\n<p>请点击了解各<a href=\"http://dubbo.apache.org/zh-cn/docs/user/versions/index.html\">版本详情和升级注意事项</a></p>\n<blockquote>\n<p>发布说明:<a href=\"https://github.com/apache/incubator-dubbo/releases\">https://github.com/apache/incubator-dubbo/releases</a></p>\n</blockquote>\n<h [...]
"link": "/zh-cn/blog/download.html",
"meta": {}
}
\ No newline at end of file
diff --git a/zh-cn/blog/dubbo-stub-mock.html b/zh-cn/blog/dubbo-stub-mock.html
new file mode 100644
index 0000000..153bd93
--- /dev/null
+++ b/zh-cn/blog/dubbo-stub-mock.html
@@ -0,0 +1,238 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+ <meta name="keywords" content="dubbo-stub-mock" />
+ <meta name="description" content="dubbo-stub-mock" />
+ <!-- 网页标签标题 -->
+ <title>dubbo-stub-mock</title>
+ <link rel="shortcut icon" href="/img/dubbo.ico"/>
+ <link rel="stylesheet" href="/build/blogDetail.css" />
+</head>
+<body>
+ <div id="root"><div class="blog-detail-page" data-reactroot=""><header class="header-container header-container-normal"><div class="header-body"><a href="/zh-cn/index.html"><img class="logo" src="/img/dubbo_colorful.png"/></a><div class="search search-normal"><span class="icon-search"></span></div><span class="language-switch language-switch-normal">En</span><div class="header-menu"><img class="header-menu-toggle" src="/img/menu_gray.png"/><ul><li class="menu-item menu-item-normal"><a h [...]
+<h2>基本概念</h2>
+<p>典型的 RPC 调用客户端是依赖并且只依赖接口编程来进行远程调用的。在真正发起远程调用之前,用户往往需要做一些预处理的工作,比如提前校验参数。在拿到返回调用结果之后,用户可能需要缓存结果,或者是在调用失败的时候构造容错数据,而不是简单的抛出异常。</p>
+<p>这个时候,用户可以编写出类似以下的代码来处理上面提出的这些场景:</p>
+<pre><code class="language-java"><span class="hljs-keyword">try</span> {
+ preProcess();
+ <span class="hljs-keyword">return</span> service.invoke(...);
+} <span class="hljs-keyword">catch</span> (Throwable e) {
+ <span class="hljs-keyword">return</span> mockValue;
+} <span class="hljs-keyword">finally</span> {
+ postProcess();
+}
+</code></pre>
+<p>类似的,用户也可以通过面向切面编程 <em>AOP</em> 的高级技巧来解决上面的诉求,比如通过 <em>Spring AOP</em> 的方式可以通过类似下面的这段配置来完成。使用 <em>AOP</em> 的技巧相比上面的代码来说,避免了容错处理等与业务无关的代码对业务代码的侵入,使得业务处理主逻辑更简洁。</p>
+<pre><code class="language-xml"><span class="hljs-tag"><<span class="hljs-name">bean</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"demo-service-stub"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"org.apache.dubbo.demo.DemoServiceStub"</span>/></span>
+<span class="hljs-tag"><<span class="hljs-name">bean</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"demo-service-mock"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"org.apache.dubbo.demo.DemoServiceMock"</span>/></span>
+<span class="hljs-tag"><<span class="hljs-name">aop:config</span>></span>
+ <span class="hljs-tag"><<span class="hljs-name">aop:aspect</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"stub"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"demo-service-stub"</span>></span>
+ <span class="hljs-tag"><<span class="hljs-name">aop:pointcut</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"stubPointcut"</span> <span class="hljs-attr">expression</span>=<span class="hljs-string">"execution(* org.apache.dubbo.samples.DemoService+.*(..))"</span>/></span>
+ <span class="hljs-tag"><<span class="hljs-name">aop:before</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"preProcess"</span> <span class="hljs-attr">pointcut-ref</span>=<span class="hljs-string">"stubPointcut"</span>/></span>
+ <span class="hljs-tag"><<span class="hljs-name">aop:after-returning</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"postProcess"</span> <span class="hljs-attr">pointcut-ref</span>=<span class="hljs-string">"stubPointcut"</span>/></span>
+ <span class="hljs-tag"></<span class="hljs-name">aop:aspect</span>></span>
+ <span class="hljs-tag"><<span class="hljs-name">aop:aspect</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"mock"</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"demo-service-mock"</span>></span>
+ <span class="hljs-tag"><<span class="hljs-name">aop:pointcut</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"mockPointcut"</span> <span class="hljs-attr">expression</span>=<span class="hljs-string">"execution(* org.apache.dubbo.samples.DemoService+.*(..))"</span>/></span>
+ <span class="hljs-tag"><<span class="hljs-name">aop:after-throwing</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"mock"</span> <span class="hljs-attr">pointcut-ref</span>=<span class="hljs-string">"mockPointcut"</span>/></span>
+ <span class="hljs-tag"></<span class="hljs-name">aop:aspect</span>></span>
+<span class="hljs-tag"></<span class="hljs-name">aop:config</span>></span>
+</code></pre>
+<p>为了进一步的方便用户做 Dubbo 开发,框架提出了本地存根 <em>Stub</em> 和本地伪装 <em>Mock</em> 的概念。通过约定大于配置的理念,进一步的简化了配置,使用起来更加方便,并且不依赖额外的 <em>AOP</em> 框架就达到了 <em>AOP</em> 的效果。</p>
+<p>本地存根的工作方式与 <em>AOP</em> 的 <strong>around</strong> advice 类似,而本地伪装的工作方式等同于 <em>AOP</em> 中的 <strong>after-throwing</strong> advice,也就是说,只有当远程调用发生 <em>exception</em> 的时候才会执行本地伪装。本地存根和本地伪装的工作流程如下图所示:</p>
+<p><img src="../../img/blog/dubbo-mock-stub-flow.png" alt="dubbo-mock-stub-flow"></p>
+<ol>
+<li>服务消费者发起调用</li>
+<li>如果服务消费者端存在本地存根 <em>Stub</em> 的话,会先执行本地存根</li>
+<li>本地存根 Stub 持有远程服务的 <em>Proxy</em> 对象,<em>Stub</em> 在执行的时候,会先执行自己的逻辑 (<em>before</em>),然后通过 <em>Proxy</em> 发起远程调用,最后在返回过程之前也会执行自己的逻辑 (<em>after-returning</em>)</li>
+<li>如果远程服务的 <em>Proxy</em> 对象在执行过程中抛出了 <em>exception</em>,会执行服务消费端的本地伪装 <em>Mock</em> 的逻辑 (<em>after-throwing</em>),返回容错数据,从而达到服务降级的目的</li>
+</ol>
+<h2>开发一个本地存根 Stub</h2>
+<p>本地存根 <em>Stub</em> 由用户来提供,并在服务消费方部署。完整的示例可以在这里 <sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> 获得。</p>
+<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DemoServiceStub</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">DemoService</span> </span>{ <span class="hljs-comment">// #1</span>
+ <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Logger logger = LoggerFactory.getLogger(DemoServiceStub.class);
+
+ <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> DemoService demoService;
+
+ <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">DemoServiceStub</span><span class="hljs-params">(DemoService demoService)</span> </span>{ <span class="hljs-comment">// #2</span>
+ <span class="hljs-keyword">this</span>.demoService = demoService;
+ }
+
+ <span class="hljs-meta">@Override</span>
+ <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">sayHello</span><span class="hljs-params">(String name)</span> </span>{ <span class="hljs-comment">// #3</span>
+ logger.info(<span class="hljs-string">"before execute remote service, parameter: "</span> + name); <span class="hljs-comment">// #4</span>
+ <span class="hljs-keyword">try</span> {
+ String result = demoService.sayHello(name); <span class="hljs-comment">// #5</span>
+ logger.info(<span class="hljs-string">"after execute remote service, result: "</span> + result); <span class="hljs-comment">// #6</span>
+ <span class="hljs-keyword">return</span> result;
+ } <span class="hljs-keyword">catch</span> (Exception e) {
+ logger.warn(<span class="hljs-string">"fail to execute service"</span>, e); <span class="hljs-comment">// #7</span>
+ <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
+ }
+ }
+}
+</code></pre>
+<p>要和框架在一起工作,本地存根的实现需要遵循一些与框架事先做出的约定:</p>
+<ol>
+<li>首先本地存根 <em>Stub</em> 是服务接口的一个实现</li>
+<li>本地存根的实现需要提供一个拷贝构造方法,方便框架将远程调用的 <em>Proxy</em> 对象注入进来</li>
+<li>同样的,本地存根需要提供服务接口中所有方法的实现。在本例中,需要实现 <em>sayHello</em> 方法</li>
+<li>在真正发起远程调用之前,用户可以在本地执行一些操作。在本例中,在日志中记录传入的参数</li>
+<li>通过框架传入的 <em>Proxy</em> 对象真正发起远程调用</li>
+<li>在远程调用结束后,也可以加入本地代码的执行。在本例中,在日志中记录远程调用的返回结果</li>
+<li>如果发生错误的时候,也可以做一些错误恢复的动作。在本例中,在日志中记录异常。当然,如果提供了本地伪装的话,<em>catch</em> 中的逻辑是可以省略掉的</li>
+</ol>
+<p>其中步骤 4、步骤 6、和步骤 7 共同构建了等同于面向切面编程中的概念,分别对应于 <strong>before</strong>、<strong>after-returning</strong>、以及 <strong>after-throwing</strong>。</p>
+<p><em>DemoServiceStub</em> 运行在客户端,要使用本地存根的话,还需要在 <em>stub-consumer.xml</em> 中配置属性 <em>stub</em>。可以简单的通过指定 <em>stub="true"</em> 来告诉 Dubbo 框架使用本地存根,这个时候,本地存根的包名需要和服务接口的包名一致,类名必须在服务接口的类名后加上 <strong>Stub</strong> 的后缀。例如,当服务接口名是 <em>org.apache.dubbo.samples.stub.api.DemoService</em> 时,本地存根的全类名应该是 <em>org.apache.dubbo.samples.stub.api.DemoServiceStub</em>。</p>
+<pre><code class="language-xml"><span class="hljs-tag"><<span class="hljs-name">dubbo:reference</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"demoService"</span> <span class="hljs-attr">check</span>=<span class="hljs-string">"false"</span> <span class="hljs-attr">interface</span>=<span class="hljs-string">"org.apache.dubbo.samples.stub.api.DemoService"</span> <span class="hljs-attr">stub</span>=<span class="hljs-string">"true"</span>/></span>
+</code></pre>
+<p>如果不希望使用默认的命名规则,也可以直接通过 <em>stub</em> 属性来指定本地存根的全类名。</p>
+<pre><code class="language-xml"><span class="hljs-tag"><<span class="hljs-name">dubbo:reference</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"demoService"</span> <span class="hljs-attr">check</span>=<span class="hljs-string">"false"</span> <span class="hljs-attr">interface</span>=<span class="hljs-string">"org.apache.dubbo.samples.stub.api.DemoService"</span> <span class="hljs-attr">stub</span>=<span class="hljs-string">"org.apache.dubbo.samples.stub.impl.DemoStu [...]
+</code></pre>
+<p>启动服务端 <em>StubProvider</em> 后,再运行客户端 <em>StubConsumer</em>,可以通过观察客户端的日志来验证本地存根的运行结果。</p>
+<pre><code class="language-bash">[09/04/19 11:52:21:021 CST] main INFO api.DemoServiceStub: before execute remote service, parameter: dubbo
+[09/04/19 11:52:21:021 CST] main INFO api.DemoServiceStub: after execute remote service, result: greeting dubbo
+[09/04/19 11:52:21:021 CST] main INFO stub.StubConsumer: result: greeting dubbo
+</code></pre>
+<h2>开发一个本地伪装 Mock</h2>
+<p>上面说了本地伪装通常用于在远程调用出错的情况下服务降级。完整的示例可以在这里 <sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup> 获得。</p>
+<p>这里通过在服务提供方的代码中睡眠来模拟调用端超时,从而执行本地伪装来做容错处理。</p>
+<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DemoServiceImpl</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">DemoService</span> </span>{
+ <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">sayHello</span><span class="hljs-params">(String name)</span> </span>{
+ <span class="hljs-keyword">try</span> {
+ Thread.sleep(<span class="hljs-number">5000</span>); <span class="hljs-comment">// #1</span>
+ } <span class="hljs-keyword">catch</span> (InterruptedException e) {
+ e.printStackTrace();
+ }
+ <span class="hljs-keyword">return</span> <span class="hljs-string">"hello "</span> + name; <span class="hljs-comment">// #2</span>
+ }
+}
+</code></pre>
+<ol>
+<li>Dubbo 默认的超时时间是 <em>1000 ms</em>,这里通过睡眠 <em>5000ms</em> 来达到触发超时异常的发生</li>
+<li>由于超时的发生,这个结果并不会被返回给客户端,取而代之的是 <em>org.apache.dubbo.remoting.TimeoutException</em></li>
+</ol>
+<p>在客户端提供本地伪装的实现。当远程调用发生错误的时候,返回给调用方的不是服务端的 "hello name",取而代之的是 "mock name"。</p>
+<pre><code class="language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DemoServiceMock</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">DemoService</span> </span>{
+ <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Logger logger = LoggerFactory.getLogger(DemoServiceMock.class);
+
+ <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">sayHello</span><span class="hljs-params">(String name)</span> </span>{
+ logger.warn(<span class="hljs-string">"about to execute mock: "</span> + DemoServiceMock.class.getSimpleName());
+ <span class="hljs-keyword">return</span> <span class="hljs-string">"mock "</span> + name;
+ }
+}
+</code></pre>
+<p>同样的,要使用本地伪装的话,还需要在 <em>mock-consumer.xml</em> 中配置属性 <em>mock</em>。可以简单的通过指定 <em>mock="true"</em> 来告诉 Dubbo 框架使用本地伪装,这个时候,本地伪装的包名需要和服务接口的包名一致,类名必须在服务接口的类名后加上 <strong>Mock</strong> 的后缀。例如,当服务接口名是 <em>org.apache.dubbo.samples.stub.api.DemoService</em> 时,本地存根的全类名应该是 <em>org.apache.dubbo.samples.stub.api.DemoServiceMock</em>。</p>
+<pre><code class="language-xml"> <span class="hljs-tag"><<span class="hljs-name">dubbo:reference</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"demoService"</span> <span class="hljs-attr">check</span>=<span class="hljs-string">"false"</span> <span class="hljs-attr">interface</span>=<span class="hljs-string">"org.apache.dubbo.samples.mock.api.DemoService"</span>
+ <span class="hljs-attr">mock</span>=<span class="hljs-string">"true"</span>/></span>
+</code></pre>
+<p>如果不希望使用默认的命名规则,也可以直接通过 <em>mock</em> 属性来指定本地伪装的全类名。</p>
+<pre><code class="language-xml"><span class="hljs-tag"><<span class="hljs-name">dubbo:reference</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"demoService"</span> <span class="hljs-attr">check</span>=<span class="hljs-string">"false"</span> <span class="hljs-attr">interface</span>=<span class="hljs-string">"org.apache.dubbo.samples.mock.api.DemoService"</span> <span class="hljs-attr">stub</span>=<span class="hljs-string">"org.apache.dubbo.samples.mock.impl.DemoMoc [...]
+</code></pre>
+<p>通过提供一个本地伪装的类,可以最大限度的控制出错之后的容错逻辑。有的时候,业务上并不需要这样灵活的机制,只有返回一个默认值的诉求,这个时候提供一个完整的本地伪装的实现就显得有点重了。或者线上出错的时候,应用并没有打包本地伪装,需要通过推送规则的方式临时对服务降级。Dubbo 框架为上面的这两种诉求都提供了快捷方式,帮助用户快速配置服务降级。</p>
+<p>启动服务端 <em>MockProvider</em> 之后,然后再执行 <em>MockConsumer</em> 就可以看到下面的结果:</p>
+<pre><code class="language-bash">Caused by: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response timeout by scan timer. start time: 2019-04-09 14:20:48.061, end time: 2019-04-09 14:20:49.077, client elapsed: 0 ms, server elapsed: 1015 ms, timeout: 1000 ms, request: Request [id=2, version=2.0.2, twoway=<span class="hljs-literal">true</span>, event=<span class="hljs-literal">false</span>, broken=<span class="hljs-literal">false</span>, data=RpcInvocation [methodName=say [...]
+ at org.apache.dubbo.remoting.exchange.support.DefaultFuture.returnFromResponse(DefaultFuture.java:295)
+ at org.apache.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:191)
+ at org.apache.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:164)
+ at org.apache.dubbo.rpc.protocol.dubbo.DubboInvoker.doInvoke(DubboInvoker.java:108)
+ at org.apache.dubbo.rpc.protocol.AbstractInvoker.invoke(AbstractInvoker.java:157)
+ at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:88)
+ at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper<span class="hljs-variable">$1</span>.invoke(ProtocolFilterWrapper.java:73)
+ at org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:49)
+ at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper<span class="hljs-variable">$1</span>.invoke(ProtocolFilterWrapper.java:73)
+ at org.apache.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:54)
+ at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper<span class="hljs-variable">$1</span>.invoke(ProtocolFilterWrapper.java:73)
+ at org.apache.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:78)
+ at org.apache.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56)
+ at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:80)
+ ... 5 more
+[09/04/19 02:20:49:049 CST] main WARN api.DemoServiceMock: about to execute mock: DemoServiceMock
+[09/04/19 02:20:49:049 CST] main INFO mock.MockConsumer: result: mock world
+</code></pre>
+<p>下面通过规则推送为例展示这种快捷方式的用法,更多的用法请参考 Dubbo 官方用户手册 <sup class="footnote-ref"><a href="#fn3" id="fnref3">[3]</a></sup>。通过向配置中心推送指定服务的配置,就可以做到动态服务降级的目的。</p>
+<pre><code class="language-yaml"><span class="hljs-bullet">-</span><span class="hljs-bullet">--</span> <span class="hljs-comment"># 1</span>
+<span class="hljs-attr">configVersion:</span> <span class="hljs-string">v2.7</span>
+<span class="hljs-attr">scope:</span> <span class="hljs-string">service</span>
+<span class="hljs-attr"> key:</span> <span class="hljs-string">org.apache.dubbo.samples.mock.api.DemoService</span> <span class="hljs-comment">#2</span>
+<span class="hljs-attr"> enabled:</span> <span class="hljs-literal">true</span>
+<span class="hljs-attr"> configs:</span>
+<span class="hljs-attr"> - addresses:</span> <span class="hljs-string">[0.0.0.0]</span>
+<span class="hljs-attr"> side:</span> <span class="hljs-string">consumer</span> <span class="hljs-comment">#3</span>
+<span class="hljs-attr"> parameters:</span>
+<span class="hljs-attr"> mock:</span> <span class="hljs-string">return</span> <span class="hljs-string">configured-mock-value</span> <span class="hljs-comment">#4</span>
+ <span class="hljs-string">...</span>
+</code></pre>
+<ol>
+<li>以 <em>Zookeeper</em> 为例,规则的路径是 /dubbo/config/org.apache.dubbo.samples.mock.api.DemoService/configurators</li>
+<li>该规则作用在 <em>org.apache.dubbo.samples.mock.api.DemoService</em> 服务上</li>
+<li>该规则作用在客户端</li>
+<li>当错误发送时,对服务的调用返回默认值 <em>configured-mock-value</em></li>
+</ol>
+<p>启动服务端 <em>MockProvider</em> 之后,再执行例子<sup class="footnote-ref"><a href="#fn2" id="fnref2:1">[2:1]</a></sup>中的 <em>Configurator</em> 完成对服务降级规则的配置,然后再执行 <em>MockConsumer</em> 就可以看到下面的结果:</p>
+<pre><code class="language-bash">[09/04/19 02:19:01:001 CST] main INFO integration.AbstractConfiguratorListener: [DUBBO] Notification of overriding rule, change <span class="hljs-built_in">type</span> is: MODIFIED, raw config content is:
+ ---
+configVersion: v2.7
+scope: service
+key: org.apache.dubbo.samples.mock.api.DemoService
+enabled: <span class="hljs-literal">true</span>
+configs:
+- addresses: [0.0.0.0]
+ side: consumer
+ parameters:
+ mock: <span class="hljs-built_in">return</span> configured-mock-value
+...
+, dubbo version: 2.7.1, current host: 30.5.125.99
+
+...
+
+Caused by: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response timeout. start time: 2019-04-09 14:19:03.737, end time: 2019-04-09 14:19:04.741, client elapsed: 1 ms, server elapsed: 1002 ms, timeout: 1000 ms, request: Request [id=2, version=2.0.2, twoway=<span class="hljs-literal">true</span>, event=<span class="hljs-literal">false</span>, broken=<span class="hljs-literal">false</span>, data=RpcInvocation [methodName=sayHello, parameterTypes=[class java.lang.String], [...]
+ at org.apache.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:188)
+ at org.apache.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:164)
+ at org.apache.dubbo.rpc.protocol.dubbo.DubboInvoker.doInvoke(DubboInvoker.java:108)
+ at org.apache.dubbo.rpc.protocol.AbstractInvoker.invoke(AbstractInvoker.java:157)
+ at org.apache.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:88)
+ at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper<span class="hljs-variable">$1</span>.invoke(ProtocolFilterWrapper.java:73)
+ at org.apache.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:49)
+ at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper<span class="hljs-variable">$1</span>.invoke(ProtocolFilterWrapper.java:73)
+ at org.apache.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:54)
+ at org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper<span class="hljs-variable">$1</span>.invoke(ProtocolFilterWrapper.java:73)
+ at org.apache.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:78)
+ at org.apache.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:56)
+ at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:80)
+ ... 5 more
+[09/04/19 02:19:04:004 CST] main INFO mock.MockConsumer: result: configured-mock-value
+</code></pre>
+<h2>总结</h2>
+<p>本文介绍了 Dubbo 中本地存根和本地伪装的概念和用法。从本质来讲,本地存根和本地伪装等同于面向切面编程中的概念,通过诸如 Spring 框架提供的 <em>AOP</em> 编程可以达到同样的目的。通过本文如何开发一个本地存根和本地伪装的示例,读者可以直观的感受到通过框架提供的机制更加方便快捷。同时,对于很多简单的场景和动态配置推送的场景,框架提供了仅通过配置而无需编码的方式来满足,进一步提升了框架使用者的效率。</p>
+<hr class="footnotes-sep">
+<section class="footnotes">
+<ol class="footnotes-list">
+<li id="fn1" class="footnote-item"><p><a href="https://github.com/apache/incubator-dubbo-samples/tree/master/dubbo-samples-stub">https://github.com/apache/incubator-dubbo-samples/tree/master/dubbo-samples-stub</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p>
+</li>
+<li id="fn2" class="footnote-item"><p><a href="https://github.com/apache/incubator-dubbo-samples/tree/master/dubbo-samples-mock">https://github.com/apache/incubator-dubbo-samples/tree/master/dubbo-samples-mock</a> <a href="#fnref2" class="footnote-backref">↩︎</a> <a href="#fnref2:1" class="footnote-backref">↩︎</a></p>
+</li>
+<li id="fn3" class="footnote-item"><p><a href="http://dubbo.apache.org/zh-cn/docs/user/demos/local-mock.html">http://dubbo.apache.org/zh-cn/docs/user/demos/local-mock.html</a> <a href="#fnref3" class="footnote-backref">↩︎</a></p>
+</li>
+</ol>
+</section>
+</section><footer class="footer-container"><div class="footer-body"><img src="/img/dubbo_gray.png"/><img class="apache" src="/img/apache_logo.png"/><div class="cols-container"><div class="col col-12"><h3>Disclaimer</h3><p>Apache Dubbo is an effort undergoing incubation at The Apache Software Foundation (ASF), sponsored by the Incubator. Incubation is required of all newly accepted projects until a further review indicates that the infrastructure, communications, and decision making proce [...]
+ <script src="https://f.alicdn.com/react/15.4.1/react-with-addons.min.js"></script>
+ <script src="https://f.alicdn.com/react/15.4.1/react-dom.min.js"></script>
+ <script>
+ window.rootPath = '';
+ </script>
+ <script src="/build/blogDetail.js"></script>
+ <!-- Global site tag (gtag.js) - Google Analytics -->
+ <script async src="https://www.googletagmanager.com/gtag/js?id=UA-112489517-1"></script>
+ <script>
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+
+ gtag('config', 'UA-112489517-1');
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/zh-cn/blog/dubbo-stub-mock.json b/zh-cn/blog/dubbo-stub-mock.json
new file mode 100644
index 0000000..f9d1899
--- /dev/null
+++ b/zh-cn/blog/dubbo-stub-mock.json
@@ -0,0 +1,6 @@
+{
+ "filename": "dubbo-stub-mock.md",
+ "__html": "<h1>本地存根和本地伪装</h1>\n<h2>基本概念</h2>\n<p>典型的 RPC 调用客户端是依赖并且只依赖接口编程来进行远程调用的。在真正发起远程调用之前,用户往往需要做一些预处理的工作,比如提前校验参数。在拿到返回调用结果之后,用户可能需要缓存结果,或者是在调用失败的时候构造容错数据,而不是简单的抛出异常。</p>\n<p>这个时候,用户可以编写出类似以下的代码来处理上面提出的这些场景:</p>\n<pre><code class=\"language-java\"><span class=\"hljs-keyword\">try</span> {\n preProcess();\n <span class=\"hljs-keyword\">return</span> service.invoke(...);\n} <span class=\"hljs-keyword\">catch</span> (Throwable e) {\n <span class=\"hljs-keyword\">return</spa [...]
+ "link": "/zh-cn/blog/dubbo-stub-mock.html",
+ "meta": {}
+}
\ No newline at end of file
diff --git a/zh-cn/blog/index.html b/zh-cn/blog/index.html
index 39ccbc4..84e8e6d 100644
--- a/zh-cn/blog/index.html
+++ b/zh-cn/blog/index.html
@@ -12,7 +12,7 @@
<link rel="stylesheet" href="/build/blog.css" />
</head>
<body>
- <div id="root"><div class="blog-list-page" data-reactroot=""><header class="header-container header-container-normal"><div class="header-body"><a href="/zh-cn/index.html"><img class="logo" src="/img/dubbo_colorful.png"/></a><div class="search search-normal"><span class="icon-search"></span></div><span class="language-switch language-switch-normal">En</span><div class="header-menu"><img class="header-menu-toggle" src="/img/menu_gray.png"/><ul><li class="menu-item menu-item-normal"><a hre [...]
+ <div id="root"><div class="blog-list-page" data-reactroot=""><header class="header-container header-container-normal"><div class="header-body"><a href="/zh-cn/index.html"><img class="logo" src="/img/dubbo_colorful.png"/></a><div class="search search-normal"><span class="icon-search"></span></div><span class="language-switch language-switch-normal">En</span><div class="header-menu"><img class="header-menu-toggle" src="/img/menu_gray.png"/><ul><li class="menu-item menu-item-normal"><a hre [...]
<script src="https://f.alicdn.com/react/15.4.1/react-with-addons.min.js"></script>
<script src="https://f.alicdn.com/react/15.4.1/react-dom.min.js"></script>
<script>