原创

Sentinel记录

温馨提示:
本文最后更新于 2023年02月01日,已超过 821 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

sentinel官网https://sentinelguard.io/zh-cn/docs/quick-start.html

一. 什么是Sentinel

1. sentinel介绍

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由流量控制流量整形熔断降级系统自适应过载保护热点流量防护等多个维度来帮助开发者保障微服务的稳定性。

来源官网-----目前使用最多的是流量控制、熔断降级

2. sentinel基本概念

首先定义资源,再为资源定义规则

1.资源

资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码

只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

2.规则

围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

3. sentinel核心部分

  • 核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持(见 主流框架适配)。
  • 控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。

二. Sentinel能做什么

1. 流量控制

流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:

将随机的不同时间内的到来的请求,通过调配器(sentinel)整理为有序的、可控的请求。

image-20230112162350704

流量控制有以下几个角度

  • 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
  • 运行指标,例如 QPS、线程池、系统负载等;
  • 控制的效果,例如直接限流、冷启动、排队等

2. 熔断降级

1.什么是熔断降级

熔断降级可以拆分为熔断和降级进行理解,两者有一定的关联关系。

  • 熔断:当调用链路中的某个资源出现不稳定,比如请求超时、异常比例升高时,就对这个资源进行限制,并让请求快速失败,避免影响到其他资源。

    在高并发环境下,服务A调用服务B,此时服务B由于网络波动或业务长时间执行,导致服务A一直得不到服务B的响应,此时资源就会一直堆叠,最终导致服务A也不可用。

  • 降级:当请求快速失败时,需要有对应的响应策略。降级也可能是手动进行,当系统资源占用过高时,手动设置一些非热门(或非主要)资源进行快速失败,保证主要资源能正常运行。

2. 熔断降级设计

sentinel采用的是信号量限制,Hystrix采用的是线程池和信号量。两者对比图:

image-20230112164855463

3. 系统过载保护

Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。

针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

4. 其他

  • 集群流控(分布式流控)
  • 网关流控
  • 热点参数限流
  • 系统自适应限流
  • 黑名单控制
  • 实时监控数据
  • 动态规则
  • ......

三. Sentinel解决了哪些问题

  1. 高并发下服务器超负载

    分析服务器的负载能力,将流量限制到服务器能承载的数量级以下

  2. 高并发下服务器集群雪崩

    通过熔断降级的机制,当部分服务不可用时,不会影响到其他服务。

  3. 流量监控

    通过sentinel提供的dashboard控制台,能实时的监控服务集群的健康状态

  4. 服务器系统自适应过载保护

    从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 和线程数四个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

。。。。。。。。。

四. Sentinel使用

1. 依赖包

使用版本为当前最新的1.8.6版本,微服务项目中加入依赖

<!--sentinel核心包-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.6</version>
</dependency>

<!--sentinel客户端接入控制台模块(如果不需要控制台配置和监控客户端,则可以不用该模块)-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>1.8.6</version>
</dependency>

目前使用最多的是通过控制台动态配置注解方式抛出异常方式

官网文档已经比较全面,此处仅对一些配置做记录

2. 使用配置

1. 通过控制台配置使用

1. 配置对控制台的链接

sentinel.properties

参考启动配置项:https://sentinelguard.io/zh-cn/docs/general-configuration.html

csp.sentinel.dashboard.server=127.0.0.1:8111
2. 通过控制台配置

先请求接口,然后控制台会刷新出对应的链路,找到需要的限制的资源,进行流控、熔断、热点资源、授权等配置

image-20230129165657478

2. 注解方式

来源官网:https://sentinelguard.io/zh-cn/docs/annotation-support.html

注意:注解方式埋点不支持 private 方法。

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT
  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallback:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所以类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理

特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandlerfallbackdefaultFallback,则被限流降级时会将 BlockException 直接抛出

示例:

public class TestService {

    // 对应的 `handleException` 函数需要位于 `ExceptionUtil` 类中,并且必须为 static 函数.
    @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
    public void test() {
        System.out.println("Test");
    }

    // 原函数
    @SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
    public String hello(long s) {
        return String.format("Hello at %d", s);
    }

    // Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
    public String helloFallback(long s) {
        return String.format("Halooooo %d", s);
    }

    // Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
    public String exceptionHandler(long s, BlockException ex) {
        // Do some log here.
        ex.printStackTrace();
        return "Oops, error occurred at " + s;
    }
}

3. 抛出异常方式

参考:https://sentinelguard.io/zh-cn/docs/parameter-flow-control.html

以下方式仅仅定义了资源(skuData),对应的规则可以在控制台或通过代码进行定制。

Entry entry = null;
try {
    entry = SphU.entry("skuData");
    // 被保护的业务逻辑
    return ResponseResult.success();
} catch (BlockException ex) {
    // 资源访问阻止,被限流或被降级
    // 在此处进行相应的处理操作
    return ResponseResult.error();
}finally {
    if (null!=entry){
        entry.exit();
    }
}

代码定义规则示例:

//定义资源规则
ParamFlowRule rule = new ParamFlowRule("skuData")
                .setParamIdx(0)
                .setCount(1);
//加载规则
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
Entry entry = null;
try {
    //rule是规则参数,可以有多个
    entry = SphU.entry("skuData",EntryType.IN, 1, rule);
    // 被保护的业务逻辑
    return ResponseResult.success();
} catch (BlockException ex) {
    // 资源访问阻止,被限流或被降级
    // 在此处进行相应的处理操作
    return ResponseResult.error();
}finally {
    if (null!=entry){
        //如果定义了规则参数,则在退出的时候也需要加入,否则可能会统计错误(官网)
        entry.exit(1,rule);
    }
}

热点参数规则

属性 说明 默认值
resource 资源名,必填
count 限流阈值,必填
grade 限流模式 QPS 模式
durationInSec 统计窗口时间长度(单位为秒),1.6.0 版本开始支持 1s
controlBehavior 流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持 快速失败
maxQueueingTimeMs 最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持 0ms
paramIdx 热点参数的索引,必填,对应 SphU.entry(xxx, args) 中的参数索引位置
paramFlowItemList 参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型
clusterMode 是否是集群参数流控规则 false
clusterConfig 集群流控相关配置

3. 自定义异常返回

1. BlockException自定义返回内容配置

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class SentinelConfig {


    public SentinelConfig(){
        WebCallbackManager.setUrlBlockHandler(new UrlBlockHandler() {
            @Override
            public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
                httpServletResponse.setContentType("application/json");
                httpServletResponse.setCharacterEncoding("UTF-8");
                R r = R.error(ResponseType.REQUEST_TOO_MANY.getCode(),ResponseType.REQUEST_TOO_MANY.getMessage());
                httpServletResponse.getWriter().write(JSON.toJSONString(r));
            }
        });
    }

}

2. FlowException配置

使用全局异常处理

@Slf4j
@RestControllerAdvice
public class ProductException {

    @ExceptionHandler(value = FlowException.class)
    public R CompanyRuntimeException(FlowException e){
        return R.error(ResponseType.REQUEST_TOO_MANY.getCode(), ResponseType.REQUEST_TOO_MANY.getMessage());
    }
}

4. 动态规则扩展使用

1. 文档介绍

sentinel控制台定义的规则仅存在于内存中,当服务重启时则会丢失规则。因此,我们需要将规则进行持久化。

官网文档介绍https://sentinelguard.io/zh-cn/docs/dynamic-rule-configuration.html

我们推荐通过控制台设置规则后将规则推送到统一的规则中心,客户端实现 ReadableDataSource 接口端监听规则中心实时获取变更,流程如下:

image-20230201150249222

DataSource 扩展常见的实现方式有:

  • 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更;
  • 推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。

Sentinel 目前支持以下数据源扩展:

其中三种模式的优缺点及对比如下:

推送模式 说明 优点 缺点
原始模式 API 将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource 简单,无任何依赖 不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境
Pull 模式 扩展写数据源(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等 简单,无任何依赖;规则持久化 不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。
Push 模式 扩展读数据源(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。 规则持久化;一致性;快速 引入第三方依赖

2. 使用nacos配置规则

1. 加入依赖
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>1.8.6</version>
</dependency>
2. 在nacos中编写规则

规则采用json数组的方式进行编写,如下图为定义一个流控规则

image-20230201151509204

以下只记录三个常用规则,其他查阅文档https://sentinelguard.io/zh-cn/docs/basic-api-resource-rule.html

1.流控规则定义
Field 说明 默认值
resource 资源名,资源名是限流规则的作用对象
count 限流阈值
grade 限流阈值类型,QPS 或线程数模式 QPS 模式
limitApp 流控针对的调用来源 default,代表不区分调用来源
strategy 调用关系限流策略:直接、链路、关联 根据资源本身(直接)
controlBehavior 流控效果(直接拒绝 / 排队等待 / 慢启动模式),不支持按调用关系限流 直接拒绝
2. 熔断降级规则定义
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 引入)
3. 系统保护规则定义
Field 说明 默认值
highestSystemLoad load1 触发值,用于触发自适应控制阶段 -1 (不生效)
avgRt 所有入口流量的平均响应时间 -1 (不生效)
maxThread 入口流量的最大并发数 -1 (不生效)
qps 所有入口资源的 QPS -1 (不生效)
highestCpuUsage 当前系统的 CPU 使用率(0.0-1.0) -1 (不生效)
3. 配置数据源

创建 NacosDataSource 并将其注册至对应的 RuleManager 上。

import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class SentinelDataSourceConfig {

    final String remoteAddress = "192.168.1.25:8848";
    final String groupId = "DEFAULT_GROUP";
    final String dataId = "sentinel_rule";

    public SentinelDataSourceConfig(){
        //流控规则
        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
                source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
        //熔断降级规则
        ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
                source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {}));
        DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());
        //系统保护规则 (SystemRule)
        /**
         * ReadableDataSource<String, List<SystemRule>> systemRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
         *                 source -> JSON.parseObject(source, new TypeReference<List<SystemRule>>() {}));
         * SystemRuleManager.register2Property(systemRuleDataSource.getProperty())
         */
        //访问控制规则 (AuthorityRule)
        //热点规则 (ParamFlowRule)
    }

}
4. 测试

编写一个接口,通过@SentinelResource注解定义该接口为一个资源,资源名为listData,在浏览器快速请求该接口时,部分请求则会提示失败(流控生效)。

    @GetMapping("/test")
    @SentinelResource(value = "listData")
    public R test(){
        //业务代码
        return R.ok();
    }

也可以使用代码定义资源的方式实现

    @GetMapping("/test")
    public R test(){
        try (Entry entry = SphU.entry("listData")) {
            // 被保护的业务逻辑
            return R.ok();
        } catch (BlockException ex) {
            // 资源访问阻止,被限流或被降级
            return R.error();
        }
    }

当修改nacos中的规则时,会自动向客户端推送最新的规则。

正文到此结束