注:本文是对 Dubbo 官方文档的示例部分整理而成。
日常使用 Dubbo 一般来说仅仅使用了其服务远程调用的最基础功能,其他的额外功能例如参数验证、结果缓存、异步、回调等都需要各服务自己去处理。
其实在使用 Dubbo 的时候,除了 RPC ,Dubbo 还为我们提供了很多的增强功能和特性。学习技术是一个 怎么用->会用->用好->不够用自己加 的一个过程。在会用的基础上,逐渐走向用好。用好的前提一部分除了对 Dubbo 本身的特殊功能的了解使用,还有其底层的工作逻辑也是一部分。So,在对 Dubbo 的工作逻辑进行深入了解前,在本篇,先来看看 Dubbo 为我们提供了哪些额外福利吧?
check="true/false"
指定是否开启启动检查。详细参考:《Dubbo示例-启动检查》
说明:
retries="2"
来设置重试次数。方式:
通过 cluster="failsafe"
指定集群容错模式。
详细参考:《Dubbo示例-集群容错》
说明:
方式:
通过loadbalance="roundrobin"
指定负载均衡策略。
详细参考:《Dubbo示例-负载均衡》
dispatcher="all"
和 threadpool="fixed"
分别来设置不同的分发策略和线程池配置。详细参考:《Dubbo示例-线程模型》
说明:
Dubbo 允许配置多协议,在不同服务上支持不同协议或者同一服务上同时支持多种协议。
方式:
配置多个协议,不同服务可以通过protocol="dubbo,hessian"
的方式来指定暴露服务的协议。
详细参考:《Dubbo示例-多协议》
registry="xxxRegistry"
的方式制定注册中心。详细参考:《Dubbo示例-多注册中心》
group="member"
区分不同实现。详细参考:《Dubbo示例-服务分组》
version="1.0.0"
来指定不同的版本号,版本号不同的服务之间相互不引用。详细参考:《Dubbo示例-多版本》
<dubbo:reference interface="com.xxx.MenuService" group="*" merger="true" />
详细参考:《Dubbo示例-分组聚合》
cache="lru"
来选择缓存的策略。详细参考:《Dubbo示例-结果缓存》
RpcContext
获得服务的上下文信息。详细参考:《Dubbo示例-上下文信息》
setAttachment
设置的 KV 对,在完成下面一次远程调用会被清空,即多次远程调用要多次设置。
RpcContext.getContext().setAttachment("index", "1"); // 隐式传参,后面的远程调用都会隐式将这些参数发送到服务器端,类似cookie,用于框架集成,不建议常规业务使用
xxxService.xxx(); // 远程调用
// ...
在服务提供方端获取隐式参数
public class XxxServiceImpl implements XxxService {
public void xxx() {
// 获取客户端隐式传入的参数,用于框架集成,不建议常规业务使用
String index = RpcContext.getContext().getAttachment("index");
}
}
详细参考:《Dubbo示例-隐式参数》
说明:
方式:
需要服务提供者事先定义CompletableFuture签名的服务,具体参见服务端异步执行接口定义:
public interface AsyncService {
CompletableFuture<String> sayHello(String name);
}
注意接口的返回类型是CompletableFuture<String>
。
XML引用服务:
<dubbo:reference id="asyncService" timeout="10000" interface="com.alibaba.dubbo.samples.async.api.AsyncService"/>
调用远程服务:
// 调用直接返回CompletableFuture
CompletableFuture<String> future = asyncService.sayHello("async call request");
// 增加回调
future.whenComplete((v, t) -> {
if (t != null) {
t.printStackTrace();
} else {
System.out.println("Response: " + v);
}
});
// 早于结果输出
System.out.println("Executed before response return.");
服务接口定义:
public interface AsyncService {
CompletableFuture<String> sayHello(String name);
}
服务实现:
public class AsyncServiceImpl implements AsyncService {
@Override
public CompletableFuture<String> sayHello(String name) {
RpcContext savedContext = RpcContext.getContext();
// 建议为supplyAsync提供自定义线程池,避免使用JDK公用线程池
return CompletableFuture.supplyAsync(() -> {
System.out.println(savedContext.getAttachment("consumer-key1"));
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "async response from provider.";
});
}
}
通过return CompletableFuture.supplyAsync()
,业务执行已从Dubbo线程切换到业务线程,避免了对Dubbo线程池的阻塞。
Dubbo提供了一个类似Serverlet 3.0的异步接口AsyncContext
,在没有CompletableFuture签名接口的情况下,也可以实现Provider端的异步执行。
服务接口定义:
public interface AsyncService {
String sayHello(String name);
}
服务暴露,和普通服务完全一致:
<bean id="asyncService" class="org.apache.dubbo.samples.governance.impl.AsyncServiceImpl"/>
<dubbo:service interface="org.apache.dubbo.samples.governance.api.AsyncService" ref="asyncService"/>
服务实现:
public class AsyncServiceImpl implements AsyncService {
public String sayHello(String name) {
final AsyncContext asyncContext = RpcContext.startAsync();
new Thread(() -> {
// 如果要使用上下文,则必须要放在第一句执行
asyncContext.signalContextSwitch();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 写回响应
asyncContext.write("Hello " + name + ", response from provider.");
}).start();
return null;
}
}
2.2.0
开始,每个服务默认都会在本地暴露。在引用服务的时候,默认优先引用本地服务。如果希望引用远程服务可以使用一下配置强制引用远程服务。<dubbo:reference ... scope="remote" />
详细参考:《Dubbo示例-本地调用》
bean id="callbackService" class="com.callback.impl.CallbackServiceImpl" />
<dubbo:service interface="com.callback.CallbackService" ref="callbackService" connections="1" callbacks="1000">
<dubbo:method name="addListener">
<dubbo:argument index="1" callback="true" />
<!--也可以通过指定类型的方式-->
<!--<dubbo:argument type="com.demo.CallbackListener" callback="true" />-->
</dubbo:method>
</dubbo:service>
详细参考:《Dubbo示例-参数回调》
oninvoke
、onreturn
、onthrow
三个事件,可以配置当事件发生时,通知哪个类的哪个方法。<bean id ="demoCallback" class = "org.apache.dubbo.callback.implicit.NofifyImpl" />
<dubbo:reference id="demoService" interface="org.apache.dubbo.callback.implicit.IDemoService" version="1.0.0" group="cn" >
<dubbo:method name="get" async="true" onreturn = "demoCallback.onreturn" onthrow="demoCallback.onthrow" />
</dubbo:reference>
详细参考:《Dubbo示例-事件通知》
<dubbo:service interface="com.foo.BarService" stub="true" />
或
<dubbo:service interface="com.foo.BarService" stub="com.foo.BarServiceStub" />
提供 Stub 的实现 [2]:
package com.foo;
public class BarServiceStub implements BarService {
private final BarService barService;
// 构造函数传入真正的远程代理对象
public BarServiceStub(BarService barService){
this.barService = barService;
}
public String sayHello(String name) {
// 此代码在客户端执行, 你可以在客户端做ThreadLocal本地缓存,或预先验证参数是否合法,等等
try {
return barService.sayHello(name);
} catch (Exception e) {
// 你可以容错,可以做任何AOP拦截事项
return "容错数据";
}
}
}
<dubbo:reference interface="com.foo.BarService" mock="true" />
或
<dubbo:reference interface="com.foo.BarService" mock="com.foo.BarServiceMock" />
在工程中提供 Mock 实现 [2]:
package com.foo;
public class BarServiceMock implements BarService {
public String sayHello(String name) {
// 你可以伪造容错数据,此方法只在出现RpcException时被执行
return "容错数据";
}
}
如果服务的消费方经常需要 try-catch 捕获异常,如:
Offer offer = null;
try {
offer = offerService.findOffer(offerId);
} catch (RpcException e) {
logger.error(e);
}
请考虑改为 Mock 实现,并在 Mock 实现中 return null。如果只是想简单的忽略异常,在 2.0.11
以上版本可用:
<dubbo:reference interface="com.foo.BarService" mock="return null" />
详细参考:《Dubbo示例-本地伪装》
delay="5000"
。详细参考:《Dubbo示例-延迟暴露》
说明:
Dubbo 可以从客户端、服务端维度对接口和方法级别进行并发控制,限制每个方法执行的最大线程数量。
方式:
在服务提供者配置并发数量和规则:
<dubbo:service interface="com.foo.BarService" executes="10" />
<dubbo:service interface="com.foo.BarService" actives="10" />
或
<dubbo:reference interface="com.foo.BarService" actives="10" />
详细参考:《Dubbo示例-并发控制》
说明:
Dubbo 可以设置服务器端接受的连接数和客户端使用的连接数。
方式:
<dubbo:provider protocol="dubbo" accepts="10" />
或
<dubbo:protocol name="dubbo" accepts="10" />
<dubbo:reference interface="com.foo.BarService" connections="10" />
或
<dubbo:service interface="com.foo.BarService" connections="10" />
详细参考:《Dubbo示例-连接控制》
lazy="true"
<dubbo:protocol name="dubbo" lazy="true" />
说明:
方式:
使用 sticky="true"
开启粘滞连接。
详细参考:《Dubbo示例-粘滞连接》
token="true"
或者指定密码token="123456"
开启令牌验证。详细参考:《Dubbo示例-令牌验证》
url="dubbo://localhost:20890"
配置点对点直连。详细参考:《Dubbo示例-直连提供者》
register="false"
对服务是否注册进行控制。详细参考:《Dubbo示例-只订阅》
subscribe="false"
对注册中心是否订阅服务来进行控制。详细参考:《Dubbo示例-只注册》
说明:
方式:
通过dynamic="false"
设置注册中心的非动态管理模式。
详细参考:《Dubbo示例-静态服务》
Map
表示,通常用于框架集成。generic="true"
<dubbo:reference id="barService" interface="com.foo.BarService" generic="true" />
在 Java 代码获取 barService 并开始泛化调用:
GenericService barService = (GenericService) applicationContext.getBean("barService");
Object result = barService.$invoke("sayHello", new String[] { "java.lang.String" }, new Object[] { "World" });
详细参考:《Dubbo示例-泛化引用》
-说明:
泛接口实现方式主要用于服务器端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的远程服务Mock框架,可通过实现GenericService接口处理所有服务请求。
GenericService
接口:package com.foo;
public class MyGenericService implements GenericService {
public Object $invoke(String methodName, String[] parameterTypes, Object[] args) throws GenericException {
if ("sayHello".equals(methodName)) {
return "Welcome " + args[0];
}
}
}
在 Spring 配置申明服务的实现:
<bean id="genericService" class="com.foo.MyGenericService" />
<dubbo:service interface="com.foo.BarService" ref="genericService" />
详细参考:《Dubbo示例-泛化实现》
EchoService
接口,只需将任意服务引用强制转型为 EchoService
,即可使用。Spring 配置:
<dubbo:reference id="memberService" interface="com.xxx.MemberService" />
代码:
// 远程服务引用
MemberService memberService = ctx.getBean("memberService");
EchoService echoService = (EchoService) memberService; // 强制转型为EchoService
// 回声测试可用性
String status = echoService.$echo("OK");
assert(status.equals("OK"));
详细参考:《Dubbo示例-回声测试》
关于配置更加详细的说明,参考 Dubbo 官方文档 schema 配置参考手册