首页 > 其他分享 >高可用之限流-05-slide window 滑动窗口

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

时间:2024-10-14 23:01:01浏览次数:9  
标签:窗口 05 0.0 算法 window 限流 context 滑动

限流系列

开源组件 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中的令牌桶算法的实现

标签:窗口,05,0.0,算法,window,限流,context,滑动
From: https://www.cnblogs.com/houbbBlogs/p/18466386

相关文章

  • 代码随想录算法训练营第三十二天|122.买卖股票的最佳时机 II 55. 跳跃游戏 45.跳跃游
    122.买卖股票的最佳时机II给定一个数组,它的第 i个元素是一支给定股票第i天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。示例1:输入:[7,1,5......
  • 题解:P2315 [HNOI2005] 数三角形
    ProblemLink[HNOI2005]数三角形题意输入一个大三角形的各个边存在情况,输出里面有多少个正三角形。Solution简单暴力即可,用\(4\)个数组维护每条边能延伸的最大长度,然后逐个判断三角形是否可行即可。如图,l_upper维护左端点向上(即$\ell_{BA}$),l_lower维护左端点向下(即......
  • springboot 大学生兼职平台系统-计算机毕业设计源码05282
    摘  要在当代大学生活中,兼职工作已经成为了许多学生的重要组成部分。校园兼职现象的普遍性及其对大学生生活的影响不容忽视。然而,现有的校园兼职系统往往存在信息不对称、管理不规范等问题。因此,我们需要深入理解校园兼职现象,分析其对大学生生活的影响,并探讨如何优化和改进......
  • Windows11下安装wsl报错:无法解析服务器的名称或地址
    问题描述之前在自己的笔记本电脑(Windows10)上下载安装WSL很顺利,具体教程见前面的文章,但是在新电脑(Windows11)上下载就报错:无法解析服务器的名称或地址,按照网上说的两个解决方案:修改 DNS 为手动114.114.114.114;查询 raw.githubusercontent.com 这个域名对应的能ping通的ip,......
  • Windows11右键菜单一取消/恢复Win11二级菜单
    Win11更新后,右键菜单很多功能使用时需要点击“显示更多选型”才能获取完整功能,以下有几种方法可以自动展开Win11的二级菜单,恢复到Win10模式。​方法一:reg1.按住win+R打开运行窗口​2.输入【regedit】进入注册表编辑器定位到以下位置:计算机\HKEY_CURRENT_USER\Software\Cl......
  • 使用vmware个人版下载安装windows xp系统
    首先是安装个人版的VMWare,现在已经提供了个人版进行免费使用,所以如果是个人使用直接去官网下载正版就行了官网下载的方法使用下载安装VMWare的详细步骤当我们按照上面的步骤安装好了个人版本的VMWare之后,下载一个windowsxp的镜像文件下载WinddowsXP的ed2k地址使用迅雷进行......
  • [Windows]文件搜索利器Everything(附zip)
    前言写代码过程中,老大突然发一条信息老大:这周周报发一下。我:好的。然后我就显示桌面打开-我的电脑找到E盘,找到周报文件夹寻找到所有周报中今天的周报复制发送当我用上Everything之后打开,输入周报copy发送Everything是一款高效实用的系统文件搜索工具,相信大部分......
  • aardio入门到精通05-名字空间
    名字空间importconsole;/*名字空间组织、归类、标识一组具名对象的名字,是模块化编程的重要基础。1.var定义的局部变量有保护变量的作用,其它文件不能调用2.成员变量是名字空间里的变量,加前缀名字空间名来访问,在全局名字空间里可以不加前缀名字空间名3.不同的名字空间,相......
  • [Windows]文件搜索利器Everything(附zip)
    前言写代码过程中,老大突然发一条信息老大:这周周报发一下。我:好的。然后我就显示桌面打开-我的电脑找到E盘,找到周报文件夹寻找到所有周报中今天的周报复制发送当我用上Everything之后打开,输入周报copy发送Everything是一款高效实用的系统文件搜索工具,相信大部分坛友......
  • jsp大学新生军训管理系统57a05(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表教官,学生,连队,教官评价,军训项目,考核制度,军训风采,考核成绩,应急知识开题报告内容一、研究背景与意义大学新生军训是高等教育的重要组成部分,旨在培养学生......