限流算法详解

news2024/10/2 3:31:15

限流是我们经常会碰到的东西,顾名思义就是限制流量。它能保证我们的系统不会被突然的流量打爆,保证系统的稳定运行。像我们生活中,地铁就会有很多护栏,弯弯绕绕的,这个就是一种限流。像我们抢茅台,肯定大部分流量也是会被限流的,你的请求可能根本没进到下单等环节就被拦截了。

计数限流

最简单的限流算法就是计数限流了,例如系统能同时处理 100 个请求,保存一个计数器,处理了一个请求,计数器加一,一个请求处理完毕之后计数器减一。

每次请求来的时候看看计数器的值,如果超过阈值要么拒绝。

非常的简单粗暴,计数器的值要是存内存中就算单机限流算法。存中心存储里,例如 Redis 中,集群机器访问就算分布式限流算法。

优点就是:简单粗暴,单机在 Java 中可用 Atomic 等原子类、分布式就 Redis incr。

缺点就是:只有数量,没有时间的概念。一秒处理100个请求,和一分钟处理100个请求是不一样的。

固定窗口限流

它相比于计数限流主要是多了个时间窗口的概念。计数器每过一个时间窗口就重置。 规则是

在时间窗口内,累加访问次数,这个时间窗口过了之后,计数器清零;

缺点就是在临界时间如果出现突发流量的情况,会超过阈值。

——|——|——

0 1 2

滑动窗口限流

滑动窗口限流解决固定窗口临界值的问题,可以保证在任意时间窗口内都不会超过阈值。

相对于固定窗口,滑动窗口除了需要引入计数器之外还需要记录时间窗口内每个请求到达的时间点,因此对内存的占用会比较多

规则如下,假设时间窗口为 1 秒:

  • 记录每次请求的时间

  • 统计每次请求的时间 至 往前推 1 秒这个时间窗口内请求数,并且 1 秒前的数据可以删除。

  • 统计的请求数小于阈值就记录这个请求的时间,并允许通过,反之拒绝。

在这里插入图片描述

但是滑动窗口和固定窗口都无法解决短时间之内集中流量的突击

我们所想的限流场景,例如每秒限制 100 个请求。希望请求每 10ms 来一个,这样我们的流量处理就很平滑,但是真实场景很难控制请求的频率。因此可能存在 5ms 内就打满了阈值的情况。

当然对于这种情况还是有变型处理的,例如设置多条限流规则。不仅限制每秒 100 个请求,再设置每 10ms 不超过 2 个。

漏桶算法

如下图所示,水滴持续滴入漏桶中,底部定速流出。如果水滴滴入的速率大于流出的速率,当存水超过桶的大小的时候就会溢出。

规则如下:

  • 请求来了放入桶中

  • 桶内请求量满了拒绝请求

  • 服务定速从桶内拿请求处理

在这里插入图片描述

优点既是缺点,流量处理太过平滑。面对突发请求,服务的处理速度和平时是一样的,这其实不是我们想要的,在面对突发流量我们希望在系统平稳的同时,提升用户体验即能更快的处理请求,而不是和正常流量一样,循规蹈矩的处理

令牌桶算法

令牌桶其实和漏桶的原理类似,只不过漏桶是定速地流出,而令牌桶是定速地往桶里塞入令牌,然后请求只有拿到了令牌才能通过,之后再被服务器处理。

当然令牌桶的大小也是有限制的,假设桶里的令牌满了之后,定速生成的令牌会丢弃。

规则:

  • 定速的往桶内放入令牌

  • 令牌数量超过桶的限制,丢弃

  • 请求来了先向桶内索要令牌,索要成功则通过被处理,反之拒绝

一般而言我们不需要自己实现限流算法来达到限流的目的,不管是接入层限流还是细粒度的接口限流其实都有现成的轮子使用,其实现也是用了上述我们所说的限流算法。

比如Google Guava 提供的限流工具类 RateLimiter,是基于令牌桶实现的,并且扩展了算法,支持预热功能。

阿里开源的限流框架 Sentinel 中的匀速排队限流策略,就采用了滑动窗口。

Nginx 中的限流模块 limit_req_zone,采用了漏桶算法,还有 OpenResty 中的 resty.limit.req库等等。

Guava如何实现令牌桶算法

根据令牌桶的原理,我们可能第一直觉会觉得使用生产者-消费者模式,一个线程定时向阻塞队列添加令牌,而请求作为消费线程去获取令牌。如果并发量不大的情况,这个实现没有什么问题。但一般情况下,使用限流都是高并发的场景,而且系统压力已经临界极限了,这个时候cpu忙碌,放令牌的线程可能没法被及时唤醒,造成放令牌延迟,同时定时器会创建调度线程,也会对系统性能产生影响。

所以Guava没有使用定时线程。它的办法很简单,就是通过经过的时间来判断有多少令牌产生,并保存下目前的令牌数。

举个例子,假设限制1s一个请求。启动之后,刚开始没有请求,到10秒的时候来了个请求。因为过了10s,所以就可以判断有10个令牌产生,然后消耗一个,剩9个令牌,记录下来。又过了10s再来了一个请求。那么又有10个令牌产生,消耗一个,还剩18个令牌。

通过这种简单的方式,就可以实现令牌桶了。

  public double acquire(int permits) {
    long microsToWait = reserve(permits); 
    // sleep的时间
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return 1.0 * microsToWait / SECONDS.toMicros(1L);
  }
  final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
    resync(nowMicros);
    long returnValue = nextFreeTicketMicros;
    // 需要消耗储存的令牌的数量
    double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
    // 需要等待新创建的令牌的数量
    double freshPermits = requiredPermits - storedPermitsToSpend;
    // 需要等待的时间
    long waitMicros =
        storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
            + (long) (freshPermits * stableIntervalMicros);
    // 下次令牌生成的时间
    this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
    this.storedPermits -= storedPermitsToSpend;
    return returnValue;
  /**
   * Updates {@code storedPermits} and {@code nextFreeTicketMicros} based on the current time.
   */
  void resync(long nowMicros) {
    // if nextFreeTicket is in the past, resync to now
    if (nowMicros > nextFreeTicketMicros) {
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
      // 存储新生成的令牌
      storedPermits = min(maxPermits, storedPermits + newPermits);
      nextFreeTicketMicros = nowMicros;
    }
  }

sentinel滑动窗口算法

    @Override
    public void addPass(int count) {
        WindowWrap<MetricBucket> wrap = data.currentWindow();
        wrap.value().addPass(count);
    }
public WindowWrap<T> currentWindow(long timeMillis) {
        if (timeMillis < 0) {
            return null;
        }

        int idx = calculateTimeIdx(timeMillis);
        // Calculate current bucket start time.
        long windowStart = calculateWindowStart(timeMillis);

        /*
         * Get bucket item at given time from the array.
         *
         * (1) Bucket is absent, then just create a new bucket and CAS update to circular array.
         * (2) Bucket is up-to-date, then just return the bucket.
         * (3) Bucket is deprecated, then reset current bucket.
         */
        while (true) {
            WindowWrap<T> old = array.get(idx);
            if (old == null) {
                WindowWrap<T> window = new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
                if (array.compareAndSet(idx, null, window)) {
                    // Successfully updated, return the created bucket.
                    return window;
                } else {
                    // Contention failed, the thread will yield its time slice to wait for bucket available.
                    Thread.yield();
                }
            } else if (windowStart == old.windowStart()) {
                return old;
            } else if (windowStart > old.windowStart()) {
                if (updateLock.tryLock()) {
                    try {
                        // Successfully get the update lock, now we reset the bucket.
                        return resetWindowTo(old, windowStart);
                    } finally {
                        updateLock.unlock();
                    }
                } else {
                    // Contention failed, the thread will yield its time slice to wait for bucket available.
                    Thread.yield();
                }
            } else if (windowStart < old.windowStart()) {
                // Should not go through here, as the provided time is already behind.
                return new WindowWrap<T>(windowLengthInMs, windowStart, newEmptyBucket(timeMillis));
            }
        }
    }

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

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

相关文章

案例17-环境混用带来的影响

目录一、背景介绍背景事故二、思路&方案三、过程四、总结nginx做转发fastdfs&#xff08;文件上传下载&#xff09;五、升华一、背景介绍 本篇博客主要介绍开发中项目使用依赖项环境闭一只带来的恶劣影响&#xff0c;在错误中成长进步。 背景 本公司另外一个产品开发God…

爱因斯坦求和约定 含代码einsum

目录 一、简介 1.哑标 2.自由标 二、torch实现 1.计算迹 2.取矩阵对角线 3.计算外积 4.batch矩阵乘法 5.带有子列表和省略号 一、简介 爱因斯坦求和约定(Einstein summation convention)是一种标记的约定, 又称为爱因斯坦标记法(Einstein notation), 可以基于一些约定…

position:absolute详解

position:absolute详解 日常开发中&#xff0c;经常会涉及元素的定位&#xff0c;我们都知道&#xff0c;绝对定位相对于最近position不为static的父级元素来定位&#xff0c;但其中定位的位置还是有细微的差别的。 绝对定位根据left和top属性来规定绝对定位元素的位置。 基…

2023年前端面试题集锦

2023年又是行情惨淡的一年&#xff0c;为此我从 「枇杷村IT面试宝典」小程序里收集了一些题目&#xff0c;更多题目可以扫下方二维码查看 现做个总结如下&#xff1a; 1. 在JavaScript中, 0 -0的结果是什么&#xff1f; 结果为true&#xff01; 严格等于比较的是值和类型&…

tcpdump写文件-w文件大小为0字节问题处理

一同事找来&#xff0c;说用tcpdump在一台linux服务器上抓包写文件&#xff0c;文件大小为0&#xff0c;不知道是什么原因造成&#xff0c;让协助解决。 自己登陆服务器试了一下&#xff0c;发现问题确实如此 不用-w&#xff0c;让打印在平面上&#xff0c;发现正常 以为权限不…

虹科案例 | 如何可持续的对变压器进行温度监控?

为了延长变压器的使用寿命&#xff0c;需要一个测量系统来监测内部整个绕组区域的温度。它必须明确温度升高发生的位置及其强度。您可以在此处了解为什么会这样以及如何在实践中实施? PART 1 变压器多点测温问题 变压器的工作温度越高&#xff0c;使用寿命越短。这里主要存在…

C++核心知识(五)—— 继承和派生

1. 继承概述1.1 为什么需要继承网页类 class IndexPage{ public://网页头部void Header(){cout << "网页头部!" << endl;}//网页左侧菜单void LeftNavigation(){cout << "左侧导航菜单!" << endl;}//网页主体部分void MainBody(){…

[SQL Statements] 基本的SQL知识 之DDL针对表结构和表空间的基本操作

[SQL Statements] 基本的SQL知识 之DDL针对表结构和表空间的基本操作 什么是数据库的表以及表空间 在MySQL中&#xff0c;一个数据库可以包含多个表&#xff0c;每个表是由若干个列&#xff08;column&#xff09;和行&#xff08;row&#xff09;组成的。表是存储数据的基本…

2023年clang12编译问题与解决的记录

最近编译clang12以及尝试基于clang开发一个C的静态代码分析工具&#xff0c;如下是遇到的环境相关的编译问题与解决方案。在此做个记录&#xff0c;同时供可能会遇到同样问题的同学参考 环境说明 注&#xff1a;如下是最终编译成功的环境 clang分支&#xff1a; branch : a…

HTTP 请求头中的 Remote_Addr,X-Forwarded-For,X-Real-IP | Spring Cloud 13

一、$remote_addr 表示发出请求的客户端主机的 IP 地址&#xff0c;但它的值不是由客户端提供的&#xff0c;而是Nginx与客户端进行TCP连接过程中&#xff0c;获得的客户端的真实地址 IP 地址&#xff0c;REMOTE_ADDR 无法伪造&#xff0c;因为建立 TCP 连接需要三次握手&…

Java学习教程,Java基础教程(从入门到精通)

Java 是一门面向对象编程语言&#xff0c;不仅吸收了 C 语言的各种优点&#xff0c;还摒弃了 C 里难以理解的多继承、指针等概念。Java 不但可以用来开发网站后台、PC 客户端和 Android APP&#xff0c;还在数据分析、网络爬虫、云计算领域大显身手。 从学术的角度讲&#xff…

VR全景云展厅,实现7*24小时的线上宣传能力!

数字化时代&#xff0c;虚拟现实技术的应用越来越广泛&#xff0c;其中VR全景云展厅是一种新兴的展示方式&#xff0c;具有独特的展示优势。随着VR技术的不断发展&#xff0c;越来越多的企业、机构和个人开始使用VR全景云展厅来展示他们的产品和服务。一、展厅营销痛点1、实地到…

内网渗透-基础环境

解决依赖&#xff0c;scope安装 打开要给cmd powershell 打开远程 Set-ExecutionPolicy RemoteSigned -scope CurrentUser; 我试了好多装这东西还是得科学上网&#xff0c;不然不好用 iwr -useb get.scoop.sh | iex 查看下载过的软件 安装sudo 安装git 这里一定要配置bu…

105.第十九章 MySQL数据库 -- MySQL半同步复制、复制过滤器、复制的问题和解决方案(十五)

6.1.6 半同步复制 Mysql的主从复制它的复制机制我们称为所谓的异步复制,这里面提到了一个概念异步,那什么叫异步复制呢,所谓异步复制实际上说白了就是在用户发请求到数据库做一些修改的时候,那我们在前面讲过主从复制,如果我们有一个主节点,另外带若干个从节点,假设有2个…

Typescript 全栈最值得学习的技术栈 TRPC

如果你想成为一个 Typescript 全栈工程师&#xff0c;那么你可能需要关注一下 tRPC 框架。本文总共会接触到以下主要技术栈。Next.jsTRPCPrismaZodAuth.js不是介绍 tRPC 吗&#xff0c;怎么突然出现这么多技术栈。好吧&#xff0c;主要这些技术栈都与 typescript 相关&#xff…

知道一个服务器IP应该怎么进入

首先我是国内&#xff0c;访问国外的网站比如谷歌等&#xff0c;访问特别慢&#xff0c;有时候甚至登录不进去。现在知道了一个台湾或者国外的服务器应该怎么登录进去呢&#xff1f;知道服务器IP之后&#xff0c;你还需要知道服务器的远程端口帐号密码才能登录的。知道上面信息…

Java 19和IntelliJ IDEA,如何和谐共生?

Java仍然是目前比较流行的编程语言&#xff0c;它更短的发布节奏让开发者每六个月左右就可以试用新的语言或平台功能&#xff0c;IntelliJ IDEA帮助我们更流畅地发现和使用这些新功能。IntelliJ IDEA v2022.3正式版下载(Q技术交流&#xff1a;786598704&#xff09;在本文中&am…

小樽C++ 多章⑧ (叁) 指针与字符串、(肆) 函数与指针

目录 叁、函数与字符串 肆、函数与指针 4.1 指针作为函数参数 4.2 函数返回指针 4.3 函数指针与函数指针数组 4.4 结构体指针 ​​​​​​​​​​​​​​小樽C 多章⑧ (壹) 指针变量https://blog.csdn.net/weixin_44775255/article/details/129031168 小樽C 多章⑧ …

数据可视化的正确逻辑和关键点

由于移动互联网和手机的普及发展&#xff0c;截至2022年6月&#xff0c;我国短视频的用户规模达9.62亿&#xff1b;即时通信用户规模达10.27亿&#xff1b;网络新闻用户规模达7.88亿&#xff1b;网络直播用户规模达7.16亿......这些数据都意味着互联网已经涉及我们的方方面面&a…

Java并发编程与API详解

文章目录前言操作系统——进程和线程进程进程组成进程状态进程控制进程创建进程终止进程阻塞和唤醒进程通信线程线程组成线程状态线程控制线程的实现方式用户线程内核线程混合方式CPU调度调度的层次调度的实现调度器调度的时机、切换与过程进程调度的方式闲逛进程两种线程的调度…