使用 Laravel 实现阿里云短信服务队列

2018-02-27 11:03:06来源:https://www.sunzhongwei.com/using-laravel-queue-for-aliyun-s作者:极客头条人点击

分享

首先,基于 Laravel 5.2 实现一个任务队列,用于存储待发送短信的相关信息,及 seeder/worker 的处理逻辑


短信模板 ID
模板参数
短信签名
目标手机号码
创建存储任务的数据表

第一步,首先创建表 (表结构是 Laravel 默认的)


php artisan queue:table
php artisan queue:failed-table
php artisan migrate

第二个表 failed_jobs 是存储失败任务的


CREATE TABLE `jobs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
# 这是否意味着可以有多个 queue, 以名字区分?是的,可以在程序中指定
`queue` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
# payload 是啥,难道是将逻辑代码写入 payload? worker 怎么识别 payload?
# 逻辑代码在 app/Jobs/XXX.php 的 handle 中,payload 存错的应该是 dispatch 的参数
`payload` longtext COLLATE utf8_unicode_ci NOT NULL,
`attempts` tinyint(3) unsigned NOT NULL,
# reserve 预定跟 available 有什么区别?
`reserved_at` int(10) unsigned DEFAULT NULL,
`available_at` int(10) unsigned NOT NULL,
`created_at` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `jobs_queue_reserved_at_index` (`queue`,`reserved_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `failed_jobs` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
# 这是啥意思?从 config/queue.php 来看,connection 代表存储 jobs 的方式,例如,database/sync/redis
# 但是这玩意不是已经在 .env 中指定了么,为何还要说明一次?
`connection` text COLLATE utf8_unicode_ci NOT NULL,
`queue` text COLLATE utf8_unicode_ci NOT NULL,
`payload` longtext COLLATE utf8_unicode_ci NOT NULL,
`exception` longtext COLLATE utf8_unicode_ci NOT NULL,
`failed_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

.env 中需要修改


QUEUE_DRIVER=database
创建队列的 job worker
php artisan make:job SendSms

对应的,在 app/Jobs/ 目录下出现了对应的 worker 文件 SendSms.php, 在生成的代码基础上添加 pseudo handler (worker). 这里只打印个日志意思一下


<?php
namespace App/Jobs;
use Illuminate/Bus/Queueable;
use Illuminate/Queue/SerializesModels;
use Illuminate/Queue/InteractsWithQueue;
use Illuminate/Contracts/Queue/ShouldQueue;
use Log;
class SendSms implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
protected $number;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($number)
{
$this->number = $number;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
Log::info('This is an sms sent to ' . $this->number);
}
}

注意!注意!注意!


一定不要漏了 protected $xxx; 否则你会发现插入到数据表中的 payload 缺少了对应参数,从而导致 worker 在执行时报错


local.ERROR: exception 'ErrorException' with message 'Undefined property: App/Jobs/SendSms::$number'
创建队列的 job seeder
php artisan make:controller SmsController

添加逻辑


<?php
namespace App/Http/Controllers;
use Illuminate/Http/Request;
use App/Jobs/SendSms;
class SmsController extends Controller
{
public function sendSms(Request $request) {
$this->dispatch(new SendSms("13800000000", null, null, null));
return "ok";
}
}
如何启动 worker

我们在 app/Jobs 中只是定义了如何存储任务,而执行任务的需要我们手动启动


php artisan queue:listen

如果,不执行这个命令,那么存入数据的任务永远不会被执行处理。


我的疑问是,这个服务起来之后,只会处理新到的任务,还是把历史没有处理完的任务都一次处理? 历史任务都会被处理。


jobs 表中都存储了些什么
mysql> select * from jobs /G;
*************************** 1. row ***************************
id: 46
queue: default
payload: {"job":"Illuminate//Queue//[email protected]","data":{"commandName":"App//Jobs//SendSms","command":"O:16:/"App//Jobs//SendSms/":5:{s:9:/"/u0000*/u0000number/";s:11:/"13800000000/";s:6:/"/u0000*/u0000job/";N;s:10:/"connection/";N;s:5:/"queue/";N;s:5:/"delay/";N;}"}}
attempts: 0
reserved_at: NULL
available_at: 1481031619
created_at: 1481031619
1 row in set (0.00 sec)
如何将 job 加入指定队列

以发送邮件为例


public function sendReminderEmail(Request $request, $id)
{
$user = User::findOrFail($id);
$job = (new SendReminderEmail($user))->onQueue('emails');
$this->dispatch($job);
}

onQueue 即是指定了队列,当是仍然是存错在一个数据表中,只不过是 jobs 表的 queue 字段不同而已。


这个特性非常的方便,例如我们有4个不同业务/产品/客户,我们需要独立统计,那么就可以通过指定不同的 queue 来达到统计的目的。


如何提交手机号列表


假设有 5000 个手机号,那么通过 http post 的方式,一次性提交是否可行。参考php - What is the size limit of a post request? - Stack Overflow
对于不可控的参数长度,最好还是换个方案。


一种替代方案是


在前端使用 ajax 的方式提交手机号列表,例如一次提交 10 个手机号。
后台接收之后,逐个验证手机号码格式,并添加队列。避免一次处理几千个耗时过长,用户在前端等不耐烦了怎么办
后台对于格式错误的手机号,后台通过 json 数据返回给前端,单独显示出来。我觉得会有不少固定电话的格式。
前端对于格式错误的手机号,在单独一列给出来。或者,给出一列错误信息。我觉得这种方式比较麻烦,担心错误号码多了,用户也懒得去一个一个改。还不如发送之后集中给出显示。
推广短信

对于推广短信需要注意的事项


短信模板需要单独提交审核。每次更改文案,都需要再审核
单价比普通短信要贵 1 分钱
短信签名也需要单独申请审核。好在阿里云的审核速度比较快,基本10分钟就能通过人工审核
推广短信模板中不能使用变量。对应的 $request->setParamString("{}"); 参数写成空 array 即可。

就算短信签名完全相同,但是如果你使用了“验证码或者短信通知”的短信签名,那么会被报错


InvalidSignName.MalformedThe specified sign name is wrongly formed.

没错,阿里云的 sdk 就是写的这么 low, 错误信息让人抓狂,代码风格也烂的要死。


如何处理失败任务

默认如果不限制 retry 的次数,从测试的结果看,对应的失败任务会被无数次重试。但是是在其他任务执行之后。实现的原理是每次将 job id 置成最大。


延迟发送的方法
$job = (new SendSMSMessages($member, $message))->delay(60);

整点定时发送的时间差计算方法


Carbon::tomorrow()->startOfDay()->diffInSeconds(Carbon::now())
完成的任务会被 delete 掉么?

会的


Laravel Queue 默认的 sync driver 是何物

文档上只是说,sync driver 只适合本地使用,即开发环境。具体原理并未说明。


从 sync 这个名字上我猜测是,调用 dispatch 之后,这个 job 会被立即执行。看了几个介绍 Laravel Queue 的文章,也验证了我的这种猜测。


测试环境

Laravel 5.2


参考
Laravel Queues 官方文档
Laravel 官方文档写了些 jb
阿里云短信服务 PHP SDK使用手册
php - Laravel add multiple Jobs at a time - Stack Overflow
Laravel 5.1 and Job Queues Tutorial - SpiderWeb Solutions
极好的教程

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台