Skip to content
全部文档

限流

简介

Laravel 内置了简单易用的限流抽象。结合应用的缓存,可以轻松在指定时间窗口内限制任意操作。

INFO

若要限制传入的 HTTP 请求,请参阅限流中间件文档

缓存配置

通常,限流器会使用应用 cache 配置文件中 default 键所定义的默认缓存。不过,你也可以在 cache 配置文件中定义 limiter 键,指定限流器应使用的缓存驱动:

php
'default' => env('CACHE_STORE', 'database'),

'limiter' => 'redis', // [tl! add]

基本用法

可通过 Illuminate\Support\Facades\RateLimiter Facade 与限流器交互。最简单的方法是 attempt:它会在给定秒数内对指定回调进行限流。

当回调已无剩余尝试次数时,attempt 返回 false;否则返回回调的结果或 trueattempt 的第一个参数是限流「键」,可以是你选择的任意字符串,用于表示被限流的操作:

php
use Illuminate\Support\Facades\RateLimiter;

$executed = RateLimiter::attempt(
    'send-message:'.$user->id,
    $perMinute = 5,
    function() {
        // Send message...
    }
);

if (! $executed) {
    return 'Too many messages sent!';
}

如有需要,可为 attempt 提供第四个参数,即「衰减速率」——可用尝试次数重置前的秒数。例如,可将上面的示例改为每两分钟允许五次尝试:

php
$executed = RateLimiter::attempt(
    'send-message:'.$user->id,
    $perTwoMinutes = 5,
    function() {
        // Send message...
    },
    $decayRate = 120,
);

手动增加尝试次数

若希望手动与限流器交互,还有多种其他方法可用。例如,可调用 tooManyAttempts 判断给定限流键是否已超过每分钟允许的最大尝试次数:

php
use Illuminate\Support\Facades\RateLimiter;

if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $perMinute = 5)) {
    return 'Too many attempts!';
}

RateLimiter::increment('send-message:'.$user->id);

// Send message...

当对可能收到大量并发请求的端点限流时,你可能希望检查 increment 的返回值,而不是将 tooManyAttemptsincrement 分开调用。在使用 redismemcacheddatabase 缓存存储时,该值会原子递增,确保每个并发请求获得唯一计数:

php
use Illuminate\Support\Facades\RateLimiter;

$perMinute = 5;

if (RateLimiter::increment('send-message:'.$user->id) > $perMinute) {
    return 'Too many attempts!';
}

// Send message...

或者,可用 remaining 方法获取给定键的剩余尝试次数。若仍有剩余次数,可调用 increment 增加总尝试次数:

php
use Illuminate\Support\Facades\RateLimiter;

if (RateLimiter::remaining('send-message:'.$user->id, $perMinute = 5)) {
    RateLimiter::increment('send-message:'.$user->id);

    // Send message...
}

若希望将给定限流键的值一次增加超过 1,可向 increment 方法传入期望的增量:

php
RateLimiter::increment('send-message:'.$user->id, amount: 5);

判断限流器何时可用

当某个键已无剩余尝试次数时,availableIn 方法会返回还需多少秒才能再次获得尝试机会:

php
use Illuminate\Support\Facades\RateLimiter;

if (RateLimiter::tooManyAttempts('send-message:'.$user->id, $perMinute = 5)) {
    $seconds = RateLimiter::availableIn('send-message:'.$user->id);

    return 'You may try again in '.$seconds.' seconds.';
}

RateLimiter::increment('send-message:'.$user->id);

// Send message...

清除尝试次数

可使用 clear 方法重置给定限流键的尝试次数。例如,当接收方已读某条消息时,可重置尝试次数:

php
use App\Models\Message;
use Illuminate\Support\Facades\RateLimiter;

/**
 * Mark the message as read.
 */
public function read(Message $message): Message
{
    $message->markAsRead();

    RateLimiter::clear('send-message:'.$message->user_id);

    return $message;
}