Nacos注册中心和配置中心
Nacos 是 Alibaba 开发的用于微服务管理的平台,核心功能:服务注册与发现和集中配置管理。
- Nacos 作为服务注册发现组件,可以替换Spring Cloud 应用中传统的服务注册于发现组件,如:Eureka、Consul 等,支持服务的健康检查。
- Nacos 作为服务配置中心,可以替换 Spring Cloud Config、Apollo(阿波罗的分布式配置中心) 等。
为什么叫 Nacos?Naming 与 Configuration 的前两个字母的组合,最后的 s 代表 service 。从其命名也能看出其核心功能。
Nacos 的下载和安装
首先去 nacos 的 github 地址下载 release 安装包。下载地址
进入到 nacos/bin 目录下面,startup 命令用于启动 nacos ,shutdown 命令用于停掉 nacos 。
单模式启动一
-
windows 系统
执行 startup.cmd -m standalone 启动,单模式启动
-
linux/unix 系统
执行 startup.sh -m standalone 启动。
-
docker
编写docker-compose.yml文件 启动该文件 命令: docker-compose up
nacos: image: nacos/nacos-server:latest container_name: nacos-standalone-8848 environment: - PREFER_HOST_MODE=hostname - MODE=standalone #单机模式启动 volumes: - ./8848/logs/:/home/nacos/logs #前面是宿主机名 后面是容器目录名 - ./8848/init.d/custom.properties:/home/nacos/init.d/custom.properties ports: - "8848:8848"
单模式启动二
1、修改startup.cmd文件
2、启动
-
windows 系统
执行 startup.cmd启动,单模式启动
-
linux/unix 系统
执行 startup.sh 启动。
运行界面
nacos 的默认服务端口是 8848 ,启动完成之后通过浏览器访问 nacos:http://192.168.1.44:8848/nacos/index.html。
看到如下界面,需要登陆,默认的用户名密码都是 nacos ,登陆之后看到如下界面:
nacos 的单机 standalone 模式是开发环境中使用的启动方式,它对用户而言非常友好,几乎不需要的更多的操作就可以搭建 nacos 单节点。另外,standalone 模式安装默认是使用了 nacos 本身的嵌入式数据库 apache derby(Derby是一个Open source的产品,是一个小型的数据库) 。
注册中心
虽然 Eureka Server 会被我们用 Nacos 替换掉,但是我们仍会使用 Ribbon、OpenFeign 作为远程调用的基础组件。
微服务整合 nacos 服务发现:
- spring-cloud-starter-alibaba-nacos-discovery 是 spring-cloud-alibaba-dependencies 子项目。所以它们的版本号都不需要我们手动维护,继承自父项目 dependencyManagement 中的定义。
- 因为我们之前使用了 eureka ,所以用 nacos 的spring-cloud-starter-alibaba-nacos-discovery 将spring-cloud-starter-netflix-eureka-client 在 pom 文件中替换掉。
- spring-cloud-starter-alibaba-nacos-discovery 也默认包含了 spring-cloud-starter-netflix-ribbon ,不需要单独引入 ribbon 。我们之前学习的所有的 ribbion 和 openfeign 相关的负载均衡、远程服务调用的知识在 nacos 下依然适用。
父POM文件
- spring cloud alibaba version:2.2.5.RELEASE
- spring boot version :2.3.11.RELEASE
- spring cloud version:Hoxton.SR8
<?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>
<modules>
<module>order</module>
<module>gateway</module>
</modules>
<groupId>com.woniu</groupId>
<artifactId>alibaba</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>alibaba</name>
<description>Demo project for Spring Boot</description>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
<spring.cloud.alibaba.version>2.2.5.RELEASE</spring.cloud.alibaba.version>
<spring.boot.version>2.3.11.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR8</spring.cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</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>
</dependencies>
<dependencyManagement>
<dependencies>
<!--Spring Cloud alibaba的版本管理, 通过dependency完成继承-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--Spring Cloud的版本管理-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--SpringBoot的版本管理-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
模块POM
<dependency> <!-- 自动引入 Ribbon -->
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在 application.yml(或 bootstrap.yml)中加入必要的服务注册中心信息配置(替换掉 eureka 相关配置):
spring:
application:
name: spring-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: public
username: nacos
password: nacos
加上 @EnableDiscoveryClient 注解(去掉 @EnableEurekaClient 注解),开启 Spring Cloud 的服务注册与发现功能。spring cloud alibaba 遵守 spring cloud 规范,因此 @EnableDiscoveryClient 注解能激活、启用 nacos 的服务发现功能。
@SpringBootApplication
@EnableDiscoveryClient
public class SpringProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SpringProviderApplication.class, args);
}
}
访问 Nacos 服务,通过 服务管理
-> 服务列表
,看到我们注册的服务已经在列表中
zuul网关启动的时候,把springcloud-alibaba的版本升级到 2.2.5
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j9KdaqzM-1684929585891)(null)]
服务注册中心相关概念和配置
微服务 group 分组
Nacos 的微服务分组概念,有两层含义:
- 不同分组的微服务,彼此之间不能发现对方,也就不能进行远程服务调用。逻辑上,不同的分组意味着这是两个不同的独立项目。即微服务从配置中拉取到的注册表是微服务所在组的注册表。
- 将微服务分组,方便我们查看,以及方便配置管理分类。
可以通过如下属性对微服务所属分组进行配置:
spring:
cloud:
nacos:
discovery:
group: public_group
由于多个项目可能、可以使用同一个 nacos 作为注册中心,这种情况下,group
就是区分你我的标识,每个微服务从 nacos 上拉取的只有本组的注册表。 如果微服务没有指定组,默认分组是 default_group
示例:
spring:
application:
name: spring-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: public
username: nacos
password: nacos
group: public_group
配置中心
Nacos 作为配置管理中心,实现的核心功能就是配置的统一管理。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
连接和使用配置中心
新建配置文件 bootstrap.yml ,新增 spring.cloud.nacos.config 节点配置,将服务指向正确的 nacos 服务端。
该配置文件中只保留 nacos 相关的配置即可,其他的配置放到 nacos 中统一管理。
注意,和 Spring Cloud Config 一样,连接配置中心的配置信息『必须』写在 bootstrap.yml 配置文件中,而不是 application 配置文件中。bootstrap 优先级高于apllication
server:
port: 8180
spring:
application:
name: spring-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: public
username: nacos
password: nacos
group: public_group
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
file-extension: yaml # nacos 配置文件后缀。注意是 yaml,不是 yml
group: public_group # 配置分组。未配置时,默认分组是 DEFAULT_GROUP
通过配置列表右侧的 +
按钮添加配置文件:
点击+之后
Data ID
是该配置文件在 Nacos 系统内的唯一标识。
在 Nacos Spring Cloud 中,dataId 的完整格式语法如下:
${prefix}-${spring.profile.active}.${file-extension}
-
prefix 的值默认与
spring.application.name
(即服务名)的值相同。也可以通过配置项 spring.cloud.nacos.config.prefix 来手动配置,指定一个与 spring.application.name 不一样的值,不过一般不会动它。 -
spring.profile.active 即为当前环境对应的 profile ,如:
xxx-service-dev.yml
中的dev
就是指开发环境。**注意:**当 spring.profile.active 为空时,对应的环境定义字符部分将不存在,即为 xxx-service.yml,而不是 xxx-service-.yml 。
-
file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。
注意,我们使用的『是 yaml 类型,不是 yml』。虽然二者是一个意思,但是『nacos 只认 yaml』。
Group
的值同 spring.cloud.nacos.config.group 的配置,界面填写的内容与项目中的配置二者『一定要统一』,否则无法正确读取配置,Group 起到配置『隔离』的作用。
入门案例
核心pom
<!--nacos配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
创建application.yml
servername: provider
创建bootstrap.yml
server:
port: 8180
spring:
application:
name: spring-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: public
username: nacos
password: nacos
group: public_group
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
file-extension: yaml # nacos 配置文件后缀。注意是 yaml,不是 yml
group: public_group # 配置分组。未配置时,默认分组是 DEFAULT_GROUP
controller类
@RestController
@RefreshScope
public class ProviderController {
@Value("${server.port}")
private String port;
@Value("${servername}")
private String servername;
@RequestMapping(value = "/provider/{id}")
public String provider(@PathVariable String id){
if(id.equals("1")){
throw new RuntimeException("异常");
}
return "provider id = " + id + "port = " + port + " servername = " + servername;
}
}
验证和动态刷新
执行以下代码进行验证:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uE84jJtc-1684929584931)(null)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dKFZYeGD-1684929586078)(null)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pkLbrz6q-1684929586646)(null)]
如果想要实现动态刷新功能,那么在 @Value 所在的 @Component(@Controller、@Service、@Repository)上加上 @RefreshScope 即可实现动态刷新。
Nacos 的数据存储
Nacos 的数据是存储在它自带的内嵌 derby 数据库中的,数据文件就在 Nacos 的解压目录下的 data
文件夹中。
你也可以通过修改配置,让 Nacos 将它的数据存储在你指定的 mysql 数据库中。
Nacos 在它的 conf
目录下已经为你准备好了建表脚本:nacos-mysql.sql
。不过脚本中没有建库语句,为了后续配置简单起见,建议创建的库命名为 nacos 。
创建一个nacos数据库,然后在nacos的bin目录下找到nacos-mysql.sql文件,把该文件的建表语句拷贝mysql客户端下执行,注意要使用你刚才创建的nacos数据库下
create database nacos
DEFAULT CHARACTER SET utf8mb4 -- 乱码问题
DEFAULT COLLATE utf8mb4_bin -- 英文大小写不敏感问题
;
在 conf 文件夹下的 application.properties
配置文件。从 31 行开始的一段配置就是数据库连接相关配置。把如下行数前面的注释去掉
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=root
修改完 application.properties
配置文件之后,重启 Nacos,你会发现 Nacos 重新编程了一个『干净』的环境。
测试:登陆localhost:8848/nacos,在配置中心上新建一个配置,如:spring-provider.yaml,发现这个配置保存到了我们自己创建的nacos数据库里面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7zRbS804-1684929584834)(null)]
nacos集群配置
拷贝cluster.conf.example文件,并改名为cluster.conf
192.168.31.203:8848
192.168.31.203:8849
192.168.31.203:8850
直接双击启动启动脚本
微服务配置:
server-addr: 192.168.31.203:8848,192.168.31.203:8849,192.168.31.203:8850
spring:
application:
name: spring-provider
cloud:
nacos:
discovery:
server-addr: 192.168.31.203:8848,192.168.31.203:8849,192.168.31.203:8850
namespace: public
username: nacos
password: nacos
group: public_group
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
file-extension: yaml # nacos 配置文件后缀。注意是 yaml,不是 yml
group: public_group # 配置分组。未配置时,默认分组是 DEFAULT_GROUP
Sentinel服务熔断和流控
简介
Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
源码地址:https://github.com/alibaba/Sentinel
官方文档:https://github.com/alibaba/Sentinel/wiki
https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
Spring Cloud Alibaba Sentinel 同时兼具了熔断器和流控的功能。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lTpOlTBy-1684929585048)(null)]
Sentinel具有以下特征:
-
丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控
制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。 -
完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
-
广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、
gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。 -
完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。
Spring的spi是通过ClassLoader去META-INF/spring.factories加载class,然后反射实例化返回。像SpringBoot用这种方式去加载一些自动配置类,即引入xx-starter就能够自动向spring容器中注入许多配置好的组件。
阿里云提供了 企业级的 Sentinel 服务,应用高可用服务 AHAS
Sentinel和Hystrix对比
https://github.com/alibaba/Sentinel/wiki/Sentinel-%E4%B8%8E-Hystrix-%E7%9A%84%E5%AF%B9%E6%AF%94
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持慢启动、匀速器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
熔断
微服务架构的系统通常会包含多个微服务,各个微服务可能部署在不同的机器上并通过网络进行通信,那么就不可避免会遇到 “网络请求超时” 、“微服务不可用” 等问题,这就会进一步引起依赖它的微服务不可用,这样不断引发服务故障的现象称为『雪崩效应』,最终的结果是整个应用系统瘫痪。
为了解决上述问题,编程领域(参考现实生活)提出了熔断器:
使用熔断器模式,如果请求出现异常,所有请求都会直接返回,而不会等待或阻塞,这样可以减少资源的浪费。
熔断器所造成的这种现象也叫『快速失败(fast fall)』。
流控
限流功能指的是 Sentinel(类似于过滤器、拦截器的效果)在收到请求后,拒绝请求的放行(至 Controller),而是直接返回,从而减少对 Controller,乃至 Service 的触发执行。
熔断和限流的区别在于,熔断是确确实实发生了错误,而限流是人为(根据设置)强行让一部分请求被打回。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ErVOWxP9-1684929586312)(null)]
sentinel安装
sentinel-dashboard 的下载安装
sentinel-dashboard 是基于 Spring Boot 开发的控制台。打包后可以直接运行,不需要额外的 Tomcat 等应用容器。Sentinel 控制台不仅能展示服务流控、熔断降级相关的数据,还可以通过配置的方式动态的为 Sentinel 客户端下发流量控制的指令
我们需要下载并安装的是 sentinel-dashBoard ,下载地址:https://github.com/alibaba/Sentinel/releases
注意:启动 sentinel-dashboard 需要 JDK 版本为 1.8 及以上版本。
使用如下命令启动控制台:
java -Dserver.port=8840 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
-
-Dserver.port=8840
用于指定 Sentinel 控制台端口为 8840。默认是 8080 。
-
-Dproject.name=sentinel-dashboard
指定 Sentinel 控制台程序的名称。
说明
如果你有多张网卡的话,你还需要指定使用哪张网卡(IP)来接受各个微服务上报的信息:
-Dcsp.sentinel.heartbeat.client.ip=192.168.xxx.xxx
访问网址:[http://127.0.0.1:8840]
从 1.6.0 起,sentinel-dashboard 引入基本的登录功能,默认用户名和密码都是 sentinel 。当然也可以通过 JVM 参数的方式进行修改
-
-Dsentinel.dashboard.auth.username=sentinel
用于指定控制台的登录用户名为 sentinel ;
-
-Dsentinel.dashboard.auth.password=123456
用于指定控制台的登录密码为 123456;如果省略这两个参数,默认用户和密码均为 sentinel;
-
-Dserver.servlet.session.timeout=7200
用于指定 Spring Boot 服务端 session 的过期时间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟;
Sentinel 本身就是一个 Spring Boot 应用,所以jar 包内部的 application.properties 文件也是可以修改配置的。
Sentinel实现限流
Spring Cloud Alibaba Sentinel 可以分别用在服务的 “请求发起方” 和 “请求被调方”
- 请求发起方使用的是 OpenFeign ,因此这种情况下 Sentinel 是和 OpenFeign 进行整合;
- 请求被调用使用的是 Spring MVC,因此这种情况下 Sentinel 是和 Spring MVC 进行整合
同时又由于 Sentinel 兼具熔断和流控两个功能,因此这里就有 4 种情况:
- 在服务发起方项目中,整合 OpenFeign 进行实现熔断功能;
- 在服务发起方项目中,整合 OpenFeign 进行实现限流功能;
- 在服务被调方项目中,整合 Spring MVC 进行实现熔断功能;
- 在服务被调方项目中,整合 Spring MVC 进行实现限流功能。
这样以来功能上就出现了重叠冗余,因此在实际使用中我们是这样安排的:
- 在服务发起方,Sentinel 整合 OpenFeign 实现熔断功能;
- 在服务被调方,Sentinel 整合 Spring MVC 实现限流功能。
总结:流控针对provider,熔断降级针对consumer
sentinel实现限流
回顾前面笔记中的 “关于 Sentinel 的使用方式” 章节,在这里,我们在服务的 “被调方” 使用 Sentinel 整合 Spring MVC 进行流量控制。
在这里,Sentinel 借助 Spring MVC 框架的 “拦截器” 机制整合进入 Spring MVC ,“抢先” 在 Controller 执行之前进行流控(和熔断)的判断,从而决定当前请求是否被放行至 Controller
sentinel整合mvc
1.引入相关依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 其实真正起作用的是被关联引入的 sentinel-spring-webmvc-adapter 包 -->
2.添加配置(连接到 sentinel-dashboard)
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
port: 8719
feign:
sentinel:
enabled: true
3.访问 sentinel-dashboard
只需要完成上述的配置,代码不需要有任何的调整,我们就可以通过实时监控查看服务内的流量 QPS(每秒查询率) 以及平均响应时长等信息。
只有服务接口被访问的情况下,在 sentinel 里面才可以看到监控信息。这可能会让你『等』一段时间。
相关概念
- 上下文( Context )和 context-name
Context 代表调用链路上下文。是一个根节点,在整个调用链路的开始处,Sentinel 会创建上下文 Context 对象,并且为它指定一个 name ,相当于根资源。在 Sentinel 中,不同的调用链路可能使用同一个上下文 Context 对象(共一个根节点)。在这里( 和 Spring MVC 整合 ),我们的调用链路都是在 sentinel_spring_web_context 中:
- 资源(Resource)和 resource-name
在 Sentinel 中,对于每一份资源,Sentinel 会为赋予一个 name(或者你手动指定),和 Spring MVC 整合时,Sentinel 使用的是 URI 来作为 Controller 方法的资源名( 在这里,Controller 方法就是资源 )
流控规则
在菜单左侧的 簇点链路和流控规则都可以针对 服务接口添加流控规则:
当我们的服务接口资源被访问的时候,就会出现在 簇点链路 列表中,我们可以针对该服务接口资源配置流程控制规则
说明:
-
资源名:表示我们针对哪个接口资源进行流控规则配置,如:/test2/{id}
-
针对来源:表示针对哪一个服务访问当前接口资源的时候进行限流,default 表示不区分访问来源。如填写服务名称:xxx-service,表示 xxx-service 访问前接口资源的时候进行限流,其他服务访问该接口资源的时候不限流,一般就是默认为default即可。
-
阈值类型/单机阈值:QPS,每秒钟请求数量。上图配置表示每秒钟超过2次请求的时候进行限流;当然我们可以设置线程数,表示开启 n 个线程处理资源请求,这个不是只每秒2个线程,对服务端 /test1请求,资源接口的 2 个线程都被占用的时候,其他访问失败!一般用的都是QPS
-
是否集群:默认情况下我们的限流策略都是针对单个服务的,当然sentinel 提供了集群限流的功能。
除非你的微服务规模特别大,一般不要使用集群模式。集群模式需要各节点与 token server 交互才可以,会增加网络交互次数,一定程度上会拖慢你的服务响应时间。
上面的限流规则用一句话说:对于任何来源的请求,当超过每秒 2 次的标准之后就直接限流,访问失败抛出 BlockException 异常!
流控规则高级选项
1.流控模式
- 直接:当前资源达到限流标准时就直接限流,默认值
- 关联:/important接口的重要程度要高于 /normal接口,如果,/important接口的访问压力很大,那么,可以『牺牲』掉 /normal` 接口,全力保证 /important 接口的正常运行
例如:我们在resttemplate-a微服务中,创建两个接口
@GetMapping("/query")
public String query(){
return "ok:query";
}
@GetMapping("/add")
public String add(){
return "ok:add";
}
先访问:http://localhost:9527/add和http://localhost:9527/query
我们对/query接口进行限流,这个配置的意思就是,当每秒对/add接口的请求超过2次时,就对/query接口进行限流,要注意这样是优先保证/add不限流,牺牲的是/query接口。10s内对/add接口超过20次请求,那么就对/query接口限流
测试:
- 链路
链路限流和关联限流的思路很像,假设我们要去请求某个微服务,该微服务有2个接口(/query和/add),而这两个接口又调用了同一个service层的方法(如:doSomething()方法),那么,我们可以『站在 doSomething的方法』的角度上进行设置:如果是 /query接口在调用service层的 doSomething方法,那么就进行限流,而 /add接口的调用就不限流,或设置为更宽松一些的流控
1.配置设置:通过配置关闭 sentinel 的 URL 收敛功能
spring:
application:
name: resttemplate-a
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8840
web-context-unify: false #默认是开启上下文整合,所有链路在根节点下,链路监控就是将请求分开统计
2.代码设计
controller层
@GetMapping("/query")
public String query(){
return userService.doSomething();
}
@GetMapping("/add")
public String add(){
return userService.doSomething();
}
//service层
@Component
public class UserService {
@SentinelResource("doSomething")
public String doSomething() {
return "hello world";
}
}
3.重新运行,先在界面发送请求 http://localhost:9527/query和http://localhost:9527/add,然后sentinel配置
链路配置:
4.测试:在jmeter对/add测试时不限流(如10秒钟对/add请求80次),但是同时在浏览器对/query测试时,每秒只能是2次请求。
2.流控效果
- 快速失败:很简单的说就是达到限流标准后,请求就被拦截,直接失败。(HTTP状态码:429 too many request),默认值
- Warm up:预热模式,也有叫冷启动,主要是为系统启动时设置预热时间,底层有预热因子是3,在系统刚启动时,使用的阈值不再是每秒多少个请求,而是设置的阈值除以预热因子,在预热的时间内,逐渐提升阈值,最后达到设置的阈值(也就是每秒多少个请求),好处是预防系统刚启动时,突发大量的请求,服务容易宕机。
1.代码设计
@GetMapping("/test/{id}")
public ResponseEntity<String> test1(@PathVariable Integer id){
return new ResponseEntity<>("ok", HttpStatus.OK);
}
2.配置设置
在10秒内,每秒允许的请求数会逐渐增加,也就是每秒的阈值逐渐提高
3.测试
- 排队等待:也叫流量整形,它让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的在队列排队等待一段时间,(即我们设置的时间,单位是毫秒),没有超过这个时间都能被及时处理,如果超过了这个等待时间针对请求的接口没有线程来处理,则抛出异常
测试:每秒50个请求
Sentinel 和 SpringMVC 整合原理
Sentinel 和 Spring MVC 的整合利用了 Spring MVC 的拦截器机制,Sentinel 实现了一个名为 SentinelWebInterceptor 的拦截器,其逻辑伪代码如下
public SentinelWebInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
try {
1. 初始化上下文;
2. 熔断、流控逻辑的判断,判断当前请求是否能继续执行;
return true; // 此时 Controller 方法会被调用。Controller 方法就是 3 。
} catch (BlockException e) {
4. 上述第 2 步未能通过,会抛出 BlockException ,表示请求被拒绝
return false;
}
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
if (发生了异常) {
5. 业务异常。记录、统计异常信息
}
6. 收尾工作:曾经创建的资源该回收的回收,该清除的清除
}
自定义限流异常信息返回
Sentinel 返回的默认信息是 Blocked by Sentinel (flow limiting),如果你对默认响应信息不满意,你可以自定义限流返回信息。
Sentinel 提供了 BlockExceptionHandler 接口。不管什么原因触发了 Sentinel 阻断用户的正常请求,Sentinel 都将『进入』到用户自定义的 BlockExceptionHandler 接口的实现类中,执行 handle 方法,并传入当前的请求、响应对象以及异常对象,并以 handle 方法的执行结果作为返回,回传给用户。
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws Exception {
String msg = null;
if (ex instanceof FlowException) {
msg = "该请求限流了,请稍后重试";
} else if (ex instanceof DegradeException) {
msg = "被熔断了";
} else {
msg = "其它原因";
// ParamFlowException "热点参数限流";
// SystemBlockException "系统规则(负载/...不满足要求)";
// AuthorityException "授权规则不通过";
}
// http 状态码
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.setContentType("application/json;charset=utf-8");
new ObjectMapper().writeValue(response.getWriter(), msg);
}
}
需要说明的是:不仅仅是因为限流和熔断这一个原因会导致 BlockExceptionhandler 的 handle 方法的执行,还有其它的原因也会调用这个handler方法,因此,需要对 handle 方法的 BlockException 参数对象进行 instanceof 判断
sentinel服务熔断降级
我们在服务的 “请求发起方” 使用 Sentinel 整合 OpenFeign 进行熔断降级
sentinel整合feign
1.添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.添加配置(连接到 sentinel-dashboard)
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8840
feign:
sentinel:
enabled: true ##启用 Sentinel 与 OpenFeign 整合适配器
3.代码实现spring-service-a-feign微服务,调用spring-service-b微服务
@FeignClient(value = "spring-service-b",qualifier = "feignclient",
fallback = PermFeignClientImpl.class )
public interface PermFeignClient {
@GetMapping("/perms/{id}")
public Perms getPermsById(@PathVariable("id") Integer id);
}
=========降级代码==============
@Component
public class PermFeignClientImpl implements PermFeignClient {
@Override
public Perms getPermsById(Integer id) {
Perms perms = new Perms();
perms.setName("服务器忙,请稍后再试");
return perms;
}
}
===========controller=======================
@RestController
public class PermController {
@Autowired @Qualifier("feignclient")
private PermFeignClient permFeignClient;
@GetMapping("/perm")
public Perms getPermsById(Integer id) {
Perms perms = permFeignClient.getPermsById(id);
return perms;
}
}
spring-service-b微服务
@GetMapping("/perms/{id}")
public Perms getPermsById(@PathVariable("id") Integer id) {
if(id==1){
try {
Thread.sleep(800); //当id为1时,800毫秒才有相应
}catch (Exception ex){ }
}
if(id==2){
throw new RuntimeException();
}
return permService.getPermsById(id);
}
4.熔断规则
在 sentinel-dashboard 上你可以看到有 降级规则,它指的就是『设置当满足什么条件时,对服务进行降级』
慢调用比例
如下配置:在一秒内,发5次请求,如果每次请求的响应时间超过500毫秒,这种比例达到0.5(50%),就进行熔断,熔断时长就是10秒。如:1秒内有5次请求,其中有3次请求响应时间超过了500毫秒,那么这个比例就是60%,大于50%,此时就熔断,然后降级。
用jmeter测试,程序中当id=1时,每次响应都是800毫秒。所以每次的请求都大于500毫秒,失败率100%,这个时候去请求id=4的资源也是无法请求的,因为熔断了,所以也是直接降级。10s后再次请求id=4的就正常了。
异常数
如下配置:一秒内发送5次请求,如果有3次失败(异常),则直接熔断,然后降级
测试:localhost:9527/perm?id=2,结果5次都失败了,此时熔断时长是9秒,在这9秒内,如果去请求id=4的依然是降级,9s之后试着去发送id=4的请求,如果通过则进入闭合正常状态。
异常比例
如下配置:1秒钟发送5次请求,如果调用接口最终失败的比例超过了20%,则熔断9s。
blockhandler fallback,blockhandler 优先级高