如何对我的网站API用户进行限速?

54

我的网站的合法用户偶尔会用API请求猛攻服务器,导致不良结果。我想实施一个限制,比如每5秒钟或每分钟n次调用一次API(还没有确定确切的限制)。显然,我可以在数据库中记录每个API调用并在每个请求上进行计算以查看它们是否超过了限制,但每个请求上的所有这些额外开销将打击其本意。还有什么其他资源占用较少的方法可以用来实施限制吗?我使用的是PHP / Apache / Linux,如果有用的话。


这只是在您调整API或添加更多服务器时的临时措施吗?从开发人员那里拿走某些东西或加入限制非常危险... - Austin Salonen
13
不,我试图制定合理的限制以使网站可持续发展。为了满足个别过度热衷的用户而增加服务器容量并不在计划之内。 - scotts
8个回答

58

好的,没有任何方法可以在不向服务器进行任何写入的情况下完成我所要求的操作,但我至少可以消除每个请求的日志记录。其中一种方法是使用“漏桶”限流方法,在该方法中它只会跟踪最后一个请求($last_api_request)和时间范围内请求数量/限制比率($minute_throttle)。漏桶永远不会重置其计数器(与Twitter API的限速不同,后者每小时重置一次),但如果漏桶变满(用户达到限制),他们必须等待n秒钟让漏桶空出一些才能发起另一个请求。换句话说,这就像是一个滚动限制:如果在时间范围内有先前的请求,则它们会慢慢地从漏桶中泄漏出来;它仅在您填满漏桶时才对您进行限制。

此代码片段将在每个请求上计算一个新的$minute_throttle值。我在$minute_throttle中指定了分钟,因为您可以针对任何时间段(例如每小时、每日等)添加限流,尽管添加多个会很快使用户感到混乱。

$minute = 60;
$minute_limit = 100; # users are limited to 100 requests/minute
$last_api_request = $this->get_last_api_request(); # get from the DB; in epoch seconds
$last_api_diff = time() - $last_api_request; # in seconds
$minute_throttle = $this->get_throttle_minute(); # get from the DB
if ( is_null( $minute_limit ) ) {
    $new_minute_throttle = 0;
} else {
    $new_minute_throttle = $minute_throttle - $last_api_diff;
    $new_minute_throttle = $new_minute_throttle < 0 ? 0 : $new_minute_throttle;
    $new_minute_throttle += $minute / $minute_limit;
    $minute_hits_remaining = floor( ( $minute - $new_minute_throttle ) * $minute_limit / $minute  );
    # can output this value with the request if desired:
    $minute_hits_remaining = $minute_hits_remaining >= 0 ? $minute_hits_remaining : 0;
}

if ( $new_minute_throttle > $minute ) {
    $wait = ceil( $new_minute_throttle - $minute );
    usleep( 250000 );
    throw new My_Exception ( 'The one-minute API limit of ' . $minute_limit 
        . ' requests has been exceeded. Please wait ' . $wait . ' seconds before attempting again.' );
}
# Save the values back to the database.
$this->save_last_api_request( time() );
$this->save_throttle_minute( $new_minute_throttle );

1
你能解释一下为什么$minute_limit会为空吗? - Noam Rathaus
我认为应该是$minute_throttle,因为它来自数据库。 - Sljux
1
考虑将RateLimiter提取到自己的类中了吗? - mblaettermann

10
你可以通过令牌桶算法控制速率,它类似于漏桶算法。请注意,您将需要在进程之间共享存储桶的状态(即令牌数量)(或者您想要控制的任何范围)。因此,您可能需要考虑加锁以避免竞争条件。
好消息是:我已经为您完成了所有操作:bandwidth-throttle/token-bucket
use bandwidthThrottle\tokenBucket\Rate;
use bandwidthThrottle\tokenBucket\TokenBucket;
use bandwidthThrottle\tokenBucket\storage\FileStorage;

$storage = new FileStorage(__DIR__ . "/api.bucket");
$rate    = new Rate(10, Rate::SECOND);
$bucket  = new TokenBucket(10, $rate, $storage);
$bucket->bootstrap(10);

if (!$bucket->consume(1, $seconds)) {
    http_response_code(429);
    header(sprintf("Retry-After: %d", floor($seconds)));
    exit();
}

感谢提供令牌桶算法的链接 - 如果没有这个链接,我就不会意识到它和漏桶算法是正式的算法。 - Colin

6

最简单的解决方案是为每个API密钥限制24小时内的请求数量,并在某个已知、固定的时间重置它们。

如果他们用尽了API请求(即计数器达到零或上限,具体取决于您计数的方向),请停止为他们提供数据,直到您重置他们的计数器。

这样一来,对于他们来说,不通过大量请求来攻击你才是最有利的。


5

我不知道这个帖子是否还活着,但我建议将这些统计数据存储在内存缓存中,例如memcached。这样可以减少记录请求到数据库的开销,但仍然能够实现目的。


我完全同意,我们也是这样实现的,而且它也是原子性的。你可以使用类似AWS Elasticache的东西来存储它们,然后有一个cronjob将聚合结果记录到数据库中。我们实际上每个服务器都有一个小的memcached实例来进行增量操作,然后每分钟将其刷新/增量到Elasticache中 - 这样你也不会将瓶颈移动到Elasticache。 - Ross
@Kedar,你仍然可以将所有调用记录在一个文件中,以进行不同类型的分析,这不会影响你的数据库,只需要将写操作排队到磁盘缓冲区即可。 - kommradHomer
Redis会是更好的解决方案吗?它在内存中,但也是非易失性的? - BeardedGeek

1

你说“每个请求上的额外开销会打败目的”,但我不确定这是正确的。难道目的不是为了防止服务器被攻击吗?这可能是我实现它的方式,因为它只需要快速读/写。如果你担心性能问题,甚至可以将API服务器检查分配到不同的数据库/磁盘。

然而,如果你想要其他选择,你应该看看mod_cband,这是一个第三方Apache模块,旨在帮助限制带宽。尽管主要用于带宽限制,但也可以根据每秒请求数进行限制。我从未使用过它,所以我不确定你会得到什么样的结果。还有另一个名为mod-throttle的模块,但该项目似乎已经关闭,而且从未发布过任何高于Apache 1.3系列的版本。


是的,我可能需要将一些东西保存在磁盘上...最好不是每个日志请求都保存。我可以只保存最后一个成功的API请求,并确保它比那个晚n秒。 - scotts

1
除了从头开始实现,您还可以查看 API 基础设施,例如 3scale(http://www.3scale.net),它可以进行速率限制以及其他一堆东西(分析等)。它有一个 PHP 插件:https://github.com/3scale/3scale_ws_api_for_php
您还可以把类似 Varnish 的东西放在 API 前面,这样就可以做 API 速率限制。

0

使用会话(session)不能轻松地完成这个问题吗?

microtime()$_SESSION['last_access_microtime'] 进行比较。


0
在Node.js中,有一个名为expess-rate-limiter的包,它正好可以实现你想要达到的目标。
它可以限制一段时间内的请求次数。我不知道PHP中是否有类似的东西。

用户特别要求与PHP相关的答案。不幸的是,在这方面,你的评论一点帮助都没有。 - Sascha

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接