Как реализовать задержку очереди с Redis :: throttle при отправке почты в Laravel? - PullRequest
0 голосов
/ 05 июня 2019

Я делаю рассылку для сайта. И я хочу ограничить количество отправляемых писем (очередей) в минуту. Для лимита я решил использовать очереди и redis :: throttle. Но когда я запускаю php artisan queue: work --tries = 2, некоторые электронные письма в журналах отсутствуют ...

// Console command
$mailingList = MailingList::find(1);
dispatch(new SendDailyNewsletter($mailingList));

//App\Jobs\SendDailyNewsletter.php
class SendDailyNewsletter implements ShouldQueue
{
    use InteractsWithQueue, Queueable, SerializesModels;

    //...

    public function handle()
    {
        $subscriptions = DB::table('mail as m')->select(['m.email'])->where('m.id', $this->mailing_list->id)->get();

        $subscriptions->each(function ($subscription) {
            logger($subscription->email);
        });

        $subscriptions->each(function ($subscription) {
            logger('+');
            Redis::throttle('key')->allow(1)->every(5)->then(function () use ($subscription) {
                logger($subscription->email);
            }, function () {
                return $this->release(5);
            });
        });
    }
}

Выход:

// foreach here all emails good
[2019-06-05 13:24:30] local.DEBUG: korwru@example.com  
[2019-06-05 13:24:30] local.DEBUG: test@example.com  
[2019-06-05 13:24:30] local.DEBUG: jackson33@example.com  
[2019-06-05 13:24:30] local.DEBUG: hollie.emmerich@example.com  
[2019-06-05 13:24:30] local.DEBUG: nbrakus@example.com  
[2019-06-05 13:24:30] local.DEBUG: estrella.christiansen@example.com  
[2019-06-05 13:24:30] local.DEBUG: elinor.frami@example.com  

//Redis::throttle some emails missed. Why?
[2019-06-05 13:24:30] local.DEBUG: +  
[2019-06-05 13:24:30] local.DEBUG: korwru@example.com  
[2019-06-05 13:24:30] local.DEBUG: +  
[2019-06-05 13:24:33] local.DEBUG: +  
[2019-06-05 13:24:35] local.DEBUG: jackson33@example.com  
[2019-06-05 13:24:35] local.DEBUG: +  
[2019-06-05 13:24:38] local.DEBUG: +  
[2019-06-05 13:24:40] local.DEBUG: nbrakus@example.com  
[2019-06-05 13:24:40] local.DEBUG: +  
[2019-06-05 13:24:43] local.DEBUG: +

Скажите, почему скрипт пропускает некоторые данные (электронная почта)?

1 Ответ

2 голосов
/ 06 июня 2019

Вы хотите отправлять одно письмо каждые 5 секунд. Одним из простых решений этой проблемы является команда sleep:

$subscriptions->each(function ($subscription) {
    // send email to:
    logger($subscription->email);
    sleep(5);
});

У этого решения есть существенных недостатков : у вас очень долго выполняемое задание, блокирующее вашу очередь. Кроме того, если это не удается, повторное выполнение этой работы может привести к повторной отправке некоторых писем.

Вместо этого вам нужно задание для каждого подписчика:

// SendDailyNewsletter
public function handle()
{
    $subscriptions = DB::table()..;

    // for every recipient of your newsletter create a new job
    $subscriptions->each(function ($subscription) {            
        SendDailyNewsletterToSubscriber::dispatch($subscription->email);
    });
}

Теперь мы можем использовать Redis :: throttle, чтобы отправлять только одно письмо каждые 5 секунд:

// handle function of SendDailyNewsletterToSubscriber
public function handle()
{
    Redis::throttle('key')->allow(1)->every(5)->then(function () {
        // send email to subscriber
        logger($this->email);
    }, function () {
        // could not obtain lock, retry this job in 5 seconds.
        return $this->release(5);
    });
}

Позвольте мне объяснить, что произошло в вашем сценарии: при первой попытке цикла each() Redis пытается получить блокировку на key и может получить ее. Во второй итерации (test@example.com) Redis снова пытается получить блокировку, но через 3 секунды она сдается (пропускает письмо). На третьей итерации он может получить блокировку через 2 секунды ...

Вы можете увеличить время ожидания блокировки, используя block(). Но это решение, по сути, будет таким же, как использование команды sleep() со всеми недостатками.

$subscriptions->each(function ($subscription) {
    logger('+');
    Redis::throttle('key')->allow(1)->every(5)->block(5)->then(function () use ($subscription) {
        logger($subscription->email);
    }, function () {
        return $this->release(5);
    });
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...