Php 将功能限制为每分钟最多执行100次
我有一个脚本,可以向API发出多个POST请求。脚本的大致轮廓如下:Php 将功能限制为每分钟最多执行100次,php,sql,time,Php,Sql,Time,我有一个脚本,可以向API发出多个POST请求。脚本的大致轮廓如下: define("MAX_REQUESTS_PER_MINUTE", 100); function apirequest ($data) { // post data using cURL } while ($data = getdata ()) { apirequest($data); } API是受限制的,它允许用户每分钟最多发布100个请求。其他请求在响应后返回HTTP错误+重试,直到窗口重置。请注意
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秒内发出的请求数
我需要一个尽可能少地浪费时间的解决方案。我会从记录第一个请求发出的初始时间开始,然后计算发出的请求数。发出60个请求后,确保当前时间至少比初始时间晚1分钟。如果不是usleep,则需要等待多长时间,直到到达分钟。达到分钟时,重置计数和初始时间值 我会先记录第一个请求发出的初始时间,然后计算发出的请求数量。发出60个请求后,确保当前时间至少比初始时间晚1分钟。如果不是usleep,则需要等待多长时间,直到到达分钟。达到分钟时,重置计数和初始时间值 我尝试过静态睡眠、计算请求数和做简单的数学运算等天真的解决方案,但它们往往非常不准确、不可靠,并且通常会引入更多的睡眠,这在他们本可以工作的时候是必要的。你想要的是当你接近你的利率限制时才开始睡觉的东西 将我的解决方案从一个全新的平台提升到一个全新的平台,以满足那些甜蜜的互联网需求:
我用一些数学计算出一个函数,该函数将在给定的请求中休眠正确的时间总和,并允许我在接近结束时以指数方式递增它 如果我们将睡眠表示为:
y = e^( (x-A)/B )
如果A
和B
是控制曲线形状的任意值,则从0
到N
请求的所有睡眠之和M
:
M = 0∫N e^( (x-A)/B ) dx
这相当于:
M = B * e^(-A/B) * ( e^(N/B) - 1 )
并可通过以下方式解决:
A = B * ln( -1 * (B - B * e^(N/B)) / M )
虽然解B会有用得多,因为指定A
可以定义一个曲线图急剧上升的点,该点的解在数学上是复杂的,我自己或其他人都无法解
/**
* @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
似乎是一个不错的默认值,尽管我没有数学基础。某物某物斜坡咕哝咕哝
还有,如果有人想帮我解B
的方程
尽管我相信我们的情况略有不同,因为我的API提供者的响应都包括可用API调用的数量,以及窗口中剩余的数量。您可能需要额外的代码来跟踪这一点。我尝试过静态睡眠、计算请求数和做简单的数学运算等天真的解决方案,但它们往往非常不准确、不可靠,并且通常会引入更多的睡眠,这在他们本可以工作时是必要的。你想要的是当你接近你的利率限制时才开始睡觉的东西 将我的解决方案从一个全新的平台提升到一个全新的平台,以满足那些甜蜜的互联网需求:
我用一些数学计算出一个函数,该函数将在给定的请求中休眠正确的时间总和,并允许我在接近结束时以指数方式递增它 如果我们将睡眠表示为:
y = e^( (x-A)/B )
如果A
和B
是控制曲线形状的任意值,则从0
到N
请求的所有睡眠之和M
:
M = 0∫N e^( (x-A)/B ) dx
这相当于:
M = B * e^(-A/B) * ( e^(N/B) - 1 )
并可通过以下方式解决:
A = B * ln( -1 * (B - B * e^(N/B)) / M )
虽然解B会有用得多,因为指定A
可以定义一个曲线图急剧上升的点,该点的解在数学上是复杂的,我自己或其他人都无法解
/**
* @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
似乎是一个不错的默认值,尽管我没有数学基础。某物某物斜坡咕哝咕哝
还有,如果有人想帮我解B
的方程
尽管我相信我们的情况略有不同,因为我的API提供者的响应都包括可用API调用的数量,以及窗口中剩余的数量。您可能需要额外的代码来跟踪这一点。我已经将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。我已将其更新版本放在PackageGist上,以便您可以将其与Composer一起使用:
要安装:
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());
请注意,还有其他提供相同功能的软件包(我没有测试过任何一个):
- 我已将德里克的建议写入代码
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。我已将其更新版本放在PackageGist上,以便您可以将其与Composer一起使用:
要安装:
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());
请注意,还有其他提供相同功能的软件包(我没有测试过任何一个):
- 以下是我的想法:
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();
}
定义(“每分钟最大请求数”,100);
函数apirequest(){
静态启动时间;
静态$requestCount;