常用的限流算法

本文深入解析了四种常见限流算法:计数器、滑动窗口、漏桶和令牌桶,详细介绍了它们的工作原理、优缺点及应用场景。通过对比,帮助读者理解不同场景下选择合适算法的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

限流主要是请求并发上来的时候,在服务器处理能力不足的情况下,限定服务器的请求数量,保证服务器的平稳响应

常见的限流算法有

漏桶 令牌桶 滑动窗口计数

分类

按照记数范围,可以分为:单机限流 \全局限流
单机限流,一般是为了应对突发流量
全局限流,通常是为了给有限资源进行流量分配

漏桶算法

在这里插入图片描述
上图为漏桶算法,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大时,会直接溢出,可以看出漏桶算法能强行限制数据的传输速率,楼桶算法(leaky Bucket)是网络世界中流量整型(traffic shaping)或速率限制(rate limiting)时经常使用的一种算法,它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量

实现方法

漏桶算法可以使用redis队列来实现,生产者发送消息前先检查队列长度时候超过阈值,超过法制值则被丢弃消息,否则发送消息到redis队列中;消费者以固定速率从redis队列中取消息.redis队列在这里起到一个缓冲池的作用,起到削峰填谷\流量整形的作用.
服务器的处理能力是一定的.
① 桶的容量是固定的(单位时间,例如1秒—key的有限期),请求来的时候,检查是否存在key,不超过阈值+1,超过阈值 ,拒绝请求
问题是当并发上来的时候,阈值假设是1000,此时是999,大量并发达到,就会都获取到999,进行+1,造成了某一时刻的假限流,只有超过才后请求的才被限流了

②针对以上问题,先设置好单位时间,例如1秒的key的数量为1000,请求来的时候去decr 减1,当值为<0的数的时候,进行请求等待,redis是串行的,即使有很多并发在key的value是1的时候大量去请求redis进行decr,但是当有个人进行decr后,第二人再去decr的时候返回的值是-1,此时进行了限流(此方法也是防超卖的思路)

伪代码:

class LeakyDemo{
private  $timeStamp;
public  $capacity;// 桶的容量
public  $rate; // 水漏出的速度
public  $water;// 当前水量(当前累积请求数)

public function __construct()
{
    $this->timeStamp = time();
}
public function grant(){
    $now = time();
    $this->water = max(0,$this->water - ($now-$this->timeStamp)*$this->rate);// 先执行漏水,计算剩余水量
    $this->timeStamp = $now;
    if(($this->water+1) < $this->capacity){
        // 尝试加水,并且水还未满
        $this->water+=1;
        return true;
    }else{
        // 水满,拒绝加水
        return false;
    }

}

}

令牌桶算法

在这里插入图片描述
对于很多很多应用场景来说,除了要求限制数据的平均传输速率外,还要求允许某种程度的的突发传输,这时候漏桶算法可能就不适合了,令牌桶算法更为合适,令牌桶算法的原理是系统会一一个恒定的速率往桶里放入令牌,而请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时候,请求被拒绝或者等到令牌的释放,桶里能够存放的最高数量,技术允许的突发传输量

一定时间内的令牌数量是一定的,消耗了一部分,会补充一部分,最大数不会超过最大的令牌数
具体实现
https://blog.youkuaiyun.com/fdipzone/article/details/79352685
开始看图,没有仔细看,没懂…后来看了概念,明白了!

其实是这样的!先以一个恒定的速率生成令牌,把令牌放到桶里!然后每进来一个请求,每个请求去桶里找,有没有令牌,如果有令牌,则”拿着”令牌,继续下一步处理!如果桶里没有令牌了,则这个处理可以”抛弃掉”

令牌桶的好处就是,可以允许匀速,也允许范围内的突发处理!

类似于 我桶容量是100! 这时候1s一个请求,令牌速度也是1s一个!那么速率是恒定的, 突然,来了100个请求,发现桶里有100个令牌,那么这100个可以立即处理了!这时速率是100!

伪代码:

class TokenBucketDemo{
    private  $timeStamp;
    public  $capacity;// 桶的容量
    public  $rate; // 令牌放入的速度
    public  $tokens;// 当前令牌的数量


    public function __construct()
    {
        $this->timeStamp = time();
    }
    public  function grant(){
        $now=time();
        $this->tokens=min(0,$this->tokens+($now-$this->timeStamp)*$this->rate);
        $this->timeStamp=$now;
        if($this->tokens<1){
            // 若不到1个令牌,则拒绝
            return false;
        }else{
            // 还有令牌,领取令牌
            $this->tokens -= 1;
            return true;
        }

    }

}

计数器

计数器是限流里最简单的,简单来说,比如 我限制1分钟内 请求数最多为60个! 当此刻 2018-02-27 16:23:00 到 2018-02-27 16:24:00 时间内,请求最多只能是60个!到了2018-02-27 16:24:00,把计数器归零! 周而复始!
在这里插入图片描述
但这种会有问题!比如我在前58s都不请求,而在最后一秒请求60次!这样的效果跟木有啥区别…

2018-02-27 16:52:38 +0800 CST | PHP, 算法, 限流,
计数器、滑动窗口、漏桶、令牌算法比较和伪代码实现

缓存:说白了,就是让数据尽早进入缓存,离程序近一点,不要大量频繁的访问DB。

降级:如果不是核心链路,那么就把这个服务降级掉。打个比喻,现在的APP都讲究千人千面,拿到数据后,做个性化排序展示,如果在大流量下,这个排序就可以降级掉!

限流:大家都知道,北京地铁早高峰,地铁站都会做一件事情,就是限流了!想法很直接,就是想在一定时间内把请求限制在一定范围内,保证系统不被冲垮,同时尽可能提升系统的吞吐量

限流常用的方式

计数器、滑动窗口、漏桶、令牌

计数器是限流里最简单的,简单来说,比如 我限制1分钟内 请求数最多为60个! 当此刻 2018-02-27 16:23:00 到 2018-02-27 16:24:00 时间内,请求最多只能是60个!到了2018-02-27 16:24:00,把计数器归零! 周而复始!

但这种会有问题!比如我在前58s都不请求,而在最后一秒请求60次!这样的效果跟木有啥区别…

伪代码:

class Counter
{
    protected $timeStamp;

    protected $limitCount = 100;

    protected $interval = 60;

    protected $reqCount = 0;

    public function __construct()
    {
        $this->timeStamp = strtotime(date('Y-m-d H:i').':00',time());
        $this->reqCount = Cache::get('reqCount');
    }

    public function doLimit()
    {
        $now = time();
        if ($now < $this->timeStamp + $this->interval) {
            if ($this->reqCount < $this->limitCount ) {
                $this->reqCount ++;
                Cache::put('reqCount',$this->reqCount,1);
                return true;
            } else {
                return false;
            }
        } else {
            Cache::put('reqCount',0,1);
            return false;
        }
    }

    public function test()
    {
        $res =  $this->doLimit();
        if ($res) {
            //执行正常业务
        } else {
            //拦截掉
        }
    }
}

窗口滑动计数

滑动窗口其实就是 细分之后的计数器!
在这里插入图片描述这样假设, 先把一分钟划分成6段! 也就是10s一个段!在第一段里,假如请求61次,那么直接触发了规则!肯定就过不去了!如果只请求了1次!则是正常的! 当时间走到第二个段里,即10s~20s这段范围里,我请求数不能超过总的限定条件,且当前段的请求数量 加上 之前段的总数量也不能超过总限定数量!

当时间到了50s~60s,依然是一样!

如果过了60s,所以请求数都是正常的,则把划分段往右移一段!那么此时的6个分段是 10 ~ 20,20 ~ 30,30 ~ 40,40 ~ 50,50 ~ 60,60 ~ 70

然后统计规则还跟上面一样!

所以,只有划分的越细,请求限制越平滑!
伪代码:

class SlidingWindow
{
    protected $timeStamp;

//限定时间内请求的最多次数
protected $limitCount = 100;

//时间间隔
protected $interval = 60;

//请求数
protected $reqCount = 0;

//分成多少份
protected $count = 6;

public function __construct()
{
    $this->timeStamp = strtotime(date('Y-m-d H:i').':00',time());
    $this->reqCount = Cache::get('reqCount');
}

public function grant()
{
    $allCounter = Cache::get('allCounterArr');
    $nowMinute = date('Y-m-d H:i');

    if (empty($allCounter)) {
        for ($i=1;$i< $this->count;$i++) {
            $key = date('Y-m-d H:i',strtotime($nowMinute.'00') +  ($i * 60));
            $allCounter[$key] = 0;
        }
        Cache::put('allCounterArr',$allCounter,10);
    }

    //当所有间隔的总和大于限制条数 开始限流 || 当前分区请求量大于限定条数, 开始限流
    if (array_sum($allCounter) > $this->limitCount || $allCounter[$nowMinute] > $this->limitCount) return false;

    $allCounter[$nowMinute]++;

    //如果当前区块是最后一块,那么整体右移一个区块
    if (key(end($allCounter)) ==  $nowMinute) {
        array_shift($allCounter);
        $allCounter[date('Y-m-d H:i',strtotime($nowMinute.'00') +  60)] = 0;
        Cache::put('allCounterArr',$allCounter,10);
    }
    return true;
}

public function test()
{
    $res = $this->grant();

    if ($res) {
        //执行正常程序
    } else {
        //进行限流
    }
}
}

计数法是限流算法里面最好理解的一种,该方法统计最近一段时间的请求量,如果超过一定的阈值,就开始限流,在tcp网络协议中,也是用到了滑动窗口来限制数据传输速率

滑动窗口计数有两个关键因素:窗口时长\滚动时间间隔.滚动时间间隔一般等于上图中的一个桶bucket,窗口时长除以滚动时间间隔,就是一个窗口所包含的bucket数目

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值