当前位置: 代码迷 >> 综合 >> SpringCloud(十一)SpringCloudAlibaba Sentinel 分布式系统的流量防卫兵
  详细解决方案

SpringCloud(十一)SpringCloudAlibaba Sentinel 分布式系统的流量防卫兵

热度:83   发布时间:2024-02-21 12:42:58.0

文章目录

  • 一、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.javaAliababSentinel12001App.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, 返回类型需要与原方法相匹配, 参数类型需要和原方法相匹配并且最后增加一个额外的属性, 类型为 BlockExceptionblockHandler 函数默认需要和原方法在同一个类中。如果希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的对象。注意对应的函数必须为static 否则无法解析。

fallback : Fallback 函数名称, 可选项用于在抛出异常的时候,提供 fallback 处理。 fallback 函数可以针对所有的类型异常。 除了 exceptionsToIgnore 里面派出的异常类型进行处理。

  1. 返回值类型必须与原函数返回值类型一致
  2. 方法参数列表需要和原函数一致,或者可以额外多一个 throwable 类型的异常参数
  3. fallback 函数默认需要和原方法在同一个类中,若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类 class 对象。 注意对应的函数必须为 static 函数,否则无法解析
    defaultFallback : 默认的fallback 函数名称。 可选项,通常用于通用的fallback逻辑, 默认 fallback 函数可以针对所有类型的异常进行处理, 若同时配置了 fallback 和 defaultFallback, 则只有 fallback 会生效, defaultFallback 函数签名要求。
  4. 返回值类型必须与原函数返回值类型一致
  5. 方法参数列表需要和原函数一致,或者可以额外多一个 throwable 类型的异常参数
  6. 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 次。 最后一次会被拒绝
在这里插入图片描述

  相关解决方案