文章目录
- 一、Sentinel是什么?
-
- 1.1 Sentinel 具有 以下特性
- 二、Sentinel 使用
-
- 2.1 Sentinel 安装。
- 2.2 创建 `sgg-alibaba-sentinel12001` module
- 2.3 自定义异常
- 2.4 熔断降级
- 2.5 热点参数限流
- 2.6 系统自适应限流
- 2.7 黑白名单控制
- 2.8 注解支持
- 2.9 规则管理以及推送
-
- 1.1 使用 Nacos 作为数据源
一、Sentinel是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制,熔断降级,系统负载保护等多个维度维护服务的稳定性
1.1 Sentinel 具有 以下特性
- 丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一促流量的核心场景,比如秒杀,消息削峰填谷,集群流量控设置,实时熔断下游不可用应用等。
- 完备的实时监控: Sentinel 同时提供了实时的监控服务,您可以在控制台中看到接入应用的单台机器秒级数据,甚至500台以下规模的集群汇总运行情况
- 广泛的开源生态: Sentinel 提供了开箱即用的与其他开源框架的整合模块,例如与 SpringCloud,Dubbo,gRPC 的整合
- 完善的 SPI 扩展点: Sentinel 提供简单易用,完善的SPI 扩展接口,可以通过实现扩展接口来快速指定逻辑,例如定制管理规则,适配动态数据源等
以上来自官方文档
二、Sentinel 使用
2.1 Sentinel 安装。
直接github 下载
Sentinel 下载地址
我下载的 1.7.0 版本的。 下载下来直接解压。是一个 jar 包。
通过 cmd 直接运行。
java -jar sentinel-dashboard-1.7.0.jar
Sentinel 默认运行在 8080 端口上。
直接访问 http://localhost:8080 通过sentinel / sentinel
登录
因为 Sentinel 控制台是基于 Spring Boot 实现,所以我们可以通过启动时的命令行参数,自定义配置。
- –server.port:自定义服务器端口。默认为 8080 端口。
- –auth.username 和 --auth.password:自定义账号和密码。默认为「sentinel / sentinel」。
- –logging.file:自定义日志文件。默认为 ${user.home}/logs/csp/sentinel-dashboard.log。
2.2 创建 sgg-alibaba-sentinel12001
module
引入依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Sentinel 依赖 -->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
创建配置文件 , 因为我们依旧使用nacos 作为注册中心。 但是为了方便。我把配置文件直接放在 yml 文件中
server:port: 12001
spring:application:name: sgg-alibaba-sentinel-servicecloud:nacos:discovery:server-addr: 127.0.0.1:8848service: ${
spring.application.name}config:group: DEFAULT_GROUPnamespace:file-extension: yamlname: ${
spring.application.name}sentinel:transport:dashboard: 127.0.0.1:8080 # 配置 Sentinel DashBoard 的地址port: 8719 # 默认为 8719 端口。如果被占用 则自动回复从 8719 开启扫描。 一直到未找到没有被占用的端口enable: true # 是否开启 默认为true 开启eager: true # 是否懒加载。 意思就是如果没有发生过访问。 我们的 Sentinel 控制台中是没有记录的。filter:url-patterns: /** # 拦截请求的地址 默认为 /*
management:endpoints:web:exposure:include: "*"
我只是吧配置贴了出来。 希望还是正常吧配置放入到 nacos 中,练练手
enable
:配置是否开启Sentinel , 默认为 true 开启。 、
eageer
: 配置是否懒加载。 默认为true 开启。 默认情况下。Sentinel 是延迟初始化, 在首次使用 Sentinel 才进行初始化, 通过设置为 true 时。在项目启动时就会将 Sentinel 直接初始化,完成 Sentinel 控制台进行注册。
transport.dashboard
: 配置 Sentinel 控制台地址
filter.url-patterns
: 配置 拦截请求的地址 默认为 /**
在 Sentinel 的子项目中, sentinel-spring-webmvc-adapter
中, 对SpringMVC 进行适配。 通过 SentinelWebInterceptor 拦截器, 实现对 SpringMVC的拦截请求。 使用 Sentinel 进行保护, 通过 filter.url-patterns
配置项。 可以定义拦截器的拦截请求地址
创建我们的controller 和 启动类
SentinelController.java
和 AliababSentinel12001App.java
@RestController
@Slf4j
@RequestMapping(value = "sentinel")
public class SentinelController {
@GetMapping(value = "/testA")public String testA() {
return "testA";}@GetMapping(value = "/testB")public String testB() {
Thread.sleep(800);return "testB";}
}
@SpringBootApplication
@EnableDiscoveryClient
public class AliababSentinel12001App {
public static void main(String[] args) {
SpringApplication.run(AliababSentinel12001App.class, args);}
}
启动项目 。 访问我们的接口 http://localhost:12001/sentinel/testA
。
当我们疯狂点击 testA 接口的时候。这里就会出现我们的机器的流量监控等等。
找到我们的接口。 点击流控。 通过 qps 限制我们的流量 。
QPS: 就是每秒我们接口的请求数。 我们这里设置单机阈值为 1 , 也就是说我们每秒只接受 1 个请求。 设置完之后,点击新增 。
然后再疯狂访问我们的接口。 你就会发现他就会出现访问失败的情况
2.3 自定义异常
在SentinelWebInterceptor 拦截器中, 当请求满足配置的 Sentinel block 条件时。 Sentinel 会抛出 BlockException 异常。并通过 定义 BlockExceptionHandler 接口的实现类。可以实现对BlockException 异常处理
默认情况下, BlockException 有一个默认的 DefaultBlockExceptionHandler 类。 返回 Block 字符串提示。 ·
返回的就是我们访问接口时返回的字符串 , 所以我们只需要重写 BlockExceptionHandler 直接抛出异常, 交给 SpringMVC的全局处理器处理噻
@Component
public class CustomBlockExceptionHandler implements BlockExceptionHandler {
@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
throw e;}
}
配置 全局处理器
@Component
@RestControllerAdvice(basePackages = "cn.fllday.controller")
public class GlobalExceptionAdviceHandler {
@ExceptionHandler(value = BlockException.class)public JSONObject blockExceptionHandler(BlockException blockException){
JSONObject error = new JSONObject();error.put("code", 2048);error.put("msg", "服务器繁忙,请稍后再试");error.put("ex", blockException.getClass().getSimpleName());return error;}
}
重启服务。 访问 http://localhost:12001/sentinel/testA
可以看到这次返回的信息就非常友好了。
测试一下基于线程数的限流。 使用 testB 接口。 testB 使用了沉睡。 大概 800 ms。
我们开两个窗口同同时访问 testB 接口。
同时只允许一个线程执行。 有点不太好测试 嘻嘻
2.4 熔断降级
Sentinel 和 Hystrix 的原则是一致的。 当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间过长, 异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其他的资源而导致级联故障。
Sentinel 和 Hystrix 采用了完全不一致的方法
Hystrix 通过 线程池隔离
的方式,来对依赖进行了隔离。这样的好处是 资源和资源之间做到了最彻底的隔离。缺点是增加了线程切换的成本。 还需要预先给各个资源分配线程池的大小
Sentinel 对这个问题采用了两种手段
1、 通过并发线程数进行控制
和资源池隔离的方式不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其他资源的影响,这样不但没有线程切换的损耗,也不需要预先分配线程池的大小,当某个资源出现不稳定的情况下,例如 响应时间边长,对资源的直接影响就会造成线程数的逐步堆积,当线程数在特定资源上堆积到一定的数量之后,对该线程的心情求就被拒绝,堆积的线程完成任务后,才会继续接受新的请求
2、通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源, 当以来的资源出现响应时间过长之后, 所有对该资源的访问都会被直接拒绝,直到过了指定时间窗口之后才会重新恢复
我们还是使用刚刚创建的服务 ,在我们的Sentinel 控制台中找到 降级按钮 ,对我们的 /testB 接口
点击降级按钮
平均响应时间:DEGRADE_GRADE_RT
:
当一秒内持续进入 5 个请求,对应时刻的平均响应时间(秒级) 均超过阈值(count, 以 ms 为单位), 那么在接下来的时间窗口(DegradeRule
中的timeWindow
, 以 s 为单位)\之内 , 这个方法的调用都会自动熔断。 抛出异常 : DegradeException
注意: Sentinel 默认的RT上限是 4900ms。 超出此阈值都会算作 4900 ms
. 若需要变更此上限,可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx
来配置
异常比例: DEGRADE_GRADE_EXCEPTION_RATIO
: 当资源的每秒请求量 >= 5, 并且每秒的异常总量占数总通过量的比值超过 阈值 (DegradeRule
中的 timeWindow
) 以 s 为单位之内。 对这个方法都会自动的返回。 异常比率的阈值范围是 [0.0, 1.0]
代表 0% - 100%
异常数: DEGRADE_GRADE_EXCEPTION_COUNT
当资源近 1 分钟的异常数目超过阈值之后会进行熔断,注意 由于统计时间窗口十分钟级别的。 若是 timeWindow
小于 60s。 则结束熔断状态后仍可能再进入熔断状态。
点击新增之后。完成降级规则的添加。
这边配置的是 阈值是 500ms , 时间窗口是 10 s。 也就是说 我们的平均响应如果超出了 500ms。 那么在10s 内都不能访问了。
一直访问一直访问。 每次都超出了 500ms。 等 过了 10秒钟之后
我觉得我又行了
2.5 热点参数限流
什么是热点?热点即经常访问的数据,很多时候我们希望统计某个热点数据中访问频次最高的 Top k 数据,并对其访问进行限制,比如:
- 商品ID 为参数,统计一段时间内最常购买的商品ID并进行限制
- 用户ID 为参数,统计一段时间内频繁访问的用户ID并进行限制
热点参数限流, 会统计传入参数的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用LRU策略统计最近最常访问的热点参数, 结合令牌桶算法来进行参数级别的流控,热点参数限流支持集群模式
简单测试一下。 在我们的controller 中添加方法
@GetMapping(value = "/testC")@SentinelResource(value = "getTestC")public String testC(@RequestParam(value = "p1", required = false) String p1,@RequestParam(value = "p2", required = false) String p2){
return "testC : " + p1 + p2;}
在方法上,我们添加了 @SentinelResource
注解, 自定义了 getTestC
资源。 用于定义资源,并提供可选的异常处理和 fallback 配置项
重新启动
当我们访问过一次 testC 接口之后,就会在我们的簇点链路里面找到我们的接口,点击热点。
新增以上配置 ,索引0, 统计时长为 60 秒,请求最大次数是 10. 点击新增之后跳转到以下页面
点击编辑
这边配置的意思是。 我们的 testC
接口当携带第一个参数的时候。 60秒内只允许访问 10次,但是如果第一个参数的值为 11
的 时候,则只允许访问 1 次。 点击保存之后简单测试一下
访问第 11 次的时候就会出现,服务器繁忙。 可以看到抛出的异常时 ParamFlowException 。 我们换个参数 p2 继续访问
这边是不会限制访问的。 当使用 p1 = 11
的时候,仅仅只访问了一次。 第二次就已经报错了。
可以看到热点参数限流和名字一样。是针对每一个参数限流。
2.6 系统自适应限流
Sentinel 同时提供系统维度的自适应保护能力, 防止雪崩,是系统防护中最重要的一环,当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应,在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其他的机器上,如果这个时候其他的机器也处于一个边缘状态的情况下,这个增加的流量就会导致这台机器的崩溃。最后导致整个集群不可用。
针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
Sentinel 一共有5种系统规则
1、 Load 自适应
系统的 load1 作为启发指标。 进行自适应系统保护, 当系统 load1 超过设定的启发值, 且系统的并发线程超过估算的系统容量时才会触发系统保护, 系统容量由 系统的 maxQps * minRt
估算得出,设定参考值一般是 0CPU cores * 2.5
2、CPU usage 1.5.0+ 版本
当系统CPU使用率超过阈值时,触发保护系统。 取值 0.0 - 1.0
3、平均RT
当单台机器上所有入口流量的平均RT达到阈值时即触发系统保护。单位是毫秒
4、并发线程数
当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护
5、入口QPS
当单台机器上所有入口流量的QPS达到阈值即触发系统保护
选择系统规则
我们配置一个系统规则。根据cpu 配置
设置为 百分之 1, 比较好实现。 然后访我们的接口
记得重启一下。清空其他的流控规则。 可以看到,每次请求都是 失败。
2.7 黑白名单控制
我们想要使用 Sentinel 黑白名单控制的时候,所以需要获得请求的调用方, RequestOriginParser
没有提供默认的实现。所以我们可以自定义 CustomRequestOriginParser 实现类。 解析请求头 s-user
创建 CustomRequestOriginParser
实现类。
@Component
public class CustomRequestOriginParser implements RequestOriginParser {
private static final String DEFAULT_S_USER = "default";@Overridepublic String parseOrigin(HttpServletRequest request) {
String origin = request.getHeader("s-user");if (StringUtils.isEmpty(origin)) {
return DEFAULT_S_USER;}return origin;}
}
获取 x-user 的值。 作为请求来源。注意: Sentinel 黑白名单的控制,一般是服务和服务之间的调用。 例如说 配置订单服务允许用户服务调用
判断未获得请求来源的时候,设置值为 default , 原因是 Sentinel 提供的 AuthorityRuleChecker
在进行黑白名的控制时, 如果请求来源为空。 就直接通过了
简单测试, 重启服务, 重启之后,访问一下 http://localhost:12001/sentinel/testA
保证 资源的初始化。
选择授权规则。点击新增授权规则
点击保存之后。我们用浏览器访问接口
可以看到返回的异常是 AuthorityException
,我们使用 PostMan 访问接口。
2.8 注解支持
在之前的的模块中,有使用到一个注解。使用 @SentinelResource
注解声明自定义资源 ,通过Spring AOP 注解该注解的方法,自动调用 Sentinel 客户端 API
@SentinelResource
用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource
注解包含以下属性:
value
: 资源名称, 必须向。 不能为空
entryType
: entry 类型, 可选项 (默认 EntryType.OUT
)
bloickHandler / blockHandlerClass
: blockHandler
对应处理 BlockException
的函数名称。可选项, blockHandler 函数访问范围需要是 public
, 返回类型需要与原方法相匹配, 参数类型需要和原方法相匹配并且最后增加一个额外的属性, 类型为 BlockException
, blockHandler
函数默认需要和原方法在同一个类中。如果希望使用其他类的函数,则可以指定 blockHandlerClass
为对应的类的对象。注意对应的函数必须为static
否则无法解析。
fallback
: Fallback 函数名称, 可选项用于在抛出异常的时候,提供 fallback 处理。 fallback 函数可以针对所有的类型异常。 除了 exceptionsToIgnore
里面派出的异常类型进行处理。
- 返回值类型必须与原函数返回值类型一致
- 方法参数列表需要和原函数一致,或者可以额外多一个 throwable 类型的异常参数
fallback
函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定fallbackClass
为对应的类 class 对象。 注意对应的函数必须为 static 函数,否则无法解析
defaultFallback
: 默认的fallback 函数名称。 可选项,通常用于通用的fallback逻辑, 默认 fallback 函数可以针对所有类型的异常进行处理, 若同时配置了 fallback 和 defaultFallback, 则只有 fallback 会生效, defaultFallback 函数签名要求。- 返回值类型必须与原函数返回值类型一致
- 方法参数列表需要和原函数一致,或者可以额外多一个 throwable 类型的异常参数
fallback
函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定fallbackClass
为对应的类 class 对象。 注意对应的函数必须为 static 函数,否则无法解析
exceptionToIngore
: 用于指定哪些异常被排除掉。 不会计入异常统计中, 也不会进入 fallback 逻辑中, 而是原样抛出
搭建一个基于 @SentinelResource
的demo 示例, 还是在我们的 12001 服务上面修改
/*** blockHandler 只会处理 {@link com.alibaba.csp.sentinel.slots.block.BlockException} 的异常* fallback 用于抛出异常的时候提供 fallback 的处理逻辑。 可以针对所有类型的异常。 除了 exceptionToIgnore 忽略的异常* @param id* @return*/@GetMapping(value = "/annotationsDemo")@SentinelResource(value = "getAnnotationsDemo", blockHandler = "blockHandler", fallback = "fallback", exceptionsToIgnore = IllegalArgumentException.class)public String annotationDemo(@RequestParam(required = false) Integer id) {
if (id < 0) {
throw new IllegalArgumentException("id 参数不能为空");}else if (id > 10) {
int v = id /0;}return "success" ;}/*** // BlockHandler 处理参数* @param id* @param ex* @return*/public String blockHandler(Integer id, BlockException ex) {
return "block: " + ex.getClass().getSimpleName();}public String fallback(Integer id, Throwable throwable) {
return "error: " + throwable.getClass().getSimpleName();}
重启服务。 打开 Sentinel 控制台。 对我们的资源添加流控规则 QPS单机阈值为 1 。 访问接口 ,访问接口,使用 id 参数为 负数。可以看到我们忽略了 IllegalArgumentException.class
。 可以看到这里抛出了这个异常,但是没有走fallback
方法。
我们使用 大于 10 的参数。
可以看到抛出的异常,被我们的 fallback 逻辑处理了。 如果疯狂访问接口。 正确的方法,就会发现会被限流了 走的是 blockHandler
指定的方法
2.9 规则管理以及推送
集中管理及推送:集中管理以及推送规则, sentinel-core
提供了API 和 拓展接口来接受信息。 开发者需要根据自己的环境选择一个可靠的推送方式; 同时 规则最好在控制台集中飞过
监控:支持可靠,快速的实时监控和历史监控数据查询, sentinel-core
记录秒级的资源运行情况,并且提供 API 来拉取资源运行信息, 当机器大于一台以上的时候, 可以通过 DashBoard 来拉取 聚合, 并且存储这些信息。 这个时候, Dashboard 需要有一个存储媒介,来存储历史运行情况
权限控制: 区分用户角色,来进行操作。 生产环境上的权限控制是非常重要的,理论上只有管理员等高级用户才有权限去修改应用的规则。
一般来说, 规则的推送有下面三种模式:
推送模式 | 说明 | 优点 | 缺点 |
---|---|---|---|
原始模式 | API 将规则推送到客户端,并且直接更新到内存中,拓展写数据源,WritableDataSource |
简单无任何依赖 | 不能保证一致性,规则保存在内存中,重启后就消失,严重不建议用于生产环境 |
Pull 模式 | 拓展写数据源(WritableDataSource ), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS, 文件等, |
简单,无任何依赖。规则持久化 | 不保证一致性;实时性不保证, 拉取过于频繁,也有可能会有性能问题 |
PUSH | 拓展读数据源(ReadableDataSource ), 规则中心统一推送,客户端通过注册监听器这种方式时刻监听变化,比如使用 Nacos, Zookeeper 等配置中心。 这种方式有更好的实时性和一致性保证。 生产环境推荐。 |
规则持久化, 一致性, 快速。 | 引入第三方依赖 |
原始模式
Pull 模式
Push 模式
原始模式,最开始演示的就是原始模式了。 Push 和 Pull 模式都是可以持久化的。 但是推荐生产环境下使用的只有 Push 模式。所以只演示Push 模式。 这边使用 nacos 作为数据源
1.1 使用 Nacos 作为数据源
引入依赖
pom.xml
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId></dependency>
修改配置 文件 application.yml
server:port: 12001
spring:application:name: sgg-alibaba-sentinel-servicecloud:nacos:discovery:server-addr: 127.0.0.1:8848service: ${
spring.application.name}config:group: DEFAULT_GROUPnamespace:file-extension: yamlname: ${
spring.application.name}sentinel:datasource:ds1:nacos:server-addr: 127.0.0.1:8848 # Nacos 服务器地址namespace: # Nacos 命名空间group-id: DEFAULT_GROUP # nacos 分组data-id: ${
spring.application.name}-flow-ruledata-type: json # 数据格式rule-type: FLOW # 规则类型
在Nacos 中创建配置集。
resource
: 资源名。即限流规则的作用对象
count
: 限流阈值
grade
: 限流阈值类型 (QPS 或者 并发线程数)
limitApp
: 流控针对的调用来源,若为 default
则不区分调用来源
strategy
: 调用关系限流策略
controlBehavior
: 流量控制效果 (直接拒绝, Warm Up, 匀速排队)
简单测试
重启 12001 服务。 打开我们的 Sentinel 控制台,找到流控规则,可以看到这边有一个已经存在的流控规则,是从Nacos数据源加载而来的。
按照规则快速访问 6 次。 最后一次会被拒绝