PHP速率限制客户端

PHP速率限制客户端,php,Php,我使用各种各样的第三方web API,其中许多都强制执行速率限制。如果有一个相当通用的PHP库,我可以用它来限制调用的速率,这将非常有用。我可以想出一些方法来实现这一点,也许可以将调用放入一个带有调用时间戳的队列中,但如果其他人已经做得很好,我希望避免重蹈覆辙。作为替代方法,我(过去)创建了一个“缓存”文件夹来存储API调用,因此如果我再次尝试进行相同的调用,在特定的时间范围内,它首先从缓存抓取(更无缝),直到可以进行新的调用。短期内可能会以存档信息结束,但从长远来看,可以避免API阻止您。我意

我使用各种各样的第三方web API,其中许多都强制执行速率限制。如果有一个相当通用的PHP库,我可以用它来限制调用的速率,这将非常有用。我可以想出一些方法来实现这一点,也许可以将调用放入一个带有调用时间戳的队列中,但如果其他人已经做得很好,我希望避免重蹈覆辙。

作为替代方法,我(过去)创建了一个“缓存”文件夹来存储API调用,因此如果我再次尝试进行相同的调用,在特定的时间范围内,它首先从缓存抓取(更无缝),直到可以进行新的调用。短期内可能会以存档信息结束,但从长远来看,可以避免API阻止您。

我意识到这是一个旧线程,但我想发布我的解决方案,因为它基于我在SE上找到的其他内容。我自己寻找了一会儿答案,但很难找到好的答案。它基于前面讨论的Python解决方案,但我添加了对可变大小请求的支持,并使用PHP闭包将其转换为函数生成器

function ratelimiter($rate = 5, $per = 8) {
  $last_check = microtime(True);
  $allowance = $rate;

  return function ($consumed = 1) use (
    &$last_check,
    &$allowance,
    $rate,
    $per
  ) {
    $current = microtime(True);
    $time_passed = $current - $last_check;
    $last_check = $current;

    $allowance += $time_passed * ($rate / $per);
    if ($allowance > $rate)
      $allowance = $rate;

    if ($allowance < $consumed) {
      $duration = ($consumed - $allowance) * ($per / $rate);
      $last_check += $duration;
      usleep($duration * 1000000);
      $allowance = 0;
    }
    else
      $allowance -= $consumed;

    return;
  };
}
下面是我如何使用它将针对Facebook Graph API的批处理请求限制为每600秒600个请求,具体取决于批处理的大小:

$ratelimit = ratelimiter(600, 600);
while (..) {
  ..

  $ratelimit(count($requests));
  $response = (new FacebookRequest(
    $session, 'POST', '/', ['batch' => json_encode($requests)]
  ))->execute();

  foreach ($response->..) {
    ..
  }
}

希望这对别人有帮助

您可以使用进行速率限制。我用PHP为您实现了这一点:


PHP源代码,允许任何用户每5秒发出一次请求并使用Redix来限制对API的访问。

安装Redis/Redix客户端:

<?php
 require_once 'class.ratelimit.redix.php';

 $rl = new RateLimit();
 $waitfor = $rl->getSleepTime($_SERVER['REMOTE_ADDR']);
 if ($waitfor>0) {
   echo 'Rate limit exceeded, please try again in '.$waitfor.'s';
   exit;    
 }

 // Your API response
 echo 'API response';
<?php
require_once __DIR__.'/vendor/autoload.php';
Predis\Autoloader::register();

class RateLimit {

  private $redis;
  const RATE_LIMIT_SECS = 5; // allow 1 request every x seconds

  public function __construct() {
     $this->redis = new Predis\Client([
         'scheme' => 'tcp',
         'host'   => 'localhost', // or the server IP on which Redix is running
         'port'   => 6380
     ]);
  }

 /**
  * Returns the number of seconds to wait until the next time the IP is allowed
  * @param ip {String}
  */
 public function getSleepTime($ip) {
     $value = $this->redis->get($ip);
     if(empty($value)) {
       // if the key doesn't exists, we insert it with the current datetime, and an expiration in seconds
         $this->redis->set($ip, time(), self::RATE_LIMIT_SECS*1000);
         return 0;
       } 
       return self::RATE_LIMIT_SECS - (time() - intval(strval($value)));
     } // getSleepTime
 } // class RateLimit
作曲者需要predis/predis

根据您的操作系统下载Redix(),然后启动服务:

<?php
 require_once 'class.ratelimit.redix.php';

 $rl = new RateLimit();
 $waitfor = $rl->getSleepTime($_SERVER['REMOTE_ADDR']);
 if ($waitfor>0) {
   echo 'Rate limit exceeded, please try again in '.$waitfor.'s';
   exit;    
 }

 // Your API response
 echo 'API response';
<?php
require_once __DIR__.'/vendor/autoload.php';
Predis\Autoloader::register();

class RateLimit {

  private $redis;
  const RATE_LIMIT_SECS = 5; // allow 1 request every x seconds

  public function __construct() {
     $this->redis = new Predis\Client([
         'scheme' => 'tcp',
         'host'   => 'localhost', // or the server IP on which Redix is running
         'port'   => 6380
     ]);
  }

 /**
  * Returns the number of seconds to wait until the next time the IP is allowed
  * @param ip {String}
  */
 public function getSleepTime($ip) {
     $value = $this->redis->get($ip);
     if(empty($value)) {
       // if the key doesn't exists, we insert it with the current datetime, and an expiration in seconds
         $this->redis->set($ip, time(), self::RATE_LIMIT_SECS*1000);
         return 0;
       } 
       return self::RATE_LIMIT_SECS - (time() - intval(strval($value)));
     } // getSleepTime
 } // class RateLimit
./redix\u linux\u amd64

以下回答表示Redix正在侦听端口6380上的RESP协议和端口7090上的HTTP协议。

redix resp服务器位于:localhost:6380
redix http服务器位于:localhost:7090

在API中,将以下代码添加到标题中:

<?php
 require_once 'class.ratelimit.redix.php';

 $rl = new RateLimit();
 $waitfor = $rl->getSleepTime($_SERVER['REMOTE_ADDR']);
 if ($waitfor>0) {
   echo 'Rate limit exceeded, please try again in '.$waitfor.'s';
   exit;    
 }

 // Your API response
 echo 'API response';
<?php
require_once __DIR__.'/vendor/autoload.php';
Predis\Autoloader::register();

class RateLimit {

  private $redis;
  const RATE_LIMIT_SECS = 5; // allow 1 request every x seconds

  public function __construct() {
     $this->redis = new Predis\Client([
         'scheme' => 'tcp',
         'host'   => 'localhost', // or the server IP on which Redix is running
         'port'   => 6380
     ]);
  }

 /**
  * Returns the number of seconds to wait until the next time the IP is allowed
  * @param ip {String}
  */
 public function getSleepTime($ip) {
     $value = $this->redis->get($ip);
     if(empty($value)) {
       // if the key doesn't exists, we insert it with the current datetime, and an expiration in seconds
         $this->redis->set($ip, time(), self::RATE_LIMIT_SECS*1000);
         return 0;
       } 
       return self::RATE_LIMIT_SECS - (time() - intval(strval($value)));
     } // getSleepTime
 } // class RateLimit

我喜欢mwp的答案,我想把它转换成OO,让我感觉温暖和模糊。我最终彻底地重写了它,以至于它完全无法从他的版本中辨认出来。这是我受mwp启发的OO版本

基本说明:每次调用
wait
时,它都会将当前时间戳保存在一个数组中,并抛出所有不再相关的旧时间戳(大于间隔的持续时间)。如果超过了速率限制,那么它将计算直到再次释放并在此之前休眠的时间

用法:

$limiter = new RateLimiter(4, 1); // can be called 4 times per 1 second
for($i = 0; $i < 10; $i++) {
    $limiter->await();
    echo microtime(true) . "\n";
}


这与@Jeff的答案基本相同,但我已经整理了很多代码,并添加了PHP7.4类型/返回提示

我还将此作为作曲家软件包发布:

composer需要macroman/速率限制器

/**
 * Class RateLimiter
 *
 * @package App\Components
 */
class Limiter
{
    /**
     * Limit to this many requests
     *
     * @var int
     */
    private int $frequency = 0;

    /**
     * Limit for this duration
     *
     * @var int
     */
    private int $duration = 0;

    /**
     * Current instances
     *
     * @var array
     */
    private array $instances = [];

    /**
     * RateLimiter constructor.
     *
     * @param int $frequency
     * @param int $duration #
     */
    public function __construct(int $frequency, int $duration)
    {
        $this->frequency = $frequency;
        $this->duration = $duration;
    }

    /**
     * Sleep if the bucket is full
     */
    public function await(): void
    {
        $this->purge();
        $this->instances[] = microtime(true);

        if (!$this->is_free()) {
            $wait_duration = $this->duration_until_free();
            usleep($wait_duration);
        }
    }

    /**
     * Remove expired instances
     */
    private function purge(): void
    {
        $cutoff = microtime(true) - $this->duration;

        $this->instances = array_filter($this->instances, function ($a) use ($cutoff) {
            return $a >= $cutoff;
        });
    }

    /**
     * Can we run now?
     *
     * @return bool
     */
    private function is_free(): bool
    {
        return count($this->instances) < $this->frequency;
    }

    /**
     * Get the number of microseconds until we can run the next instance
     *
     * @return float
     */
    private function duration_until_free(): float
    {
        $oldest = $this->instances[0];
        $free_at = $oldest + $this->duration * 1000000;
        $now = microtime(true);

        return ($free_at < $now) ? 0 : $free_at - $now;
    }
}
/**
*等级费率限制器
*
*@package-App\Components
*/
类限制器
{
/**
*限制到这么多的请求
*
*@var int
*/
私人int$频率=0;
/**
*此期限的限制
*
*@var int
*/
私有整数$duration=0;
/**
*当前实例
*
*@var数组
*/
私有数组$instances=[];
/**
*速率限制器构造函数。
*
*@param int$频率
*@param int$duration#
*/
公共函数构造(int$频率,int$持续时间)
{
$this->frequency=$frequency;
$this->duration=$duration;
}
/**
*如果桶满了就睡觉
*/
公共函数wait():void
{
$this->purge();
$this->instances[]=microtime(true);
如果(!$this->is_free()){
$wait_duration=$this->duration_until_free();
usleep(等待时间);
}
}
/**
*删除过期实例
*/
私有函数purge():void
{
$cutoff=微时间(true)-$this->duration;
$this->instances=array\u filter($this->instances,function($a)use($cutoff){
返回$a>=$cutoff;
});
}
/**
*我们现在可以跑了吗?
*
*@returnbool
*/
私有函数是\u free():bool
{
返回计数($this->instances)<$this->频率;
}
/**
*获取运行下一个实例之前的微秒数
*
*@返回浮动
*/
私有函数持续时间\u到\u free():float
{
$OLESTER=$this->instances[0];
$free_at=$oldest+$this->duration*1000000;
$now=微时间(真);
返回($free_at<$now)?0:$free_at-$now;
}
}
用法是一样的

use RateLimiter\Limiter;

// Limit to 6 iterations per second
$limiter = new Limiter(6, 1);

for ($i = 0; $i < 50; $i++) {
    $limiter->await();

    echo "Iteration $i" . PHP_EOL;
}
使用速率限制器\限制器;
//限制为每秒6次迭代
$limiter=新的限制器(6,1);
对于($i=0;$i<50;$i++){
$limiter->wait();
echo“Iteration$i”;
}

只有在使用相同参数调用给定API时,缓存才有用。这是朝着正确方向迈出的一步,但我经常会改变参数,期望得到不同的结果。另外,一些API禁止在TOS中缓存。这正是我想要的。谢谢你!我不能相信这一点,但我使用了这种方法,因为没有“通用”包——但我想你可以根据你的编码方法来做到这一点。