CakePHPでSQSを使って非同期にメール送信し、送信プロセスの監視を行う


最近はもっぱらメール送信は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以降の記法が使われているので注意。

- https://github.com/lorenzo/cakephp-sqs

サクッとcomposerでインストールする。ついでにAWS SDKも一緒にインストールする。

```composer.json
{
    "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プラグインを使うための設定を記述。

```/app/Config/bootstrap.php
<?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を使う。

```MailWorkerShell.php
<?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](http://cakeresque.kamisama.me/)っていうナイスなCakePHPプラグインもあるけど、ちょっと今回の用途とは違う。

## Supervisorを使ってプロセス管理

- [Supervisor](http://supervisord.org/)

### Supervisorをインストール

python製なのでeasy_installでインストール可能（pipでもおｋ）。

```
# 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の設定はシンプル。

```/etc/supervisord.conf
[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にプロセスを追加

```/etc/supervisord.conf
[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
```

### 参考

- [supervisorでデーモンプロセスを管理する](http://int128.hatenablog.com/entry/20130208/1360327252)