PHP是如何对API进行限流的

什么是接口限流那么什么是限流呢?顾名思义 , 限流就是限制流量 , 包括并发的流量和一定时间内的总流量 , 就像你宽带包了1个G的流量 , 用完了就没了 , 所以控制你的使用频率和单次使用的总消耗 。
通过限流 , 我们可以很好地控制系统的qps , 从而达到保护系统或者接口服务器稳定的目的 。
接口限流的常用算法计数器法计数器法是限流算法里最简单也是最容易实现的一种算法 。
比如我们规定 , 对于A接口来说 , 我们1分钟的访问次数不能超过100个 。 那么我们可以这么做:在一开始的时候 , 我们可以设置一个计数器counter , 每当一个请求过来的时候 , counter就加1 , 如果counter的值大于100并且该请求与第一个请求的间隔时间还在1分钟之内 , 那么说明请求数过多;
如果该请求与第一个请求的间隔时间大于1分钟 , 且counter的值还在限流范围内 , 那么就重置counter 。
具体算法的示意图如下:
PHP是如何对API进行限流的文章插图
代码如下
class CounterDemo{private $first_request_time;private $request_count = 0; //已请求的次数public $limit = 100; //时间窗口内的最大请求数public $interval = 60; //时间窗口 spublic function __construct() {$this->first_request_time = time();}public function grant(){$now = time();if($now < $this->first_request_time + $this->interval){//时间窗口内if($this->request_count < $this->limit) {$this->request_count++;return true;}else{return false;}}else{//超出前一个时间窗口后, 重置第一次请求时间和请求总次数$this->first_request_time = $now;$this->request_count = 1;return true;}}}$m = new CounterDemo();$n_success = 0;for($i=0; $i < 200; $i++){$rt = $m->grant();if($rt){$n_success ++;}}echo '成功请求 '.$n_success.' 次';计数器算法很简单 , 但是有个严重的bug:
PHP是如何对API进行限流的文章插图
一个恶意用户在0:59时瞬间发送了100个请求 , 然后再1:00时又瞬间发送了100个请求 , 那么这个用户在2秒内发送了200个请求 。
上面我们规定1分钟最多处理100个请求 ,也就是每秒1.7个请求 。 用户通过在时间窗口的重置节点处突发请求 ,可以瞬间超过系统的承载能力 , 导致系统挂起或宕机 。
上面的问题 , 其实是因为我们统计的精度太低造成的 。 那么如何很好地处理这个问题呢?或者说 , 如何将临界问题的影响降低呢?我们可以看下面的滑动窗口算法 。
PHP是如何对API进行限流的文章插图
上图中 , 我们把一个时间窗口(一分钟)分成6份 , 每份(小格)代表10秒 。 每过10秒钟我们就把时间窗口往右滑动一格 ,每一个格子都有自己独立的计数器 。
比如一个请求在0:35秒到达的时候 , 就会落在0:30-0:39这个区间 , 并将此区间的计数器加1 。
从上图可以看出, 0:59到达的100个请求会落在0:50-0:59这个灰色的格子中, 而1:00到达的100个请求会落在黄色的格子中 。
而在1:00时间统计时 ,窗口会往右移动一格 , 那么此时的时间窗口内的请求数量一共是200个 , 超出了限制的100个 , 触发了限流 , 后面的100个请求被抛弃或者等待 。
如果我们把窗口时间划分越多 ,比如60格 , 每格1s ,那么限流统计会更精确 。
漏桶算法 (Leaky Bucket)漏桶算法(Leaky Bucket): 平滑网络上的突发流量 。 使其整流为一个稳定的流量 。
PHP是如何对API进行限流的文章插图
PHP是如何对API进行限流的文章插图
PHP是如何对API进行限流的文章插图
有一个固定容量的桶 , 有水流进来 , 也有水流出 去 。 对于流进来的水来说 , 我们无法预计一共有多少水会流进来 , 也无法预计水流的速度 。 但是对于流出去的水来说 , 这个桶可以固定水流出的速率 。 当桶满了之后 , 多余的水将会溢出(多余的请求会被丢弃) 。
简单的算法实现:
class LeakyBucketDemo{private $last_req_time; //上一次请求的时间public $capacity; //桶的容量public $rate; //水漏出的速度(个/秒)public $water; //当前水量(当前累积请求数)public function __construct(){$this->last_req_time = time();$this->capacity = 100;$this->rate = 20;$this->water = 0;}public function grant(){$now = time();$water = max(0,$this->water - ($now - $this->last_req_time) * $this->rate);// 先执行漏水 , 计算剩余水量$this->water = $water;$this->last_req_time = $now;if($water