API 限流策略
api rate limit strategy

API 请求速率总归是要限制的,不能无法无天的让客户端随意请求吧。

常见的限流算法有两种:

下面分别描述下这两个算法思想。

漏桶算法

漏桶算法把 API 请求比作水,当请求到达服务端像水流入桶中等待,同时桶有一个缺口,水从缺口中流出比作服务端处理请求。由于服务端处理速度和接口请求速率不一定,所以会产生以下几种情况:

  1. 服务端响应速度比 API 请求速率快,那客户端请求就会及时被响应,这种情况不必要考虑限流。
  2. 服务端响应速度比 API 慢一点,那么客户端请求在桶中等待一会儿就会从缺口中流出,被服务端处理掉。
  3. 服务端响应速度非常慢,新来请求不断堆积在桶中,导致桶已经满了,这个时候新来的请求就会自己被抛弃掉不处理。就像水桶中的水满了多余的水会溢出。

从上面的描述中可以看出,漏桶算法可以使得打到服务端的请求保持一定的速率,可以保证服务端不会被大量的请求拖垮。不过,漏桶算法的缺点也是明显的:如果桶满了,那么新来的请求会被丢弃掉。对应在使用场景中,如果某个时间段突然有大量的请求,导致桶满了,那么新的请求会直接被抛弃。所以,漏桶算法在大量突发性的请求中无法保证所有的请求能得到相应。这种情况下,服务端会返回 429 too many requests 的响应。

令牌桶算法

看名字就知道肯定也和桶有关系,不过这次桶中装的不是请求,而是令牌 (token)。令牌桶算法思路如下:

从上面的描述可以看出,令牌桶算法解决了漏桶算法无法处理大量突发性请求的弊端。如果一下子涌入大量请求,由于桶中一直保存若干令牌,可以避免所有的请求都无法响应。

在实际作用中,令牌桶的实用性比漏桶算法高很多,很多语言都有实现令牌桶算法的限流库。比如 PHP 版本的 token-bucket。这里我们讲算法的具体实现,在实际情况中,限流更多在网关直接处理,不会进入到应用中。所以下面来看下在实际中如何进行 API 限流。

使用 redis-cell 限流

Redis 4.0 之后加入了 modules 的特性,各种有意思的功能可以通过 module 的方式引入到 redis 中。其中有一个实现限流的模块:redis-cell。使用这个模块可以轻松实现限流。注意一点:这个模块是使用漏桶算法来实现限流。

要在 redis 上使用这个模块,首先 redis 版本必须在 4.0 以上,然后在引入这个模块。redis-cell 的项目地址上有说明如何安装该模块,不过为了雨露均沾到所有同学,这边大概说明下安装过程:

以上,redis 已经集成了 cell 模块,可以体验 redis-cell 的限流功能。

cell 的指令有一个:cl.throttle,这个命令有 5 个参数,大概使用方式如下:cl.throttle key 15 60 30 2。这里解释下每个参数的含义:

这个命令的返回值也比较多,含义如下:

> cl.throttle key 15 60 30 
1) (integer) 0  
2) (integer) 16   
3) (integer) 14   
4) (integer) -1 
5) (integer) 2 

这里很容易有一个疑惑,cl.throttle 的第二个参数和返回值的第二个值貌似是同一个含义,但是值为什么会不一样?

原因是这样子的:第二个参数漏斗容量的含义代表的是在漏斗没有漏水的情况下可以容纳的水容量。而在实际情况下,在第一次执行命令之后,不管漏斗速率多慢,第一次倒入漏斗中的水必然已经从漏斗中漏出。所以在被限制频率之前最多可以倒入容量大小为 16 的水。

基于上面的说明,假设一个社区需要对用户的发帖频率进行限制,规则:任意半个小时的时间窗口内最多允许用户发帖 3 个。那么大概伪代码逻辑如下:

$res = $redis->exec("cl.throttle username 2 1 1800 1");
if($res[0] == 1) {
	echo "全身心拒绝";
}else{
	echo "客官来玩啊";
}
*****
Written by JayChen on 24 October 2018