SpringBoot+Dubbo+Zookeeper 实现负载均衡、服务降级
SpringBoot + Dubbo + Zookeeper 实现负载均衡、服务降级
参考网址
http://dubbo.apache.org/zh-cn/ Dubbo 官网
http://dubbo.apache.org/zh-cn/docs/user/quick-start.html Dubbo 文档
框架组成
springboot 2.3.4+ zookeeper 3.6.2 + dubbo 2.6.5;
工具:IDEA 2019.1.2
Dubbo 体系结构

执行流程:
-
启动注册中心:例如 zookeeper,接受 provider 的注册,接受 consumer 的订阅,当 provider 有变化的时候,会主动通知 consumer;
-
启动Provider:provider是服务的提供者,例如提供 com.xian.service.impl.UserServiceImpl 这个服务,它是服务的具体实现,provider 启动的时候会向 zookeeper 这个注册中心注册,表明自己可以提供哪些服务;
-
启动Consumer:consumer 是服务的消费者,例如通过 com.xian.controller.UserController 去调用 UserServiceImpl,consumer 启动的时候会去 zookeeper 这个注册中心 订阅自己需要的服务;
-
启动Registry:注册中心,例如 zookeeper,接受 provider 的注册,接受 consumer 的订阅,当 provider 有变化的时候,会主动通知 consumer;
-
consumer 调用 provider 的具体服务,例如 UserController 调用 UserServiceImpl,但是具体由那一台 provider 服务器提供服务,取决于 负载均衡调度算法;
-
Monitor:监控中心,统计服务被调用的次数和时间,服务的消费者和提供者会在内存中累计调用次数和
调用时间,定时每分钟向监控中心发送一次统计数据;
创建 Project
菜单栏:File| New Project, 创建一个空的 Project

填 Project 的名称和存放路径,然后点击右下角的【Finish】:

要在这个 Project 下创建 3 个子 module,最终目录 module 结构如下:

module:dubbo-spring-common
创建工程


填项目的坐标:GroupId, ArtifactId


工程结构

pom.xml
<?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.xian</groupId>
<artifactId>dubbo-spring-common</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
</project>
User.java
package com.xian.entity;
import java.io.Serializable;
/**
* Author: xian
* Date: 2020-10-19 21:02
* Description: 描述
*/
public class User implements Serializable {
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
UserService.java
package com.xian.service;
import com.xian.entity.User;
/**
* Author: xian
* Date: 2020-10-19 21:04
* Description: 描述
*/
public interface UserService {
public User findById(int id);
}
module:dubbo-springboot-provider
创建工程





删除不需要的文件、文件夹

将【java 】文件夹标记为 【Source Root】

工程结构

pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xian</groupId>
<artifactId>dubbo-springboot-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dubbo-springboot-provider</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 依赖 dubbo-common -->
<dependency>
<groupId>com.xian</groupId>
<artifactId>dubbo-spring-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- dubbo 的 starter -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
将 application.properties 重命名为 application.yml

server:
port: 8881
dubbo:
application:
name: dubbo-springboot-provider
registry:
address: zookeeper://127.0.0.1:2181 # 配置 zookeeper 的地址
protocol:
name: dubbo
port: 9991
monitor:
protocol: registry # 配置监控中心的地址从注册中心 registry 里面找
UserServiceImpl.java
这里导入的 Service 包是阿里 dubbo 下面的包;
package com.xian.service.impl;
import com.xian.entity.User;
import com.xian.service.UserService;
import org.springframework.stereotype.Component;
import com.alibaba.dubbo.config.annotation.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Author: xian
* Date: 2020-10-25 9:35
* Description: 描述
*/
@Component
@Service
public class UserServiceImpl implements UserService {
@Override
public User findById(int id) {
System.out.println("UserServiceImpl.findById====================== ");
User user = new User();
user.setId(id);
user.setUsername("xian");
user.setPassword("123");
return user;
}
}
DubboSpringbootProviderApplication.java
添加 @EnableDubbo 开启dubbo
package com.xian;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo
public class DubboSpringbootProviderApplication {
public static void main(String[] args) {
SpringApplication.run(DubboSpringbootProviderApplication.class, args);
}
}
module:dubbo-springboot-consumer
创建工程


pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xian</groupId>
<artifactId>dubbo-springboot-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dubbo-springboot-consumer</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 依赖 dubbo-common -->
<dependency>
<groupId>com.xian</groupId>
<artifactId>dubbo-spring-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 依赖 dubbo 的 starter -->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
<!-- 依赖 thymleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 8882
dubbo:
application:
name: dubbo-springboot-consumer
registry:
address: zookeeper://127.0.0.1:2181
monitor:
protocol: registry
spring:
thymeleaf:
cache: false
UserController.java
package com.xian.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.xian.entity.User;
import com.xian.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Author: xian
* Date: 2020-10-25 9:53
* Description: 描述
*/
@Controller
@RequestMapping("/user")
public class UserController {
@Reference
private UserService userService;
@RequestMapping("/findUser")
public String findUser(int id, Model model) {
User user = userService.findById(id);
model.addAttribute("user", user);
return "success";
}
}
DubboSpringbootConsumerApplication.java
添加 @EnableDubbo 开启dubbo
package com.xian;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo
public class DubboSpringbootConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(DubboSpringbootConsumerApplication.class, args);
}
}
启动 zookeeper
下载
https://zookeeper.apache.org/releases.html

解压

配置
建文件夹:data

进入文件夹 conf,

复制 zoo_sample.cfg 并另存为 zoo.cfg
编辑 zoo.cfg,修改变量 dataDir 的值为:
dataDir=../data
启动 Server
进入 bin 目录,双击 zkServer.cmd
启动效果:

客户端连接
进入 bin 目录,双击 zkCli.cmd,回车,输入 ls /
双击 zkCli.cmd

启动 dubbo-springboot-provider
启动这个工程下的应用程序 DubboSpringbootProviderApplication.java ,可以看到启动端口是 8881:

启动 dubbo-springboot-consumer
启动这个工程下的应用程序 DubboSpringbootProviderApplication.java ,可以看到启动端口是 8882

访问

可以到能访问到 provider 提供的服务了;
查看 zookeeper 客户端
在 zookeeper 客户端下,可以看到根节点里面多了 dubbo 节点,dubbo 里面有服务的提供者 provider、服务的消费者 consumer、配置中心等节点;

查看 Dubbo 管理中心
Dubbo 管理中心名字是 dubbo-admin,要去 github 下载,通过 maven 打包,得到的 jar 包用来运行,然后就可以看到管理中心了,可以用来控制 provider 的升权、降权,还可以控制 consumer 的屏蔽和容错;
下载

解压:


打包
进入 dubbo-admin 目录,在地址了输入 cmd 并回车,打开 cmd 窗口,输入命令 mvn package 开始打包,过程大概几分钟;



打包成功后,就能看到 dubbo-admin/target 文件夹下生成了 jar 文件;


启动
在 cmd 命令窗口里,进入 target 目录,输入命令启动 jar 文件:java -jar dubbo-admin-0.0.1-SNAPSHOT.jar

用户名、密码默认都是 root;

访问
在浏览器中访问:localhost:7001,就能看到配置首页了;

可以看到服务的提供者有 1 个,消费者有 3 个;

提供者
进入【提供者】

可以看到有【提供者、消费者、。。。】

消费者

当消费者选择【屏蔽】以后,用户就会无法访问;

未屏蔽、可访问的状态:

已屏蔽、不能访问的状态:

监控中心
名字:dubbo-monitor-simple
dubbo:
monitor:
protocol: registry # 配置监控中心,从注册中心查找监控中心的地址
进入文件夹:

配置

dubbo.registry.address: 配置注册中心 zookeeper 的地址,
dubbo.jetty.port: 配置浏览器访问用的端口号,因为 8080 端口已经占用了,所以这里改成 8081

打包:mvn package



运行 start.bat
进入 assembly.bin,双击 start.bat,

运行效果

访问
在浏览器中输入 localhost:8081,就能访问到监控中心了:

这个时候去看 Dubbo 的管理中心, 就能看到多了一个 7070端口的提供者,这个 7070 就是监控中心 dubbo-monitor-simple;

Services
在这里可以看到服务被调用的次数和时间,这里的次数和时间是 provider 和 consumer 每分钟像监控中心发送的;


看图表


负载均衡
创建多个 Provider,每个 Provider 的设置不同的提供服务的端口,或者一个 Provider 启动多次,每次启动之前改动端口号;
Consumer 只需要一台就可以了;
Provider
配置 provider 集群,要把每个 provider 的 tomcat 端口、dubbo 提供服务的端口都改成不一样的;
在 资源管理器 中给 dubbo-springboot-provider 建立 2 个副本,结尾分别写 2 和 3;

application.yml
server:
port: 8882 # tomcat 的端口号,第二台 provider 改成 8882,第三台 8883
dubbo:
application:
name: dubbo-springboot-provider2 # 应用的名称,第二台 provider 结尾加个 2,第三台加 3
registry:
address: zookeeper://127.0.0.1:2181
protocol:
name: dubbo
port: 9992 # dubbo 提供服务的端口号;,第二台 provider 改成 9992,第三台 9993
monitor:
protocol: registry # 配置监控中心,从注册中心查找监控中心的地址
UserServiceImpl.java
然后在服务代码中做记号:
package com.xian.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.xian.entity.User;
import com.xian.service.UserService;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Author: xian
* Date: 2020-10-25 9:35
* Description: 描述
*/
@Component
@Service
public class UserServiceImpl implements UserService {
@Override
public User findById(int id) {
System.out.println("UserServiceImpl.findById====================== 1"); // 这里做个标记,打印的时候就知道调用的是哪台 provider;第一台结尾写 1,第二台 2,第三台 3
User user = new User();
user.setId(id);
user.setUsername("xian");
user.setPassword("123");
return user;
}
}
pom.xml
把 artifactId 和 name 修改成和项目名一致;

导入 module
把 dubbo-springboot-provider2 和 dubbo-springboot-provider3 导入到工程里:

后面的全是默认,最后【Finish】就可以了,两个 provider 导入后的效果:

dubbo-springboot-consumer
消费者这边只需要有一个就可以了;
UserController.java
在 UserService 接口中设置负载均衡的策略:loadbalance = "roundrobin"
@Controller
@RequestMapping("/user")
public class UserController {
// 修改负载均衡策略
@Reference(loadbalance = "roundrobin")
private UserService userService;
application.yml
server:
port: 7771 # tomcat 端口号改成 7771,因为原来的 8882 被 dubbo-springboot-provider2占用了
dubbo:
application:
name: dubbo-springboot-consumer
registry:
address: zookeeper://127.0.0.1:2181
monitor:
protocol: registry
spring:
thymeleaf:
cache: false
启动
分别启动 dubbo-springboot-provider、dubbo-springboot-provider2、dubbo-springboot-provider3,然后启动 dubbo-springboot-consumer;
Dubbo Admin
可以看到 3 个 dubbo 服务了:

访问
多刷新几次:

看打印记录可以知道是调用了哪个 provider 提供的服务,设置不同的负载均衡策略,则每个 provider 被调用的次数会有变化;
dubbo-springboot-provider 的控制台打印:

dubbo-springboot-provider2 的控制台打印:

dubbo-springboot-provider3 的控制台打印:

服务降级
服务器压力过大的时候,需要把非核心业务暂停处理,就需要用到服务降级,服务降级分为两种:
- consumer 不调用 provider 提供的服务,直接给客户端返回 null;
- consumer 调用 provider 失败后, 再给客户端返回 null;
核心的服务例如:支付、下订单;
非核心的服务例如:广告、评论、确认收货;
服务降级是在【消费者】consumer 里面设置的,配合 Dubbo Admin 管理中心来设置;
直接返回 null
这种方式是通过点击 consumer 的【屏蔽】按钮来操作的:

UserController.java
package com.xian.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.xian.entity.User;
import com.xian.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Author: xian
* Date: 2020-10-25 9:53
* Description: 描述
*/
@Controller
@RequestMapping("/user")
public class UserController {
@Reference(loadbalance = "random")
private UserService userService;
@RequestMapping("/findUser")
public String findUser(int id, Model model) {
User user = userService.findById(id);
System.out.println("服务降级=========" + user); // 做个记号:这里将会打印 null
if (user != null) {
model.addAttribute("user", user);
return "success";
} else {
return "degraded"; // 服务降级的时候,就返回 degraded 页面
}
}
}
degraded.html
在 resources/templates 下添加 degraded.html 页面,当服务降级的时候访问;
<!DOCTYPE html>
<html lang="en">
<head>
<title>Title</title>
</head>
<body>
服务降级,就调用这个界面
</body>
</html>
访问
未降级,可访问的状态:

已降级、不可访问的状态:

后台打印
可以看到,consumer 直接 返回了 null 给客户端:

consumer 调用服务失败的时候返回 null
Dubbo Admin 中设置 Consumer 为【已容错】,
设置 provider 服务中 水淼 3 秒再返回 user 对象,
因为 consumer 默认的超时时间是 1秒,所以当 1 秒之内还没有调用到 provider 服务,那么 consumer 就会默认重试 2 次,一共调用 3 次,3 次都失败以后,才返回一个 null 给客户端;
Dubbo Admin 设置服务容错
点击【容错】按钮,

已容错:

UserServiceImpl.java
package com.xian.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.xian.entity.User;
import com.xian.service.UserService;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Author: xian
* Date: 2020-10-25 9:35
* Description: 描述
*/
@Component
@Service
public class UserServiceImpl implements UserService {
@Override
public User findById(int id) {
System.out.println("UserServiceImpl.findById====================== 1");
// 设置睡眠 3 秒,实现服务调用失败的效果
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
User user = new User();
user.setId(id);
user.setUsername("xian");
user.setPassword("123");
return user;
}
}
访问
已容错、provider 未睡眠 3 秒时,可以正常访问

已容错、provider 睡眠 3 秒时,consumer 调用 provider 的服务失败,页面显示访问失败

后台打印:
provider 的打印显示:consumer 在调用 provider 提供的服务失败了以后,又重试了 2 次,一共调用了 3 次:

consumer 打印:显示调用失败了,consumer 返回了null 给客户端:

至此,服务降级的效果就实现了;
浙公网安备 33010602011771号