将功能限制为每分钟最多100次执行

时间:2018-01-31 17:22:06

标签: 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错误+重试 - 响应后直到窗口重置。请注意,服务器可以在100毫秒到100秒之间的任何时间来处理请求。

我需要确保我的功能每分钟执行次数不超过100次。我尝试usleep函数引入0.66秒的恒定延迟,但这只是每分钟增加一分钟。诸如0.1秒之类的任意值导致错误一次或另一次。我记录数据库表中的所有请求以及时间,我使用的另一个解决方案是探测表并计算在过去60秒内发出的请求数。

我需要一个尽可能少浪费时间的解决方案。

4 个答案:

答案 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 )

其中AB是控制曲线形状的任意值,然后是从M0的所有睡眠总和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();
}