Dubbo的高级特性:服务管控篇
王有志,一个分享硬核Java技术的互金摸鱼侠
加入Java人的提桶跑路群:共同富裕的Java人
上一篇,我们已经介绍了 DUbbo 在服务治理方面提供的特性,今天我们一起来看看 Dubbo 在其它方面提供的特性。同服务治理篇一样,本文的目的在于学会使用 Dubbo 在服务管控方面提供的特性,依旧不涉及任何实现原理。
工程结构
嗯~~
是这样的,因为电脑过于拉胯,而且 IDEA 着实有些吃内存了,所以我将测试工程按照子项目合并到一起了,目前我使用的工程结构是这样的:

子模块名由两部分组成:配置方式+功能,如: XMLProvider ,表示以 XML 配置方式为主的服务提供方。
Tips:IDEA 快要追上“内存雄狮” CLion 了。
本地存根(Stub)
使用 Dubbo 时,服务使用方只集成了接口,所有的实现全都在服务提供方,但部分场景中,我们希望服务使用方完成一些逻辑的处理,以此来减少 RPC 交互带来的性能消耗,例如:将参数校验放在服务使用方去做,减少一次与服务调用方的网络交互。

这种场景中,我们可以使用 Dubbo 提供的本地存根特性。我们有如下的服务提供方的工程结构:

xml-provider-api 模块中定义了对外提供服务的接口 XMLProviderService ,代码如下:
public interface XMLProviderService {
String say(String message);
}
以及接口存根 XMLProviderServiceStub,代码如下:
public class XMLProviderServiceStub implements XMLProviderService {
private final XMLProviderService xmlProviderService;
public XMLProviderServiceStub(XMLProviderService xmlProviderService) {
this.xmlProviderService = xmlProviderService;
}
@Override
public String say(String message) {
if (StringUtils.isBlank(message)) {
return "message不能为空!";
}
try {
return this.xmlProviderService.say(message);
} catch (Exception e) {
return "远程调用失败:" + e.getMessage();
}
}
}
接着我们在服务使用方的工程中配置接口存根:
<dubbo:reference id="xmlProviderService" interface="com.wyz.api.XMLProviderService" stub="com.wyz.api.stub.XMLProviderServiceStub"/>
Tips:使用本地存根,要求存根的实现类必须有传入 Proxy 实例(服务使用方生成的 Proxy 实例)的构造函数。
本地伪装(Mock)
本地伪装即我们在《Dubbo的高级特性:服务治理篇》中提到的服务降级,我们今天再稍微做一个补充。本地伪装是本地存根的一个子集,本地存根可以处理 RPC 调用环节中各种各样的错误和异常,而本地伪装则专注于处理 RpcException (如网络失败,响应超时等)这种需要容错处理的异常。
我们为 XMLProviderService 添加一个本地伪装服务 XMLProviderServiceMock,工程结构如下:

XMLProviderServiceMock 的代码如下:
public class XMLProviderServiceMock implements XMLProviderService {
@Override
public String say(String message) {
return "服务出错了!";
}
}
配置文件可以按如下方式配置:
<dubbo:reference id="xmlProviderService" interface="com.wyz.api.XMLProviderService" mock="true"/>
这种配置中,要求 Mock 的实现必须按照“接口名+Mock 后缀”的方式进行命名;如果不想使用这种命名方式,可以使用全限名:
<dubbo:reference id="xmlProviderService" interface="com.wyz.api.XMLProviderService" mock="com.wyz.api.mock.XMLProviderServiceMock"/>
Tips:再“重复”一遍 Mock 的原因是,上一篇中出了一点错误,本应在<dubbo:reference>标签中做的配置,我写到了<dubbo:service>标签中,产生错误的原因还是没有动手在项目中歇一歇,哎,真应了那句“纸上得来终觉浅,绝知此事要躬行”。
参数回调
Dubbo 支持参数回调功能,使服务提供方可以“反向”调用服务使用方,该功能是基于长链接生成的反向代理实现的,效果类似于异步调用。我们举个支付的例子:
XMLProvider 工程的 xml-provider-api 模块中添加 PaymentService 接口,同时添加 PaymentNotifyService 用于通知PaymentService 的结果:
public interface PaymentService {
void payment(String cardNo, PaymentNotifyService paymentNotifyService);
}
public interface PaymentNotifyService {
void paymentNotify(String message);
}
XMLProvider 工程的 xml-provider-service 模块中实现 PaymentService 接口:
public class PaymentServiceImpl implements PaymentService {
@Override
public void payment(String cardNo, PaymentNotifyService paymentNotifyService) {
System.out.println("向卡号[" + cardNo + "]付钱!");
// 业务逻辑
paymentNotifyService.paymentNotify("付款成功");
}
}
执行PaymentService#payment方法,并调用PaymentNotifyService#paymentNotify方法通知服务调用方执行结果。
XMLConsumer 工程中实现 PaymentNotifyService 接口:
public class PaymentNotifyServiceImpl implements PaymentNotifyService {
@Override
public void paymentNotify(String message) {
System.out.println("支付结果:" + message);
}
}
来看一下此时的工程结构:

接下来是 XML 的配置,参数回调中,我们需要关注的是服务提供方 XMLProvider 工程的 xml-provider-service 模块的配置:
<bean id="paymentServiceImpl" class="com.wyz.service.impl.PaymentServiceImpl"/>
<dubbo:service interface="com.wyz.api.PaymentService" ref="paymentServiceImpl" callbacks="10">
<dubbo:method name="payment">
<dubbo:argument index="1" callback="true"/>
</dubbo:method>
</dubbo:service>
配置通过第 4 行的<dubbo:argument index="1" callback="true"/>来确定 PaymentService#payment 方法中第 2 个(index 从 0 开始)参数是回调参数 ;callbacks限制了同一个长链接下回调的次数,而不是总共回调的次数。
Tips:在实际的支付业务场景中,更倾向于异步处理,比如服务提供方在接收到时,启动新线程处理支付业务并调用通知接口,主线程返回成功接收支付请求。
异步调用
异步调用允许服务提供方立即返回响应,同时后台继续执行请求处理,当服务使用方请求响应结果时,服务提供方将结果返回。

DUbbo 支持两种异步调用方式:
使用 CompletableFuture 接口
使用 RpcContext
DUbbo 2.7 之后,DUbbo 以CompletableFuture 接口为异步编程的基础。
使用 CompletableFuture 实现异步调用
我们先来看如何使用 CompletableFuture 实现异步调用,声明 CompletableFutureAsyncService 接口:
public interface CompletableFutureAsyncService {
CompletableFuture<String> async(String message);
}
接着是接口实现:
public class CompletableFutureAsyncServiceImpl implements CompletableFutureAsyncService {
@Override
public CompletableFuture<String> async(String message) {
return CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " say : " + message);
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "异步调用成功!";
});
}
}
XML 配置与普通的 Dubbo RPC 接口配置相同,xml-provider-service 模块的配置:
<bean id="completableFutureAsyncServiceImpl" class="com.wyz.service.impl.CompletableFutureAsyncServiceImpl" />
<dubbo:service interface="com.wyz.api.CompletableFutureAsyncService" ref="completableFutureAsyncServiceImpl" />
XMLConsumer 模块的配置:
<dubbo:reference id="completableFutureAsyncService" interface="com.wyz.api.CompletableFutureAsyncService"/>
使用方式也非常简单:
CompletableFuture<String> completableFuture = completableFutureAsyncService.async("Hello");
System.out.println(completableFuture.get());
Tips:
Dubbo 中使用 CompletableFuture 与单独使用 CompletableFuture 并无什么差异~~
CompletableFutureAsyncServiceImpl 的实现中打印接口名称的目的是为了清晰的展示出异步调用的效果;
CompletableFuture#supplyAsync(Supplier<U> supplier)默认使用ForkJoinPool#commonPool();重载方法
CompletableFuture#supplyAsync(Supplier<U> supplier, Executor executor)允许使用自定义线程池。
使用 AsyncContext 实现异步调用
除了使用 CompletableFuture 外,还可以通过 Dubbo 定义的 AsyncContext 实现异步调用。先来编写接口和接口实现:
public interface RpcContextAsyncService {
String async(String message);
}
public class RpcContextAsyncServiceImpl implements RpcContextAsyncService {
@Override
public String async(String message) {
final AsyncContext asyncContext = RpcContext.startAsync();
new Thread(() -> {
asyncContext.signalContextSwitch();
asyncContext.write(Thread.currentThread().getName() + " say : " + message);
}).start();
// 异步调用中,这个返回值完全没有意义
return null;
}
}
服务提供方的配置与其它 Dubbo 接口的配置并无不同:
<bean id="rpcContextAsyncServiceImpl" class="com.wyz.service.impl.RpcContextAsyncServiceImpl"/>
<dubbo:service interface="com.wyz.api.RpcContextAsyncService" ref="rpcContextAsyncServiceImpl"/>
接着是服务使用方的配置,需要添加 async 参数:
<dubbo:reference id="rpcContextAsyncService" interface="com.wyz.api.RpcContextAsyncService" async="true"/>
最后是在服务使用方中调用 RPC 接口:
rpcContextAsyncService.async("Thanks");
Future<String> future = RpcContext.getServiceContext().getFuture();
System.out.println(future.get());
泛化调用
Dubbo 的泛化调用提供了一种不依赖服务提供方 API (SDK)的而调用服务的实现方式。主要场景在于网关平台的实现,通常网关的实现不应该依赖于其他服务的 API(SDK)。
Dubbo 官方提供了 3 种泛化调用的方式:
- 通过API使用泛化调用
- 通过 Spring 使用泛化调用(XML 形式)
- Protobuf 对象泛化调用
这里我们介绍以 XML 的形式配置泛化调用的方式。
准备工作
首先我们再准备一个服务提供的工程 GenericProvider,工程结构如下:

工程中定义了接口即实现类 GenericProviderService 和 GenericProviderServiceImpl,代码如下:
public interface GenericProviderService {
String say(String message);
}
public class GenericProviderServiceImpl implements GenericProviderService {
@Override
public String say(String message) {
return "GenericProvider say:" + message;
}
}
generic-dubbo-provider.xml 中只需要正常配置 GenericProvider 提供的服务即可:
<bean id="genericProviderServiceImpl" class="com.wyz.service.impl.GenericProviderServiceImpl"/>
<dubbo:service interface="com.wyz.service.api.GenericProviderService" ref="genericProviderServiceImpl" generic="true"/>
application.yml 文件的配置我们就不多赘述了。
服务使用方的配置
回到 XMLConsumer 工程中,先配置 Dubbo 服务引用,xml-dubbo-consumer.xml 中添加如下内容:
<dubbo:reference id="genericProviderService" generic="true" interface="com.wyz.service.api.GenericProviderService"/>
参数 generic 声明这是一个泛化调用的服务。此时 IDEA 会将interface="com.wyz.service.api.GenericProviderService"标红,提示“Cannot resolve class 'GenericProviderService' ”,这个我们不需要关注,因为com.wyz.service.api包下确实不存在 GenericProviderService 接口。
接着我们来使用 GenericProviderService 接口:
ApplicationContext context = SpringContextUtils.getApplicationContext();
// genericProviderService是XML中定义的服务id
GenericService genericService = (GenericService) context.getBean("genericProviderService");
// $invoke的3个参数分别为:方法名,参数类型,参数
Object result = genericService.$invoke("say", new String[]{"java.lang.String"}, new Object[]{"wyz"});
System.out.println(result);
这样,我们就可以通过 ApplicationContext 获取到 GenericProviderService 接口提供的服务了。
Tips:SpringContextUtils 用于获取 ApplicationContext,代码如下:
@Component
public class SpringContextUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
public static ApplicationContext getApplicationContext() {
return SpringContextUtils.applicationContext;
}
@Override
public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
}
}
结语
好了,到目前为止,我们已经一起认识并学习了 Dubbo 中常用特性的配置与使用,当然了,经历了多年的发展,Dubbo 的提供的特性远不止于此,如果想要了解更多内容,可以查看阿里巴巴提供的文档《Apache Dubbo微服务框架从入门到精通》。
下一篇,我们从服务注册部分正式开启对 Dubbo 实现原理的探索。
如果本文对你有帮助的话,还请多多点赞支持。如果文章中出现任何错误,还请批评指正。最后欢迎大家关注分享硬核 Java 技术的金融摸鱼侠王有志,我们下次再见!
Dubbo的高级特性:服务管控篇的更多相关文章
- Dubbo之高级特性
Dubbo 注意当启动服务时,该服务会占用本机一个端口号,故在一台电脑启动多个服务时需要在配置文件中更占用本机的端口号 <!--服务占用本机的端口-当本机启动多个服务时须保持不同--> & ...
- Dubbo高级特性实践-泛化调用
引言 当后端Java服务用Dubbo协议作为RPC方案的基础,但部分消费方是前端Restful的PHP服务,不能直接调用,于是在中间架设了Router服务提供统一的基于HTTP的后端调用入口. 而Ro ...
- Dubbo 高级特性实践-泛化调用
引言 当后端Java服务用Dubbo协议作为RPC方案的基础,但部分消费方是前端Restful的PHP服务,不能直接调用,于是在中间架设了Router服务提供统一的基于HTTP的后端调用入口. 而Ro ...
- Java高级特性——反射机制(第二篇)
在Java高级特性——反射机制(第一篇)中,写了很多反射的实例,可能对于Class的了解还是有点迷糊,那么我们试着从内存角度去分析一下. Java内存 从上图可以看出,Java将内存分为堆.栈.方法区 ...
- 基于Dubbo框架构建分布式服务
Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配 ...
- [转载] 基于Dubbo框架构建分布式服务
转载自http://shiyanjun.cn/archives/1075.html Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务 ...
- 消息中间件——RabbitMQ(七)高级特性全在这里!(上)
前言 前面我们介绍了RabbitMQ的安装.各大消息中间件的对比.AMQP核心概念.管控台的使用.快速入门RabbitMQ.本章将介绍RabbitMQ的高级特性.分两篇(上/下)进行介绍. 消息如何保 ...
- 消息中间件——RabbitMQ(八)高级特性全在这里!(下)
前言 上一篇消息中间件--RabbitMQ(七)高级特性全在这里!(上)中我们介绍了消息如何保障100%的投递成功?,幂等性概念详解,在海量订单产生的业务高峰期,如何避免消息的重复消费的问题?,Con ...
- 基于Dubbo框架构建分布式服务(三)
我们将上面开发的服务提供方服务,部署到2个独立的节点上(192.168.14.1和10.10.4.125),然后可以通过Dubbo管理中心查看对应服务的状况,如图所示: 上图中可以看出,该服务有两个独 ...
- 基于Dubbo框架构建分布式服务(一)
Dubbo是Alibaba开源的分布式服务框架,我们可以非常容易地通过Dubbo来构建分布式服务,并根据自己实际业务应用场景来选择合适的集群容错模式,这个对于很多应用都是迫切希望的,只需要通过简单的配 ...
随机推荐
- 家用wife密码设置
1.在浏览器上面输入ip地址:http://192.168.1.1/或http://192.168.0.1/出现路由器登陆窗口输入用户名跟密码.用户名默认一般为:admin,密码为空或为:admin ...
- C# 从0到实战 命名空间
什么是命名空间 命名空间是C#为了解决类名冲突而产生的一种方案,通过特定的前缀来标识一个类,使得编程者可以在自己的命名空间中自由使用各种类名,这很类似于Java中的包. 一般新手学习C#都会使用一个 ...
- 从源码角度分析ScheduleMaster的节点管理流程
ScheduleMaster是一个开源的分布式任务调度系统,它基于.NET Core 3.1平台构建,支持跨平台多节点部署运行. 项目主页:https://github.com/hey-hoho/Sc ...
- JS 实现关键字文本搜索 高亮显示
示例: 利用字符串的 split 方法,通过搜索的关键字分割成数组 在利用数组的 join 方法拼接成字符串 我是利用mock的省份 1 <template> 2 <div cl ...
- C++ Primer 5th Edition, Chapter 2, Solutions
Exercise 2.1 Questions What are the differences between int, long, long long, and short? Between an ...
- OceanBase的学习与使用
OceanBase的学习与使用 简介 1. OceanBase数据库 注意这一块下载的其实是rpm包. 一般是通过下面的OAT或者是OCP工具进行安装. 有x86还有ARM两种架构. 虽然是el7结尾 ...
- ai问答:使用vite如何配置多入口页面
Vite 是一个 web 开发构建工具,它可以用于开发单页应用和多页应用.要在 Vite 中配置多入口,可以: 在 vite.config.js 中定义多个 entry 入口: export defa ...
- 在基于nuxt的移动端页面中引用mint UI的popup组件之父子组件传值
最近在做移动端的wap页面,考虑到要做SEO,所以选定了nuxt+vue+mint ui. 有一个需求是这样的,点击头部菜单栏,出现一个气泡,点击返回首页. 由于一些页面没有统一引用mint的mt-h ...
- 如何理解 Spring Boot 中的 Starter ?
假如 没有 Spring Boot Starter,我们有两种方式来创建 Spring Bean. spring xml 模式 (远古模式,并不推荐) spring API 来创建 Spring Be ...
- 在docker容器里,ffmpeg给视频文件内嵌字幕文件,不生效,如何解决?
用ffmpeg命令,发现执行成功,但视频文件就是没有字幕.看不出问题出现在什么地方.后来直接用ffmpeg添加水印命令测试,发现是缺少字体文件,如下图所示: 报Fontconfig error: Ca ...