我有一个脚本可以向API发出多个POST请求。脚本的粗略轮廓如下:
define("MAX_REQUESTS_PER_MINUTE", 100);
function apirequest ($data) {
// post data using cURL
}
while ($data = getdata ()) {
apirequest($data);
}
API受到限制,它允许用户每分钟发布最多100个请求。其他请求返回HTTP错误+重试 - 响应后直到窗口重置。请注意,服务器可以在100毫秒到100秒之间的任何时间来处理请求。
我需要确保我的功能每分钟执行次数不超过100次。我尝试usleep
函数引入0.66秒的恒定延迟,但这只是每分钟增加一分钟。诸如0.1秒之类的任意值导致错误一次或另一次。我记录数据库表中的所有请求以及时间,我使用的另一个解决方案是探测表并计算在过去60秒内发出的请求数。
我需要一个尽可能少浪费时间的解决方案。
答案 0 :(得分:2)
我已将Derek的建议纳入代码。
class Throttler {
private $maxRequestsPerMinute;
private $getdata;
private $apirequest;
private $firstRequestTime = null;
private $requestCount = 0;
public function __construct(
int $maxRequestsPerMinute,
$getdata,
$apirequest
) {
$this->maxRequestsPerMinute = $maxRequestsPerMinute;
$this->getdata = $getdata;
$this->apirequest = $apirequest;
}
public function run() {
while ($data = call_user_func($this->getdata)) {
if ($this->requestCount >= $this->maxRequestsPerMinute) {
sleep(ceil($this->firstRequestTime + 60 - microtime(true)));
$this->firstRequestTime = null;
$this->requestCount = 0;
}
if ($this->firstRequestTime === null) {
$this->firstRequestTime = microtime(true);
}
++$this->requestCount;
call_user_func($this->apirequest, $data);
}
}
}
$throttler = new Throttler(100, 'getdata', 'apirequest');
$throttler->run();
UPD。我已将其更新版本放在Packagist上,以便您可以将其与Composer一起使用:https://packagist.org/packages/ob-ivan/throttler
安装:
composer require ob-ivan/throttler
使用:
use Ob_Ivan\Throttler\JobInterface;
use Ob_Ivan\Throttler\Throttler;
class SalmanJob implements JobInterface {
private $data;
public function next(): bool {
$this->data = getdata();
return (bool)$this->data;
}
public function execute() {
apirequest($this->data);
}
}
$throttler = new Throttler(100, 60);
$throttler->run(new SalmanJob());
请注意,还有其他软件包提供相同的功能(我没有测试过任何软件包):
答案 1 :(得分:1)
我首先记录第一次请求的初始时间,然后计算正在进行的请求数量。一旦提出60个请求,请确保当前时间在初始时间后至少1分钟。如果没有睡觉,不管多久都要等到达到分钟。达到分钟时重置计数和初始时间值。
答案 2 :(得分:0)
我尝试了静态睡眠,计数请求和简单数学的天真解决方案,但它们往往非常不准确,不可靠,并且通常会引入更多的睡眠,这是他们本来可以工作时所必需的。你想要的只是当你接近你的速率限制时才开始发出相应的睡眠。
从previous problem解除那些甜蜜,甜蜜的互联网点的解决方案:
我使用了一些数学来找出一个函数,该函数将在给定的请求中以正确的时间总和进行休眠,并允许我在结束时以指数方式将其斜升。
如果我们将睡眠表达为:
y = e^( (x-A)/B )
其中A
和B
是控制曲线形状的任意值,然后是从M
到0
的所有睡眠总和N
请求将是:
M = 0∫N e^( (x-A)/B ) dx
这相当于:
M = B * e^(-A/B) * ( e^(N/B) - 1 )
并且可以针对A解决:
A = B * ln( -1 * (B - B * e^(N/B)) / M )
虽然解决B会更有用,但是由于指定A
可以让你定义图表积极地增加的点,解决方案在数学上是复杂的,我无法解决它我自己或找到任何 else 可以。
/**
* @param int $period M, window size in seconds
* @param int $limit N, number of requests permitted in the window
* @param int $used x, current request number
* @param int $bias B, "bias" value
*/
protected static function ratelimit($period, $limit, $used, $bias=20) {
$period = $period * pow(10,6);
$sleep = pow(M_E, ($used - self::biasCoeff($period, $limit, $bias))/$bias);
usleep($sleep);
}
protected static function biasCoeff($period, $limit, $bias) {
$key = sprintf('%s-%s-%s', $period, $limit, $bias);
if( ! key_exists($key, self::$_bcache) ) {
self::$_bcache[$key] = $bias * log( -1 * ( ($bias - $bias * pow(M_E, $limit/$bias)) / $period ) );
}
return self::$_bcache[$key];
}
通过一些修修补补,我发现B = 20
似乎是一个不错的默认值,尽管我没有数学依据。 某事倾斜嘟m笨拙指数 bs bs 。
此外,如果有人想为我B
解决_.filter(users, obj => (obj.name == 'john' || obj.name == 'jeff') && obj.age == '20'));
的等式。
虽然我认为我们的情况略有不同,因为我的API提供商的回复都包括可用API调用的数量,以及仍在窗口内的数字。您可能需要其他代码来代替您进行跟踪。
答案 3 :(得分:0)
这是我的理由:
define("MAX_REQUESTS_PER_MINUTE", 100);
function apirequest() {
static $startingTime;
static $requestCount;
if ($startingTime === null) {
$startingTime = time();
}
if ($requestCount === null) {
$requestCount = 0;
}
$consumedTime = time() - $startingTime;
if ($consumedTime >= 60) {
$startingTime = time();
$requestCount = 0;
} elseif ($requestCount === MAX_REQUESTS_PER_MINUTE) {
sleep(60 - $consumedTime);
$startingTime = time();
$requestCount = 0;
}
$requestCount++;
echo sprintf("Request %3d, Range [%d, %d)", $requestCount, $startingTime, $startingTime + 60) . PHP_EOL;
file_get_contents("http://localhost/apirequest.php");
// the above script sleeps for 200-400ms
}
for ($i = 0; $i < 1000; $i++) {
apirequest();
}