文章目录
一、降级规则
官网
1. 基本介绍
- RT(平均响应时间,秒级)
平均响应时间超出阈值且在时间窗口内通过的请求>=5,两个条件同时满足后触发降级窗口期过后关闭断路器
- RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
异常比列(秒级)
QPS >=5且比例(秒级统计)超过阈值时,触发降级,时间窗口结束后,关闭降级
1.1进一步说明
Sentinel的断路器是没有半开状态的
Sentinel熔断隆级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高) ,对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)。
半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用,具体参考Hystrix
1.2 复习Hystrix
2. 降级策略实战
2.1RT是什么
- 平均响应时间( DEGRADE_GRADE_RT ):当1s内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阈值( count ,以ms为单位),那么在接下的时间窗口( DegradeRule中的
timewindow,以s为单位)之内,对这个方法的调用都会自动地熔断(抛出
DegradeException )。注意Sentinel默认统计的RT上限是4900 ms,超出此阈值的都会算作4900 ms,若需要变更此上限可以通过启动配置项-Dcsp.sentine1.statistic.max. rt=xxx来配置。
测试
配置降级规则
@GetMapping("/testD")
public String testD() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("testD 测试RT");
return "----testD";
}
用Jmeter压测
然后浏览器http://127.0.0.1:8401/testD发现无法访问了
代码
- 按照上述配置,
永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,
如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了
后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK
2.2 异常比例
介绍
异常比例( DEGRADE_GRADE_EXCEPTION_RATIO ):当资源的每秒请求量>=5,并且每秒异常总数占通过量的比值超过阈值( DegradeRule中的count )之后,资源进入降级状态,即在接下的时间窗口( DegradeRule中的timewindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0,1.0],代表0%- 100%。
测试
@GetMapping("/testD")
public String testD()
{
log.info("testD 测试RT");
int age = 10/0;
return "------testD";
}
运行Jmeter测试
浏览器中输入http://127.0.0.1:8401/testD
已经熔断
2.3 熔断数
异常数( DEGRADF_GRADE_EXCEPTION_cOuNT ):当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timewindow小于60s,则结束熔断状态后仍可能再进入熔断状态。<mark>时间窗口一定要大于60s</mark>
测试
controller控制层代码
@GetMapping("/testE")
public String testE()
{
log.info("testE 测试异常数");
int age = 10/0;
return "------testE 测试异常数";
}
浏览器http://localhost:8401/testE
http://localhost:8401/testE,第一次访问绝对报错,因为除数不能为零,
我们看到error窗口,但是达到5次报错后,进入熔断后降级。
二、@SentinelResource
1. 兜底方法
兜底方法分两种
- 系统默认 :
之前的例子,限流出问题后,都是用sentinel系统默认的提示: Blocked by Sentinel (flow limiting)
我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法? - 客户自定义
从HystrixCommand到SentinelResource
从HystrixCommand到哨兵资源
兜底方法
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2) {
//int age = 10/0;
return "------testHotKey";
}
//兜底方法
public String deal_testHotKey (String p1, String p2, BlockException exception){
return "------deal_testHotKey,o(╥﹏╥)o";
}
测试代码
@RestController
public class RateLimitController {
@GetMapping("/byResource")
@SentinelResource(value = "byResource", blockHandler = "handleException")
public CommonResult byResource() {
return new CommonResult(200, "按照资源名称限流测试", new Payment(2020L, "serial001"));
}
@GetMapping("/byResource/exection")
@SentinelResource(value = "byResourceForExection", blockHandler = "handleException")
public CommonResult byResourceForExection() {
int i = 10 / 0;
return new CommonResult(200, "按照异常数做降级", new Payment(2020L, "serial001"));
}
public CommonResult handleException(BlockException exception) {
return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用");
}
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
//注意如果使用url进行限流降级那么SentinelResource配置的blockHandler将无效
public CommonResult byUrl() {
return new CommonResult(200, "按照byUrl限流测试", new Payment(2020L, "serial002"));
}
//CustomerBlockHandler
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")
public CommonResult customerBlockHandler() {
return new CommonResult(200, "按照客户自定义限流测试", new Payment(2020L, "serial003"));
}
}
2. value属性测试
配置
访问
注意资源名称要和@SentinelResource()的value属性相同才会限流
3. blockHandler
包括限流降级等都可以使用 <mark>注意</mark> 要根据@SentinelResource中的value属性进行配置规则 根据url不会有效果
替换默认显示Blocked by Sentinel (flow limiting)
为我们自定义的
参考上边代码 返回值参数要一致并且在参数中添加BlockException exception
4. 自定义处理类
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
/** * @author 夏天 */
public class CustomerBlockHandler {
public static CommonResult handlerException(BlockException exception) {
return new CommonResult(444, "按照客户自定义的Glogal 全局异常处理 ---- 1", new Payment(2020L, "serial003"));
}
public static CommonResult handlerException2(BlockException exception) {
return new CommonResult(444, "按照客户自定义的Glogal 全局异常处理 ---- 2", new Payment(2020L, "serial003"));
}
}
使用处理类
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class,
blockHandler = "handlerException2")
public CommonResult customerBlockHandler()
{
return new CommonResult(200,"按客戶自定义",new Payment(2020L,"serial003"));
}
配置
测试
http://127.0.0.1:8401/rateLimit/customerBlockHandler
当点击过快时候触发限制
5. 统一处理
# 只包含主要配置其余省略
spring:
cloud:
sentinel:
servlet:
blockPage: https://www.baidu.com/ #配置统一处理页面
三、热点key限流
官方介绍:github热点key限流
1 配置
1.1普通版
<mark>注意</mark>
资源名要对应 @SentinelResource中的value才会有效果
浏览器继续使用@getMapper中的路径进行访问 http://127.0.0.1:8401/testHotKey?p1=1
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKeyB", blockHandler = "deal_testHotKey")
//value 唯一即可 规范一般为方法名 blockHandler如果不配置页面直接抛出信息
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "----testHotKey";
}
//返回值 请求参数一致 并且添加上 BlockException exception 异常
public String deal_testHotKey(String p1, String p2, BlockException exception) {
return "----deal_testHotKey, o(╥﹏╥)o"; // sentinel的默认提示都是: Blocked by Sentinel (flow limiting)
}
测试
http://127.0.0.1:8401/testHotKey?p1=1
根据配置1秒中的QPS的阈值为1次 所以点击过快会出现错误提示,这个提示是通过@SentinelResource注解自定义的
<mark>注意</mark>
资源名要对应 @SentinelResource中的value才会有效果 请注意看边的配置
1.2 高级版
上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流
需求
我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样
假如当p1的值等于5时,它的阈值可以达到200
实现
结论
当p1等于5的时候,阈值变为200
当p1不等于5的时候,阈值就是平常的1
<mark>前提条件</mark>:热点参数的注意点,参数必须是基本类型或者String
手贱添加异常看看…
@sentinelResource
处理的是sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
RuntimeException
int age = 10/0,这个是java运行时报出的运行时异常RunTimeException(<mark>其他异常也不管</mark>),@SentinelResource不管
总结
@sentinelResource主管配置出错,运行出错该走异常走异常
四、系统规则
先看看官网介绍:gibhub系统自适应限流
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持以下的模式:
- 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 达到阈值即触发系统保护。
原理
先用经典图来镇楼:
我们把系统处理请求的过程想象为一个水管,到来的请求是往这个水管灌水,当系统处理顺畅的时候,请求不需要排队,直接从水管中穿过,这个请求的RT是最短的;反之,当请求堆积的时候,那么处理请求的时间则会变为:排队时间 + 最短处理时间。
- 推论一: 如果我们能够保证水管里的水量,能够让水顺畅的流动,则不会增加排队的请求;也就是说,这个时候的系统负载不会进一步恶化。
我们用 T 来表示(水管内部的水量),用RT来表示请求的处理时间,用P来表示进来的请求数,那么一个请求从进入水管道到从水管出来,这个水管会存在 P * RT 个请求。换一句话来说,当 T ≈ QPS * Avg(RT) 的时候,我们可以认为系统的处理能力和允许进入的请求个数达到了平衡,系统的负载不会进一步恶化。
接下来的问题是,水管的水位是可以达到了一个平衡点,但是这个平衡点只能保证水管的水位不再继续增高,但是还面临一个问题,就是在达到平衡点之前,这个水管里已经堆积了多少水。如果之前水管的水已经在一个量级了,那么这个时候系统允许通过的水量可能只能缓慢通过,RT会大,之前堆积在水管里的水会滞留;反之,如果之前的水管水位偏低,那么又会浪费了系统的处理能力。
- 推论二: 当保持入口的流量是水管出来的流量的最大的值的时候,可以最大利用水管的处理能力。
然而,和 TCP BBR 的不一样的地方在于,还需要用一个系统负载的值(load1)来激发这套机制启动。
注:这种系统自适应算法对于低 load 的请求,它的效果是一个“兜底”的角色。对于不是应用本身造成的 load 高的情况(如其它进程导致的不稳定的情况),效果不明显。
配置
配置
测试