我发现了很多关于如何对API的用户进行评级限制的信息和脚本示例,但我找不到任何关于在施加这些限制时如何对您自己的API请求进行评级限制的示例。
我总是用sleep
或usleep
命令之类的代码来限制我的脚本的速率,但这感觉是一种低效的方式,尤其是当API端点的速率限制非常高,并且在达到限制之前敲打API也是低效的。
例如,Google的API限制因您使用的API而异,并且可以增加/减少,在这种情况下,硬编码到代码中的固定速率限制看起来像是原始的猜测工作!
我是不是错过了一些显而易见的东西?还是这并不像我所期望的那样普遍?
好吧,为了好玩,我创建了一个限制器类,它允许您指定每秒、分钟和小时的限制。我忍不住有充分的理由使用循环队列!
如果您有多个进程进行消耗,无论是否同时进行,您都必须自行设计一种存储和/或共享使用历史的方法。
// LIMITER.PHP
class Limiter
{
private $queue = array();
private $size;
private $next;
private $perSecond;
private $perMinute;
private $perHour;
// Set any constructor parameter to non-zero to allow adherence to the
// limit represented. The largest value present will be the size of a
// circular queue used to track usage.
// -------------------------------------------------------------------
function __construct($perSecond=0,$perMinute=0,$perHour=0)
{
$this->size = max($perSecond,$perMinute,$perHour);
$this->next = 0;
$this->perSecond = $perSecond;
$this->perMinute = $perMinute;
$this->perHour = $perHour;
for($i=0; $i < $this->size; $i++)
$this->queue[$i] = 0;
}
// See if a use would violate any of the limits specified. We return true
// if a limit has been hit.
// ----------------------------------------------------------------------
public function limitHit($verbose=0)
{
$inSecond = 0;
$inMinute = 0;
$inHour = 0;
$doneSecond = 0;
$doneMinute = 0;
$doneHour = 0;
$now = microtime(true);
if ( $verbose )
echo "Checking if limitHit at $now<br>'n";
for ($offset=1; $offset <= $this->size; $offset++)
{
$spot = $this->next - $offset;
if ( $spot < 0 )
$spot = $this->size - $offset + $this->next;
if ( $verbose )
echo "... next $this->next size $this->size offset $offset spot $spot utime " . $this->queue[$spot] . "<br>'n";
// Count and track within second
// -----------------------------
if ( $this->perSecond && !$doneSecond && $this->queue[$spot] >= microtime(true) - 1.0 )
$inSecond++;
else
$doneSecond = 1;
// Count and track within minute
// -----------------------------
if ( $this->perMinute && !$doneMinute && $this->queue[$spot] >= microtime(true) - 60.0 )
$inMinute++;
else
$doneMinute = 1;
// Count and track within hour
// ---------------------------
if ( $this->perHour && !$doneHour && $this->queue[$spot] >= microtime(true) - 3600.0 )
$inHour++;
else
$doneHour = 1;
if ( $doneSecond && $doneMinute && $doneHour )
break;
}
if ( $verbose )
echo "... inSecond $inSecond inMinute $inMinute inHour $inHour<br>'n";
if ( $inSecond && $inSecond >= $this->perSecond )
{
if ( $verbose )
echo "... limit perSecond hit<br>'n";
return TRUE;
}
if ( $inMinute && $inMinute >= $this->perMinute )
{
if ( $verbose )
echo "... limit perMinute hit<br>'n";
return TRUE;
}
if ( $inHour && $inHour >= $this->perHour )
{
if ( $verbose )
echo "... limit perHour hit<br>'n";
return TRUE;
}
return FALSE;
}
// When an API is called the using program should voluntarily track usage
// via the use function.
// ----------------------------------------------------------------------
public function usage()
{
$this->queue[$this->next++] = microtime(true);
if ( $this->next >= $this->size )
$this->next = 0;
}
}
// ##############################
// ### Test the limiter class ###
// ##############################
$psec = 2;
$pmin = 4;
$phr = 0;
echo "Creating limiter with limits of $psec/sec and $pmin/min and $phr/hr<br><br>'n";
$monitorA = new Limiter($psec,$pmin,$phr);
for ($i=0; $i<15; $i++)
{
if ( !$monitorA->limitHit(1) )
{
echo "<br>'n";
echo "API call A here (utime " . microtime(true) . ")<br>'n";
echo "Voluntarily registering usage<br>'n";
$monitorA->usage();
usleep(250000);
}
else
{
echo "<br>'n";
usleep(500000);
}
}
为了在实际操作中演示它,我在限制检查函数中加入了一些"详细模式"语句。以下是一些示例输出。
Creating limiter with limits of 2/sec and 4/min and 0/hr
Checking if limitHit at 1436267440.9957
... next 0 size 4 offset 1 spot 3 utime 0
... inSecond 0 inMinute 0 inHour 0
API call A here (utime 1436267440.9957)
Voluntarily registering usage
Checking if limitHit at 1436267441.2497
... next 1 size 4 offset 1 spot 0 utime 1436267440.9957
... next 1 size 4 offset 2 spot 3 utime 0
... inSecond 1 inMinute 1 inHour 0
API call A here (utime 1436267441.2497)
Voluntarily registering usage
Checking if limitHit at 1436267441.5007
... next 2 size 4 offset 1 spot 1 utime 1436267441.2497
... next 2 size 4 offset 2 spot 0 utime 1436267440.9957
... next 2 size 4 offset 3 spot 3 utime 0
... inSecond 2 inMinute 2 inHour 0
... limit perSecond hit
Checking if limitHit at 1436267442.0007
... next 2 size 4 offset 1 spot 1 utime 1436267441.2497
... next 2 size 4 offset 2 spot 0 utime 1436267440.9957
... next 2 size 4 offset 3 spot 3 utime 0
... inSecond 1 inMinute 2 inHour 0
API call A here (utime 1436267442.0007)
Voluntarily registering usage
Checking if limitHit at 1436267442.2507
... next 3 size 4 offset 1 spot 2 utime 1436267442.0007
... next 3 size 4 offset 2 spot 1 utime 1436267441.2497
... next 3 size 4 offset 3 spot 0 utime 1436267440.9957
... next 3 size 4 offset 4 spot 3 utime 0
... inSecond 1 inMinute 3 inHour 0
API call A here (utime 1436267442.2507)
Voluntarily registering usage
Checking if limitHit at 1436267442.5007
... next 0 size 4 offset 1 spot 3 utime 1436267442.2507
... next 0 size 4 offset 2 spot 2 utime 1436267442.0007
... next 0 size 4 offset 3 spot 1 utime 1436267441.2497
... next 0 size 4 offset 4 spot 0 utime 1436267440.9957
... inSecond 2 inMinute 4 inHour 0
... limit perSecond hit
Checking if limitHit at 1436267443.0007
... next 0 size 4 offset 1 spot 3 utime 1436267442.2507
... next 0 size 4 offset 2 spot 2 utime 1436267442.0007
... next 0 size 4 offset 3 spot 1 utime 1436267441.2497
... next 0 size 4 offset 4 spot 0 utime 1436267440.9957
... inSecond 2 inMinute 4 inHour 0
... limit perSecond hit
Checking if limitHit at 1436267443.5027
... next 0 size 4 offset 1 spot 3 utime 1436267442.2507
... next 0 size 4 offset 2 spot 2 utime 1436267442.0007
... next 0 size 4 offset 3 spot 1 utime 1436267441.2497
... next 0 size 4 offset 4 spot 0 utime 1436267440.9957
... inSecond 0 inMinute 4 inHour 0
... limit perMinute hit
Checking if limitHit at 1436267444.0027
... next 0 size 4 offset 1 spot 3 utime 1436267442.2507
... next 0 size 4 offset 2 spot 2 utime 1436267442.0007
... next 0 size 4 offset 3 spot 1 utime 1436267441.2497
... next 0 size 4 offset 4 spot 0 utime 1436267440.9957
... inSecond 0 inMinute 4 inHour 0
... limit perMinute hit
首先,您应该只在实际需要时调用任何外部API,提供商将非常感谢您。
我通常有两种方法来限制我自己的API使用——如果可能的话,将结果缓存N段时间,通常比API本身的硬限制要短得多。然而,这只适用于非常具体的情况。
第二种是持久性/半持久性计数器,将计数器与限制期开始时的时间一起存储在某种内存后端。每次调用API之前,请检查存储,查看当前时间减去开始时间间隔和已发出的请求数是否小于API规定的时间。如果是,你可以提出请求-如果间隔较大,你可以重置限制,如果你的下一个请求将超过限制,而你仍在上一个间隔中,你可能会显示一个相当大的错误。在每个外部请求上,如果超过间隔时间,则更新间隔时间,并递增计数器。
-
用Jobs包装API调用,并将它们推送到单独的队列:
ApiJob::dispatch()->onQueue('api');
-
将队列速率限制与Redis或mxl/laravel队列速率限制包一起使用(我是作者(。另请参阅SO关于其用法的回答。
-
如果使用
mxl/laravel-queue-rate-limit
,则在其设置之后运行队列工作程序:$ php artisan queue:work --queue api
我想我们不能用几句话回答你的问题。它真实地反映了链接到您的应用程序的体系结构。对于我来说,为了对重复进行API速率限制,我使用存储值的缓存以及我的API的使用。到目前为止,我还没有发现任何代码准备好。