API Rate Limiting 限速

1,令牌桶算法(Token Bucket)


令牌桶算法(Token Bucket) 随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token,如果桶已经满了就不再加了。新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务。


令牌桶的另外一个好处是可以方便的改变速度。 一旦需要提高速率,则按需提高放入桶中的令牌的速率。 一般会定时(比如100毫秒)往桶中增加一定数量的令牌, 有些变种算法则实时的计算应该增加的令牌的数量。


2,算法实现

对于每个 Bucket 设置一个定时器,而一个定时器就是一条线程。那么在你的服务器上,光是分配给定时器的线程就需要和你的用户数量是一个量级的, 几万几十万条线程在服务器上运行,是完全是脱离了实际情况的。 所以最合适的方案是使用时间戳。


1. 当一个请求执行后,先获取缓存中的 Token Bucket 的 key;

2. 如果这个 Token Bucket 在缓存中不存在,那么就新建一个 Token Bucket,然后设置该 Bucket 的 Token 数量为最大值减一(去掉了这次请求获取的 Token)。

3. 如果这个 Token Bucket 在缓存中存在,而且其上一次加入 Token 的时间到现在时间的时间间隔,大于 Token Bucket 的 interval,那么也将 Bucket 的 Token 值重置为最大值减一;

4. 如果 Token Bucket 上次加入 Token 的时间到现在时间的时间间隔,没有大于 interval,那么就计算这次需要补充的 Token 数量,将补充过后的 Token 数量更新到 Token Bucket 中。 


为了增加读取速度,可以使用 Redis 作为缓存。



3,伪代码实现

$max_tokens  = 5; // 最大的访问量
$interval   = 10; // 间隔时间,单位秒

$user_mark = '127.0.0.1';       // 用户标记
$api_mark  = 'index/Test/text';    // API 标记
$cache_mark = $user_mark.'|'.$api_mark; // 缓存标记

$store_tokens = Cache::get($cache_mark);

if($store_tokens === false){
  // 如果 Token Bucket 不存在,创建一个
  $store_tokens = --$max_tokens;
  Cache::set( $cache_mark, $store_tokens, $interval);
} elseif($store_tokens > 0){
  // 如果 Token Bucket > 0,自减
  Cache::dec($cache_mark);
  $store_tokens = Cache::get($cache_mark);
}

return $store_tokens;



4,处理超过限速的请求

github是这样处理超过限速请求的,返回如下信息:

HTTP/1.1 403 Forbidden
Date: Tue, 20 Aug 2013 14:50:41 GMT
Status: 403 Forbidden
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1377013266
{
  "message": "API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)",
  "documentation_url": "https://developer.github.com/v3/#rate-limiting"
}



5,注意web server 防DDOS 工具的rate limiting

比如我使用的Apache mod_evasive模块,它也有类似令牌桶算法的功能。api的 rate limiting 应该小于 apache mod_evasive的rate limiting。

LoadModule evasive2_module modules/mod_evasive2.so

DOSDisplayToken   On
DOSHashTableSize  3097
DOSSiteCount    100
DOSSiteInterval   1
DOSPageCount    6
DOSPageInterval   1
DOSBlockingPeriod  30
DOSLogDir      "${SRVROOT}/logs/mod_evasive"



参考:

https://javascript.net.cn/article?id=443

https://developer.github.com/v3/#rate-limiting

https://www.cnblogs.com/exceptioneye/p/4783904.html

https://www.jianshu.com/p/2a223024b8de

声明:本站所有文章和图片,如无特殊说明,均为原创发布。商业转载请联系作者获得授权,非商业转载请注明出处。
随机推荐
HTML input 元素
JavaScript screen对象
MySQL 使用 DATE_FORMAT() 和 FROM_UNIXTIME() 格式化时间
github Support for password authentication was removed
Light 主题
WordPress用户的user_login, user_nicename, display_name 和 nickname有什么区别
JavaScript video 教程
JavaScript location对象