一、介绍
Dubbo是一款高性能、轻量级的开源Java RPC框架
,它提供面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现等功能。
RPC(Remote Procedure Call)是远程过程调用,包含两个核心模块,网络通讯和序列化。
官网:https://cn.dubbo.apache.org/zh-cn/
RPC流程图:
二、安装控制台dubbo-admin
下载地址:Release 0.5.0 · apache/dubbo-admin · GitHub
双击/bin/startup.cmd运行:
三、整合Springboot
使用Zookeeper3.7.1版本 Dubbo3.2.3版本
创建Maven工程,控制依赖版本:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.qingsongxyz</groupId> <artifactId>DubboDemo</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>DubboCommon</module> <module>DubboConsumer</module> <module>DubboProvider</module> </modules> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.boot.version>2.6.7</spring.boot.version> <dubbo.version>3.2.3</dubbo.version> <lombok.version>1.18.28</lombok.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> <version>${lombok.version}</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>${dubbo.version}</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-dependencies-zookeeper-curator5</artifactId> <version>${dubbo.version}</version> <type>pom</type> </dependency> </dependencies> </dependencyManagement> </project>
1.公共模块
创建公共子模块DubboCommon,存放实体类、Service接口:
导入依赖:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-dependencies-zookeeper-curator5</artifactId> <type>pom</type> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies>
@Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { @NotNull(message = "用户id不能为空") private Long id; @NotBlank(message = "用户名不能为空") private String username; @NotBlank(message = "用户性别不能为空") private String gender; @Min(value = 1, message = "用户年龄必须在1~100之间") @Max(value = 100, message = "用户年龄必须在1~100之间") private Integer age; }
public interface UserService { List<User> getUserList(); }
将公共模块clean、install存入本地仓库
2.服务提供者模块
创建子模块DubboProvider,导入公共模块:
<dependency> <groupId>com.qingsongxyz</groupId> <artifactId>DubboCommon</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency>
application.yml配置文件:
server: port: 8081 spring: application: name: DubboProvider dubbo: application: name: DubboProvider # 注册的服务名称 protocol: name: dubbo # 协议 port: 30000 # 端口 registry: address: zookeeper://127.0.0.1:2181 # 注册中心地址 monitor: protocol: registry # 从注册中心获取监控中心地址
开启Dubbo注解功能
@EnableDubbo @SpringBootApplication public class DubboProviderApplication { public static void main(String[] args) { SpringApplication.run(DubboProviderApplication.class, args); } }
使用@DubboService
注解暴露服务
@Component @DubboService(version = "v1") public class UserServiceImplV1 implements UserService { @Override public List<User> getUserList() { ArrayList<User> userList = new ArrayList<>(2); userList.add(new User(1L, "tom", "男", 18)); userList.add(new User(2L, "alice", "女", 20)); return userList; } }
运行查看控制台:
3.服务消费者模块
创建子模块DubboConsumer,导入公共模块:
<dependency> <groupId>com.qingsongxyz</groupId> <artifactId>DubboCommon</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency>
application.yml配置文件:
server: port: 8082 spring: application: name: DubboConsumer dubbo: application: name: DubboConsumer # 注册的服务名称 protocol: name: dubbo # 协议 port: 30001 # 端口 registry: address: zookeeper://127.0.0.1:2181 # 注册中心地址 monitor: protocol: registry # 从注册中心获取监控中心地址
开启Dubbo注解功能
@EnableDubbo @SpringBootApplication public class DubboConsumerApplication { public static void main(String[] args) { SpringApplication.run(DubboConsumerApplication.class, args); } }
使用@DubboReference
注解引用远程服务
@RequestMapping("/user") @RestController public class UserController { @DubboReference(version = "v1") private UserService userService; @GetMapping("/list") public List<User> getUserList(){ return userService.getUserList(); } }
运行测试:
四、Dubbo配置和特性
配置优先级别规则:
-
方法上配置优先级大于全局配置
-
服务消费方配置优先级大于服务提供方
1.启动时检查
Dubbo 会在启动时检查依赖的服务是否可用,不可用时会抛出异常,测试时可以关闭加快程序运行速度
dubbo: registry: check: false # 不检查注册中心地址是否正确 consumer: check: false # 不检查远程服务是否存在于注册中心(全局)
不检查某个特定的远程服务(局部)
@DubboReference(check = false)
2.超时时间和重试次数
调用远程服务时间太长会导致超时异常,对于复杂的服务可以设置更长的超时时间
dubbo: registry: timeout: 3000 # 连接到注册中心的超时时间 consumer: timeout: 3000 # 服务调用方超时时间(全局) provider: timeout: 3000 # 服务提供方超时时间(全局)
@DubboReference(timeout = 3000) //某个特定的远程服务调用方超时时间(局部) @DubboService(timeout = 3000) //某个特定的远程服务提供方超时时间(局部)
当远程调用出现异常时,默认会进行重试,一般对于删除、修改、查询等幂等操作设置重试
,对于新增非幂等操作不设置重试(即retries=0)
dubbo: consumer: retries: 3 # 服务调用方重试次数(全局) provider: retries: 3 # 服务提供方重试次数(全局)
@DubboReference(retries = 3) //某个特定的远程服务调用方重试次数(局部) @DubboService(retries = 3) //某个特定的远程服务提供方重试次数(局部)
3.服务多版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。按照以下的步骤进行版本升级:
-
在低压力时间段,先升级一半提供者为新版本
-
再将所有消费者升级为新版本
-
然后将剩下的一半提供者升级为新版本
服务提供方暴露服务V2版本:
@Component @DubboService(version = "v2") public class UserServiceImplV2 implements UserService { @Override public List<User> getUserList() { ArrayList<User> userList = new ArrayList<>(2); userList.add(new User(1L, "测试", "测试", 0)); return userList; } }
服务消费方引用V2版本远程服务:
@DubboReference(version = "v2") private UserService userService;
运行测试:
4.本地存根
调用远程服务,实现全在服务提供方,但有时想在服务调用方也执行部分逻辑,如参数校验等。服务调用方该实现类中需要提供一个包含远程服务接口类型参数的构造函数
。
接口:
public interface UserService { User getUserById(@NotNull @Min(value = 1, message = "用户id必须大于1") Long id); }
服务提供方:
@Component @DubboService(version = "v1") public class UserServiceImplV1 implements UserService { @Override public User getUserById(Long id) { return new User(1L, "tom", "男", 18); } }
服务调用方:
@RequestMapping("/user") @RestController public class UserController { //指定本地存根 类全限定名 @DubboReference(version = "v1", stub = "com.qingsongxyz.service.UserServiceStub") private UserService userService; @GetMapping("/{id}") public User getUserById(@PathVariable("id") long id) { return userService.getUserById(id); } }
public class UserServiceStub implements UserService { private final UserService userService; // 构造函数传入真正的远程代理对象 public UserServiceStub(UserService userService) { this.userService = userService; } @Override public User getUserById(Long id) { if (id != null && id > 0) { return userService.getUserById(id); } throw new RuntimeException("id不能为空, id必须大于0..."); } }
测试:
5.服务降级(本地伪装)
Mock 是 Stub 的一个子集,便于服务提供方在客户端执行容错逻辑,在出现RpcException
(比如网络失败,超时等)异常时进行容错
public class UserServiceMock implements UserService { @Override public List<User> getUserList() { return Collections.singletonList(new User(0L, "mock", "mock", 0)); } }
服务提供者:
@Component @DubboService(version = "v3") public class UserServiceImplV3 implements UserService { @Override public List<User> getUserList() { throw new RpcException("出错了!!!"); } }
服务调用者:
@RequestMapping("/user") @RestController public class UserController { @DubboReference(version = "v3", mock = "com.qingsongxyz.service.UserServiceMock") private UserService userService; @GetMapping("/list") public List<User> getUserList() { return userService.getUserList(); } }
测试:
6.异步调用
基于 NIO 的非阻塞实现并行调用,相对多线程开销较小
可以设置异步调用是否待消息发出
@DubboReference(sent=true) //等待消息发出,消息发送失败将抛出异常 @DubboReference(sent=false) //不等待消息发出,将消息放入 IO 队列,即刻返回
public interface UserService { CompletableFuture<String> addUser(User user); }
服务提供者:
@Component @DubboService(version = "v1") public class UserServiceImplV1 implements UserService { @Override public CompletableFuture<String> addUser(User user) { return CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(1); return "添加用户成功!"; } catch (InterruptedException e) { throw new RuntimeException(e); } }); } }
服务调用者:
@RequestMapping("/user") @RestController public class UserController { @DubboReference(version = "v1") private UserService userService; @PostMapping("/a") public void addUser(@RequestBody User user) { CompletableFuture<String> future = userService.addUser(user); future.whenComplete((v, t) -> { if (t != null) { t.printStackTrace(); } else { System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")) + ": " + v); } }); System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")) + ": 操作完成"); } }
测试:
7.RpcContext
RpcContext 上下文是一个 ThreadLocal 的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,其状态都会变化,分为四大模块:
-
ServiceContext:
Dubbo 内部使用
,用于传递调用链路上的参数信息,如 invoker 对象等 -
ClientAttachment:
Client 端使用
,往 ClientAttachment 中写入的参数将被传递到 Server 端 -
ServerAttachment:
Server 端使用
,从 ServerAttachment 中读取的参数是从 Client 中传递过来的 -
ServerContext:
Client 端和 Server 端使用
,用于从 Server 端回传 Client 端使用,Server 端写入到 ServerContext 的参数在调用结束后可以在 Client 端的 ServerContext 获取到
服务提供者:
@Component @DubboService(version = "v1") public class UserServiceImplV1 implements UserService { @Override public CompletableFuture<String> addUser(User user) { //获取Client端传递的参数 System.out.println("参数: " + RpcContext.getServerAttachment().getAttachment("key")); return CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(1); //回传参数给Client端 RpcContext.getServerContext().setAttachment("result", "bye"); return "添加用户成功!"; } catch (InterruptedException e) { throw new RuntimeException(e); } }); } }
服务调用者:
@RequestMapping("/user") @RestController public class UserController { @DubboReference(version = "v1") private UserService userService; @PostMapping("/a") public void addUser(@RequestBody User user) { //向Server端传递参数 RpcContext.getClientAttachment().setAttachment("key", "1"); CompletableFuture<String> future = userService.addUser(user); future.whenComplete((v, t) -> { if (t != null) { t.printStackTrace(); } else { //获取远程调用回传的参数 System.out.println("回传参数: " + RpcContext.getServerContext().getAttachment("result")); System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")) + ": " + v); } }); System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")) + ": 操作完成"); } }
测试:
8.直连服务提供者
在开发及测试环境下,经常需要绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直连方式,将以服务接口为单位,忽略注册中心的提供者列表。
服务提供方:
@Component @DubboService(version = "v2") public class UserServiceImplV2 implements UserService { @Override public List<User> getUserList() { ArrayList<User> userList = new ArrayList<>(2); userList.add(new User(1L, "测试", "测试", 0)); return userList; } }
服务调用方:
@RequestMapping("/user") @RestController public class UserController { @DubboReference(version = "v2", url = "dubbo://192.168.31.70:30000") private UserService userService; @GetMapping("/list") public List<User> getUserList() { return userService.getUserList(); } }
测试:
9.高可用
-
数据库宕机后,注册中心仍然能通过缓存提供服务列表查询,但不能注册新服务
-
注册中心集群任意一台宕机,将会自动切换到另一台
-
注册中心全部宕机后,服务提供者和服务调用者仍然能
通过本地缓存通讯
-
服务提供者任意一台宕机不影响使用
-
服务提供者全部宕机后,服务调用者将无法使用,并进行重试等待服务提供者恢复
10.负载均衡
public interface LoadbalanceRules { //随机 String RANDOM = "random"; //轮询 String ROUND_ROBIN = "roundrobin"; //最近调用次数最多并权重最高的 String LEAST_ACTIVE = "leastactive"; //一致性Hash String CONSISTENT_HASH = "consistenthash"; //成功响应时间最短并权重最高的 String SHORTEST_RESPONSE = "shortestresponse"; //自适应 String ADAPTIVE = "adaptive"; //默认 String EMPTY = ""; }
指定服务提供者和服务消费者的负载均衡策略(全局)
dubbo: consumer: loadbalance: LoadbalanceRules.XXX provider: loadbalance: LoadbalanceRules.XXX
指定某个远程服务的负载均衡策略(局部):
@DubboReference(loadbalance=LoadbalanceRules.XXX) @DubboService(loadbalance=LoadbalanceRules.XXX)