高可用之限流-05-slide window 滑动窗口

news2025/1/16 10:59:49

限流系列

开源组件 rate-limit: 限流

高可用之限流-01-入门介绍

高可用之限流-02-如何设计限流框架

高可用之限流-03-Semaphore 信号量做限流

高可用之限流-04-fixed window 固定窗口

高可用之限流-05-slide window 滑动窗口

高可用之限流-06-slide window 滑动窗口 sentinel 源码

高可用之限流-07-token bucket 令牌桶算法

高可用之限流 08-leaky bucket漏桶算法

高可用之限流 09-guava RateLimiter 入门使用简介 & 源码分析

滑动日志-Sliding Log

滑动日志算法,利用记录下来的用户的请求时间,请求数,当该用户的一个新的请求进来时,比较这个用户在这个窗口内的请求数是否超过了限定值,超过的话就拒绝这个请求。

优点:

  1. 避免了固定窗口算法在窗口边界可能出现的两倍流量问题

  2. 由于是针对每个用户进行统计的,不会引发惊群效应

缺点:

  1. 需要保存大量的请求日志

  2. 每个请求都需要考虑该用户之前的请求情况,在分布式系统中尤其难做到

时间比例

滑动窗口算法,结合了固定窗口算法的低开销和滑动日志算法能够解决的边界情况。

  1. 为每个窗口进行请求量的计数

  2. 结合上一个窗口的请求量和这一个窗口已经经过的时间来计算出上限,以此平滑请求尖锋

举例来说,限流的上限是每分钟 10 个请求,窗口大小为 1 分钟,上一个窗口中总共处理了 6 个请求。

在假设这个新的窗口已经经过了 20 秒,那么 到目前为止允许的请求上限就是 10 - 6 * (1 - 20 / 60) = 8。

滑动窗口算法是这些算法中最实用的算法:

  1. 有很好的性能

  2. 避免了漏桶算法带来的饥饿问题

  3. 避免了固定窗口算法的请求量突增的问题

ps: 这里是一种思路,但却不是正宗的滑动窗口算法。

滑动窗口

滑动窗口将固定窗口再等分为多个小的窗口。

image

滑动窗口可以通过更细粒度对数据进行统计。

在限流算法里:假设我们将1s划分为4个窗口,则每个窗口对应250ms。

假设恶意用户还是在上一秒的最后一刻和下一秒的第一刻冲击服务,按照滑动窗口的原理,此时统计上一秒的最后750毫秒和下一秒的前250毫秒,这种方式能够判断出用户的访问依旧超过了1s的访问数量,因此依然会阻拦用户的访问。

特点

滑动窗口具有以下特点:

1、每个小窗口的大小可以均等,dubbo的默认负载均衡算法random就是通过滑动窗口设计的,可以调整每个每个窗口的大小,进行负载。

2、滑动窗口的个数及大小可以根据实际应用进行控制

滑动时间窗口

滑动时间窗口就是把一段时间片分为多个窗口,然后计算对应的时间落在那个窗口上,来对数据统计;

如上图其实就是即时的滑动时间窗口,随着时间流失,最开始的窗口将会失效,但是也会生成新的窗口;sentinel的就是通过这个原理来实时的限流数据统计。

关于滑动窗口,这里介绍还是比较简单,主要是大致的介绍滑动的原理以及时间窗口的设计;其实关于滑动窗口在我们学习的计算机网络中也涉及到。

java 实现

伪代码

全局数组 链表[]  counterList = new 链表[切分的滑动窗口数量];
//有一个定时器,在每一次统计时间段起点需要变化的时候就将索引0位置的元素移除,并在末端追加一个新元素。
int sum = counterList.Sum();
if(sum > 限流阈值) {
    return; //不继续处理请求。
}

int 当前索引 = 当前时间的秒数 % 切分的滑动窗口数量;
counterList[当前索引]++;
// do something...

java 核心实现

该方法将时间直接切分为10分,然后慢慢处理。

暂时没有做更加细致的可配置化,后期考虑添加。

/**
 * 全局的限制次数
 *
 * 固定时间窗口
 * @author houbinbin
 * Created by bbhou on 2017/9/20.
 * @since 0.0.5
 */
public class LimitFixedWindow extends LimitAdaptor {

    /**
     * 日志
     * @since 0.0.4
     */
    private static final Log LOG = LogFactory.getLog(LimitFixedWindow.class);

    /**
     * 上下文
     * @since 0.0.4
     */
    private final ILimitContext context;

    /**
     * 计数器
     * @since 0.0.4
     */
    private AtomicInteger counter = new AtomicInteger(0);

    /**
     * 限制状态的工具
     *
     * 避免不同线程的 notify+wait 报错问题
     *
     * @since 0.0.4
     */
    private CountDownLatch latch = new CountDownLatch(1);

    /**
     * 构造器
     * @param context 上下文
     * @since 0.0.4
     */
    public LimitFixedWindow(ILimitContext context) {
        this.context = context;

        // 定时将 count 清零。
        final long interval = context.interval();
        final TimeUnit timeUnit = context.timeUnit();

        // 任务调度
        ExecutorServiceUtil.singleSchedule(new Runnable() {
            @Override
            public void run() {
                initCounter();
            }
        }, interval, timeUnit);
    }

    @Override
    public synchronized void acquire() {

        // 超过阈值,则进行等待
        if (counter.get() >= this.context.count()) {
            try {
                LOG.debug("[Limit] fixed count need wait for notify.");
                latch.await();
                LOG.debug("[Limit] fixed count need wait end ");
                this.latch = new CountDownLatch(1);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                LOG.error("[Limit] fixed count is interrupt", e);
            }
        }

        // 结束
        int value = this.counter.incrementAndGet();
        LOG.debug("[Limit] fixed count is " + value);
    }

    /**
     * 初始化计数器
     * @since 0.0.4
     */
    private void initCounter() {
        LOG.debug("[Limit] fixed count init counter start");

        // 通知可以继续执行(这里不能无脑 notify)会卡主
        if(this.counter.get() >= this.context.count()) {
            this.counter = new AtomicInteger(0);

            LOG.debug("[Limit] fixed count notify all start");
            latch.countDown();
            LOG.debug("[Limit] fixed count notify all end");
        }  else {
            this.counter = new AtomicInteger(0);
        }
    }

}

基于 queue 的解法

另外一种解法,个人也是比较喜欢的。

直接创建一个队列,队列大小等于限制的数量。

直接对比队首队尾的时间,从而保证固定当达到指定固定的次数时,时间一定是满足的。

ps: 这个后续在看看,不一定是滑动窗口的。

public class LimitSlideWindowQueue extends LimitAdaptor {

    private static final Log LOG = LogFactory.getLog(LimitSlideWindowQueue.class);

    /**
     * 用于存放时间的队列
     * @since 0.0.3
     */
    private final BlockingQueue<Long> timeBlockQueue;

    /**
     * 当前时间
     * @since 0.0.5
     */
    private final ICurrentTime currentTime = Instances.singleton(CurrentTime.class);

    /**
     * 等待间隔时间
     * @since 0.0.5
     */
    private final long intervalInMills;

    /**
     * 构造器
     * @param context 上下文
     * @since 0.0.3
     */
    public LimitSlideWindowQueue(ILimitContext context) {
        this.timeBlockQueue = new ArrayBlockingQueue<>(context.count());
        this.intervalInMills = context.timeUnit().toMillis(context.interval());
    }

    @Override
    public synchronized void acquire() {
        long currentTimeInMills = currentTime.currentTimeInMills();

        //1. 将时间放入队列中 如果放得下,直接可以执行。反之,需要等待
        //2. 等待完成之后,将第一个元素剔除。将最新的时间加入队列中。
        boolean offerResult = timeBlockQueue.offer(currentTimeInMills);
        if(!offerResult) {
            //获取队列头的元素
            //1. 取出头节点,获取最初的时间
            //2. 将头结点移除
            long headTimeInMills = timeBlockQueue.poll();

            //当前时间和头的时间差
            long durationInMills = currentTimeInMills - headTimeInMills;
            if(intervalInMills > durationInMills) {
                //需要沉睡的时间
                long sleepInMills = intervalInMills - durationInMills;
                DateUtil.sleep(sleepInMills);
            }

            currentTimeInMills = currentTime.currentTimeInMills();
            boolean addResult = timeBlockQueue.offer(currentTimeInMills);
            LOG.debug("[Limit] acquire add result: " + addResult);
        }
    }

}

参考资料

限流技术总结

固定窗口和滑动窗口算法了解一下

Sentinel之滑动时间窗口设计(二)

限流滑动窗口

限流算法之固定窗口与滑动窗口

限流--基于某个滑动时间窗口限流

【限流算法】java实现滑动时间窗口算法

谈谈高并发系统的限流

TCP协议的滑动窗口具体是怎样控制流量的?

漏铜令牌桶

漏桶算法&令牌桶算法理解及常用的算法

流量控制算法——漏桶算法和令牌桶算法

Token Bucket 令牌桶算法

华为-令牌桶算法

简单分析Guava中RateLimiter中的令牌桶算法的实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2214680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ReferenceError: MutationEvent is not defined

解决&#xff1a;关闭tampermonkey&#xff08;篡改猴&#xff09;插件后也不可以&#xff0c;移除tampermonkey&#xff08;篡改猴&#xff09;插件仔刷新就可以了

Linux:Ubuntu系统开启SSH服务

在Ubuntu上开启SSH服务&#xff0c;可以按照以下步骤进行&#xff1a; 1.安装OpenSSH服务 如果你还没有安装OpenSSH服务&#xff0c;可以使用以下命令安装&#xff1a; sudo apt update sudo apt install openssh-server2. 启动SSH服务 安装完成后&#xff0c;启动SSH服务&a…

Leetcode 721 账户合并

Leetcode 721 账户合并 给定一个列表 accounts&#xff0c;每个元素 accounts[i] 是一个字符串列表&#xff0c;其中第一个元素 accounts[i][0] 是 名称 (name)&#xff0c;其余元素是 *emails * 表示该账户的邮箱地址。 现在&#xff0c;我们想合并这些账户。如果两个账户都…

jmeter在beanshell中使用props.put()方法的注意事项

在jmeter中&#xff0c;通常使用beanshell去处理一些属性的设置和获取的操作&#xff0c;而这些操作也是有一定的规则的。 1. 设置属性时&#xff0c;在属性名上要加双引号&#xff0c;这代表它不是一个需要用var去声明的变量 这种设置属性的方式才是有效可行的&#xff0c;在…

[权威出刊|稳定检索]2024年云计算、大数据与计算机应用技术国际会议(CCBDCAT 2024)

2024年云计算、大数据与计算机应用技术国际会议 2024 International Conference on Cloud Computing, Big Data, and Computer Application Technology 【1】大会信息 会议名称&#xff1a;2024年云计算、大数据与计算机应用技术国际会议 会议简称&#xff1a;CCBDCAT 2024 大…

【算法设计与分析】第2关:背包问题

任务描述 设有编号为0、1、2、…、n-1的n个物品&#xff0c;它们的重量分别为w0、w1、…、wn-1&#xff0c;价值分别为p0、p1、…、pn-1&#xff0c;其中wi、pi&#xff08;0≤i≤n-1&#xff09;均为正数。  有一个背包可以携带的最大重量不超过W。求解目标&#xff1a;在不…

C++类和对象——第三关

在阅读此篇文章之前&#xff0c;请先阅读博主之前的文章&#xff1a; C类和对象第一关-CSDN博客 C类和对象——第二关-CSDN博客 以便更好的理解本文章。 目录 运算符重载 &#xff08;一&#xff09;运算符重载 &#xff08;二&#xff09;赋值类运算符函数的重载&#x…

基于EBAZ4205矿板的图像处理:16基于小波变换的图像分解及其重建

基于EBAZ4205矿板的图像处理&#xff1a;17基于小波变换的图像分解及其重建 特别说明 这个项目的代码不会开源&#xff0c;因为这个项目的一大部分是在实习的公司做的&#xff0c;所以仅提供思路和展示&#xff0c;展示一下我的能力。 先看效果 这次让小牛和小绿做模特 经过…

C++模板初阶,只需稍微学习;直接起飞;泛型编程

&#x1f913;泛型编程 假设像以前交换两个函数需要&#xff0c;函数写很多个或者要重载很多个&#xff1b;那么有什么办法实现一个通用的函数呢&#xff1f; void Swap(int& x, int& y) {int tmp x;x y;y tmp; } void Swap(double& x, double& y) {doubl…

胤娲科技:AI短视频——创意无界,即梦启航

在这个快节奏的时代&#xff0c;你是否曾梦想过用几秒钟的短视频&#xff0c;捕捉生活中的每一个精彩瞬间&#xff1f;是否曾幻想过&#xff0c;即使没有专业的摄影和剪辑技能&#xff0c;也能创作出令人惊艳的作品&#xff1f; 现在&#xff0c;这一切都不再是遥不可及的梦想。…

微前端学习以及分享

微前端学习以及分享 注&#xff1a;本次分享demo的源码github地址&#xff1a;https://github.com/rondout/micro-frontend 什么是微前端 微前端的概念是由ThoughtWorks在2016年提出的&#xff0c;它借鉴了微服务的架构理念&#xff0c;核心在于将一个庞大的前端应用拆分成多…

从MySQL到OceanBase离线数据迁移的实践

本文作者&#xff1a;玉璁&#xff0c;OceanBase 生态产品技术专家。工作十余年&#xff0c;一直在基础架构与中间件领域从事研发工作。现负责OceanBase离线导数产品工具的研发工作&#xff0c;致力于为 OceanBase 建设一套完善的生态工具体系。 背景介绍 在互联网与云数据库技…

LEAP 瞬移工具场景试点游戏关卡

你是否厌倦了在Unity编辑器中浪费时间浏览大型游戏关卡&#xff1f;不要看得比Leap更远&#xff01;这个功能强大的编辑器脚本允许您只需单击一下即可即时传输到场景中的任何位置。告别繁琐的手动导航&#xff0c;迎接闪电般快速的关卡设计。有了Leap&#xff0c;你就可以专注于…

Gin框架官方文档详解04:HTTP/2 推送,JSON相关

官方文档&#xff1a;https://gin-gonic.com/zh-cn/docs/ 注&#xff1a;强烈建议没用过Gin的读者先阅读第一节&#xff1a;第一个Gin应用。 目录 一、HTTP/2 推送二、JSONP三、PureJSON四、SecureJSON五、总结 一、HTTP/2 推送 首先&#xff0c;以“04HTTP2server推送”为根目…

linux 时区问题

一、修改系统时间和时区 查看当前下系统时间和时区 timedatectl设置系统时区 ​sudo timedatectl set-timezone <时区>​&#xff0c;例如&#xff1a; sudo timedatectl set-timezone Asia/Shanghai执行成功则没有输出。 更推荐使用 tzselect​ ​命令&#xff0c;…

通信工程学习:什么是VHDL超高速集成电路硬件描述语言

VHDL&#xff1a;超高速集成电路硬件描述语言 VHDL&#xff0c;全称为Very-High-Speed Integrated Circuit Hardware Description Language&#xff0c;即超高速集成电路硬件描述语言&#xff0c;是一种用于电路设计的高级语言。以下是关于VHDL的详细介绍&#xff1a; 一、起源…

ThingsBoard规则链节点:Split Array Msg节点详解

引言 拆分数组消息节点简介 用法 含义 应用场景 实际项目运用示例 智能仓储管理系统 智能电网监控系统 车联网平台 结论 引言 ThingsBoard是一个功能强大的物联网平台&#xff0c;它提供了设备管理、数据收集与处理以及实时监控等核心功能。其规则引擎允许用户定义复杂…

时序图分析(IIC通信为例)

一、时序图分析&#xff08;IIC通信为例&#xff09; 时序图-->编程 解析&#xff1a;时序概念&#xff1a;一般指可编程器件的编程方法&#xff0c;在单片机编程时&#xff0c;需要根据被控芯片的时序去写程序&#xff0c;把芯片上的时序用代码来实现&#xff0c;方可实…

数据结构4——栈

1. 栈的概念及结构 栈的概念&#xff1a; 栈是一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则…

<Linux> 线程池

一、线程池 1. 池化技术 池化技术是一种在计算机科学中广泛应用的优化技术&#xff0c;它的核心思想是&#xff1a;预先创建并维护一组资源&#xff08;例如线程、连接、对象&#xff09;&#xff0c;供多个任务共享使用&#xff0c;以减少创建和销毁资源的开销&#xff0c;提…