SpringCloud Alibaba-Sentinel
1.Sentinel 基础
1.官网
Github: https://github.com/alibaba/Sentinel
快速开始: https://sentinelguard.io/zh-cn/docs/quick-start.html
中文:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
2.Sentinel 是什么?
- Sentinel 是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点, 从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
- Sentinel 的主要特性
sentinel 可以完成的功能: 绿色方框列出的部分
3.Sentinel 的开源生态
一句话: Sentinel: 分布式系统的流量防卫兵, 保护你的微服务
3.Sentinel 核心功能
1.流量控制
拿旅游景点举个示例,每个旅游景点通常都会有最大的接待量,不可能无限制的放游 客进入,比如长城每天只卖八万张票,超过八万的游客,无法买票进入,因为如果超过 八万人,景点的工作人员可能就忙不过来,过于拥挤的景点也会影响游客的体验和心情, 并且还会有安全隐患;只卖 N 张票,这就是一种限流的手段
- 示意图
2.熔断降级
在调用系统的时候,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积,如下图
解读:熔断降级可以解决这个问题,所谓的熔断降级就是当检测到调用链路中某个资源出现不 稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限 制,让请求快速失败,避免影响到其它的资源而导致级联故障
3.系统负载保护
根据系统能够处理的请求,和允许进来的请求,来做平衡,追求的目标是在系统不被 拖垮的情况下, 提高系统的吞吐率
4.消息削峰填谷
某瞬时来了大流量的请求, 而如果此时要处理所有请求,很可能会导致系统负载过高, 影响稳定性。但其实可能后面几秒之内都没有消息投递,若直接把多余的消息丢掉则没 有充分利用系统处理消息的能力
Sentinel 的 Rate Limiter 模式能在某一段时间间隔内以匀速方式处理这样的请求, 充分利 用系统的处理能力, 也就是削峰填谷, 保证资源的稳定性
4.Sentinel 两个组成部分
1.核心库:(Java 客户端)不依赖任何框架/库,能够运行在所有 Java 运行时环境, 对 Spring Cloud 有较好的支持
2.控制台:(Dashboard)基于 Spring Boot 开发,打包后可以直接运行, 不需要额外的 Tomcat 等 应用容器
2.Sentinel 控制台
1.需求分析/图解
1.需求: 搭建 Sentinel 控制台,用于显示各个微服务的使用情况
2.下载
https://github.com/alibaba/Sentinel/releases/tag/v1.8.0
3.运行
- 指令: java -jar sentinel-dashboard-1.8.0.jar
- 注意: Sentinel 控制台 默认端口是 8080
4.访问
1.控制台页面,浏览器输入: http://localhost:8080 , 用户/密码都是 sentinel
2.登录成功后的页面, 目前是空的,因为 sentinel 还没有进行流量监控
5.注意事项和细节
1、更改 Sentinel 控制台的端口
java -jar sentinel-dashboard-1.8.0.jar --server.port=9999
3.Sentinel 监控微服务
1.需求分析/图解
- 需求: 使用 Sentinel 控制台对 member-service-nacos-provider-10004 微服务 进行实时 监控
– 示意图
- 当调用了 member-service-nacos-provider-10004 微服务时, 可以监控到请求的 url/QPS(每秒请求量)/ 响应时间/流量
2.代码/配置实现
- 修改 member-service-nacos-provider-10004 的 pom.xml, 引入 alibaba-sentinel
<!--引入alibaba sentinel 场景启动器-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 修改 member-service-nacos-provider-10004 的 application.yml
spring:
application:
name: member-service-nacos-provider #配置应用的名称
#配置nacos
cloud:
nacos:
discovery:
#配置nacos Server地址,localhost-nacos部署的服务器ip 8848-服务器端口
server-addr: localhost:8848
sentinel:
transport:
#指定sentinel控制台的地址
dashboard: localhost:8080
#指定端口
#1.transport.port端口配置会在被监控的微服务对应的主机上启动http server
#2.该Http Server会与Sentinel控制台进行交互
#3.比如Sentinel控制台添加了一个限流规则机会把规则数据push 给这个Http Server接收
#Http Server再将这个规则注册到Sentinel中
#4.简而言之:transport指定被监控的微服务应用与sentinel控制台交互的端口
#5.默认这个端口时8719,假如被暂用了,就会自动的从8719开始依次加1扫描,知道找到一个没有被占用的端口
port: 8719
datasource:
#指定数据源类型,不需要在指定driver-class-name
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/e_commerce_center_db?useUnicode=true&characterEn coding=utf-8&useSSL=false
username: root
password: root
- 成功启动后, netstat -anb |more可以查看到该端口.
3.测试
- 浏览器输入: http://localhost:10004/member/get/1
- 进入到 Sentinel 查看实时监控效果, http://localhost:8080/#/dashboard
4.注意事项和细节
- QPS: Queries Per Second(每秒查询率),是服务器每秒响应的查询次数
- Sentinel 采用的是懒加载, 只有调用了某个接口/服务,才能看到监控数据(实时监控)
- Sentinel的使用并不依赖于Nacos或者说其他的注册中心,可以单独使用
4.Sentinel 流量控制
1.规则
1.先看一张图
2.对上图的解读
- 资源名∶唯一名称,默认请求路径(某个微服务的接口地址)
- 针对来源∶Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来 源)
- 阈值类型/单机阈值∶
- QPS(每秒钟的请求数量)∶当调用该 api 的 QPS 达到阈值的时候,进行限流
- 线程数∶当调用该 api 的线程数达到阈值的时候,进行限流
QPS 和线程数的区别, 比如 QPS 和线程我们都设置阈值为 1
(1) 对 QPS 而言, 如果在 1 秒内, 客户端发出了 2 次请求, 就达到阈值, 从而限流
(2) 对线程数而言, 如果在 1 秒内, 客户端发出了 2 次请求, 不一定达到线程限制的阈值, 为什么呢? 假设我们 1 次请求后台会创建一个线程, 但是这个请求完成时间是 0.1 秒(可以 视为该请求对应的线程存活 0.1 秒), 所以当客户端第 2 次请求时(比如客户端是在 0.3 秒发 出的), 这时第 1 个请求的线程就已经结束了, 因此就没有达到线程的阈值, 也不会限流.
(3) 可以这样理解, 如果 1 个请求对应的线程平均执行时间为 0.1 那么, 就相当于 QPS 为 10
-
是否集群∶不需要集群
-
流控模式∶
- 直接∶ api 达到限流条件时,直接限流
- 关联∶ 当关联的资源达到阈值时,就限流自己
- 链路∶ 当从某个接口过来的资源达到限流条件时,开启限流
-
流控效果∶
- 快速失败∶直接失败,抛异常
- Warm Up∶根据 codeFactor(冷加载因子,默认 3)的值,从阈值/codeFactor,经过预热时长,才达到设置的 QPS 阈值
- 排队等待∶匀速排队,让请求以匀速的速度通过,阈值类型必须设置为 QPS,否则 无效
2.流量控制实例-QPS
1.需求分析/图解
- 需求: 通过 Sentinel 实现 流量控制
- 当调用 member-service-nacos-provider-10004 的 /member/get/接口/API 时,限制 1 秒内最多访问 1 次,否则直接失败,抛异常.
2.配置实现步骤
- 为/member/get/1 增加流控规则
- 在流控规则菜单,可以看到新增的流控规则
3.测试
1.启动 Nacos Server 8848
2.启动 Sentinel8080 控制台/Sentinel dashboard
3.启动 member-service-nacos-provider-10004
4.浏览器: localhost:10004/member/get/1
5.Sentinel 控制台监控页面
- 浏览器输入: http://localhost:10004/member/get/1 , 1 秒钟内访问次数不超过 1 次, 页 面显示正常
- 浏览器输入: http://localhost:10004/member/get/1 , 1 秒钟内访问次数超过 1 次, 页面 出现错误提示
4.注意事项和细节
- 流量规则改动,实时生效,不需重启微服务 , Sentine 控制台
- 在 sentinel 配置流量规则时,如何配置通配符问题, 比如 /member/get/1 /member/get/2 统一使用一个规则
- 方案1: 在sentinel中 /member/get?id=1 和 /member/get?id=2 被统一认为是 /member/get 所以只要对/member/get 限流就OK了.
//http://localhost:10004/member/get?id=1
@GetMapping(value = "/member/get", params = "id")
public Result getMemberById(Long id) {
//String color = request.getParameter("color");
//String name = request.getParameter("name");
Member member = memberService.queryMemberById(id);
//使用Result把查询到的结果返回
if (member != null) {
//return Result.success("查询会员成功member-service-provider-10000 name=" +name+","+"color="+color, member);
return Result.success("查询会员成功member-service-nacos-provider-10004", member);
} else {
return Result.error("402", "ID= " + id + "不存在");
}
}
- 方案2: URL资源清洗
可以通过 UrlCleaner 接口来实现资源清洗,也就是对于/member/get/{id}这个 URL,我们可以统一归集到/member/get/*资源下,具体配置代码如下,实现 UrlCleaner 接口, 并重写 clean 方法即可
/**
* sentinel Url清洗
* 1.sentinel流控规则:资源名根据请求的路径进行匹配
* 比如: 在流控规则我们配置的资源名为:/member/get/* 那么在浏览器进行请求时也是 /member/get/*
* 而在restful 中 * 时通配符
*
* 2.那么假如我们在sentinel中配置了/member/get/* 而又要实现 /member/get/{id}的流控规则的匹配就需要用sentinel的url清洗了
* 3.当我们在浏览器发起如 http://localhost:10004/member/get/1 这样的请求是sentinel会先进行url的清洗 --->http://localhost:10004/member/get/* 从而和流控规则进行匹配
* 再以http://localhost:10004/member/get/1 的方式去访问controller层
*/
@Component
public class CustomUrlCleaner implements UrlCleaner {
@Override
public String clean(String originalUrl) {
/**
* public static boolean isBlank(CharSequence cs) {
* int strLen;
* if (cs != null && (strLen = cs.length()) != 0) {
* for(int i = 0; i < strLen; ++i) {
* if (!Character.isWhitespace(cs.charAt(i))) {
* return false;
* }
* }
*
* return true;
* } else {
* return true;
* }
* }
*/
//1.判断字符串是否为空
//2.如果不为空是否是空字符组成
//如果为空/null或者空字符组成则返回true
if(StringUtils.isBlank(originalUrl)){
return originalUrl;
}
if(originalUrl.startsWith("/member/get")){
return "/member/get/*";
}
return originalUrl;
}
}
- 如果 sentinel 流控规则没有持久化,当我们重启调用 API 所在微服务模块后,规则会丢失,需要重新加入
3.流量控制实例-线程数
1.需求分析/图解
- 需求: 通过 Sentinel 实现 流量控制
- 当调用 member-service-nacos-provider-10004 的 /member/get/* 接口/API 时,限制 只有一个工作线程,否则直接失败,抛异常
2.配置实现步骤
-
为/member/get/* 增加流控规则
-
在流控规则菜单,可以看到新增的流控规则
-
修改member-service-nacos-provider-10004 MemberController进行测试
//查询的方法/接口 //这里使用url占位符+@PathVariable @GetMapping("/member/get/{id}") public Result getMemberById(@PathVariable("id") Long id, HttpServletRequest request) { //String color = request.getParameter("color"); //String name = request.getParameter("name"); Member member = memberService.queryMemberById(id); try { //为了便于测试sentinel流控规则-线程数,我们让请求线程睡眠一秒 TimeUnit.SECONDS.sleep(1); log.info("MemberController-getMemberById-当前请求线程名为:{},线程id为:{},请求时间:{}", Thread.currentThread().getName(),Thread.currentThread().getId(),new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } //使用Result把查询到的结果返回 if (member != null) { //return Result.success("查询会员成功member-service-provider-10000 name=" +name+","+"color="+color, member); return Result.success("查询会员成功member-service-nacos-provider-10004", member); } else { return Result.error("402", "ID= " + id + "不存在"); } }
3.测试
1.每隔一秒访问一次,控制台打印效果如下
2022-09-24 10:42:37.621 INFO 14840 --- [io-10004-exec-2] c.l.s.controller.MemberController : MemberController-getMemberById-当前请求线程名为:http-nio-10004-exec-2,线程id为:49,请求时间:2022-09-24 10:42:37
2022-09-24 10:42:41.233 INFO 14840 --- [io-10004-exec-5] c.l.s.controller.MemberController : MemberController-getMemberById-当前请求线程名为:http-nio-10004-exec-5,线程id为:52,请求时间:2022-09-24 10:42:41
从上面可以看到浏览器每次请求都会对应一个新的线程,线程由tomcat进行分配,tomcat维护了一个线程池
2.一秒内浏览器连续的发起请求,我们设置的线程数是1,而又让线程在执行方法时休眠了一秒已模拟一秒内执行不玩业务的场景,可以看到当一个线程还没有执行完时,下一个线程的请求就会被sentinel限流
4.注意事项和细节
- 当我们请求一次微服务的 API 接口时,后台会启动一个线程
- 阈值类型 QPS 和 线程数的区别讨论
- 如果一个线程平均执行时间为0.05秒,就说明在1秒钟,可以执行20次(相当于 QPS为 20)
- 如果一个线程平均执行时间为1秒, 说明1秒钟,可以执行1次数(相当于 QPS为1)
- 如果一个线程平均执行时间为2秒, 说明2秒钟内,才能执行1次请求.
4.流量控制实例-关联
1.关联的含义
1.当关联的资源达到阈值时,就限流自己
2.需求分析/图解
-
需求: 通过 Sentinel 实现 流量控制
-
当调用 member-service-nacos-provider-10004 的 /t2 API 接口时,如果 QPS 超过 1,这 时调用 /t1 API 接口 直接接失败,抛异常.
梳理 /t2 是关联的资源 , 限流的资源是 /t1
@RequestMapping("/t1")
public Result t1(){
return Result.success("t1执行成功");
}
@RequestMapping("/t1")
public Result t2(){
return Result.success("t2执行成功");
}
3.配置实现步骤
-
为/t1 增加流控规则
配置说明: 当t2的访问阈值达到t1配置的QPS时(下图t1配置的QPS为1)就会对t1进行限流
-
在流控规则菜单,可以看到新增的流控规则
4.测试
为了测试出t1限流的效果,我们使用postman对t2进行循环请求
当t2达到t1配置的QPS时,这时请求t1已经被sentinel进行限流了
5.注意事项和细节
1.在 postman 执行 高并发访问 /t2 没有结束时, 去访问 /t1 才能看到流控异常出现
5.流量控制实例-Warm up
1.Warm up 介绍
-
概述
- 当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长 一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的 增多,经过预期的时间以后,到达系统处理请求个数的最大值。Warm Up(冷启动, 预热)模式就是为了实现这个目的的。
- 这个场景主要用于启动需要额外开销的场景,例如建立数据库连接等
-
一张图
梳理
- 通常冷启动的过程系统允许通过的 QPS 曲线图(上图)
- 默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值
- 这里的threshold 就是最终要达到的QPS阈值.
- 比如我们设置的QPS是9则从从9/3=3开始,经过预热时长(预热的时长由用户设定)逐渐升至设定QPS 9
(1)文 档 : https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81—%E5%86%B7%E5%90%AF%E5%8A%A8
(2) 默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈 值
(3) Warm up 称为 冷启动/预热
(4) 应用场景: 秒杀在开启瞬间,大流量很容易造成冲垮系统,Warmup 可慢慢的把流量放入,最终将阀值增长到设置阀值
2.需求分析/图解
- 需求: 通过 Sentinel 实现 流量控制,演示 Warm up
- 调用 member-service-nacos-provider-10004 的 /t2 API 接口,将 QPS 设置为 9, 设置 Warm up 值为 3
- 含义为 请求 /t2 的 QPS 从 threshold / 3( 9 /3 = 3) 开始,经预热时长(3 秒)逐渐升至 设定的 QPS 阈值(9)
- 为什么是 9 / 3, 这个是 3 就是默认冷启动启动加载因子 coldFactor=3
- 测试预期效果: 在前 3 秒,如果访问 /t2 的 QPS 超过 3, 会直接报错,在 3 秒后 访问 /t2 的 QPS 超过 3, 小于等于 9, 是正常访问
3.配置实现步骤
- 为/t2 增加流控规则
- 在流控规则菜单,可以看到新增的流控规则
4.测试
1.在前3秒QPS为 (threshold(峰值阈值) / 3 )=3 我们不断的刷新进行访问,可以很容易的看到sentinel限流的页面
2.3秒之后QPS达到峰值9,手动的刷新已经很难出现限流的页面
5.注意事项和细节
- 测试 Warm up 效果不是很好测,如果出不来可以尝试,调整 流控规则: 比如 QPS 为 11, Warm up 预热时间 6 秒
- 如果请求停止(即: 一段时间没有达到阈值), Warm up 过程将重复, 可以理解成是一个弹性过程
6.流量控制实例-排队
1.排队 介绍
-
排队方式:这种方式严格控制了请求通过的间隔时间,也即是让请求以均匀的速度通 过,对应的是漏桶算法
-
一张图
- 这种方式主要用于处理间隔性突发的流量,例如消息队列。比如这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的 空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。- 类似削峰填谷
- 匀速排队,阈值必须设置为 QPS
2.需求分析/图解
- 需求: 通过 Sentinel 实现 流量控制-排队
- 调用 member-service-nacos-provider-10004 的 /t2 API 接口,将 QPS 设置为 1
- 当调用 /t2 的 QPS 超过 1 时,不拒绝请求,而是排队等待, 依次执行
- 当等待时间超过 10 秒,则为等待超时.
3.修改业务类
@RequestMapping("/t2")
public Result t2(){
try {
//让线程休眠 1s, 模拟执行时间
TimeUnit.SECONDS.sleep(1);
log.info("MemberController-t2-当前请求线程名为:{},线程id为:{},请求时间:{}",
Thread.currentThread().getName(),Thread.currentThread().getId(),new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
return Result.success("t2执行成功");
}
4.配置实现步骤
- 为/t2 增加流控规则
说明:前面我们让线程睡眠1秒,这里QPS设置为1即(每秒处理1个请求),当我们连续的刷新页面模拟请求时,处理不过来的请求会进行排队等待,而当线程等待时间超出10秒时就会被sentinel限流
-
在流控规则菜单,可以看到新增的流控规则
5.测试
-
浏览器访问 http://localhost:10004/t2 快速刷新页面 9 次,观察前台/后台输出的情况
-
浏览器访问 http://localhost:10004/t2 快速刷新页面 20 次,当请求等待时间超过 10S, 仍然出现流控异常
5.Sentinel 熔断降级
1.线程堆积引出熔断降级
- 一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等[架构图]
- 例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需 要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现 了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用
- 这时,我们对不稳定的服务进行熔断降级,让其快速返回结果,不要造成线程堆积
2.文档地址
https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
3.基本介绍
- 一张图
- 解读上图:
- 现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成 复杂的调用链路。
- 链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。
- 因此需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩
- 熔断,降级,限流三者的关系
- 熔断强调的是服务之间的调用能实现自我恢复的状态
- 限流是从系统的流量入口考虑, 从进入的流量上进行限制, 达到保护系统的作用
- 降级, 是从系统业务的维度考虑,流量大了或者频繁异常, 可以牺牲一些非核心业务,保护核心流程正常使用
梳理
- 熔断是降级方式的一种
- 降级又是限流的一种方式
- 三者都是为了通过一定的方式在流量过大或者出现异常时, 保护系统的手段
4.熔断策略
1.慢调用比例
1、慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的 慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用
2、当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的 比例大于阈值,则接下来的熔断时长内请求会自动被熔断
3、熔断时长后, 熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断
4、配置参考
2.异常比列
1、异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
2、经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态)
3、若接下来的一个请求成功完成(没有错误)则结束熔断, 否则会再次被熔断
4、异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%
5、配置参数
6、工作示意图
3.异常数
1、异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断
2、经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态)
3、若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断
4、配置参考
5.熔断降级实例-慢调用比例
1.需求分析/图解
- 需求: 通过 Sentinel 实现 熔断降级控制-慢调用比例
- 当调用 member-service-nacos-provider-10004 的 /t3 API 接口时,如果在 1s 内持续进 入了 5 个请求,并且请求的平均响应时间超过 200ms, 那么就在未来 10 秒钟内,断路器打开, 让 /t3 API 接口 微服务不可用
- 后面对/t3 API 接口 访问降到 1S 内 1 个请求,降低访问量了,断路器关闭,微服务恢复
2.修改业务类
@GetMapping("/t3")
public Result t3() {
//让线程休眠300ms毫秒, 模拟执行时间
try {
TimeUnit.MILLISECONDS.sleep(300);
log.info("MemberController-t3-当前请求线程名为:{},线程id为:{},请求时间:{}",
Thread.currentThread().getName(),Thread.currentThread().getId(),new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
return Result.success("t3()执行成功");
}
3.配置实现步骤
- 为/t3 增加降级规则
说明:每次请求的响应时间大于200ms,且每秒请求数达到了5个及以上,就熔断10s; 如果请求t3的频率降至阈值以下则断路器关闭恢复服务。
- 在流控规则菜单,可以看到新增的降级规则
4.测试
5.慢调用比例对应的 比例阈值都是1
界面上显示的比例阈值范围是[0.0,1.0] 但其实最终都是1,可以看出慢调用比例对应的 比例阈值都是1
6.注意事项和细节
- 每一个请求的平均响应时间 超出阈值 且在 1s 内通过的请求>=5, 两个条件同时满足后触发降级
- 熔断时间过后,关闭断路器,访问恢复正常
6.熔断降级实例-异常比例
1.需求分析/图解
- 需求: 通过 Sentinel 实现 熔断降级控制
- 当调用 member-service-nacos-provider-10004 的 /t4 API 接口时,当资源的每秒请求量>=5,并且每秒异常总数占通过量的比值超过 20%(即异常比例到 20%), 断路器打开(即: 进入降级状态), 让 /t4 API 接口 微服务不可用
- 当对/t4 API 接口 访问降到 1S 内 1 个请求,降低访问量了,断路器关闭,5 秒后微服 务恢复
2.修改业务类
@RequestMapping("/t4")
public Result t4(){
if(num++ %2 == 0){
//模拟请求50%比例异常
System.out.println(num/0);
}
log.info("熔断降级测试【异常比例】执行t4()-当前请求线程名为:{},线程id为:{},请求时间:{}",
Thread.currentThread().getName(),Thread.currentThread().getId(),new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
return Result.success();
}
3.配置实现步骤
- 为/t4 增加降级规则
- 在流控规则菜单,可以看到新增的降级规则
4.测试
5.注意事项和细节
1.当资源的每秒请求量>=5,并且每秒异常总数占通过量的比值超过阈值,资源进入降级状态, 需要两个条件都满足
2.测试时,如果熔断降级和恢复服务两个状态切换不明显,将时间窗口值调整大一点比如 60, 就 OK 了
7. 熔断降级实例-异常数
1.需求分析/图解
- 需求: 通过 Sentinel 实现 熔断降级控制
- 当调用 member-service-nacos-provider-10004 的 /t5 API 接口时,当资源的每分钟请 求量>=5,并且每分钟异常总数>=5 , 断路器打开(即: 进入降级状态), 让 /t5 API 接口 微服务不可用
- 当熔断时间(比如 20S)结束后,断路器关闭, 微服务恢复
2.修改业务类
/**
* 当资源的每分钟请 求量>=5,并且每分钟异常总数>=5 , 断路器打开(即: 进入降级状态), 让 /t5 API 接口 微服务不可用
* @return
*/
@RequestMapping("/t5")
public Result t5(){
//这里num至少应该<=6 第一次进入簇点链路,第二次开始计算异常数
if(num++ <=10){
System.out.println(num/0);
}
log.info("熔断降级测试【异常数】执行t5()-当前请求线程名为:{},线程id为:{},请求时间:{}",
Thread.currentThread().getName(),Thread.currentThread().getId(),new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
return Result.success();
}
3.配置实现步骤
1、为/t5 增加降级规则
2、在流控规则菜单,可以看到新增的降级规则
4.测试
5.注意事项和细节
1.资源在 1 分钟的异常数目超过阈值之后会进行熔断降级
2.异常数统计是分钟级别的,若设置的时间窗口小于 60s,则结束熔断状态后仍可能再进入熔断状态, 测试时,最好将时间窗口设置超过 60S
6.Sentinel 热点规则
1.一个问题引出热点 key 限流
- 热点: 热点即经常访问的数据。很多时候我们希望统计热点数据中, 访问频次最高的 Top K 数据,并对其访问进行限制。
- 比如某条新闻上热搜 ,在某段时间内高频访问, 为了防止系统雪崩, 可以对该条新 闻进行热点限流
2.文档地址
https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81
3.基本介绍
- 一张图
- 解读上图
- 热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含 热点参数的资源调用进行限流。
- 热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效
- Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控
- 令牌桶算法
- 热点参数限流支持集群模式
4.热点 Key 限流-实例
1.需求分析/图解
- 需求: 通过 Sentinel 实现 热点 Key 限流
- 对 member-service-nacos-provider-10004 的 /news?id=x&type=x API 接口进行热点限流
- 假定 id=10 这一条新闻是当前的热点新闻, 当查询新闻时,对通常的 id(非热点新闻) 请求 QPS 限定为 2, 如果 id=10 QPS 限定为 100
- 如果访问超出了规定的 QPS, 触发热点限流机制, 调用自定义的方法,给出提示信息.
- 当对 /news?id=x&type=x API 接口 降低访问量,QPS 达到规定范围, 服务恢复
2.修改业务类
/**
* 1.@SentinelResource : 指定sentinel限流资源
* 2.value = "news" 表示sentinel限流资源 名称,由程序员指定并不是说强制和请求的地址一致
* 3. blockHandler = "newsBlockHandler": 当出现限流时,由newsBlockHandler方法进行处理
*/
@RequestMapping("/news")
@SentinelResource(value = "news",blockHandler = "newsBlockHandler")
public Result queryNews(@RequestParam(value = "id") Long id, @RequestParam(value = "type") String type) {
return Result.success("查询到新闻,返回id="+id+",类型="+type);
}
//热点key限制/限流异常处理方法
public Result newsBlockHandler(Long id, String type,
BlockException blockException) {
return Result.error("401","查询id=" + id + " 新闻"+type+" 触发热点key限流保护 sorry..."+blockException.getMessage());
}
3.测试
4.配置实现步骤
- 为资源 news 增加热点规则, 注意不是 /news
-
在热点参数限流规则菜单,可以看到新增规则
5.再次测试
- 浏览器输入:http://localhost:10004/news?id=1&type=media , 如果 QPS 没有超过 2,则返回正确结果
- 浏览器输入: http://localhost:10004/news?id=1&type=media , 如果 QPS 超过 2,则返回热点 key 处理信息
6.独立设置热点 id=10 的 QPS 阈值(即添加例外)
- 独立设置热点 id=10 的 QPS 阈值(即添加例外)
7.再次测试
- 浏览器输入: http://localhost:10004/news?id=10&type=media , 如 果 QPS 没有超过 100,则返回正确结果
- 浏览器访问的 id 不是 10 的,仍然遵守 QPS 不能超过 2 的热点限制
5.注意事项和细节
1.热点参数类型是(byte/int/long/float/double/char/String)
2.热点参数值,可以配置多个
3.热点规则只对指定的参数生效 (比如本实例对 id 生效, 对 type 不生效)
7.系统规则
1.一个问题引出系统规则
- 如我们系统最大性能能抗 100QPS, 如何分配 /t1 /t2 的 QPS?
- 方案 1: /t1 分配 QPS=50 /t2 分配 QPS=50 , 问题, 如果/t1 当前 QPS 达到 50 , 而 /t2 的 QPS 才 10, 会造成没有充分利用服务器性能.
- 方案 2: /t1 分配 QPS=100 /t2 分配 QPS=100 , 问题, 容易造成 系统没有流量保护, 造成请求线程堆积,形成雪崩.
- 有没有对各个资源请求的 QPS弹性设置, 只要总数不超过系统最大QPS的流量保护规则? ==> 系统规则
2.文档地址
3.一句话
系统规则作用, 在系统稳定的前提下,保持系统的吞吐量
4.基本介绍
- 一张图
- 解读上图
- 系统处理请求的过程想象为一个水管,到来的请求是往这个水管灌水,当系统处理顺畅的时候,请求不需要排队,直接从水管中穿过,这个请求的RT是最短的;
- 反之,当请求堆积的时候,那么处理请求的时间则会变为:排队时间 + 最短处理时间
- 系统规则
-
Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算 的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
-
CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
-
平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫 秒。
-
并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
-
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
5.实例
1.需求分析/图解
- 需求: 通过 Sentinel 实现 系统规则-入口 QPS
- 对 member-service-nacos-provider-10004 的 所有 API 接口进行流量保护,不管访问 哪个 API 接口, 系统入口总的 QPS 不能大于 2, 大于 2,就进行限流控制
- 提示: 上面的 QPS 是为了方便测试看效果, 设置的很小
2.配置实现步骤
-
增加入口 QPS 系统规则
-
测试
当每秒请求数超过2时
注意:入口QPS系统规则针对的是整个服务(member-service-nacos-provider-10004 ),当访问任何一个接口的QPS或者总的QPS超过设置的值时都会进行限流
8.@SentinelResource
1.自定义全局限流处理类
1.需求分析/图解
-
先看前面的一段代码
/** * 1.@SentinelResource : 指定sentinel限流资源 * 2.value = "news" 表示sentinel限流资源 名称,由程序员指定并不是说强制和请求的地址一致 * 3. blockHandler = "newsBlockHandler": 当出现限流时,由newsBlockHandler方法进行处理 */ @RequestMapping("/news") @SentinelResource(value = "news",blockHandler = "newsBlockHandler") public Result queryNews(@RequestParam(value = "id") Long id, @RequestParam(value = "type") String type) { return Result.success("查询到新闻,返回id="+id+",类型="+type); } //热点key限制/限流异常处理方法 public Result newsBlockHandler(Long id, String type, BlockException blockException) { return Result.error("401","查询id=" + id + " 新闻"+type+" 触发热点key限流保护 sorry..."+blockException.getMessage()); }
说明: 当配置的资源名 news 触发限流机制时,会调用 newsBlockHandler 方法
-
上面的处理方案存在一些问题
- 每个@SentinelResource 对应一个异常处理方法,会造成方法很多
- 异常处理方法和资源请求方法在一起,不利于业务逻辑的分离
- 解决方案-> 自定义全局限流处理类
-
需求: 请编写一个自定义全局限流处理类,完成对异常处理.
2.代码实现
1.测试方法
/**
* 1.@SentinelResource : 指定sentinel限流资源
* 2.value = "t6_resource" 表示sentinel限流资源 名称,由程序员指定并不是说强制和请求的地址一致
* 3.blockHandlerClass = CustomGlobalBlockHandler.class是全局限流处理类
* 3. blockHandler = "handlerMethod1" 全局限流处理类的哪个方法,可以指定
*/
@RequestMapping("/t6")
@SentinelResource(value = "t6_resource",blockHandlerClass = CustomGlobalBlockHandler.class,blockHandler = "handlerMethod1")
public Result t6(){
log.info("MemberController-t6-当前请求线程名为:{},线程id为:{},请求时间:{}",
Thread.currentThread().getName(), Thread.currentThread().getId(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
return Result.success("t6执行成功");
}
2.创建com.llp.springcloud.handler.CustomGlobalBlockHandler
/**
* 1.CustomGlobalBlockHandler:全局限流处理类
* 2.在CustomGlobalBlockHandler类中,可以编写限流处理方法,但是要求方法是static(静态方法)
*/
public class CustomGlobalBlockHandler {
public static Result handlerMethod1(BlockException blockException){
return Result.error("400", "客户自定义异常处理 handlerMethod1()");
}
public static Result handlerMethod2(BlockException blockException){
return Result.error("401", "客户自定义异常处理 handlerMethod2()");
}
}
3.配置实现步骤
- 为资源 /t6 增加流控规则,方便测试
-
在流控规则菜单,可以看到新增规则
4.测试
- 浏览器输入: http://localhost:10004/t6 , 如果 QPS 没有超过 1, 返回正常结果
- 浏览器输入: http://localhost:10004/t6 , 如果 QPS 超过 1, 断路器打开,返回自定义限流处理方法信息
2.fallback
1.看一段代码-引出 fallback
1.增加一段代码,当num对5取模为0时抛出运行时异常
private int num = 0;
@RequestMapping("/t6")
@SentinelResource(value = "t6_resource",
blockHandlerClass = CustomGlobalBlockHandler.class,
blockHandler = "handlerMethod1")
public Result t6() {
//假定: 当访问 t6 资源次数是 5 的倍数时,就出现了一个 java 的异常
if (++num % 5 == 0) {
throw new RuntimeException(("num 的值异常 num= " + num));
}
log.info("MemberController-t6-当前请求线程名为:{},线程id为:{},请求时间:{}",
Thread.currentThread().getName(), Thread.currentThread().getId(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
return Result.success("t6执行成功");
}
- 浏览器: http://localhost:10004/t6 , 看效果当 num 为 5 的整数时,返回的是 error 页面, 不友好.
- 怎么解决=> 使用 fallback
2.基本介绍
1.blockHandler 只负责 sentine 控制台配置违规
2.fallback 负责 Java 异常/业务异常
3.需求分析/图解
- 需求: 请编写一个自定义全局 fallback 处理类, 处理 java 异常/业务异常
- 也就是解决前面我们提出的问题
4.代码实现
1.创建com.llp.springcloud.handler.CustomGlobalFallbackHandler
/**
* CustomGlobalFallbackHandler 全局fallback处理类
* 在CustomGlobalFallbackHandler类中,可以编写处理java异常/业务异常方法-static
*/
public class CustomGlobalFallbackHandler {
public static Result fallbackHandlerMethod1(Throwable e) {
return Result.error("402", "异常信息:" + e.getMessage());
}
public static Result fallbackHandlerMethod2(Throwable e) {
return Result.error("403", "异常信息:" + e.getMessage());
}
}
2.修改MemberController
/**
* 1.@SentinelResource : 指定sentinel限流资源
* 2.value = "t6_resource" 表示sentinel限流资源 名称,由程序员指定并不是说强制和请求的地址一致
* 3.blockHandlerClass = CustomGlobalBlockHandler.class是全局限流处理类
* 3. blockHandler = "handlerMethod1" 全局限流处理类的哪个方法,可以指定
* 4.fallbackClass=CustomGlobalFallbackHandler.class 指定全局fallback处理类
* 5.fallback = "fallbackHandlerMethod1": 指定使用全局fallback处理类哪个方法来处理java异常/业务异常
*/
@RequestMapping("/t6")
@SentinelResource(value = "t6_resource",
blockHandlerClass = CustomGlobalBlockHandler.class,
blockHandler = "handlerMethod1",
fallbackClass = CustomGlobalFallbackHandler.class,
fallback = "fallbackHandlerMethod1")
public Result t6() {
if (++num % 5 == 0) {
throw new RuntimeException(("num 的值异常 num= " + num));
}
log.info("MemberController-t6-当前请求线程名为:{},线程id为:{},请求时间:{}",
Thread.currentThread().getName(), Thread.currentThread().getId(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
return Result.success("t6执行成功");
}
3.配置流控规则
5.测试
1.正常访问
2.出现异常
3.限流
3.exceptionsToIgnore
1.如果希望忽略某个异常,可以使用 exceptionsToIgnore
2.应用实例
- 如果希望忽略某个异常(支持数组),可以使用 exceptionsToIgnore
/**
* 1.@SentinelResource : 指定sentinel限流资源
* 2.value = "t6_resource" 表示sentinel限流资源 名称,由程序员指定并不是说强制和请求的地址一致
* 3.blockHandlerClass = CustomGlobalBlockHandler.class是全局限流处理类
* 3. blockHandler = "handlerMethod1" 全局限流处理类的哪个方法,可以指定
* 4.fallbackClass=CustomGlobalFallbackHandler.class 指定全局fallback处理类
* 5.fallback = "fallbackHandlerMethod1": 指定使用全局fallback处理类哪个方法来处理java异常/业务异常
* 6.exceptionsToIgnore = {RuntimeException.class}: 表示如果t6()抛出RuntimeException, 就使用系统默认方式处理
*/
@RequestMapping("/t6")
@SentinelResource(value = "t6_resource",
blockHandlerClass = CustomGlobalBlockHandler.class,
blockHandler = "handlerMethod1",
fallbackClass = CustomGlobalFallbackHandler.class,
fallback = "fallbackHandlerMethod1",
exceptionsToIgnore = {RuntimeException.class})
public Result t6() {
if (++num % 5 == 0) {
throw new RuntimeException(("num 的值异常 num= " + num));
}
log.info("MemberController-t6-当前请求线程名为:{},线程id为:{},请求时间:{}",
Thread.currentThread().getName(), Thread.currentThread().getId(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
return Result.success("t6执行成功");
}
- 浏览器输入: http://localhost:10004/t6 , 你会发现访问次数为 5 的倍数时,不再调用 fallback 指定方法处理
4.接入 Sentinel 的方式
1.代码方式(硬编码,侵入性强, 不推荐)
2.注解方式(低侵入性, 前面用过, 推荐)
https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81
-
注解方式埋点不支持private 方法:@SentinelResource修饰的方法t6不能是private修饰的,包括blockHandler、fallback的方法也不能是private修饰的,否则不会生效
/** * 1.@SentinelResource : 指定sentinel限流资源 * 2.value = "t6_resource" 表示sentinel限流资源 名称,由程序员指定并不是说强制和请求的地址一致 * 3.blockHandlerClass = CustomGlobalBlockHandler.class是全局限流处理类 * 3. blockHandler = "handlerMethod1" 全局限流处理类的哪个方法,可以指定 * 4.fallbackClass=CustomGlobalFallbackHandler.class 指定全局fallback处理类 * 5.fallback = "fallbackHandlerMethod1": 指定使用全局fallback处理类哪个方法来处理java异常/业务异常 * 6.exceptionsToIgnore = {RuntimeException.class}: 表示如果t6()抛出RuntimeException, 就使用系统默认方式处理 */ @RequestMapping("/t6") @SentinelResource(value = "t6_resource", blockHandlerClass = CustomGlobalBlockHandler.class, blockHandler = "handlerMethod1", fallbackClass = CustomGlobalFallbackHandler.class, fallback = "fallbackHandlerMethod1", exceptionsToIgnore = {RuntimeException.class}) public Result t6() { if (++num % 5 == 0) { throw new RuntimeException(("num 的值异常 num= " + num)); } log.info("MemberController-t6-当前请求线程名为:{},线程id为:{},请求时间:{}", Thread.currentThread().getName(), Thread.currentThread().getId(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); return Result.success("t6执行成功"); }
-
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。
-
@SentinelResource 注解包含以下属性(我们再梳理一下)
- value:资源名称,必需项(不能为空)
- entryType:entry 类型,可选项(默认为 EntryType.OUT)
- blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数 用于接收对应的异常。
- fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型 的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收 对应的异常。
- defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数, 则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中, 也不会进入 fallback 逻辑中,而是会原样抛出。
9.openFeign+sentinel 对远程调用熔断降级
1.当前微服务基础环境
1.当前微服务环境架构图
- 示意图
2.测试
2.服务消费者整合 Openfeign
1.需求分析/图解
- 需求:在 member-service-nacos-consumer-80 整合 Openfeign 实现远程调用
2.代码+配置实现步骤
- 修改 member-service-nacos-consumer-80 的 pom.xml 加入 openfeign 依赖
<!--引入openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.在member-service-nacos-consumer-80创建com.llp.springcloud.service.MemberOpenfeignService.java
//不需要加@Component注解,@FeignClient修饰的类会被注入到spring IOC容器中
@FeignClient(value = "member-service-nacos-provider")
public interface MemberOpenfeignService {
/**
* 1. 远程调用方式是 get
* 2. 远程调用的url 为 http://member-service-nacos-provider/member/get/{id}
* 3. member-service-nacos-provider是nacos注册中心服务名
* 4. openfeign会根据负载均衡算法来决定调用的是 10004/10006,默认是轮询算法
* 5. openfeign是通过接口方式调用服务
*/
@GetMapping("/member/get/{id}")
public Result getMemberById(@PathVariable("id") Long id);
}
-
在member-service-nacos-consumer-80 修改com.llp.springcloud.controller.MemberNacosConsumerController.java,新增测试方法
private final MemberOpenfeignService memberOpenfeignService; //查询member,通过openfeign的方式 @GetMapping("/member/nacos/openfeign/consumer/get/{id}") public Result<Member> getMemberOpenfeignById(@PathVariable("id") Long id) { log.info("调用方式是 openfeign.."); return memberOpenfeignService.getMemberById(id); }
-
在 member-service-nacos-consumer-80 的 主 启 动 类 加 入 注 解 com.llp.springcloud.MemberNacosConsumerApplication80.java
//启用nacos发现注解 @EnableDiscoveryClient @SpringBootApplication //启用openfeign @EnableFeignClients public class MemberNacosConsumerApplication80 { public static void main(String[] args) { SpringApplication.run(MemberNacosConsumerApplication80.class,args); } }
3.测试
http://localhost/member/nacos/openfeign/consumer/get/2
3.服务消费者整合 Sentinel
1.需求分析/图解
- 需求:在 member-service-nacos-consumer-80 整合 Sentinel 能被 Sentinel 监控
2.代码+配置实现步骤
-
修改 member-service-nacos-consumer-80 的 pom.xml 加入 sentinel 依赖
<!--引入sentinel starter 场景启动器--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
-
修改 member-service-nacos-consumer-80 的 application.yml 配置 sentinel
server: port: 80 spring: application: name: member-service-nacos-consumer-80 #配置nacos cloud: nacos: discovery: #配置nacos Server地址,localhost-nacos部署的服务器ip 8848-服务器端口 server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 #指定sentinel控制台地址(dash board) port: 8719 #设置端口默认是 8719, 如果该端口被占用, 就自动从 8791+1进行扫描, 直到找到一个没有占用的端口 #设置暴露所有的监控点 management: endpoints: web: exposure: include: '*' #openfeign和sentinel整合,必须配置 feign: sentinel: enabled: true
3.测试
4.openFeign+sentinel 对远程调用熔断降级
1.需求分析/图解
- 需求/如图:在 member-service-nacos-consumer-80 调用某个无效服务时,启动 Sentinel 的熔断降级机制 , 能够快速返回响应,而不是使用默认的超时机制(因为超时机制容易线 程堆积, 从而导致雪崩)
- 先测试一下,关闭 10004/10006, 这时 openfeign 去调用会怎么样? (返回 time out)
30S后再次访问,Nacos已经无法发现服务了
-
测试一下,让 10004 服务对应的 API 执行时间很长(比如休眠 2 秒)而1006不休眠, 这 时 openfeign 去调用会怎么样?
openfeign默认超时时间为1秒,即调用得到响应的时间超出1秒则会超时抛出异常,1004总是超时的,因此只会调用1006
2.代码+配置实现步骤
- 创建 member-service-nacos-consumer-80 的src\main\java\com\llp\springcloud\service\MemberOpenfeignFallbackService.java
//回调类需要注入到SpringIOC容器中
@Component
public class MemberOpenfeignFallbackService implements MemberOpenfeignService {
@Override
public Result getMemberById(Long id) {
return Result.error("500", "被调用服务异常, 熔断降级, 快速返回结果,防止线程堆积..");
}
}
2.修 改 member-service-nacos-consumer-80 的src\main\java\com\llp\springcloud\service\MemberOpenfeignService.java
//不需要加@Component注解,@FeignClient修饰的类会被注入到spring IOC容器中
@FeignClient(value = "member-service-nacos-provider",fallback = MemberOpenfeignFallbackService.class)
public interface MemberOpenfeignService {
/**
* 1. 远程调用方式是 get
* 2. 远程调用的url 为 http://member-service-nacos-provider/member/get/{id}
* 3. member-service-nacos-provider是nacos注册中心服务名
* 4. openfeign会根据负载均衡算法来决定调用的是 10004/10006,默认是轮询算法
* 5. openfeign是通过接口方式调用服务
*/
@GetMapping("/member/get/{id}")
Result getMemberById(@PathVariable("id") Long id);
}
- 修 改 member-service-nacos-consumer-80 的 application.yml , 加 入 openfeign 和 sentinel 整合配置
server:
port: 80
spring:
application:
name: member-service-nacos-consumer-80
#配置nacos
cloud:
nacos:
discovery:
#配置nacos Server地址,localhost-nacos部署的服务器ip 8848-服务器端口
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080 #指定sentinel控制台地址(dash board)
port: 8719 #设置端口默认是 8719, 如果该端口被占用, 就自动从 8791+1进行扫描, 直到找到一个没有占用的端口
#设置暴露所有的监控点
management:
endpoints:
web:
exposure:
include: '*'
#openfeign和sentinel整合,必须配置!!
feign:
sentinel:
enabled: true
3.测试
- 浏览器输入:http://localhost/member/nacos/openfeign/consumer/get/2 , 目前是 Openfeign 调用(负载均衡)
4.注意事项和细节说明
- 因为 member-service-nacos-consumer-80 已经被 sentinel 监控,所以我们可以加入相关的流控规则, 比如为/member/nacos/openfeign/consumer/get/2 加入流控规则 qps = 1
测试: 如果/member/nacos/openfeign/consumer/get/2 请求 QPS 超过 1, 会输出
QPS 没有超过 1, 会被 fallback 处理, 如图
- 如果远程服务恢复正常, 又会正常调用。openfeign-fallback快速返回失败的机制会失效,但是配置的sentinel流控规则还是会生效
10.规则持久化
1.规则没有持久化的问题
1.如果 sentinel 流控规则没有持久化,当重启调用 API/接口 所在微服务后,规则就会丢失,需要 重新加入
2.解决方案:通过 Nacos 进行持久化
2.规则持久化方案
1.阿里云 Ahas[最方便/付费]
- 官方文档 :
https://help.aliyun.com/product/87450.html?spm=5176.cnahas.0.0.78034bb7ef0y86
2.在 Nacos Server 配置规则, 完成持久化 -官方推荐
3.将规则持久化到本地文件, 定时同步
4.其他…
3.Nacos Server 配置中心-规则持久化实例
1.工作原理示意图
2.需求分析/图解
- 需 求 : 为 member-service-nacos-consumer-80 微 服 务 的 /member/openfeign/consumer/get/1 API 接口添加流控规则 QPS=1/快速失败 .
- 要求将该流控规则加入到 nacos server 配置中心,实现持久化
3.代码+配置实现步骤
- 在 Nacos Server 配置中心增加 Sentinel 客户端/微服务模块 的流控规则
示例:
[
{
"resource":"/member/nacos/openfeign/consumer/get/1",
"limitApp":"default",
"grade":1,
"count":1,
"strategy":0,
"controlBehavior":0,
"clusterMode":false
}
]
-
在 Nacos Server 配置中心增加 Sentinel 客户端/微服务模块 的流控规则参数说明
- resource∶资源名称;
- limlitApp∶ 来源应用;
- grade∶阈值类型,0表示线程数,1表示QPS;
- count∶单机阈值;
- strategy∶流控模式,0表示直接,1表示关联,2表示链路;
- controlBehavior∶流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
- clusterMode∶是否集群
-
配置实例:
[
{
"resource":"/member/nacos/openfeign/consumer/get/1",
"limitApp":"default",
"grade":1,
"count":1,
"strategy":0,
"controlBehavior":0,
"clusterMode":false
}
]
- 修改 member-service-nacos-consumer-80 的 pom.xml, 加入加入 sentinel 和 nacos 持久化整合依赖
<!-- 加入 sentinel 和 nacos 持久化整合依赖 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 修改 member-service-nacos-consumer-80 的 application.yml , 配置该微服务从 Nacos Server 获取流控规则
server:
port: 80
spring:
application:
name: member-service-nacos-consumer-80
#配置nacos
cloud:
nacos:
discovery:
#配置nacos Server地址,localhost-nacos部署的服务器ip 8848-服务器端口
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080 #指定sentinel控制台地址(dash board)
port: 8719 #设置端口默认是 8719, 如果该端口被占用, 就自动从 8791+1进行扫描, 直到找到一个没有占用的端口
datasource:
ds1:
#流控规则配置从nacos server配置中心获取,参考nacos新增配置页面进行配置
#也可以从redis Apollo获取
nacos:
#指定nacos server配置中心地址
server-addr: localhost:8848
#指定nacos server配置中心dataId
dataId: member-service-nacos-consumer-80
#指定组
groupId: DEFAULT_GROUP
#指定配置流控规则的数据类型
data-type: json
#指定配置的是流控规则
rule-type: flow
#设置暴露所有的监控点
management:
endpoints:
web:
exposure:
include: '*'
#openfeign和sentinel整合,必须配置
feign:
sentinel:
enabled: true
4.测试
QPS超过1
在sentinel控制台生成了流程规则
5.注意事项和细节
-
在 nacos server 配置 sentinel 流控规则的 Data ID 也可以自己指定,比如写成 hsp-id, 只要在 sentinel client/微服务 的 applicaion.yml 的 datasource.ds1.nacos.dataId 的值保 持一致即可
-
如图所示
3.更对规则配置参考文档:
https://sentinelguard.io/zh-cn/docs/basic-api-resource-rule.html