1. 简介

1.1 了解RPC

RPC(Remote Procedure Call) 远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。也就是说两台服务器 A,B,一个应用部署在 A 服务器上,想要调用 B 服务器上应用提供的方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。

注意:RPC 只是一种通信模式,和 http 并不冲突对立,相反 http 可以作为 RPC 传输数据的一种协议。

1.2 了解Dubbo

Apache Dubbo 是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力。这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通信能力, 同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。同时 Dubbo 是高度可扩展的,用户几乎可以在任意功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求。

Dubbo3 基于 Dubbo2 演进而来,在保持原有核心功能特性的同时, Dubbo3 在易用性、超大规模微服务实践、云原生基础设施适配、安全设计等几大方向上进行了全面升级。

2. 快速入门

2.1 引入依赖

1、第一种方式

  1. <dependency>
  2. <groupId>com.alibaba.boot</groupId>
  3. <artifactId>dubbo-spring-boot-starter</artifactId>
  4. <version>0.2.0</version>
  5. </dependency>

2、第二种方式。(推荐)

  1. <dependency>
  2. <groupId>org.apache.dubbo</groupId>
  3. <artifactId>dubbo-spring-boot-starter</artifactId>
  4. <version>3.0.5</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.zookeeper</groupId>
  8. <artifactId>zookeeper</artifactId>
  9. <version>3.4.12</version>
  10. <exclusions>
  11. <exclusion>
  12. <groupId>org.slf4j</groupId>
  13. <artifactId>slf4j-log4j12</artifactId>
  14. </exclusion>
  15. </exclusions>
  16. </dependency>
  17. <!-- zookeepre集成dubbo所需依赖 -->
  18. <dependency>
  19. <groupId>org.apache.curator</groupId>
  20. <artifactId>curator-x-discovery</artifactId>
  21. <version>5.2.0</version>
  22. <exclusions>
  23. <exclusion>
  24. <groupId>org.apache.zookeeper</groupId>
  25. <artifactId>zookeeper</artifactId>
  26. </exclusion>
  27. </exclusions>
  28. </dependency>

2.2 服务创建

我们这里使用 zookeeper 注册中心,需要提前准备好。

1、创建 3 个模块,common、provider、consumer。
2、在 common 模块中定义实体类 User,需要实现 Serializable 接口。
3、在 common 模块中定义 service 接口 UserService。
4、在 生产者服务中引入 common 模块,实现 UserService 接口。

  1. @DubboService
  2. public class UserServiceImpl implements UserService {
  3. @Override
  4. public String sayHello(String name) {
  5. return "hello " + name;
  6. }
  7. @Override
  8. public User getUserInfo(Integer id) {
  9. return new User(id, "威少");
  10. }
  11. }

4、在生产者服务中配置端口、zookeeper 信息。

  1. server:
  2. port: 8082
  3. dubbo:
  4. application:
  5. name: provide-service
  6. registry:
  7. address: zookeeper://192.168.58.50:2181

5、在生产者服务的启动类上开启 dubbo 注解扫描。

  1. @EnableDubbo // 开启dubbo的注解扫描
  2. @SpringBootApplication
  3. public class DubboProviderApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(DubboProviderApplication.class, args);
  6. }
  7. }

6、在消费者服务的启动类上开启 dubbo 注解扫描,并修改配置文件,添加端口、zookeeper 信息。

2.3 服务调用

1、在消费者服务中使用 @Reference 注解,远程调用 dubbo,即可实现像调用本地服务一样调用远程服务。

  1. @RequestMapping("/user")
  2. @RestController
  3. public class UserController {
  4. @DubboReference
  5. private UserService userService;
  6. //@GetMapping("/{name}")
  7. //public String getData(@PathVariable String name) {
  8. // String msg = userService.sayHello(name);
  9. // System.out.println("调用远程服务得到的消息:" + msg);
  10. // return msg;
  11. //}
  12. @GetMapping("/{id}")
  13. public String getUserInfo(@PathVariable Integer id) {
  14. User user = userService.getUserInfo(id);
  15. return user.toString();
  16. }
  17. }

2、按顺序启动 provider 和 consumer 服务,浏览器访问测试,查看结果。
image.png

2.4 集成Nacos

1、引入 nacos-config、nacos-discovery 依赖。

  1. server:
  2. port: 8081
  3. spring:
  4. application:
  5. name: consumer-service
  6. profiles:
  7. active: dev
  8. cloud:
  9. nacos:
  10. config:
  11. server-addr: 192.168.58.50:8848
  12. file-extension: yml
  13. namespace: a33e1e2a-fb8b-4157-ba59-fe9a50f44b2c
  14. discovery:
  15. server-addr: 192.168.58.50:8848
  16. namespace: a33e1e2a-fb8b-4157-ba59-fe9a50f44b2c
  17. dubbo:
  18. application:
  19. name: ${spring.application.name}
  20. registry:
  21. address: nacos://192.168.58.50:8848
  22. consumer: # check默认为true,表示启动的时候检查生产者是否存在,如果不存在,提示no Provider,重试3次后,就无法启动。
  23. check: false
  24. protocol:
  25. name: dubbo
  26. port: 20880

2、从 nacos 中查看已注册的服务列表,并测试 url。
image.png

2.5 多服务暴露接口

比如有一个 storage-service 服务,服务里有 storage-api 这样的一个存储模块,项目里有多个生产者都要用到 storage-api 里的接口,我们该怎么暴露接口方法呢?

1、优化服务代码,修改为两个生产者 user-provider,pay-provider,一个消费者 order-consumer,一个存储服务 storage-service。
2、在 storage-servce 中定义个存储接口,可以有自己的默认实现。

  1. public interface StorageService {
  2. InputStream getStorageStream();
  3. }

3、在生产者服务中定义存储接口的实现类,并指定分组。

  1. @DubboService(group = "userStorage")
  2. public class UserStorageServiceImpl implements StorageService {
  3. @Override
  4. public InputStream getStorageStream() {
  5. System.out.println("使用自己定义的 storageservice 方法, user-provider");
  6. return null;
  7. }
  8. }

4、修改生产者服务中协议的端口号,默认为 20880。

  1. dubbo:
  2. protocol: # dubbo协议端口默认20880,rmi协议默认1099,http和hessian协议默认80,如果没有配置端口,采用默认。如果端口为-1,则分配一个未占用的端口。
  3. name: dubbo
  4. port: 20881

5、在消费者服务中引入 StorageService 接口,指定分组。

  1. @DubboReference(group = "payStorage")
  2. private StorageService storageService;

6、启动两个生产者服务,消费者服务,查看执行结果。
image.png

3. Dubbo常用配置

3.1 配置原则

配置原则:JVM => xml => properties

  • JVM 启动:-D 参数优先,这样可以使用户在部署和启动时进行参数重写,比如在启动时需改变协议的端口 -Ddubbo.**protocol.port=20880**
  • XML 次之:如果在 XML 中有配置,则 dubbo.properties 中的相应配置项无效。
  • Properties 最后:相当于缺省值,只有 XML 没有配置时,dubbo.properties 的相应配置项才会生效,通常用于共享公共配置,比如应用名。

    3.2 重试次数

    访问失败后自动切换,当出现失败,重试其它服务器,但重试会带来更长延迟。可通过retries="2"{默认} 来设置重试次数(不含第一次)。

    1. @RequestMapping("/user")
    2. @RestController
    3. public class UserController {
    4. @DubboReference(retries = 4)
    5. private UserService userService;
    6. @DubboReference(group = "payStorage")
    7. private StorageService storageService;
    8. @GetMapping("/{id}")
    9. public String getUserInfo(@PathVariable Integer id) {
    10. User user = userService.getUserInfo(id);
    11. return user.toString();
    12. }
    13. @GetMapping("/storage")
    14. public String getStorage() {
    15. InputStream is = storageService.getStorageStream();
    16. return "使用自己定义的 storageservice 方法";
    17. }
    18. }

    3.3 超时时间

    由于网络或服务端不可靠,会导致调用出现一种不确定的中间状态(超时)。为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间。

1、yml 中配置全局超时时间。

  1. dubbo:
  2. provider:
  3. timeout: 5000 # 全局超时时间,单位毫秒

2、在接口的 @DubboService注解上定义接口超时时间。

  1. @DubboService(group = "payStorage", timeout = 3000)
  2. public class PayStorageServiceImpl implements StorageService {
  3. @Override
  4. public InputStream getStorageStream() {
  5. System.out.println("使用自己定义的 storageservice 方法, pay-provider");
  6. return null;
  7. }
  8. }

3.4 版本号

当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。具体步骤如下:
1、在 service 实现类中定义版本号。

  1. @DubboService(version = "1.0.1")
  2. public class UserServiceImpl implements UserService {}

2、控制器中引入 bean 指定版本号,dubbo3.0 中无法使用 * 表示任意版本号。

  1. @DubboReference(retries = 4, version = "1.0.1")
  2. private UserService userService;

3、在 nacos 中服务列表查看定义的超时时间。
image.png
总结:dubbo 推荐在Provider上尽量多配置 Consumer 端属性,如果 Consumer 端没有配置,会使用 Provider 中的超时时间。

4. 高可用

5. Dubbo原理

5.1 RPC原理

image.png
一次完整的 RPC 调用过程如下:

  1. 服务消费方 (client) 以本地调用方式调用服务。
  2. client stub 接收到调用后负责将方法、参数等封装成能够进行网络传输的消息体。
  3. client stub 将消息进行编码并发送到服务端。
  4. server stub 收到消息后进行解码。
  5. server stub 根据解码结果调用本地的服务。
  6. 本地服务执行并将结果返回给 server stub。
  7. server stub 将返回导入结果进行编码并发送至消费方。
  8. client stub 接收到消息并进行解码。
  9. 服务消费方(client)得到结果。

    RPC 框架的目标就是要 2~8 这些步骤都封装起来,这些细节对用户来说是透明的,不可见的。

5.2 框架设计

image.png

  • config 配置层:对外配置接口,以 ServiceConfig, ReferenceConfig 为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类
  • proxy 服务代理层:服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton, 以 ServiceProxy 为中心,扩展接口为 ProxyFactory
  • registry 注册中心层:封装服务地址的注册与发现,以服务 URL 为中心,扩展接口为 RegistryFactory, Registry, RegistryService
  • cluster 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
  • monitor 监控层:RPC 调用次数和调用时间监控,以 Statistics 为中心,扩展接口为 MonitorFactory, Monitor, MonitorService
  • protocol 远程调用层:封装 RPC 调用,以 Invocation, Result 为中心,扩展接口为 Protocol, Invoker, Exporter
  • exchange 信息交换层:封装请求响应模式,同步转异步,以 Request, Response 为中心,扩展接口为 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
  • transport 网络传输层:抽象 mina 和 netty 为统一接口,以 Message 为中心,扩展接口为 Channel, Transporter, Client, Server, Codec
  • serialize 数据序列化层:可复用的一些工具,扩展接口为 Serialization, ObjectInput, ObjectOutput, ThreadPool