最近はもっぱらメール送信はAWS SESを使っているけど、まぁ遅いんですよね。
いや、SESがっていうか、メール送信自体がそもそも処理が重いので遅い。資料請求フォームとかで完了ページに遷移するのに、ブラウザがぐるぐる回り続けるのなんて嫌なのでキューサーバにぶん投げて、非同期にメール送信できるようにしてみる。そんでもって、ポーリングするワーカープロセスを管理するためにSupervisorを利用する。
前提
- CentOS 6.x
- PHP 5.4
- Cakephp 2.x
- AWS PHP SDK 2.x
- AWS SES & SQS
CakePHP SQSプラグイン
AWS SQSを簡単に使える素敵なプラグイン。ただ、このcakephp-sqsプラグインPHP5.3以降対応って書いてあるけど最新版ではPHP5.4以降の記法が使われているので注意。
サクッとcomposerでインストールする。ついでにAWS SDKも一緒にインストールする。
{
"repositories":[
{
"type":"pear",
"vendor-alias":"cakephp",
"url":"http://pear.cakephp.org"
}
],
"require": {
"lorenzo/cakephp-sqs": "dev-master",
"aws/aws-sdk-php": "2.*"
},
"config": {
"vendor-dir": "htdocs/app/Vendor/"
},
"extra": {
"installer-paths": {
"htdocs/app/Plugin/SQS": ["lorenzo/cakephp-sqs"]
}
}
}
$ composer install
CakePHPでSQSプラグインを使うための設定を記述。
<?php
App::import('Vendor', 'autoload');
CakePlugin::load(array('SQS'));
Configure::write('SQS', array(
'connection' => array(
'key' => 'キー',
'secret' => 'シークレットキー',
'region' => 'ap-northeast-1' //東京リージョン
),
'queues' => array(
'mail' => 'https://sqs.ap-northeast-1.amazonaws.com/00000/mail',
)
));
SQSへキューを送る
<?php
ClassRegistry::init('SQS.SimpleQueue')->send('mail', array('to' => 'sample@example.com'));
データはAWS SQSで設定したサイズまでであれば、文字列/配列など送りつけられる。(実際はJSONで送りつけられる)
SQSのキューを受け取る
receiveMessage()で受け取れるんだけど、今回はCakePHP SQSプラグインが用意してくれているWorker shellを使う。
<?php
class MailWorkerShell extends AppShell {
public $tasks = array('SQS.QueueWorker');
public function main() {
$this->QueueWorker->addFunction('mail', $this, 'handleJob');
$this->QueueWorker->work();
}
public function handleJob($data) {
// メール送信
$email = new CakeEmail();
$email->to($data['to']);
$email->send();
return true;
// 処理が失敗して、再度キューを受け取りたい場合は、falseを返すとキューは削除されない
}
}
WorkerShellを実行する
$ /vagrant/htdocs/app/Console/cake -working /vagrant/htdocs/app -app /vagrant/htdocs/app MailWorker
これでプロセスが終了されない限りはSQSをポーリングしてくれる…が、ログアウトしたりセッション切れたりしたら当然ながらプロセス死ぬので、Supervisorを使ってプロセス管理することにする。
nohupしてプロセス監視するスクリプト書いてもいいけど、面倒だし、CakeResqueっていうナイスなCakePHPプラグインもあるけど、ちょっと今回の用途とは違う。
Supervisorを使ってプロセス管理
Supervisorをインストール
python製なのでeasy_installでインストール可能(pipでもおk)。
# easy_installとSupervisorをインストール
$ sudo yum install python-setuptools
$ sudo easy_install supervisor
起動スクリプトなどが、本家のリポジトリで公開されているので、それをそのまま利用する。
# 起動スクリプトを本家からコピー
$ cd /tmp
$ git clone git://github.com/Supervisor/initscripts.git
$ cd initscripts/
$ sudo cp redhat-init-jkoppe /etc/init.d/supervisord
$ sudo cp redhat-sysconfig-jkoppe /etc/sysconfig/supervisord
$ sudo chkconfig --add supervisord
Supervisorの設定
Supervisorの設定はシンプル。
[unix_http_server]
file=/var/run/supervisor.sock
[supervisord]
logfile=/var/log/supervisord.log
loglevel=info
pidfile=/var/run/supervisord.pid
; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock
Supervisorにプロセスを追加
[program:email]
command=/vagrant/htdocs/app/Console/cake -working /vagrant/htdocs/app -app /vagrant/htdocs/app MailWorker
redirect_stderr=true
stdout_logfile=/var/log/email.log
autostart=true ; Supervisor起動時に、自動実行
autorestart=true ; プロセスが死んだ時に、自動再起動
user=vagrant ; プロセスの実行ユーザ(適宜)
commandにて、実行したいコマンドを記述するが、nohupや&などをつけてバックグラウンド処理にしてはいけない。
Supervisor起動&ステータス監視
# 起動
$ sudo service supervisord start
# ステータス監視
$ sudo supervisorctl status
email RUNNING pid 3766, uptime 0:00:0
これで、プロセスを殺しても復活する。
$ ps aux | grep cake
vagrant 3801 0.0 0.2 107452 940 pts/0 S+ 17:30 0:00 grep cake
# 死ね
$ sudo kill -9 3801
# 確認してみると復活してる
$ ps aux | grep cake
vagrant 3888 0.0 0.2 107452 940 pts/0 S+ 17:30 0:00 grep cake