在上一節中我們介紹了使用Swoole發送單個郵件,那么如果是大量的郵件需要發送,比如給2萬個用戶發送優惠活動郵件,這是一個比較耗時的過程,而PHP本身不適合處理這種耗時多任務場景。本節為給大家介紹使用Swoole+Redis來實現發送批量郵件的例子。
## 建立任務
接上一節代碼,編輯src/App/Mail.php文件代碼,在public function onTask()方法中增加批量隊列發送郵件的代碼:
```
public function onTask(swoole_server $serv, $task_id, $from_id, $data)
{
$res['result'] = 'failed';
$req = json_decode($data, true);
$action = $req['action'];
echo date('Y-m-d H:i:s')." onTask: [".$action."].\n";
switch ($action) {
case 'sendMail': //發送單個郵件
$mailData = [
'emailAddress' => 'abc@example.com', //接收方,改成自己的郵箱可以測試接收郵件
'subject' => 'swoole實驗室',
'body' => '測試This is the HTML message body.',
'attach' => '/home/swoole/public/a.jpg'
];
$this->sendMail($mailData);
break;
case 'sendMailQueue': // 批量隊列發送郵件
$this->sendMailQueue();
break;
default:
break;
}
}
```
## 建立Redis隊列
由于發送的郵件比較多,我們把郵件列表事先保存在Redis隊列中。我們知道Redis的使用場景很多,其中就可以用它來做簡單的隊列。
我們在任務中調用了`sendMailQueue()`方法,繼續在Mail.php中添加:
```
// 郵件發送隊列
private function sendMailQueue()
{
$redis = new \Redis();
$redis->connect('127.0.0.1', 6379);
$password = '123';
$redis->auth($password);
swoole_timer_tick(1000, function($timer) use ($redis) { // 啟用定時器,每1秒執行一次
$value = $redis->lpop('mailerlist');
if($value){
//echo '獲取redis數據:' . $value;
$json = json_decode($value, true);
$start = microtime(true);
$rs = $this->sendMail($json);
$end = microtime(true);
if ($rs) {
echo '發送成功!'.$value.', 耗時:'. round($end - $start, 3).'秒'.PHP_EOL;
} else { // 把發送失敗的加入到失敗隊列中,人工處理
$redis->rpush("mailerlist_err", $value);
}
}else{
swoole_timer_clear($timer); // 停止定時器
echo "Emaillist出隊完成";
}
});
}
```
上述代碼中,先嘗試連接Redis,然后使用Swoole的`swoole_timer_tick()`函數,它是個定時器,這個函數跟js的interval()函數一樣,意思是每隔一定時間執行一次,它可以定義毫秒級粒度。很顯然,上述代碼中,每隔1000毫秒(1秒)從Redis隊列mailerlist中取出一條,即一個郵件對象,然后執行發送郵件`sendMail()`,當發送完所有郵件后,使用`swoole_timer_clear()`關閉定時器即可。定時器的間隔時間可以調整。
## 客戶端
在客戶端,我們先往Redis隊列里添加郵件內容,然后向服務端發起sendMailQueue批量發郵件指令。
```
<?php
class Client
{
private $client;
public function __construct() {
$this->client = new swoole_client(SWOOLE_SOCK_TCP);
}
public function connect() {
if( !$this->client->connect("127.0.0.1", 9502 , 1) ) {
echo "Error: {$this->client->errMsg}[{$this->client->errCode}]\n";
}
$action = 'sendMailQueue';
$time = time();
$key = 'MYgGnQE33ytd2jDFADS39DSEWsdD24sK';
$token = md5($action.$time.$key);
$data = [
'action' => $action,
'token' => $token,
'timestamp' => $time
];
$msg = json_encode($data);
$this->client->send( $msg );
$message = $this->client->recv();
echo "Get Message From Server:{$message}\n";
}
}
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$password = '123';
$redis->auth($password);
$arr = [];
$arr[0] = [
'subject' => '國慶大酬賓,全場1折',
'emailAddress' => 'axxxx@example.com',
'body' => '您好,國慶期間大酬賓,全場所有商品統統1折甩賣。'
];
$arr[1] = [
'subject' => '注冊會員送100金幣',
'emailAddress' => 'bxxxx@example.com',
'body' => '郵件內容'
];
$arr[2] = [
'subject' => '國慶大酬賓,全場1折',
'emailAddress' => 'cxxxxx@example.com',
'body' => '郵件內容2'
];
foreach ($arr as $k=>$v) {
$redis->rpush("mailerlist", json_encode($v, JSON_UNESCAPED_UNICODE));
}
$client = new Client();
$client->connect();
```
## 驗證
根據上一節內容,我們應該先啟動服務端,看到Swoole服務端啟動好了,我們再運行客戶端:
```
php mailClient.php
```
然后你可以去查看對方郵箱是否收到相關郵件。
本文中使用了redis作為簡單隊列,你也可以使用復雜點的隊列rabbitmq。你也可以使用Crontab來做定時任務,不過它最小粒度是分鐘級別的。當然對于批量發送郵件,如果你不用php的話,可以用Python或者Java,它們都有相當成熟的解決方案。