Apache Dubbo 是一款分布式微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力。
曾经基于各个微服务之间的接口,我们需要通过HTTP+restful或者WebService等调用方式来进行服务之间的通信进行数据交互,而Dubbo采用的是分布式SOA(服务规范体系)服务治理方案,通过RPC(远程网络通讯协议)远程服务调用。
使用Dubbo开发的微服务,相互之间具备远程发现与通信的能力,同时利用Dubbo提供的丰富服务治理能力,可以实现如服务发现、负载均衡、流量调度等服务治理的述求。Dubbo又是高度可扩展的,用户几乎可以在任意功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求。
Dubbo提供的基础能力包括:
服务发现
流式通信
负载均衡
流量治理
…
等等,以上功能下文会用实际代码做出一一讲解。
首先我们使用IDEA新建一个SpringBoot项目,然后删除掉所有多余的包,只留下必要的文件。通过new moudoule新建两个SpringBoot项目,分别命名为consumer和provider,如下结构:
如上图所示简历两个独立的模块,分别启动时,假设provider提供了提供了一个里提供了一个方法名为localhost:8080/test的方法,要在consumer中去调用这个方法。
首先我们使用常规的方式调用,如RestTemplate的调用:
@Resource
private RestTemplate restTemplate;
/**
* 测试
* @return
*/
@Override
public String testConsumer() {
return restTemplate.getForObject("http://localhost:8080/test",String.class);
}
其次我们看看Dubbo中的调用方式:
@DubboReference(version = "2.0")
private ProviderService providerService;
/**
* 测试
* @return
*/
@Override
public String testConsumer() {
// 在consumer中调用provider中的接口
return providerService.testProvider();
}
那么有的同学可能会发现上面的这个注解:@DubboReference(version = “2.0”),这个注解的意思就是通过指定接口的版本号去调用相应版本号下的方法。
@DubboService(version = "1.0")
public class ProviderServiceImpl implements ProviderService {
/**
* 测试
* @return
*/
public String testProvider() {
return "1.0调用成功...";
}
}
@DubboService(version = "2.0")
public class ProviderServiceImpl02 implements ProviderService {
/**
* 测试
* @return
*/
public String testProvider() {
return "2.0调用成功...";
}
}
简单介绍了一下Dubbo的使用,相信对于Dubbo的好处已经有了初步的理解。至于具体是怎么实现地,由于步骤较多,建议同学们搜索视频学习巩固。
在2.x版本的Dubbo中是基于接口粒度的服务发现机制,存储以key-value的形式存储;3.x中则采用基于应用粒度的服务发现机制,使得Dubbo的性能带来了质的飞跃。新模型可大幅度提高系统资源的利用率,降低(50%)Dubbo地址的单机内存消耗,降低(90%)注册中心集群的存储与推送压力,Dubbo可支持集群规模步入百万实例层次。并且能够打通与其他异构微服务体系的地址互相发现的障碍。 Dubbo3目前是支持对两台的hi之发现模型的支持,即既能注册接口,又能注册应用。
我们来看看注册接口和注册应用的区别,这里我们用的是Zookeeper的工具zookeeper-dev-ZooInspector.jar来对服务进行一个查看。在此之前需要对上面提到的两个服务的application.properties文件中分别配置一下项目的端口以及Dubbo的信息:
server.port=8082
dubbo.application.name=provider-application
dubbo.protocol.name=tri
dubbo.protocol.port=20880
dubbo.registry.address=zookeeper://127.0.0.1:2181
server.port=8081
dubbo.application.name=consumer-application
dubbo.protocol.name=dubbo
dubbo.protocol.port=20881
dubbo.registry.address=zookeeper://127.0.0.1:2181
启动项目后打开工具,就可以看到这样一个界面:
可以看到,基于接口注册的部分,明确地写出了在哪个服务器地哪个端口下注册了多少个方法,值得注意的是,如果你的这里有一百个接口它会注册一百个接口。而基于应用注册发现部分,也可以清楚地看到每个服务对应的IP以及端口,就很一目了然了。
这里两个模型同时存在的目的是为了Dubbo3.x兼容Dubbo2.x。
那么有的同学疑惑就来了,既然应用级别的注册优势这么大,那怎么关闭掉接口级别的注册呢?其实很简单,只需要在配置文件中配置如下参数就好了:
# 默认是all/interface/instance
# 设置为interface代表只进行接口级的注册
# 设置为instance代表只进行应用级的注册
dubbo.application.register-mode=instance
我们在provider模块中设置dubbo.application.register-mode=instance看一下效果:
小问题:既然不同服务之间的调用不需要通过具体的网址调用了,那么如果consumers调用了一个接口,那Dubbo怎么知道这个接口是来自于provider1还是prrovider2呢?
大家可以看到Dubbo会在mapping中存储对应的接口来自于哪个应用,进而在调用的时候能从众多服务中精准调用我们想要的方法。
官方的话式这样说的:
定义了全新的 RPC 通信协议 – Triple,一句话概括 Triple:它是基于 HTTP/2 上构建的 RPC 协议,完全兼容 gRPC,并在此基础上扩展出了更丰富的语义。
1、更容易到适配网关、Mesh架构,Triple 协议让 Dubbo 更方便的与各种网关、Sidecar 组件配合工作。
2、多语言友好,推荐配合 Protobuf 使用 Triple 协议,使用 IDL 定义服务,使用 Protobuf 编码业务数据。
3、流式通信支持。Triple 协议支持 Request Stream、Response Stream、Bi-direction Stream。
那么流式通信的支持有什么好处呢?举个例子,如果你想获取用户数据,是不是得发送一次请求,如果你想再次获取用户信息,是不是得重新发起一次请求?那么使用Triple 协议,可以让你一次请求,响应多次,满足我们实际业务开发过程中的需求。
我们通过代码来看看Triple协议的 服务端流ServerStream 和 客户端流ClientStream 是怎么通信的,这里我们只挑关键的代码块展示一下:
在provider服务中写下如下代码:
/**
* SERVER_STREAM 服务端流
* @param name
* @param response
*/
@Override
public void testServiceStream(String name, StreamObserver<String> response) {
// 处理name后响应
response.onNext("第一次响应:姓名:" + name);
// 再次处理再次响应
response.onNext("第二次响应:您的姓名:" + name);
// 处理完后调用这个方法告诉客户端的消费者本次请求结束了
response.onCompleted();
}
在consumer服务中编写如下代码:
@Override
public String testServiceStream() {
providerService.testServiceStream("肝铁侠", new StreamObserver<String>() {
// 服务端的每一次响应都会接收到参数
@Override
public void onNext(String data) {
System.out.println("接收到的结果为:" + data);
}
// 请求失败时的操作
@Override
public void onError(Throwable throwable) {
System.out.println("接收出错啦...");
}
// 请求成功时的操作
@Override
public void onCompleted() {
System.out.println("服务执行完毕!");
}
});
return "执行完毕";
}
然后在Controller中写好调用:
@RequestMapping("/testConsumer")
@ResponseBody
public String test(){
return consumerService.testServiceStream();
}
然后直接在网页上通过 localhost:端口号/testConsumer 调用即可,控制台的打印如下图所示:
ClientStream,客户端流,即客户端不断地给服务端发送数据,服务端又不断地给客户端发送数据,也可以称之为双端流,相对于服务端流更加高级一点,但是效果和服务端流一样。
在consumer服务中的主要代码:
@Override
public String testObserverStream() {
StreamObserver streamObserver = providerService.testObserverStream(new StreamObserver<String>() {
// 客户端的每一次响应都会接收到参数
@Override
public void onNext(String data) {
}
// 请求失败时的操作
@Override
public void onError(Throwable throwable) {
}
// 请求成功时的操作
@Override
public void onCompleted() {
}
});
// 给服务端发送数据
streamObserver.onNext("肝铁侠");
streamObserver.onNext("绿巨人");
streamObserver.onNext("雷神");
streamObserver.onNext("美国队长");
streamObserver.onCompleted();
return "执行完毕";
}
在consumer服务中的控制器代码:
/**
* 测试 ObserverStream
* @return
*/
@RequestMapping("/testObserverStream")
@ResponseBody
public String testObserverStream(){
return consumerService.testObserverStream();
}
在provider服务中的主要代码:
@Override
public StreamObserver<String> testObserverStream(StreamObserver<String> response) {
return new StreamObserver<String>() {
@Override
public void onNext(String data) {
System.out.println("服务端发送过来的数据:" + data);
// 处理接收数据
// ...
// 响应结果
response.onNext("响应结果:" + data);
}
@Override
public void onError(Throwable throwable) {
System.out.println("接收出错啦...");
}
@Override
public void onCompleted() {
System.out.println("服务执行完毕!");
}
};
}
然后直接在网页上通过 localhost:端口号/testObserverStream 调用即可,控制台的打印如下图所示:
今天的分享先到此为止,下一期继续分享Dubbo的负载均衡和流量治理。