Sentinel限流的应用

2,738 阅读6分钟

本文正在参加「金石计划」

Sentinel 是什么

什么是限流

  限流指的是限制系统或服务的请求流量,以保证系统的稳定性和可用性。在高并发场景下,系统容易出现请求量过大而导致资源瓶颈、响应变慢甚至崩溃的情况。

什么是Sentinel

  Sentinel是阿里巴巴开源的一款流量控制组件,主要用于分布式系统中的流量控制、熔断降级等功能。它提供了多种限流算法和限流维度,可以根据业务需求进行配置和使用,帮助开发者在高并发场景下有效地保护系统的稳定性。 limitflow.png

为什么使用 Sentinel 限流

  在高并发场景下,系统容易出现请求量过大而导致资源瓶颈、响应变慢甚至崩溃的情况。为了保证系统的可用性和稳定性,需要对请求进行限流。

  • 保护系统稳定性:限制每个用户或服务的请求速率,可以有效地降低系统的负载,避免系统崩溃。

  • 提高服务可用性:限流可以避免系统因过度压力而出现响应延迟、错误甚至宕机等问题,提高服务的可用性和稳定性。

  • 防止资源被耗尽:在高并发的情况下,系统资源很容易被耗尽,例如数据库连接池、网络带宽等,通过限流可以避免资源被耗尽,提高系统的性能。

  • 防止恶意攻击:限流可以控制请求的频率,防止恶意攻击、爬虫等非法访问。

Sentinel 特点和优势

image.png   Sentinel具有以下几个主要特点:

  • 多种限流算法:包括令牌桶、漏桶等,可以根据业务场景选择合适的算法。

  • 多种限流维度:包括QPS、并发线程数、异常比例等,可以根据不同的维度来进行限流。

  • 熔断降级:可以在服务不可用或异常时快速切换到备用服务或默认返回值,保证系统的可用性。

  • 实时监控:提供了Dashboard可视化监控界面,可以实时查看系统的运行状况、设置规则、查看报表等。

  • 报警机制:可以在系统出现异常时自动发送邮件、短信等通知。

Sentinel 限流的原理和实现

  Sentinel的使用可以分为两个部分:

  • 核心库(Java 客户端)

  • 控制台(Dashboard)

image.png

  Sentinel 的核心骨架,将不同的 Slot 按照顺序串在一起(责任链模式)),从而将不同的功能(限流、降级、系统保护)组合在一起。下面主要就是介绍其中的数据统计与流量统计的实现。

1111.png

配置实时刷新

   Sentinel Datasource 配置的规则可以动态实时调整。

  在应用启动阶段,程序会主动从 Sentinel Datasource 获取限流规则配置。而在运行期,我们也可以在 Sentinel 控制台动态修改限流规则,应用程序会实时监听配置中心的数据变化,进而获取变更后的数据。

Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel 客户端 image2022-10-19_10-13-5.png

50678855-aa6e9700-103b-11e9-83de-2a33e580325f.png   大家有兴趣的可以就着这张图对照着源码来看

640.png

数据统计

  要做流量控制,首先,就要先进行统计,它要知道当前接口的 QPS 和并发是多少,进而判断是否限流。

  数据统计的代码在 StatisticNode 中,对于 QPS 数据,它使用了滑动窗口的设计。滑动窗口算法的核心类 LeapArray

  滑动窗口算法的原理是在固定窗口中分割出多个小时间窗口,分别在每个小时间窗口中记录访问次数,然后根据时间将窗口往前滑动并删除过期的小时间窗口。最终只需要统计滑动窗口范围内的所有小时间窗口总的计数即可。 

7e907181-3fd3-453c-bd3a-51d71ea3919b.png   将单位时间切成了多个窗口,每次计算 QPS 时,计算 当前窗口 + 过去几个窗口 的流量总和,这样就避免了固定窗口的问题 (具体使用几个窗口,取决于窗口大小和单位时间大小。例如上图,每个窗口大小为 500 ms,以 1 s 为单位时间做限流,每次使用 current + last 即可)

  对于 线程数 数据,它使用了线程计数器进行累加。

public class StatisticNode implements Node {

    /**
     * 统计近一秒的数据, 按秒统计,分成两个窗口,每个窗口500ms,用来统计QPS
     * Holds statistics of the recent {@code INTERVAL} seconds. The {@code INTERVAL} is divided into time spans                       
     * by given {@code sampleCount}.
     */
    private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT,
            IntervalProperty.INTERVAL);

    /**
     * Holds statistics of the recent 60 seconds. The windowLengthInMs is deliberately set to 1000 milliseconds,
     * meaning each bucket per second, in this way we can get accurate statistics of each second.
     * 统计近一分钟的数据, 按分钟统计,分成60个窗口,每个窗口 1000ms
     */
    private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false);

    /**
     * The counter for thread count. 当前并发线程数
     */
    private LongAdder curThreadNum = new LongAdder();

    /**
     * The last timestamp when metrics were fetched.
     */
    private long lastFetchTime = -1;

}

流量控制

  Sentinel的流量控制有两个维度的控制:并发线程数和QPS

48189035-25883980-e37a-11e8-8f25-3f3f5be23f0e.png   它们都是针对某个具体的接口来设置的

   Sentinel 的限流原理主要是通过统计系统的QPS(即每秒请求数量)和并发量来控制系统的流量,从而达到限流的目的。当并发请求数量超过预设的阈值时,Sentinel 会拒绝该请求,并返回一个 FlowException 的错误响应。

  限流过滤的实现主要在 FlowSlotentry 方法进行。FlowSlot 会根据预设的规则,结合StatisticSlot 统计出来的实时信息进行流量控制。

public class FlowSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        //流控检查
        checkFlow(resourceWrapper, context, node, count, prioritized);
        fireEntry(context, resourceWrapper, node, count, prioritized, args);
    }

    void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {

        Map<String, List<FlowRule>> flowRules = FlowRuleManager.getFlowRuleMap();
        List<FlowRule> rules = flowRules.get(resource.getName());
        if (rules != null) {
            for (FlowRule rule : rules) {

                if (!canPassCheck(rule, context, node, count, prioritized)) {
                    throw new FlowException(rule.getLimitApp(), rule);
                }
            }
        }
    }


    public void checkFlow(Function<String, Collection<FlowRule>> ruleProvider, ResourceWrapper resource,
                          Context context, DefaultNode node, int count, boolean prioritized) throws BlockException {
        if (ruleProvider == null || resource == null) {
            return;
        }
        // 获取对应配置的流控规则
        Collection<FlowRule> rules = ruleProvider.apply(resource.getName());
        if (rules != null) {
            for (FlowRule rule : rules) {
                //规则逐一校验,检查是否通过
                if (!canPassCheck(rule, context, node, count, prioritized)) {
                    throw new FlowException(rule.getLimitApp(), rule);
                }
            }
        }
    }

}

Sentinel 流量控制实践

引入依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>2.1.2.RELEASE</version>
</dependency>

添加配置

spring:
  cloud:
    sentinel:
      transport:
        port: 8719 # 假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口,默认8719
        dashboard: http://localhost:8719 # 指定控制台服务的地址
      filter:
        enabled: true
      # 心跳监测
      eager: true

启动控制台,添加配置

  控制台的规则默认存储在内存中,重启就会失效,如果需要持久化配置信息,可以参考官方文章配置(在生产环境中使用 Sentinel · alibaba/Sentinel Wiki · GitHub

48189035-25883980-e37a-11e8-8f25-3f3f5be23f0e.png

测试

  返回对应的错误信息,当然具体的错误信息,大家可以自己根据项目需求去包装一下。 image.png

参考