常用限流算法详解

后端bug开发工程师
• 阅读 288

一、有哪些常用的限流算法

1.固定窗口限流; 2.滑动窗口限流; 3.漏桶算法限流; 4.令牌桶算法限流。

二、4种限流算法介绍

1.固定窗口限流

举例说明:假设时间窗口大小为5s,则0到5s为第一个窗口,5到10s为第二个窗口,依次类推。 假设限流规则为:5s内只能接受5个请求,如果在第1s的时候过来6个请求,那第6个请求会被拒绝,以及后续过来的请求都会被拒绝,知道开启一个新的窗口即第二个窗口,计数累计从0再开始。

考虑以下场景: 在第4s的时候过来5个请求,按照算法这四个请求都会被处理,然后在第6s的时候也过来5个请求,按照算法这是第二个窗口的请求了,也能够被正常处理。也就是在第4s到第6s这个时间段内处理了10个请求,这其实没有达到我们预期的限流规则:5s内只能接受5个请求。

以上场景就是固定窗口限流最大的问题:第一个窗口与第二个窗口临界没有平滑过度!

2.滑动窗口

为了解决固定窗口限流的临界问题,就引入了滑动窗口算法。那滑动窗口是如何解决的呢? 滑动窗口首先把1个固定窗口再细分为多个小窗口,继续接着上文的例子,把0到5s拆分为5个小窗口:0-1, 1-2, 2-3, 3-4, 4-5;同理6到10s也拆分为5个小窗口:5-6, 6-7, 7-8, 8-9, 9-10;然后每个小窗口都有自己的计数 (1)在第5s的时候统计的是:0-1, 1-2, 2-3, 3-4, 4-5这5个小窗口的计数之和; (2)在第6s的时候统计的是:1-2, 2-3, 3-4, 4-5, 5-6这5个小窗口的计数之后; (3)在第7s的时候统计的是:2-3, 3-4, 4-5, 5-6, 6-7这5个小窗口的计数之后; 依次类推 再回到上文中,第4s有5个请求过来,第6s有5个请求过来,按照滑动窗口的算法,第6s过来的5个请求会全部被拒绝,这就解决了固定窗口的临界问题。 总结:通过把固定窗口拆分为更小的窗口的方式,来解决临界问题,窗口拆分的越细,限流就会越精确,但是算法的时间复杂度和空间复杂度也会越高。

3.漏桶算法

简单描述就是水流入速度不固定,但是流出速度是固定的,如果桶满了,水就漏出来了。单机代码实现如下:

public class LeakyBucketRateLimiter extends MyRateLimiter {
    // 桶的容量
    private final int capacity;
    // 漏出速率
    private final int permitsPerSecond;
    // 剩余水量
    private long leftWater;
    // 上次注入时间
    private long timeStamp = System.currentTimeMillis();

    public LeakyBucketRateLimiter(int permitsPerSecond, int capacity) {
        this.capacity = capacity;
        this.permitsPerSecond = permitsPerSecond;
    }

    @Override
    public synchronized boolean tryAcquire() {
        //1. 计算剩余水量
        long now = System.currentTimeMillis();
        long timeGap = (now - timeStamp) / 1000;
        leftWater = Math.max(0, leftWater - timeGap * permitsPerSecond);
        timeStamp = now;

        // 如果未满,则放行;否则限流
        if (leftWater < capacity) {
            leftWater += 1;
            return true;
        }
        return false;
    }
}

漏桶算法不能应对突发流量,不能充分利用系统资源,适用于流量绝对平滑的场景

4.令牌桶算法

令牌痛算法就是按照固定速率往桶里面丢令牌,如果桶满了则不会继续丢,每个请求过来的时候都会去桶里面获取令牌,如果获取到则继续处理请求,否则拒绝请求。令牌桶算法是对漏桶算法的优化,允许系统的突发流量,但是又能保证平滑限流。 单机代码如下:

public class TokenBucketRateLimiter extends MyRateLimiter {
    /**
     * 令牌桶的容量「限流器允许的最大突发流量」
     */
    private final long capacity;
    /**
     * 令牌发放速率
     */
    private final long generatedPerSeconds;
    /**
     * 最后一个令牌发放的时间
     */
    long lastTokenTime = System.currentTimeMillis();
    /**
     * 当前令牌数量
     */
    private long currentTokens;

    public TokenBucketRateLimiter(long generatedPerSeconds, int capacity) {
        this.generatedPerSeconds = generatedPerSeconds;
        this.capacity = capacity;
    }

    /**
     * 尝试获取令牌
     *
     * @return true表示获取到令牌,放行;否则为限流
     */
    @Override
    public synchronized boolean tryAcquire() {
          /**
           * 计算令牌当前数量
           * 请求时间在最后令牌是产生时间相差大于等于额1s(为啥时1s?因为生成令牌的最小时间单位时s),则
           * 1. 重新计算令牌桶中的令牌数
           * 2. 将最后一个令牌发放时间重置为当前时间
           */
        long now = System.currentTimeMillis();
        if (now - lastTokenTime >= 1000) {
            long newPermits = (now - lastTokenTime) / 1000 * generatedPerSeconds;
            currentTokens = Math.min(currentTokens + newPermits, capacity);
            lastTokenTime = now;
        }
        if (currentTokens > 0) {
            currentTokens--;
            return true;
        }
        return false;
    }
}

分布式基于redis+lua代码如下:

local ratelimit_info = redis.pcall('HMGET',KEYS[1],'last_time','current_token')
local last_time = ratelimit_info[1]
local current_token = tonumber(ratelimit_info[2])
local max_token = tonumber(ARGV[1])
local token_rate = tonumber(ARGV[2])
local current_time = tonumber(ARGV[3])
local reverse_time = 1000/token_rate
if current_token == nil then
  current_token = max_token
  last_time = current_time
else
  local past_time = current_time-last_time
  local reverse_token = math.floor(past_time/reverse_time)
  current_token = current_token+reverse_token
  last_time = reverse_time*reverse_token+last_time
  if current_token>max_token then
    current_token = max_token
  end
end
local result = 0
if(current_token>0) then
  result = 1
  current_token = current_token-1
end
redis.call('HMSET',KEYS[1],'last_time',last_time,'current_token',current_token)
redis.call('pexpire',KEYS[1],math.ceil(reverse_time*(max_token-current_token)+(current_time-last_time)))
return result

代码参考:https://www.infoq.cn/article/qg2tx8fyw5vt-f3hh673

5.限流算法整体对比

(1)QPS 限流算法(固定窗口和滑动窗口)通过限制单位时间内允许通过的请求数来限流。

优点:

计算简单,是否限流只跟请求数相关,放过的请求数是可预知的(令牌桶算法放过的请求数还依赖于流量是否均匀),比较符合用户直觉和预期。

可以通过拉长限流周期来应对突发流量。如 1 秒限流 10 个,想要放过瞬间 20 个请求,可以把限流配置改成 3 秒限流 30 个。拉长限流周期会有一定风险,用户可以自主决定承担多少风险。

缺点: 放过的请求不均匀。突发流量时,请求总在限流周期的前一部分放过。如 10 秒限 100 个,高流量时放过的请求总是在限流周期的第一秒。

(2)令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。

优点:

放过的流量比较均匀,有利于保护系统。

存量令牌能应对突发流量,很多时候,我们希望能放过脉冲流量。而对于持续的高流量,后面又能均匀地放过不超过限流值的请求数。

缺点:

存量令牌没有过期时间,突发流量时第一个周期会多放过一些请求,可解释性差。即在突发流量的第一个周期,默认最多会放过 2 倍限流值的请求数。

实际限流数难以预知,跟请求数和流量分布有关。

点赞
收藏
评论区
推荐文章
MaxSky MaxSky
3年前
Lumen 中对 Dingo API 异常接管并自定义响应结果
场景描述比如我们需要对API限流抛出的异常进行接管,并重写响应消息,首先应用中间件:phpuseDingo\Api\Routing\Router;$apigroup('middleware''api.throttle',//限流中间件'expires'1,//时间范围,单位“分”'lim
3A网络 3A网络
1年前
Redis 做接口限流
Redis除了做缓存,还能干很多很多事情:分布式锁、限流、处理请求接口幂等性。。。太多太多了~今天想和小伙伴们聊聊用Redis处理接口限流,这也是最近的TienChin项目涉及到这个知识点了,我就拎出来和大家聊聊这个话题。1.准备工作首先我们创建一个SpringBoot工程,引入Web和Redis依赖,同时考虑到接口限流一般是通过
happlyfox happlyfox
3年前
go每日一库 [go-rate] 速率限制器
关于我gorate是速率限制器库,基于TokenBucket(令牌桶)算法实现。gorate被用在生产中用于遵守GitHubAPI速率限制。速率限制可以完成一些特殊的功能需求,包括但不限于服务器端垃圾邮件保护、防止api调用饱和等。库使用说明构造限流器我们首先构造一个限流器对象:golimiter:NewLimi
Chase620 Chase620
3年前
简析限流算法
简析限流算法1.简介限流顾名思义是限制流量,限制流量的目的是为了保障服务稳定运行,避免服务被流量冲垮。当流量超出服务处理能力时,部分请求将会被限流组件拦截。被拦截的请求可能会被丢弃,如果是C端请求,那么这个请求可能会被导向指定的错误页上,而不是生硬的拒绝。这里我们丢
高并发场景下常见的限流算法及方案介绍
现代互联网很多业务场景,比如秒杀、下单、查询商品详情,最大特点就是高并发,而往往我们的系统不能承受这么大的流量,这时候限流熔断就发挥作用了,限制请求数,快速失败,保证系统满负载又不超限。本文为大家介绍几种常见的限流算法及方案
Stella981 Stella981
2年前
Spring Cloud Gateway 扩展支持动态限流
之前分享过一篇《SpringCloudGateway原生的接口限流该怎么玩》(https://my.oschina.net/giegie/blog/1838560),核心是依赖SpringCloudGateway默认提供的限流过滤器来实现原生RequestRateLimiter的不足
Stella981 Stella981
2年前
Sentinel 是如何做限流的
限流是保障服务高可用的方式之一,尤其是在微服务架构中,对接口或资源进行限流可以有效地保障服务的可用性和稳定性。之前的项目中使用的限流措施主要是Guava的RateLimiter。RateLimiter是基于令牌桶流控算法,使用非常简单,但是功能相对比较少。而现在,我们有了一种新的选择,阿里提供的Sentinel。Sentinel是阿里巴巴提供
Stella981 Stella981
2年前
Spring Boot的接口限流应用
阅读目录:1\.前言2\.算法介绍计数器法3\.算法介绍滑动窗口4\.算法介绍漏桶算法5\.算法介绍令牌桶算法前言在一个高并发系统中对流量的把控是非常重要的,当巨大的流量直接请求到我们的服务器上没多久就可能造成接口不可用
京东云开发者 京东云开发者
11个月前
限速神器RateLimiter源码解析 | 京东云技术团队
作者:京东科技李玉亮目录指引限流场景软件系统中一般有两种场景会用到限流:•场景一、高并发的用户端场景。尤其是C端系统,经常面对海量用户请求,如不做限流,遇到瞬间高并发的场景,则可能压垮系统。•场景二、内部交易处理场景。如某类交易任务处理时有速率要求,再如上
京东云开发者 京东云开发者
8个月前
关于自动限流的思考 | 京东云技术团队
目标保证系统不因流量过载而挂。现状:人工限流正常的微服务限流工具都需要人工配置:支持应用负责人事先配置限流规则(接口调用方限流阈值),流量在阈值以下可以正常响应,超过阈值的流量会快速失败。这种方案存在如下问题:问题1\.接口多,无法全面覆盖要想保证系统