Sentinel介绍
官网
Sentinel是阿里巴巴开源的微服务流量控制组件,更多详情见官网文档,其具有以下特性:
- **丰富的应用场景:**Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- **完备的实时监控:**Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- **广泛的开源生态:**Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- **完善的SPI扩展点:**Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
安装控制台
Sentinel官方提供了UI控制台,方便我们对系统做限流设置。GitHub下载jar包,直接运行jar包,即可访问到默认端口8080的控制台如下,默认账户密码都是sentinel。
当然,我们也可以自定义端口、用户名和密码,配置如下:
配置项 | 默认值 | 备注 |
---|---|---|
server.port | 8080 | 服务端口 |
senteinel.deshboard.auth.username | sentinel | 用户名 |
sentinel.dashboard.auth.password | sentinel | 密码 |
服务整合Sentinel
在我们的服务中,引入Sentinel依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${spring.cloud.alibaba}</version>
</dependency>
并配置相关配置文件信息:
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
发起任意请求,即可触发Sentinel监控
簇点链路:项目内的调用链路,当请求进入微服务时,首先访问DispatcherServlet,然后依次进入Controller、Service等等,这就是一个簇点链路。
资源:链路中被监控的每个接口都是一个资源,默认情况下sentinel会监控SpringMVC的每一个端点(Endpoint:如SpringMVC中的controller里的方法),因此SpringMVC的每一个端点(Endpoint)就是调用链路中的一个资源。
流控、熔断、热点、授权等都是针对簇点链路中的资源来设置的,因此我们可以点击对应资源后面的按钮来设置规则:
流控:
点击流控按钮,就可以弹出添加流控规则的表单,如图:
我们点击高级选项可以看到
流控模式有三种可供选择,分别是:
- 直接(默认):统计当前资源的请求,触发阈值时对当前资源直接限流;
- 关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源进行限流;
当满足以下两个条件时,可以使用关联模式:
两个有竞争关系的资源
一个优先级较高,一个优先级较低- 链路:当指定链路访问到某资源的请求触发阈值时,对指定链路限流;
假设有两条链路分别:
. /query -> /service
. /update -> /service
- 如果只希望限制从/query进入/service的请求,则可以配置链路流控模式,将资源名设置为/service,入口设置为/query即可。
- sentinel默认只标记controller中的方法为资源,如果要标记其它方法,需利用@SentinelResource注解.
- 链路模式中,是对不同来源的两个链路做监控,但是Sentinel会默认将所有Controller方法做context整合(即设置同源),这样就导致链路模式的流控失效。我们需要手动将这种整合策略给关闭,如下:
spring:
cloud:
sentinel:
web-context-unify: false #关闭context整合
流控效果也有三种可供选择,分别是:
- 快速失败(默认:时间窗口算法):达到阈值时,新的请求会立即被拒绝且抛出FlowException异常。
- Warm Up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但此模式阈值会动态变化,从一个较小值逐渐增大到最大阈值。
预热模式是应对服务冷启动的一种方案。请求阈值初始值是threshold / coldFactor,持续指定时长后,逐渐提高到threshold。而coldFactor的默认值是3。- 排队等候(漏桶算法):让所有的请求按照先后次序进行排队执行,两个请求的间隔不能小于指定时常。同时给请求设置一个超时时间,当等待超过超时时间则同样抛出异常拒绝,起到流量整形的效果。
热点参数限流
之前的先六十统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流(令牌桶)是分别统计参数值相同的请求,判断是否超过QPS阈值。如图:
我们对/message资源进行限流,规则如下:
- 对当前资源的0号参数(即第一个参数)做统计,每秒相同参数值的请求不超过5。
- 给参数为1的请求设置QPS为8
- 给参数为2的请求设置QPS为10
注意:热点参数限流对默认的SpringMVC资源无效,需要给对应资源添加下面注解:
@SentinelResource("message")
小结:限流是对服务故障的一种预防措施。通过限流可以降低服务的负载,从而避免服务因过高负载而出现故障,避免因故障导致级联失败。
但,如果服务已经出现故障,就很容易把故障传递给依赖此服务的其他服务,从而导致雪崩。固我们需要增加线城隔离、降级熔断等手段避免因级联失败导致的雪崩问题出现。
隔离和降级
- 线程隔离:在调用服务提供者时,给每个调用的请求分配独立的线程池,出现故障时,最多消耗这个线程池内的资源,避免把调用者的所有资源都耗尽。
- 熔断降级:在调用方加入断路器,统计对服务提供者的调用失败比例,当达到阈值时,则熔断该业务,不允许访问该服务提供方。
- 两者都是对调用方的保护。
1.Feign整合Sentinel
SpringCloud中,微服务调用都是通过Feign来实现的,因此基于Feign和Sentinel做客户端保护。
- 修改application.yml配置文件,开启Feign的Sentinel功能
feign:
sentinel:
enabled: true # 开启Feign的Sentinel功能
- 给FeignClient编写失败后的降级逻辑并注册为Bean
- 方式一:FallbackClass,无法对远程调用的异常做处理
- 方式二:FallbackFactory,可以对远程调用的异常做处理
- 将FeignClient配置fallbackFactory
2. 线程隔离
线程隔离有两种方式:
- 线程池隔离:给每个服务调用者分配一个线程池,利用线程池本身实现隔离效果;
优点:支持主动超时、异步调用;
缺点:线程的额外开销大;
适用场景:低扇出
- 信号量隔离:用计数器模式,记录业务使用的线程数量,当达到上限时,禁止新的请求。
优点:轻量级、无额外开销;
缺点:不支持主动超时和异步调用;
适用场景:高频调用(高扇出)
线程隔离(舱壁模式):
在添加限流规则时,可以选择两种与值类型:
- QPS:每秒请求数
- 线程数:该资源能使用的tomcat线程数的最大值。
3.熔断降级
熔断降级时解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务(拦截一切请求)。当服务恢复时,断路器会放行访问该服务的请求。
断路器熔断策略有三种:慢调用、异常比例、异常数
- 慢调用:业务的响应时长(RT)大于指定时常的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。如:
RT超过1s则认为是慢调用,统计5s内请求数,如果请求量超过5次,且慢调用比例大于等于0.7,则触发熔断,熔断时间为3s。
- 异常比例、异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。如:
统计最近5s内的请求,如果请求量超过5次,且异常比例不低于0.7,则触发熔断,熔断时长为5s。
授权规则
授权规则可以对调用方的来源做控制,有黑白名单两种方式:
- 白名单:来源在白名单内的调用者允许访问
- 黑名单:来源在黑名单内的调用者不允许访问
Sentinel通过RequestOriginParser接口的parseOrigin来获取请求来源。默认情况下获取的来源名称都是default,所以我们要自己实现此接口来编写自定义业务逻辑区分来源达到授权目的。如:
@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;
}
}
修改gateway服务中的配置文件,添加defaultFilter:
spring:
cloud:
gateway:
default-filters:
- AddRequestHeader=origin,gateway
这样,从gateway路由的所有请求都会带上origin头,值为gateway。而从其它地方到达微服务的请求则没有这个头。
这样,从网关过来的所有请求都会带origin头信息,而其他请求则没有。
自定义异常结果
默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。如果要自定义异常的返回结果,需要实现BlockExceptionHandler接口:
public interface BlockExceptionHandler {
/**
* 处理请求被限流、降级、授权拦截时抛出的异常:BlockException
*/
void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception;
}
BlockException包含很多个子类,分别对应不同的场景:
异常 | 说明 |
---|---|
FlowException | 限流异常 |
ParamFlowException | 热点参数限流的异常 |
DegradeException | 降级异常 |
AuthorityException | 授权规则异常 |
SystemBlockException | 系统规则异常 |
代码示例: |
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, 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 + "}");
}
}
Sentinel规则持久化
Sentinel的所有规则都是内存存储,重启后所有规则都会丢失。在生产环境下,我们必须确保这些规则的持久化,避免丢失。
规则是否能持久化,取决于规则管理模式,Sentinel支持三种规则管理模式:
- 原始模式:Sentinel的默认模式,将规则保存在内存,重启服务会丢失。
- pull模式:pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则。
- push模式:push模式:控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新。
push模式实现最为复杂,依赖于nacos,并且需要改在Sentinel控制台。整体步骤如下:
- 引入依赖,使其监听Nacos配置中心
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 配置nacos地址
spring:
cloud:
sentinel:
datasource:
flow:
nacos:
server-addr: localhost:8848
dataId: flow-rules
groupId: SENTINEL_GROUP
rule-type: flow #degrade、outhority、oaran-flow等
degrade:
nacos:
server-addr: localhost:8848
dataId: degrade-rules
groupId: SENTINEL_GROUP
rule-type: degrade #degrade、outhority、oaran-flow等
- 修改Sentinel-dashboard源码,配置nacos数据源
在sentinel-dashboard源码的pom文件中,nacos的依赖默认的scope是test(仅测试使用)
- 添加nacos的支持
在sentinel-dashboard的test包下,已经编写了对nacos的支持,我们需要将其拷贝到main下。
- 修改nacos地址
修改测试代码中的NacosConfig类,修改其中的nacos地址为读取application.properties中的配置。
@Configuration
@ConfigurationProperties(prefix = "nacos")
public class NacosConfig {
// nacos地址
private String addr;
@Bean
public ConfigService nacosConfigService() throws Exception {
return ConfigFactory.createConfigService(addr);
}
public String getAddr(){
return addr;
}
public void setAddr(String addr){
this.addr = addr;
}
@Bean
public Convert<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
}
@Bean
public Convert<String, List<FlowRuleEntity>> flowRuleEntityEncoder(){
return s -> JSON.parseArray(s, FlowRuleEntity.class);
}
}
在sentinel-dashboard的配置文件中添加nacos地址配置:
nacos.addr=localhost:8848
- 配置nacos数据源
修改类FlowControllerV2,让数据源生效
将@Qualifier(“flowRuleDefaultProvider”)改为@Qualifier("flowRuleNacosProvider”)
将@Qualifier(“flowRuleDefaultPublisher”)改为@Qualifier(“flowRuleNacosPublisher”)
- 修改前端页面
添加支持nacos的菜单,目录地址为:src/main/webapp/resources/app/scripts/directives/sidebar/sidebar.html文件
将以下注释改为
- 重新编译、打包-dashboard源码(去掉单元测试打包)