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、第一种方式
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
2、第二种方式。(推荐)
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.12</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- zookeepre集成dubbo所需依赖 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
<version>5.2.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
2.2 服务创建
我们这里使用 zookeeper 注册中心,需要提前准备好。
1、创建 3 个模块,common、provider、consumer。
2、在 common 模块中定义实体类 User,需要实现 Serializable 接口。
3、在 common 模块中定义 service 接口 UserService。
4、在 生产者服务中引入 common 模块,实现 UserService 接口。
@DubboService
public class UserServiceImpl implements UserService {
@Override
public String sayHello(String name) {
return "hello " + name;
}
@Override
public User getUserInfo(Integer id) {
return new User(id, "威少");
}
}
4、在生产者服务中配置端口、zookeeper 信息。
server:
port: 8082
dubbo:
application:
name: provide-service
registry:
address: zookeeper://192.168.58.50:2181
5、在生产者服务的启动类上开启 dubbo 注解扫描。
@EnableDubbo // 开启dubbo的注解扫描
@SpringBootApplication
public class DubboProviderApplication {
public static void main(String[] args) {
SpringApplication.run(DubboProviderApplication.class, args);
}
}
6、在消费者服务的启动类上开启 dubbo 注解扫描,并修改配置文件,添加端口、zookeeper 信息。
2.3 服务调用
1、在消费者服务中使用 @Reference 注解,远程调用 dubbo,即可实现像调用本地服务一样调用远程服务。
@RequestMapping("/user")
@RestController
public class UserController {
@DubboReference
private UserService userService;
//@GetMapping("/{name}")
//public String getData(@PathVariable String name) {
// String msg = userService.sayHello(name);
// System.out.println("调用远程服务得到的消息:" + msg);
// return msg;
//}
@GetMapping("/{id}")
public String getUserInfo(@PathVariable Integer id) {
User user = userService.getUserInfo(id);
return user.toString();
}
}
2、按顺序启动 provider 和 consumer 服务,浏览器访问测试,查看结果。
2.4 集成Nacos
1、引入 nacos-config、nacos-discovery 依赖。
server:
port: 8081
spring:
application:
name: consumer-service
profiles:
active: dev
cloud:
nacos:
config:
server-addr: 192.168.58.50:8848
file-extension: yml
namespace: a33e1e2a-fb8b-4157-ba59-fe9a50f44b2c
discovery:
server-addr: 192.168.58.50:8848
namespace: a33e1e2a-fb8b-4157-ba59-fe9a50f44b2c
dubbo:
application:
name: ${spring.application.name}
registry:
address: nacos://192.168.58.50:8848
consumer: # check默认为true,表示启动的时候检查生产者是否存在,如果不存在,提示no Provider,重试3次后,就无法启动。
check: false
protocol:
name: dubbo
port: 20880
2、从 nacos 中查看已注册的服务列表,并测试 url。
2.5 多服务暴露接口
比如有一个 storage-service 服务,服务里有 storage-api 这样的一个存储模块,项目里有多个生产者都要用到 storage-api 里的接口,我们该怎么暴露接口方法呢?
1、优化服务代码,修改为两个生产者 user-provider,pay-provider,一个消费者 order-consumer,一个存储服务 storage-service。
2、在 storage-servce 中定义个存储接口,可以有自己的默认实现。
public interface StorageService {
InputStream getStorageStream();
}
3、在生产者服务中定义存储接口的实现类,并指定分组。
@DubboService(group = "userStorage")
public class UserStorageServiceImpl implements StorageService {
@Override
public InputStream getStorageStream() {
System.out.println("使用自己定义的 storageservice 方法, user-provider");
return null;
}
}
4、修改生产者服务中协议的端口号,默认为 20880。
dubbo:
protocol: # dubbo协议端口默认20880,rmi协议默认1099,http和hessian协议默认80,如果没有配置端口,采用默认。如果端口为-1,则分配一个未占用的端口。
name: dubbo
port: 20881
5、在消费者服务中引入 StorageService 接口,指定分组。
@DubboReference(group = "payStorage")
private StorageService storageService;
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"{默认}
来设置重试次数(不含第一次)。@RequestMapping("/user")
@RestController
public class UserController {
@DubboReference(retries = 4)
private UserService userService;
@DubboReference(group = "payStorage")
private StorageService storageService;
@GetMapping("/{id}")
public String getUserInfo(@PathVariable Integer id) {
User user = userService.getUserInfo(id);
return user.toString();
}
@GetMapping("/storage")
public String getStorage() {
InputStream is = storageService.getStorageStream();
return "使用自己定义的 storageservice 方法";
}
}
3.3 超时时间
由于网络或服务端不可靠,会导致调用出现一种不确定的中间状态(超时)。为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间。
1、yml 中配置全局超时时间。
dubbo:
provider:
timeout: 5000 # 全局超时时间,单位毫秒
2、在接口的 @DubboService
注解上定义接口超时时间。
@DubboService(group = "payStorage", timeout = 3000)
public class PayStorageServiceImpl implements StorageService {
@Override
public InputStream getStorageStream() {
System.out.println("使用自己定义的 storageservice 方法, pay-provider");
return null;
}
}
3.4 版本号
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。具体步骤如下:
1、在 service 实现类中定义版本号。
@DubboService(version = "1.0.1")
public class UserServiceImpl implements UserService {}
2、控制器中引入 bean 指定版本号,dubbo3.0 中无法使用 * 表示任意版本号。
@DubboReference(retries = 4, version = "1.0.1")
private UserService userService;
3、在 nacos 中服务列表查看定义的超时时间。
总结:dubbo 推荐在Provider上尽量多配置 Consumer 端属性,如果 Consumer 端没有配置,会使用 Provider 中的超时时间。
4. 高可用
5. Dubbo原理
5.1 RPC原理
一次完整的 RPC 调用过程如下:
- 服务消费方 (client) 以本地调用方式调用服务。
- client stub 接收到调用后负责将方法、参数等封装成能够进行网络传输的消息体。
- client stub 将消息进行编码并发送到服务端。
- server stub 收到消息后进行解码。
- server stub 根据解码结果调用本地的服务。
- 本地服务执行并将结果返回给 server stub。
- server stub 将返回导入结果进行编码并发送至消费方。
- client stub 接收到消息并进行解码。
- 服务消费方(client)得到结果。
RPC 框架的目标就是要 2~8 这些步骤都封装起来,这些细节对用户来说是透明的,不可见的。
5.2 框架设计
- 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