我的网站的合法用户偶尔会通过API请求敲击服务器,从而导致不良结果.我想建立一个限制,不过每5秒说一次API调用或每分钟n次调用(还没有弄清楚确切的限制).我显然可以在数据库中记录每个API调用,并对每个请求进行计算以查看它们是否超出限制,但是每个请求的所有额外开销都将失去目的.我可以用什么其他资源不足的方法来制定限制?我正在使用PHP/Apache/Linux,它的价值.
好吧,如果没有对服务器的任何写入,就没有办法做我的要求,但我至少可以消除每个请求的记录.一种方法是使用"漏桶"限制方法,其中它仅跟踪最后一个请求($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 );
您可以使用令牌桶算法控制速率,该算法与漏桶算法相当.请注意,您必须在进程(或您想要控制的任何范围)上共享存储桶的状态(即令牌数量).因此,您可能需要考虑锁定以避免竞争条件.
好消息:我为你做了所有这些: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(); }