Laravel 9 Rate Limiter для заданий (jobs)
Столкнулся с проблемой задания лимитов для Job в Laravel 9.
Хочу задать лимиты для заданий, с учетом пользователей.
Делаю так как в документации написано.
Первый шаг.
В провайдере задаю поминутный лимит для каждого пользователя (Задаю id пользователя как ключ)
namespace App\Providers;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\ServiceProvider;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->register(RepositoryServiceProvider::class);
$this->app->register(RequestValidationProvider::class);
$this->app->register(CustomServiceProvider::class);
}
public function boot(): void
{
RateLimiter::for('Limiter', function (object $job) {
Log::info($job->userId);
return Limit::perMinute(10)->by($job->userId)->response(function() {
Log::info('Too many attempts!');
});
});
}
}
Следующий шаг добавить в само задание метод middleware с тем именем RateLimiter-а который мы создали в AppServiceProvider.
Вот сам код задания.
namespace App\Jobs;
use App\Services\Crm\ServiceFactory;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\Middleware\RateLimitedWithRedis;
class SendEmailJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
private readonly string $crmSlug,
private readonly array $email,
public readonly int $userId
){}
public function handle(ServiceFactory $serviceFactory): void
{
$service = $serviceFactory->get($this->slug);
$service->sendEmail($this->email);
}
public function middleware(): array
{
Log::info(json_encode(new RateLimitedWithRedis('Limiter')));
return [new RateLimitedWithRedis('Limiter')];
}
}
И вот сейчас когда запустить задание, Laravel всегда должен смотреть в эту минуту сколько заданий уже сделано. Превышен лимит или нет. Тоесть запусить следующее задание или нет.
По сути из провайдера к заданию, перед его выполнением должен идти true / false. Выполнить или подождать.
Вот что в логе.
И у меня всегда идет true. И лимит не срабатывает.
Вот сама документация Laravel Job Middleware.
Это config/queue.php
И в .env QUEUE_CONNECTION=redis.
Кто столкнулся с такой проблемой? Можете подсказать что делаю не правильно.
Ответы (2 шт):
Похоже, что ты верно настроил RateLimiter, но у тебя возможно неправильно настроен middleware для Job.
Попробуй изменить метод middleware() в SendEmailJob на следующее:
public function middleware(): array
{
return [
new RateLimitedWithRedis('Limiter')->timeout(1)->block(0)->releaseAfter(60)
];
}
Обрати внимание на вызов методов timeout(), block() и releaseAfter(). Они устанавливают время ожидания между попытками выполнения задачи, определяют поведение при блокировке задачи (block(0) означает немедленное блокирование), и задают время, после которого блокировка будет снята (в данном случае, через 60 секунд).
Надеюсь, это поможет!
По сути все что написано, было верно.
Но есть один важный нюанс.
Когда задания поподают в очередь. И те которые не проходит middleware. Их попытки увеличиваются (tries). Тоесть если например в вашем задании написано.
public $tries = 2;
И это задание 2 раза не пройдет middleware. То уже вы его потеряли. Его надо будет запустить еще раз.
Для этого можно использовать метод failed для задания. И там все это реализовать.
Вот пример.
public function failed(MaxAttemptsExceededException $exception): void
{
$errorCode = $exception->getCode();
if($errorCode === 0 || $errorCode > 400) {
$this->job->release();
}
}

