Laravel 9 Rate Limiter для заданий (jobs)

Рейтинг: 0Ответов: 2Опубликовано: 27.03.2023

Столкнулся с проблемой задания лимитов для 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.

введите сюда описание изображения

Кто столкнулся с такой проблемой? Можете подсказать что делаю не правильно.

Ответы

▲ 0Принят

По сути все что написано, было верно.

Но есть один важный нюанс.

Когда задания поподают в очередь. И те которые не проходит 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();
    }
}
▲ 0

Похоже, что ты верно настроил 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 секунд).

Надеюсь, это поможет!