Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址:sentinelguard.io/zh-cn/
Sentinel的特征
- 丰富的应用场景。控制突发流量在可控制的范围内,消息削峰填谷,集群流量控制,实时熔断下游不可用的应用等等。
- 完备的实时监控。Sentinel 提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态。Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- 完善的 SPI 扩展点。Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
下载Sentinel的控制台
Sentinel官方提供了UI控制台,方便我们对系统做限流设置。
可以到github的官网去下载: github.com/alibaba/Sen…
下载下来就是一个jar包
- 将其拷贝到一个你能记住的非中文目录,然后运行命令
java -jar 你的jar包名称.jar
- 然后访问:localhost:8080 即可看到控制台页面,默认的账户和密码都是 sentinel
这个时候控制台还没有任何信息,因为还没有和我们的服务进行整合
如果修改Sentinel的默认端口、账户、密码,可以通过以下配置:
| 配置项 | 默认值 | 说明 |
|---|---|---|
| server.port | 8080 | 服务端口 |
| sentinel.dashboard.auth.username | sentinel | 默认用户名 |
| sentinel.dashboard.auth.password | sentinel | 默认密码 |
在原来的命令后面再加上 -D和配配置项的名称和值即可
java -Dserver.port=8090 -jar 你的jar包名称.jar
微服务整合Sentinel
在我们的微服务中整合Sentinel,并且连接Sentinel的控制台,步骤如下:
- 引入sentinel依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2023.0.1.0</version>
</dependency>
- 配置控制台地址:
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
- 重启微服务,访问微服务的任意端点,触发sentinel监控
先访问以下任意接口,再来Sentinel的控制台刷新以下,就可以看到具体的一些信息了
限流规则
在说限流规则之前,我们先来说一下什么是簇点链路
簇点链路
簇点链路: 就是项目内的调用链路,链路中被监控的每个接口就是一个资源。默认情况下 sentinel 会监控 SpringMVC 的每一个端点(Endpoint),因此 SpringMVC 的每一个端点(Endpoint)就是调用链路中的一个资源。
端点可以理解成 Controller 中的方法
在我们的项目中,请求进来会调用 Controller 的方法,Controller 又调用 Service,Service 又调用用 Mapper,这样就形成了一个调用链路。
流控、熔断都是针对簇点链路中的资源来设置的,因此我们可以点击对应资源后面的按钮来设置规则
流控规则入门案例
要求:给 /order/hello 这个资源设置流控规则,QPS 不能超过 200.然后利用 jemeter 测试。
点击流控按钮就会有这个设置流控规则的窗口,我这里设置了 QPS 的单机阈值为 200,然后点击新增即可
这里可以看到,被拒绝的 QPS 数量
限流的高级选项
点击流控规则窗口中的高级选项,会多出来两个配置:流控模式、流控效果
-
流控模式
- 直接: 统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式(A触发阈值,对A做限流)
- 关联: 统计当前资源相关的另一个资源,触发阈值时,对当前资源限流(A触发阈值,对B做限流)
- 链路: 统计从指点链路访问到本资源的请求,触发阈值时,对指定链路限流(A触发阈值,对请求来源做限流)
使用说明
关联模式:
当 /order/test 资源访问量触发阈值时,就会对 /order/hello 资源限流,避免影响到 /order/test
当两个资源有竞争关系,一个优先级高,一个优先级低,可以选择关联模式
链路模式:
只针对从指定链路访问到本资源的请求统计,判断是否超过阈值。
如果只希望统计 /test2 进入到当前资源的请求,可以这样配置
这里介绍一个注解@SentinelResource("hello") 这个注解可以标记一个资源,括号中的 hello 就是这个资源的名字,因为 Sentinel 默认加载的资源是Contoller中的方法,所以当有 Service 获取其他的方法需要做限流是,可以使用这个注解。
@SentinelResource("hello")
public String sayHello(String name) {
System.out.println("hello " + name);
return "调用了sayHello方法";
}
Sentinel默认会将Controller中的方法做context整合,导致链路模式的流控失效,需要修改application.yml,添加配置:
spring:
sentinel:
web-context-unify: false
- 流控效果
流控效果是指请求达到了流控阈值时应该采取的措施,包括三种:
- 快速失败: 达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。收默认处理方式。
- warm up: 预热模式,对超出阈值的请求同样是拒绝并抛出异常。但是这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
warm up模式也叫做预热模式,是应对冷启动的一种方案。请求阈值的初始值是 threshold/coldFactor,持续指定时长后逐渐提高到threshold(最大阈值)值。而coldFactory的默认值是3.
例如:我设置的 QPS 的 threshold 为 10 ,预热时间为 5 秒,那么初始阈值就是 10/3,也就是 3 ,然后在 5 秒后逐渐增长到 10
- 排队等待: 让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长。
当请求超过 QPS 阈值时,快速失败和 warm up 会拒绝新的请求并抛出异常。而排队等待则是让所有请求进入一个队列中,然后按照阈值允许的时间间隔一次执行。后来的请求必须等待前面执行完成,如果请求预期等待的时间超出最大时长,则会被拒绝。
热点参数限流
之前的限流是统计访问某个资源的所有请求,判断是否超过 QPS 阈值。而热点参数限流是分别统计参数值相同的请求,判断是否超过 QPS 阈值。
比如说现在有多个请求来访问 /goods/{id} 资源,id会有不同的值 ,QPS 统计的时候就会分开统计了,
| 参数值 | QPS |
|---|---|
| id = 1 | 3 |
| id = 2 | 1 |
配置示例:
在热点参数限流的高级选项中,可以对部分参数设置例外的配置:
注意: 热点参数资源对默认的 SpringMVC 资源无效需要加
@SentinelResource("hello")注解
隔离和降级
虽然限流可以尽量避免因高并发引起的服务故障,但服务还会因为其他的原因而故障。而要将这些故障控制在一定范围,避免雪崩,就需要靠线程隔离(舱壁模式)和熔断降级手段了。
不管是线程隔离还是熔断降级,都是对**客户端(调用方)**的保护。
- 线程隔离
线程隔离有两种方式实现:
- 线程池隔离
加入现在有个服务A,它依赖于服务C和服务D,现在来了一个请求,这个请求需要分别调用服务C和服务D,那么就会分别给服务C和服务D创建线程池,发起调用的时候,分别从这两个线程池中取线程发起调用,这样两个服务就隔离开了。把故障隔离在一个范围内。
- 信号量隔离(默认)
通过控制对共享资源的访问数量,限制了同时访问该资源的线程数量。不同的线程可以共享同一个资源,但是同时访问该资源的线程数量受到信号量的限制。
并发线程数:就是该资源能使用的线程数的最大值。也就是通过限制线程数数量,实现舱壁模式。超出的线程会被拒绝。
- 熔断降级
熔断降级是解决雪崩问题的重要手段。其思路是有路由器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;当服务恢复时,断路器会放行该服务的请求。
断路器的工作流程如下所示:
这里面有两个比较关键的属性需要我们来配置:熔断时间和失败阈值(熔断策略)
熔断策略
断路器的熔断策略有三种:慢调用、异常比例或异常数
- 慢调用: 业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。例如:
解读:
RT 超过500ms的调用是慢调用,统计最近10000ms内的请求,如果请求超过10次,并且慢调用比例不低于0.5,则触发熔断,熔断时长为5秒。然后进入half-open状态,放行一次请求做测试。
- 异常比例或异常数: 统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定的异常数),则触发熔断。
解读: 统计最近 1000ms 内的请求,如果请求量超过10次,并且异常比例不低于0.5,则触发熔断,熔断时长为5秒。然后进入half-open状态。放行一次请求做测试。
统计最近 1000ms 内的请求,如果请求量超过10次,并且异常数不低于2,则触发熔断,熔断时长为5秒。然后进入half-open状态。放行一次请求做测试。
授权规则
授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。
- 白名单: 来源在白名单内的调用者允许访问
- 黑名单: 来源在黑名单内的调用者不允许访问
是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的:
这个方法的作用就是从request对象中,获取请求者的origin值并返回。 默认情况下,sentinel不管请求者从哪里来,返回值永远是default,也就是说一切请求的来源都被认为是一样的值default。 因此,我们需要自定义这个接口的实现,让不同的请求,返回不同的origin。
@Component
// 可以自己定义规则,不过这个服务的调用者也需要遵守相应的规则
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
// 1.获取请求头
String origin = request.getHeader("origin");
// 2.非空判断
if (StringUtils.isEmpty(origin)) {
origin = "blank";
}
return origin;
}
}
自定义异常
默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。如果要自定义异常时的返回结果,需要实现 BlockExceptionHandler接口
BlockException 子类
@Component
// 可以写自己的一个异常处理的逻辑,根据不同的情况抛出不同的异常
public class BlockExceptionHandlerImpl implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws Exception {
String msg = "未知异常";
int status = 429;
if (e instanceof FlowException) {
msg = "请求被限流了";
} else if (e instanceof ParamFlowException) {
msg = "请求被热点参数限流";
} else if (e instanceof DegradeException) {
msg = "请求被降级了";
} else if (e instanceof AuthorityException) {
msg = "没有权限访问";
status = 401;
}
response.setContentType("application/json;charset=utf-8");
response.setStatus(status);
response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
}
}