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
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点, 从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
sentinel 可以完成的功能: 绿色方框列出的部分
3.Sentinel 的开源生态
一句话: Sentinel: 分布式系统的流量防卫兵, 保护你的微服务
1.流量控制
拿旅游景点举个示例,每个旅游景点通常都会有最大的接待量,不可能无限制的放游 客进入,比如长城每天只卖八万张票,超过八万的游客,无法买票进入,因为如果超过 八万人,景点的工作人员可能就忙不过来,过于拥挤的景点也会影响游客的体验和心情, 并且还会有安全隐患;只卖 N 张票,这就是一种限流的手段
2.熔断降级
在调用系统的时候,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积,如下图
解读:熔断降级可以解决这个问题,所谓的熔断降级就是当检测到调用链路中某个资源出现不 稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限 制,让请求快速失败,避免影响到其它的资源而导致级联故障
3.系统负载保护
根据系统能够处理的请求,和允许进来的请求,来做平衡,追求的目标是在系统不被 拖垮的情况下, 提高系统的吞吐率
4.消息削峰填谷
某瞬时来了大流量的请求, 而如果此时要处理所有请求,很可能会导致系统负载过高, 影响稳定性。但其实可能后面几秒之内都没有消息投递,若直接把多余的消息丢掉则没 有充分利用系统处理消息的能力
Sentinel 的 Rate Limiter 模式能在某一段时间间隔内以匀速方式处理这样的请求, 充分利 用系统的处理能力, 也就是削峰填谷, 保证资源的稳定性
1.核心库:(Java 客户端)不依赖任何框架/库,能够运行在所有 Java 运行时环境, 对 Spring Cloud 有较好的支持
2.控制台:(Dashboard)基于 Spring Boot 开发,打包后可以直接运行, 不需要额外的 Tomcat 等 应用容器
1.需求: 搭建 Sentinel 控制台,用于显示各个微服务的使用情况
https://github.com/alibaba/Sentinel/releases/tag/v1.8.0
1.控制台页面,浏览器输入: http://localhost:8080 , 用户/密码都是 sentinel
2.登录成功后的页面, 目前是空的,因为 sentinel 还没有进行流量监控
1、更改 Sentinel 控制台的端口
java -jar sentinel-dashboard-1.8.0.jar --server.port=9999
– 示意图
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
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
1.先看一张图
2.对上图的解读
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
是否集群∶不需要集群
流控模式∶
流控效果∶
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?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 + "不存在");
}
}
可以通过 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;
}
}
为/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 + "不存在");
}
}
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限流
1.当关联的资源达到阈值时,就限流自己
需求: 通过 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执行成功");
}
为/t1 增加流控规则
配置说明: 当t2的访问阈值达到t1配置的QPS时(下图t1配置的QPS为1)就会对t1进行限流
在流控规则菜单,可以看到新增的流控规则
为了测试出t1限流的效果,我们使用postman对t2进行循环请求
当t2达到t1配置的QPS时,这时请求t1已经被sentinel进行限流了
1.在 postman 执行 高并发访问 /t2 没有结束时, 去访问 /t1 才能看到流控异常出现
概述
一张图
梳理
(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 可慢慢的把流量放入,最终将阀值增长到设置阀值
1.在前3秒QPS为 (threshold(峰值阈值) / 3 )=3 我们不断的刷新进行访问,可以很容易的看到sentinel限流的页面
2.3秒之后QPS达到峰值9,手动的刷新已经很难出现限流的页面
排队方式:这种方式严格控制了请求通过的间隔时间,也即是让请求以均匀的速度通 过,对应的是漏桶算法
一张图
@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执行成功");
}
说明:前面我们让线程睡眠1秒,这里QPS设置为1即(每秒处理1个请求),当我们连续的刷新页面模拟请求时,处理不过来的请求会进行排队等待,而当线程等待时间超出10秒时就会被sentinel限流
在流控规则菜单,可以看到新增的流控规则
浏览器访问 http://localhost:10004/t2 快速刷新页面 9 次,观察前台/后台输出的情况
浏览器访问 http://localhost:10004/t2 快速刷新页面 20 次,当请求等待时间超过 10S, 仍然出现流控异常
https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
梳理
1、慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的 慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用
2、当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的 比例大于阈值,则接下来的熔断时长内请求会自动被熔断
3、熔断时长后, 熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断
4、配置参考
1、异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
2、经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态)
3、若接下来的一个请求成功完成(没有错误)则结束熔断, 否则会再次被熔断
4、异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%
5、配置参数
6、工作示意图
1、异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断
2、经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态)
3、若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断
4、配置参考
@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()执行成功");
}
说明:每次请求的响应时间大于200ms,且每秒请求数达到了5个及以上,就熔断10s; 如果请求t3的频率降至阈值以下则断路器关闭恢复服务。
界面上显示的比例阈值范围是[0.0,1.0] 但其实最终都是1,可以看出慢调用比例对应的 比例阈值都是1
@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();
}
1.当资源的每秒请求量>=5,并且每秒异常总数占通过量的比值超过阈值,资源进入降级状态, 需要两个条件都满足
2.测试时,如果熔断降级和恢复服务两个状态切换不明显,将时间窗口值调整大一点比如 60, 就 OK 了
/**
* 当资源的每分钟请 求量>=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();
}
1、为/t5 增加降级规则
2、在流控规则菜单,可以看到新增的降级规则
1.资源在 1 分钟的异常数目超过阈值之后会进行熔断降级
2.异常数统计是分钟级别的,若设置的时间窗口小于 60s,则结束熔断状态后仍可能再进入熔断状态, 测试时,最好将时间窗口设置超过 60S
https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81
/**
* 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());
}
在热点参数限流规则菜单,可以看到新增规则
1.热点参数类型是(byte/int/long/float/double/char/String)
2.热点参数值,可以配置多个
3.热点规则只对指定的参数生效 (比如本实例对 id 生效, 对 type 不生效)
系统规则作用, 在系统稳定的前提下,保持系统的吞吐量
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 达到阈值即触发系统保护。
增加入口 QPS 系统规则
测试
当每秒请求数超过2时
注意:入口QPS系统规则针对的是整个服务(member-service-nacos-provider-10004 ),当访问任何一个接口的QPS或者总的QPS超过设置的值时都会进行限流
先看前面的一段代码
/**
* 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 方法
上面的处理方案存在一些问题
需求: 请编写一个自定义全局限流处理类,完成对异常处理.
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()");
}
}
在流控规则菜单,可以看到新增规则
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执行成功");
}
1.blockHandler 只负责 sentine 控制台配置违规
2.fallback 负责 Java 异常/业务异常
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.配置流控规则
1.正常访问
2.出现异常
3.限流
1.如果希望忽略某个异常,可以使用 exceptionsToIgnore
2.应用实例
/**
* 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执行成功");
}
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 注解包含以下属性(我们再梳理一下)
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
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);
}
}
http://localhost/member/nacos/openfeign/consumer/get/2
修改 member-service-nacos-consumer-80 的 pom.xml 加入 sentinel 依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
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
30S后再次访问,Nacos已经无法发现服务了
测试一下,让 10004 服务对应的 API 执行时间很长(比如休眠 2 秒)而1006不休眠, 这 时 openfeign 去调用会怎么样?
openfeign默认超时时间为1秒,即调用得到响应的时间超出1秒则会超时抛出异常,1004总是超时的,因此只会调用1006
//回调类需要注入到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);
}
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
测试: 如果/member/nacos/openfeign/consumer/get/2 请求 QPS 超过 1, 会输出
QPS 没有超过 1, 会被 fallback 处理, 如图
1.如果 sentinel 流控规则没有持久化,当重启调用 API/接口 所在微服务后,规则就会丢失,需要 重新加入
2.解决方案:通过 Nacos 进行持久化
1.阿里云 Ahas[最方便/付费]
https://help.aliyun.com/product/87450.html?spm=5176.cnahas.0.0.78034bb7ef0y86
2.在 Nacos Server 配置规则, 完成持久化 -官方推荐
3.将规则持久化到本地文件, 定时同步
4.其他…
示例:
[
{
"resource":"/member/nacos/openfeign/consumer/get/1",
"limitApp":"default",
"grade":1,
"count":1,
"strategy":0,
"controlBehavior":0,
"clusterMode":false
}
]
在 Nacos Server 配置中心增加 Sentinel 客户端/微服务模块 的流控规则参数说明
配置实例:
[
{
"resource":"/member/nacos/openfeign/consumer/get/1",
"limitApp":"default",
"grade":1,
"count":1,
"strategy":0,
"controlBehavior":0,
"clusterMode":false
}
]
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
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
QPS超过1
在sentinel控制台生成了流程规则
在 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