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

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错误+重试,直到窗口重置。请注意

我有一个脚本,可以向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秒内发出的请求数


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

我会从记录第一个请求发出的初始时间开始,然后计算发出的请求数。发出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;