1、 什么是Sentinel
Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错 的综合性解决方案。它以流量为切入点, 从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。
Sentinel 具有以下特征:
-
丰富的应用场景:Sentinel承接了阿里巴巴近 10 年的双十一大促流量的核心场景, 例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
-
完备的实时监控:Sentinel提供了实时的监控功能。通过控制台可以看到接入应用的单台机器秒级数据, 甚至 500 台以下规模的集群的汇总运行情况。
-
广泛的开源生态:Sentinel提供开箱即用的与其它开源框架/库的整合模块, 例如与 Spring Cloud、Dubbo、gRPC 的整合。只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。
-
完善的 SPI 扩展点:Sentinel提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel 分为两个部分:
-
核心库(Java 客户端)不依赖任何框架/库,能够运行于所有Java 运行时环境,同时对Dubbo/Spring Cloud 等框架也有较好的支持。
-
控制台(Dashboard)基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。
2、基本概念
-
资源
资源就是Sentinel要保护的东西
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。
-
规则
规则就是用来定义如何进行保护资源的
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整
3、Sentinel 功能和设计理念
Sentinel的主要功能就是容错,主要体现为下面这三个:
3.1 流量控制(上游)
流量控制在网络传输中是一个常用的概念,它用于调整网络包的数据。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。

Sentinel作为一个调配器,可以根据需要把随机的请求调整成合适的形状。
3.2 熔断降级(下游)
当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败(或者其他处理方式),避免影响到其它的资源而导致级联故障。


5、Sentinel快速开始
在官方文档中,定义的Sentinel进行资源保护的几个步骤:
-
定义资源
-
定义规则
-
检验规则是否生效
5.1 抛出异常的方式定义资源
java
Entry entry = null;
// 务必保证 finally 会被执行
try {
// 资源名可使用任意有业务语义的字符串,注意数目不能太多(超过 1K),超出几千请作为参数传入而不要直接作为资源名
// EntryType 代表流量类型(inbound/outbound),其中系统规则只对 IN 类型的埋点生效
entry = SphU.entry("自定义资源名");
// 被保护的业务逻辑
// do something...
} catch (BlockException ex) {
// 资源访问阻止,被限流或被降级
// 进行相应的处理操作
} catch (Exception ex) {
// 若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(ex, entry);
} finally {
// 务必保证 exit,务必保证每个 entry 与 exit 配对
if (entry != null) {
entry.exit();
}
}
5.2 api的实现
java
<font color="red">on-user项目中</font>
引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.1</version>
</dependency>
代码测试
@Slf4j
@RestController
public class ApiController {
private static final String RESOURCE_NAME = "API-RESOURCE";
@RequestMapping("getInfo")
public String getInfo(){
Entry entry = null;
// 务必保证 finally 会被执行
try {
// 资源名可使用任意有业务语义的字符串,注意数目不能太多(超过 1K),超出几千请作为参数传入而不要直接作为资源名
// EntryType 代表流量类型(inbound/outbound),其中系统规则只对 IN 类型的埋点生效
entry = SphU.entry(RESOURCE_NAME);
// 被保护的业务逻辑
String str = "业务逻辑正常处理";
log.info("====="+str+"=====");
return str;
} catch (BlockException ex) {
// 资源访问阻止,被限流或被降级
// 进行相应的处理操作
log.info("Block...!");
return "业务被限流了!";
} catch (Exception ex) {
// 若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(ex, entry);
} finally {
// 务必保证 exit,务必保证每个 entry 与 exit 配对
if (entry != null) {
entry.exit();
}
}
return null;
}
/**
* 定义流控规则
*/
@PostConstruct
private static void initFlowRules(){
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
//设置受保护的资源
rule.setResource(RESOURCE_NAME);
// 设置流控规则 QPS
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
rule.setCount(1);
rules.add(rule);
// 加载配置好的规则
FlowRuleManager.loadRules(rules);
}
}
缺点:
-
业务侵入性很强,需要在controller中写入非业务代码.
-
配置不灵活 若需要添加新的受保护资源 需要手动添加 init方法来添加流控规则
5.3 注解方式定义资源
@SentinelResource注解实现
在定义了资源点之后,我们可以通过Dashboard来设置限流和降级策略来对资源点进行保护。同时还能 通过@SentinelResource来指定出现异常时的处理策略。
@SentinelResource用于定义资源,并提供可选的异常处理和fallback 配置项。其主要参数如下:
| 属性 | 作用 |
|---|---|
| value | 资源名称 |
| entryType | entry类型,标记流量的方向,取值IN/OUT,默认是OUT |
| blockHandler | 处理BlockException的函数名称,函数要求:<br>1.必须是public<br/>2.返回类型 参数与原方法一致<br/>3.默认需和原方法在同一个类中。若希望使用其他类的函数,可配置blockHandlerClass,并指定blockHandlerClass里面的方法。 |
| blockHandlerClass | 存放blockHandler的类,对应的处理函数必须static修饰。 |
| fallback | 用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求:<br/>1.返回类型与原方法一致<br/>2.参数类型需要和原方法相匹配<br/>3.默认需和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定fallbackClass里面的方法。 |
| fallbackClass | 存放fallback的类。对应的处理函数必须static修饰。 |
| defaultFallback | 用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函数要求:<br/>1.返回类型与原方法一致<br/>2.方法参数列表为空,或者有一个Throwable类型的参数。<br/>3.默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定 fallbackClass 里面的方法。 |
| exceptionsToIgnore | 指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。 |
| exceptionsToTrace | 需要trace的异常 |
-
引入依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-annotation-aspectj</artifactId> <version>1.8.1</version> </dependency> -
切面支持
@Configuration public class SentinelAspectConfiguration { @Bean public SentinelResourceAspect sentinelResourceAspect() { return new SentinelResourceAspect(); } } -
代码编写
<font color="red">注意 这里面必须把以前的@PostConstruct导入的规则给注释掉,否则可能有冲突</font>
java
@Slf4j
@RestController
public class SentinelResourceController {
//http://localhost:8001/testAspect/12
@GetMapping("/testAspect/{id}")
@SentinelResource(value = "testAspect",
fallback = "fallback",fallbackClass = CommonException.class,
blockHandler = "handleException",blockHandlerClass = CommonException.class
)
public Result testAspect(@PathVariable("id") Integer id){
if(Integer.compare(id,Integer.parseInt("0")) == -1){
throw new IllegalArgumentException("参数异常");
}
log.info("处理业务信息");
return Result.ok("测试注解方式限流正常");
}
@PostConstruct
private static void initFlowRules(){
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
//设置受保护的资源
rule.setResource("testAspect");
// 设置流控规则 QPS
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
rule.setCount(1);
rules.add(rule);
// 加载配置好的规则
FlowRuleManager.loadRules(rules);
}
}
@Slf4j
public class CommonException {
public static Result fallback(Integer id,Throwable e){
log.error("出现业务异常");
return Result.error(-1,"===业务异常==");
}
public static Result handleException(Integer id, BlockException e){
log.error("触发限流机制");
return Result.error(-2,"====触发限流机制==");
}
}
这里的规则,需要我们自己编写,并且我们这里
5.4 整合springboot
java
引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
修改配置
spring:
cloud:
sentinel:
transport:
port: 9999 #跟控制台交流的端口,随意指定一个未使用的端口即可
dashboard: localhost:8080 # 指定控制台服务的地址
5.5 引入控制台
Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能。
第1步 下载jar包,解压到文件夹
https://github.com/alibaba/Sentinel/releases
第2步 启动控制台
# 直接使用jar命令启动项目(控制台本身是一个SpringBoot项目)
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
把dashboard自己也当成一个资源加入到了dashboard中来进行监控,如果不想把dashboard自己加入控制台监控可以使用简单启动指令如下:
java -Dserver.port=8080 -jar sentinel-dashboard-1.8.1.jar
第4步: 访问控制台
用户可以通过如下参数进行配置:
-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 分钟;
访问http://localhost:8080/#/login ,默认用户名密码: sentinel/sentinel
<font color="red">Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包,所以要确保客户端有访问量;</font>

补充:了解控制台的使用原理
Sentinel的控制台其实就是一个SpringBoot编写的程序。我们需要将我们的微服务程序注册到控制台上, 即在微服务中指定控制台的地址, 并且还要开启一个跟控制台传递数据的端口, 控制台也可以通过此端口调用微服务中的监控程序获取微服务的各种信息。

6、Sentinel规则(dashboard)
6.1 流控规则
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
| Field | 说明 | 默认值 |
|---|---|---|
| resource | 资源名,即限流规则的作用对象 | |
| count | 限流阈值 | |
| grade | 限流阈值类型(QPS 或并发线程数) | QPS 模式 |
| limitApp | 流控针对的调用来源 | default,代表不区分调用来源 |
| strategy | 调用关系限流策略:直接、链路、关联 | 直接 |
| controlBehavior | 流量控制效果(直接拒绝、Warm Up、匀速排队) | 直接拒绝 |
| clusterMode | 是否集群限流 | 否 |
流量控制 ,其原理是监控应用流量的QPS(每秒查询率) 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
第1步: 点击【簇点链路】,我们就可以看到访问过的接口地址,然后点击对应的【流控】按钮,进入流控规则配置页面。新增流控规则界面如下:

-
**资源名:**唯一名称,默认是请求路径,可自定义。
-
针对来源: Sentinel可以针对调用者进行限流,填写【微服务名】,指定对哪个微服务进行限流 ,默认default(不区分来源,全部限制)。
-
阈值类型/单机阈值:
-
QPS(每秒请求数量): 当调用该接口的QPS达到阈值的时候,进行限流。
-
线程数:当调用该接口的线程数达到阈值的时候,进行限流。
-
-
**是否集群:**暂不需要集群
接下来我们以QPS为例来研究限流规则的配置。
6.1.1 简单配置
QPS
-
代码
java//com.on.controller.SimpleController @RestController public class SimpleController { @GetMapping("/simple/qps") public String qps(){ //模拟一次网络延时 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } return "简单测试qps"; } } -
流控规则

-
快速访问/simple/qps 接口,观察效果。
此时发现,当QPS > 1的时候,服务就不能正常响应,而是返回Blocked by Sentinel (flow limiting)结果。
http://localhost:8001/simple/qps

<font color="red">BlockException异常统一处理</font>
springwebmvc接口资源限流入口在HandlerInterceptor 的实现类AbstractSentinelInterceptor 的preHandle方法中,对异常的处理是BlockExceptionHandler的实现类
sentinel 1.7.1 引入了sentinel-spring-webmvc-adapter.jar
自定义BlockExceptionHandler 的实现类统一处理BlockException
java
package com.on.handle;
@Slf4j
@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
log.info("BlockExceptionHandler BlockException================"+e.getRule());
Result r = null;
if (e instanceof FlowException) {
r = Result.error(100,"接口限流了");
} else if (e instanceof DegradeException) {
r = Result.error(101,"服务降级了");
} else if (e instanceof ParamFlowException) {
r = Result.error(102,"热点参数限流了");
} else if (e instanceof SystemBlockException) {
r = Result.error(103,"触发系统保护规则了");
} else if (e instanceof AuthorityException) {
r = Result.error(104,"授权规则不通过");
}
//返回json数据
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getWriter(), r);
}
}
线程数
并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。
【为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。】
<font color="red">Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。</font>
-
代码
java//com.on.controller.SimpleController#testThread @GetMapping("/simple/thread") public String testThread(){ //模拟一次网络延时 try { Thread.sleep(2*1000); } catch (InterruptedException e) { e.printStackTrace(); } return "简单测试线程"; } -
流控规则

-
快速访问/simple/thread接口,观察效果。

6.1.2 配置流控模式
点击上面设置流控规则的**【编辑】** 按钮,然后在编辑页面点击**【高级选项】**,会看到有流控模式一栏。

sentinel共有三种流控模式,分别是:
-
直接(默认):指定来源对于该资源的访问达到限流条件时,开启限流。
-
关联:当与该资源设置了关联的资源达到限流条件(来源+阈值类型+单机阈值)时,开启限流 [适合做应用让步]
-
链路:当从某个上游资源接口访问过来的流量达到限流条件时,开启限流。
下面呢分别演示三种模式:
直接流控模式
直接流控模式是最简单的模式,当指定的接口达到限流条件时开启限流。上面案例使用的就是直接流控模式。
关联流控模式
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 strategy 为 RuleConstant.STRATEGY_RELATE 同时设置 refResource 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。

-
代码
java@RestController public class FlowControllerModleContoller { @GetMapping("/flowModle/read") public String readRead(Integer orderId){ return "读资源"; } @GetMapping("/flowModle/write") public String relation2(Integer orderId){ return "写资源"; } } -
流控规则

-
jmeter测试


-
查看监控图

链路流控模式
链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。它的功能有点类似于针对 来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度更细。

-
代码
java编写controller //com.on.controller.FlowControllerModleContoller @GetMapping("/linkModle/test1") public String test1(Integer userId){ userService.getUser(); return "成功"; } @GetMapping("/linkModle/test2") public String test2(Integer userId){ userService.getUser(); return "成功"; } 编写service @Service public class UserService { @SentinelResource(value = "getUser") public String getUser(){ return "获取到用户信息"; } } -
流控规则

-
添加配置
我们需要将web-context-unify参数设置为false,将其配置为false既可以根据不同的URL进行链路限流,如果不配置将不会生效

-
jmeter测试


-
访问/linkModle/test1

6.1.3 流控效果
当 QPS 超过某个阈值的时候,则采取措施进行流量控制。流量控制的效果包括以下几种:快速失败(直接拒绝)、Warm Up(预热)、匀速排队(排队等待)。对应 FlowRule 中的 controlBehavior 字段。

快速失败(默认)
<font color = "red">当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException</font>这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
Warm Up
它从开始阈值到最大QPS阈值会有一个缓冲阶段,一开始的阈值是最大QPS阈值的1/3,然后慢慢增长,直到最大阈值。
即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
<font color="red">冷加载因子: codeFactor 默认是3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。 </font>
通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:

场景主要用于启动需要额外开销的场景,例如建立数据库连接等。
- 代码
java
@RestController
public class FlowControlEffectController {
@RequestMapping("/warmup")
public String testWarmUp(){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "测试流控效果===热启动";
}
}
2、流控规则

3、jmeter测试

4、查看监控结果
查看你会发现首先会达到3 (阈值的1/3)然后等3秒后会达到阈值10

排队等待
让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排队等待; 它还会让设置一个超时时间,当请求超过超时间时间还未处理,则会被丢弃。
该方式的作用如下图所示:

这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
<font color="red">注意:匀速排队模式暂时不支持 QPS > 1000 的场景。</font>【因为他的单位毫秒,所以最多是1秒通过一个,也就是1000qps】
-
代码
javacom.on.controller.FlowControlEffectController#testRateLimiter @RequestMapping("/rateLimiter") public String testRateLimiter(){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } return "测试流控效果===匀速排队"; } -
流控规则

-
jmeter测试

-
查看监控图

6.2 降级规则
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
熔断降级规则说明熔断降级规则(DegradeRule)包含下面几个重要的属性:
| Field | 说明 | 默认值 |
|---|---|---|
| resource | 资源名,即规则的作用对象 | |
| grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
| count | 最小请求数,慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 | |
| timeWindow | 熔断时长,单位为 s | |
| minRequestAmount | 最小请求数,熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) | 5 |
| statIntervalMs | 统计时长,(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) | 1000 ms |
| slowRatioThreshold | 比例阈值,慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入) |
降级规则就是设置当满足什么条件的时候,对服务进行降级。Sentinel提供了三个衡量条件:平均响应时间、异常比例、异常数。
6.2.1 慢调用比例
慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
-
代码
java//com.on.controller.DegradationRuleController#slowRequestRatio @RequestMapping("/slowRequestRatio") public String slowRequestRatio(){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } return "熔断降级==慢调用比例"; } -
流控规则

-
jmeter测试

-
查看监控图
查看效果发生流控,每两秒重试一次,我们可以通过查看jemter中查看结果树来判断, 会发现有个重试,每次重试间隔应该是两秒

6.2.2 异常比例
当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
-
代码
java//com.on.controller.DegradationRuleController#errorRatio // 下面这个方式有待研究 private AtomicInteger atomicInteger =new AtomicInteger(1); @RequestMapping("/errorRatio") public String errorRatio(){ atomicInteger.getAndIncrement(); log.info("现在的数值:{}",atomicInteger.get() ); if (atomicInteger.get() % 2 == 0){ //模拟异常和异常比率 int i = 1/0; } return "熔断降级==异常比例"; } -
流控规则

-
jmeter测试

-
查看监控图

6.2.3 异常数
当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
<font color= "red">注意:异常降级仅针对业务异常 ,对 Sentinel 限流降级本身的异常(BlockException)不生效。</font>
-
代码
java//com.on.controller.DegradationRuleController#errorCount @RequestMapping("/errorCount") public String errorCount(){ atomicInteger.getAndIncrement(); if (atomicInteger.get() % 2 == 0){ //模拟异常和异常比率 int i = 1/0; } return "熔断降级==异常数量"; } -
流控规则

-
jmeter测试

-
查看监控图

6.3 热点规则
热点参数流控规则是一种更细粒度的流控规则, 它允许将规则具体到参数上。
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
-
商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
-
用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
<font color="red">热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。</font>

-
代码
java//http://localhost:8001/hotspot/123 @RestController public class HotSpotRuleController { @RequestMapping("/hotspot/{id}") @SentinelResource(value = "hotspot", blockHandlerClass = CommonException.class, blockHandler = "handleException", fallbackClass = CommonException.class, fallback = "fallback" ) public Result info(@PathVariable("id") Integer id){ return Result.ok("成功获取数据"); } }注意:
-
热点规则需要使用@SentinelResource("resourceName")注解,否则不生效
-
参数必须是7种基本数据类型才会生效
-
-
流控规则
注意: 资源名必须是@SentinelResource(value="资源名")中 配置的资源名,热点规则依赖于注解

这里有个坑,经过上面的配置只是对接口的限流,如果具体到参数需要再热点规则中重新设置参数
具体到参数值限流,配置参数值为1,限流阈值为2

-
jmeter测试

-
查看监控图

6.4 授权规则
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过:
-
若配置白名单,则只有请求来源位于白名单内时才可通过;
-
若配置黑名单,则请求来源位于黑名单时不通过,其余的请求通过。
例如:活动和订单都会调用用户系统获取用户信息,我们可以将活动设置为黑名单


上面的【资源名】和【授权类型】不难理解,但是【流控应用】怎么填写呢?
其实这个位置要填写的是来源标识,Sentinel提供了RequestOriginParser 接口来处理来源。
只要Sentinel保护的接口资源被访问,Sentinel就会调用RequestOriginParser 的实现类去解析访问来源。
第1步: 自定义来源处理规则
java
//http://localhost:8001/grant
@RestController
public class GrantController {
@RequestMapping("/grant")
public Result grant(){
return Result.ok("授权规则测试成功");
}
}
@Component
public class RequestOriginParserDefinition implements RequestOriginParser{
@Override
public String parseOrigin(HttpServletRequest request) {
String serviceName = request.getParameter("serviceName");
return serviceName;
}
}
第2步: 授权规则配置
这个配置的意思是只有serviceName=order不能访问(黑名单)

第3步: 访问 http://localhost:8001/grant?serviceName=order观察结果
6.4.1在feign中将参数设置进去
-
on-order项目中光阴如引入feign的代码可以参看Feign的文档
javapublic class FeignAuthRequestInterceptor implements RequestInterceptor { private String serviceName; public FeignAuthRequestInterceptor(String serviceName) { this.serviceName = serviceName; } @Override public void apply(RequestTemplate template) { template.header("serviceName",serviceName); } } @Configuration public class FeignConfig { @Value("${spring.application.name}") private String serviceName; /** * 自定义拦截器 * @return */ @Bean public FeignAuthRequestInterceptor feignAuthRequestInterceptor(){ return new FeignAuthRequestInterceptor(serviceName); } } -
在on-stock项目中增加配置
java
@Component
public class RequestOriginParserDefinition implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
String serviceName = request.getHeader("serviceName");
return serviceName;
}
}
可以对异常进行包装
java
package com.on.order.handle;
@Slf4j
@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
log.info("BlockExceptionHandler BlockException================"+e.getRule());
Result r = null;
if (e instanceof FlowException) {
r = Result.error(100,"接口限流了");
} else if (e instanceof DegradeException) {
r = Result.error(101,"服务降级了");
} else if (e instanceof ParamFlowException) {
r = Result.error(102,"热点参数限流了");
} else if (e instanceof SystemBlockException) {
r = Result.error(103,"触发系统保护规则了");
} else if (e instanceof AuthorityException) {
r = Result.error(104,"授权规则不通过");
}
//返回json数据
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getWriter(), r);
}
}
6.5 系统规则
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 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 达到阈值即触发系统保护。
这些不是很好测试,我们可以此时一下QPS
-
代码
@RestController public class SystemRuleController { @RequestMapping("/systemRule") public Result systemRule(){ return Result.ok("测试系统规则QPS"); } } -
流控规则

-
jmeter测试

-
查看监控图
大于阈值之后进行限流

6.6 集群规则
https://github.com/alibaba/Sentinel/wiki/%E9%9B%86%E7%BE%A4%E6%B5%81%E6%8E%A7
为什么要使用集群流控呢?假设我们希望给某个用户限制调用某个 API 的总 QPS 为 50,但机器数可能很多(比如有 100 台)。这时候我们很自然地就想到,找一个 server 来专门来统计总的调用量,其它的实例都与这台 server 通信来判断是否可以调用。这就是最基础的集群流控的方式。
另外集群流控还可以解决流量不均匀导致总体限流效果不佳的问题。假设集群中有 10 台机器,我们给每台机器设置单机限流阈值为 10 QPS,理想情况下整个集群的限流阈值就为 100 QPS。不过实际情况下流量到每台机器可能会不均匀,会导致总量没有到的情况下某些机器就开始限流。因此仅靠单机维度去限制的话会无法精确地限制总体流量。而集群流控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果
部署方式
-
独立模式(Alone),即作为独立的 token server 进程启动,独立部署,隔离性好,但是需要额外的部署操作。独立模式适合作为 Global Rate Limiter 给集群提供流控服务。

-
嵌入模式(Embedded),即作为内置的 token server 与服务在同一进程中启动。在此模式下,集群中各个实例都是对等的,token server 和 client 可以随时进行转变,因此无需单独部署,灵活性比较好。但是隔离性不佳,需要限制 token server 的总 QPS,防止影响应用本身。嵌入模式适合某个应用集群内部的流控。

4.8 Sentinel规则持久化
| 推送模式 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| 原始模式 | API将规则推送至客户端并直接更新到内存中,扩展写数据源 | 简单、无任何依赖 | 不保证一致性;规则保存在内存中,重启不建议用于生产环境 |
| PULL模式 | 客户端主动向某个规则管理中心定期轮询拉取规则,<br />这个规则中心可以是 RDBMS.文件等 | 简单、无任何依赖;规则持久化 | 不保证一致性;实时性不保证,拉取过于频繁,也可能会有性能问题 |
| PUSH模式 | 规则中心统一推送,客户端通过注册监听<br />方式有更好的实时性和一致性保证。生产Nacos<br />、Zookeeper等配置中心。这种方式有更好的实时性和一致性保证。<br />生产环境下一般采用push模式的数据源。 | 规则持久化;一致性;快速 | 引入第三方依赖 |
4.8.1 原始模式
如果不做任何修改,Dashboard 的推送规则方式是通过 API 将规则推送至客户端并直接更新到内存中:

4.8.2Pull模式
pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的 WritableDataSourceRegistry 中。以本地文件数据源为例:
<font color="red">本地文件数据源会定时轮询文件的变更,读取规则。</font>这样我们既可以在应用本地直接修改文件来更新规则,也可以通过 Sentinel 控制台推送规则。以本地文件数据源为例,推送过程如下图所示:

大家可以发现整个过程比较繁重,并且每次都需要更新本地文件,他的性能有一定的影响
4.8.3 Push模式
生产环境下一般更常用的是 push 模式的数据源。对于 push 模式的数据源,如远程配置中心(ZooKeeper, Nacos, Apollo等等),我们在sentinel Dashboard配置的规则会同步到Nacos中, Sentinel Dashboard也会从Nacos中获取规则,Nacos会讲规则推送给Sentinel客户端

-
复制测试代码
在sentinel-dashboard中的测试包中会有对阿波罗,nacos和zk的支持

对nacos进行复制,copy到rule规则下

我们会发现对应的类会报红,这是应为对应的sentinel包,没有引入进去

我们进入对应的pom会发现里面有不对应的sentinel包,只是scope设置的是test,我们需要去掉范围就可以

-
修改代码改造我们nacos注册地址
改造对应的NacosConfig文件

-
更改注入的类



这里就是相当于,他对规则的所有操作就是走我们nacos的操作。
-
改造页面新增菜单
app\scripts\directives\sidebar\sidebar.html

-
启动dashboard
如果启动后没有发现"流控规则 nacos" ,那我们可以使用无痕模式

此时我们增加一个流控规则

此时我们查看nacos就会发现里面已经同步了我们增加的流控规则,此时我们可以在nacos修改规则,也可以在sentinel修改规则

-
更改客户端
java增加依赖 dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> 添加配置 spring: cloud: sentinel: datasource: # 名称随意 flow: nacos: server-addr: localhost:8848 dataId: ${spring.application.name}-flow-rules groupId: SENTINEL_GROUP # 规则类型,取值见: # org.springframework.cloud.alibaba.sentinel.datasource.RuleType rule-type: flow degrade: nacos: server-addr: localhost:8848 dataId: ${spring.application.name}-degrade-rules groupId: SENTINEL_GROUP rule-type: degrade system: nacos: server-addr: localhost:8848 dataId: ${spring.application.name}-system-rules groupId: SENTINEL_GROUP rule-type: system authority: nacos: server-addr: localhost:8848 dataId: ${spring.application.name}-authority-rules groupId: SENTINEL_GROUP rule-type: authority param-flow: nacos: server-addr: localhost:8848 dataId: ${spring.application.name}-param-flow-rules groupId: SENTINEL_GROUP rule-type: param-flow
4.9 RestTemplate整合Sentinel
-
引入restTemplate
java@EnableDiscoveryClient @SpringBootApplication @EnableFeignClients public class OrderApplication { @Bean @LoadBalanced @SentinelRestTemplate public RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(OrderApplication.class); } } package com.on.controller; import com.on.util.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class RestTemplateController { @Autowired private RestTemplate restTemplate; @RequestMapping("/testRestTemplate/{userId}") public String getUser(@PathVariable Integer userId){ String result = restTemplate.getForObject("http://on-user:10001/user/" + userId, String.class); return result; } } -
流控规则

-
测试
http://localhost:8001/testRestTemplate/222

-
异常丑陋的更改
重写这些方法
java@EnableDiscoveryClient @SpringBootApplication @EnableFeignClients public class OrderApplication { @Bean @LoadBalanced @SentinelRestTemplate(blockHandler = "handleException",blockHandlerClass= GlobalException.class ,fallback = "fallback",fallbackClass = GlobalException.class) public RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(OrderApplication.class); }
}
java
```java
package com.on.exception;
import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpRequest;
import com.on.util.Result;
import org.springframework.http.client.ClientHttpRequestExecution;
public class GlobalException {
public static SentinelClientHttpResponse handleException(HttpRequest request,
byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
Result r = Result.error(-1, "===被限流啦===");
try {
return new SentinelClientHttpResponse(new ObjectMapper().writeValueAsString(r));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
public static SentinelClientHttpResponse fallback(HttpRequest request,
byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
Result r = Result.error(-2, "===被异常降级啦===");
try {
return new SentinelClientHttpResponse(new ObjectMapper().writeValueAsString(r));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
-
源码分析
我们想我们ribbon是怎样restTemplate ,以前是不是通过类LoadBalancerInterceptor 来进行整合的,那我们的sentinel是不是也可是通过拦截器来进行整合的

在这里进行拦截的。

4.10 Feign整合Sentinel
-
4.9 Feign整合Sentinel
-
引入sentinel的依赖
<!--sentinel客户端--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!-- openfeign 远程调用 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> -
在配置文件中开启Feign对Sentinel的支持
feign: sentinel: enabled: true -
创建限流处理类
java@Component public class FallbackOrderFeignServiceFactory implements FallbackFactory<UserFeignService> { @Override public UserFeignService create(Throwable throwable) { return new UserFeignService() { @Override public String getUserName(Integer userId) { if (throwable instanceof FlowException) { return Result.error(100,"接口限流了"); } return Result.error(-1,"=======服务降级了========"); } }; } } -
为被容错的接口指定容错类
java//value用于指定调用nacos下哪个微服务 //fallbackFactory指定限流处理类 @FeignClient(value = "on-user",fallbackFactory = FallbackOrderFeignServiceFactory.class) public interface UserFeignService { @GetMapping("/user/{userId}") Result getUserName(@PathVariable Integer userId); } -
修改controller
java@RestController public class FeignOrderController { @Autowired private UserFeignService userFeignService; @RequestMapping("/getOrders/{userId}") public Result getOrders(@PathVariable("userId") Integer userId){ Result resultUserInfo = userFeignService.getUserName(userId); /** * 处理其它业务 */ return resultUserInfo; } } -
修改UserController
java@RestController public class UserController { @GetMapping("/user/{userId}") public Result getUserName(@PathVariable Integer userId){ return Result.ok("善缘老师"); } } -
修改流控规则

-
进行调用
-
流控效果
-

-