在Laravel中发送邮件时,如何使用Redis ::油门实现队列延迟?

时间:2019-06-05 19:52:51

标签: php laravel

我负责该网站的新闻通讯。我想限制每分钟发送的信件(队列)。为了达到极限,我决定使用队列和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 个答案:

答案 0 :(得分:2)

您想每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);
    });
});