Laravel4、メールのキュー送信

Tags : Laravel4  

Laravel4でお気軽にサイト構築を行う場合、嬉しいのがメールが公式サポートされたことです。

パスワードリセッターまで入れ込んでしまったのは、ややおせっかいですが、キューを使い、メール送信でユーザーを待たせないアプリを簡単に作れてしまうのは、組み合わせの妙ですね。

サンプルコード

ルートにアクセスすると、メールを送信するサンプルコードです。メソッド名の通り、キューイングされ、短時間にレスポンスが返せます。

コード中、ビューにmail.queueを使用していますが、内容は<p>テスト</p>という簡単なものです。

Route::get('/', function()
{
    Mail::queue('emails.queue', array(), function($m)
    {
        $m->to('hiro.soft@gmail.com', 'Hirohisa Kawase')->subject('マイルドだろぉーぅ');
    });

    echo 'キューに登録してやったゼェぃ。';
});

キューを使用しない場合、メール送信処理が終了してから、ページにメッセージがechoされます。メールの送信処理が終了するまで、ユーザーは待たされます。

今回はメールをキューに登録して、すぐにechoします。要はメール送信を予約しておき、とりあえずページにメッセージを表示しておいて、後からゆっくり送信するというイメージです。

これは実際あったことですが、まだ東京に住んでいた時、地元の商店街の有名とんかつ店で弁当を頼もうとしました。若い女子高校生アルバイトと思われる店員に「ちょっと待ってください」と返答されました。彼女はただ、弁当の包み紙を折りたたんでいただけです。それから注文を受けてもらうまで3分待たされましたよ。

当然、その店は一ヶ月後に無くなりました。正しい対応はもちろんお客様がいらしたら、作業の手を止め、まず対応することです。対応を素早くやってから、注文をキッチンに送るなり、自分の「いつでも時間がある時で構わない」仕事を行います。皆さんもご存知の客商売の基本ですね。理由もなくお客様を待たせてはいけません。

キューの設定

使用するのはLaravel4のメール機能とキュー機能です。両方共Laravel4から取り入れられました。両者に対し、事前に設定を行う必要があります。

メールの設定は設定ファイルを見てください。そのまま指定していくだけです。難しくはありません。すでに過去記事もあります。

キューに関してはちょっと知識が必要です。ですが、他の機能と同様にドライバーを指定していく形式です。決して難しくはありません。

syncドライバー

同期ドライバーです。他に何も用意しなくても使用できます。ドライバー名に'sync'と指定するだけで、使用可能です。

ただし、「キュー」の旨みがありません。同期というのは、キューに登録したジョブの終了を待つという事です。例えばメール送信の場合、メール送信の依頼をサーバーが受け取ってくれるまで、制御が戻って来ません。ですから、普通にメール送信しているのと、ユーザーの待ち時間という点では代わりがありません。

前の例として上げた、とんかつ店の店員に例えるなら、注文を受け、キッチンにその注文を回し、それができるまでじっとキッチンの方を向いていて、他のお客様がいらしても無視することにあたるでしょう。。

今のところ、開発時、テスト時の設定と考えていたほうが良いでしょう。もしくは、とりあえずキューを使うコーディングにしておき、将来必要になった時に本当のキュー処理に切り換えることができます。設定ファイルの変更だけですので、コードを変更する必要はないわけです。

Beanstalkdドライバー

BeanstalkdはLinux環境で動作する、ジョブキューサービスです。ダウンロードし、インストールして使用します。

ちなみに、当サイトで紹介しています、仮想化環境VatualBoxの設定ツールVagrant用Ubuntu12.04+Laravel4のボックスでは、インストール済みです。

さらに、composer.jsonでパッケージを追加する必要があります。(現在ベータ4の状況でです。正式版ではわかりません。)

        "require": {
                "laravel/framework": "4.0.*",
                "pda/pheanstalk": "dev-master"
    },

上記のとおり、"pad/pheanstalk"を追加します。

とんかつ屋のキッチンの仕事を担当します。注文を受け取り、受け取った順番に処理します。

いつ注文が来るかわからないため、常に待機させておく必要があります。コマンドラインからの起動方法は2つあります。直接このコマンドを起動する方法と、Artisanを使用する方法です。

beanstalkd

もしくは

php artisan queue:listen

上記を実行すると、キューに仕事が登録、つまり仕事の依頼が来るのをじっと待ち続けます。ですから、端末に制御は戻って来ません。CTRL+Cで中断するまで、じっと待ち、仕事が来たらそれをさばき、また次の仕事をじっと待ちます。

キッチン担当ですから、それで良いわけです。お客様から注文だけ受け、「作っておきます。」と答えます。お客様は出来上がるのをただ待つ必要はありません。

コマンドをLinuxの端末で実行する場合、バックグラウンドで実行するなら&をコマンドの最後に付けましょう。

このBeanstalkdを実働時に使用する場合、きちんとこのコマンドによるサーバー(デーモン)が起動していることを監視する必要があります。なぜなら、止まってしまえば注文をさばく担当者がいなくなるからです。仕事はどこかへ行ってしまいます。Beanstalkdは仕事をどこかへ保存するのではなく、直接受け取り、直接実行するスタイルです。Beanstalkdが動作していない間にキューに登録した仕事は消えてしまいます。(生禄を保存するログを設定すると、残ります。詳しくはキュードライバにbeanstalkdを使用する をご覧ください。

ですから、実用にするには複数起動し、さらに起動し続けていることを監視する必要があると思われます。

仕事を受け、それをキューで管理するだけのWebサービスが商売として成り立つのが不思議に思われる人もいらっしゃるでしょう。キューサービスは通常ノンストップで提供されており、一度登録した仕事が無くなることはありません。そうした信頼性があるため、キューサービスでお金が取れ、商売として成り立っているわけです。

sqsドライバー

AMAZON SQSサービスを使用する方法です。

まだ試していないため、どう使用するのか分かっていません。ですが、メール送信にSQSを使用するくらいであれば、直接メール送信サービスに送ったほうが良いでしょう。なにせ天下のAMAZONですから、送りつけるだけで、キューイングされます。

ironドライバー

Iron.ioのキューサービスを使用する方法です。登録方法は以下の記事をご覧ください。

特徴はなんと行ってもPushキーの存在でしょう。キューサービスですので、キューの登録はAPIを使用し、Iron.ioへ登録します。もちろん、細かいAPIの仕様はLaravelに任せます。

Pushキーの場合、Webアプリを設置する環境へサーバーやデーモンを起動しておく必要はありません。Iron.ioへ仕事を登録したら、仕事が入ったことをIron.ioが教えてくれます。予め指定しておいたURLへ仕事を転送してくれるイメージです。

ですからもちろん、元のWebサービスと同じURLでも良いですし、他の別の場所に設置しておいた裏の仕事を担当する仕事人のURIに頼んでもよいのです。複数のURLに通知することもできます。

弱点もあります。URLへ通知するわけですから、アクセス可能なURLもしくはIPが必要になります。ネットに公開していないローカル環境で開発している場合、通常外へはサービスを開放していませんので、URLからアクセスできません。特別に連絡を取る仕組みを用意するか、もしくは表のネット社会で公開するまでテストはできないことになります。

使用する場合、Composerでパッケージを読み込む必要があります。(現在ベータ4です。正式版では指定する必要はなくなるかも知れません。)

    "require": {
            "laravel/framework": "4.0.*",
                "iron-io/iron_mq": "1.4.5"
    },

queue.php設定ファイルには、Iron.phpで登録したプロジェクト名のプロジェクトIDとトークン、それとキュー名を指定します。キュー名はお好きな名前をどうぞ。プロジェクトIDとトークンはIron.ioのサイトで確認します。

プロジェクトIDとトークンを確認するためにはIron.ioへログインしてください。

管理画面

プロジェクト名と並んで標示される、鍵アイコンをクリックします。

プロジェクト情報

すると上記のように、IDとトークンが表示されます。下部のDownload json fileをクリックし、ダウンロードすることも可能です。

両方の値を設定ファイルに登録しましょう。一つのプロジェクトにキューは複数登録可能ですので、キュー名は分かりやすいお好きな名前を付けてください。

実はドキュメントを詳しく調べたわけではありませんが、Pushキューの他、こちらからキューの状態を調べに行く必要のある通常のキューサービスもIron.ioは提供しているようです。

Pushキューとして動作させるには、キューサービスの使用を開始する前、つまり未処理のキューがない状態で、利用者側から仕事が登録されたことを知らせるURLを登録する必要があるようです。Iron.ioの管理画面から、Pushキューにする方法は用意されていないようで、APIを通じて有効にするらしいです。(ここにはまってしまいました。もし先行して試されているのでしたら、未処理で溜まっているジョブを削除しておきましょう。)

いちいちAPIを使って登録するのは面倒ですので、Artisanコマンドとして用意されています。

php artisan queue:subscribe キュー名 http://dummy.com/do

URLは通常、自分のアプリのルートに存在するものになるでしょう。ですが適当でかまいません。一回これでPushキューとして使用することを通知すれば、あとはIron.ioの管理画面から、変更したり、追加したりできます。

通知先URL設定

緑のXアイコンで登録URL削除、緑の部分で新たに追加です。全部削除してしまうと、未登録扱いになりますので、もう一度Artisanコマンドで登録する必要が起きます。

このコマンドはキューに対する未処理のジョブが存在する場合、エラーになります。前述の通り、もし未処理ジョブが存在する場合、削除しておくことをお忘れなく。

さて、仮に仕事が登録されたことを知らせるURIを'do'としましょう。それに対するルートを定義します。

Route::post('/do', function(){
    return Queue::marshal();
});

これだけです。POSTで受け、Queueクラスのmarshalメソッドをリターンするだけです。他には必要ありません。後はLaravelにおまかせです。

流れをおさらいしますと、今回の例の場合、ルートにアクセスすると、メール送信のジョブをキューに登録するためシリアライズしIron.ioへ送り、すぐにメッセージを表示し、ユーザのリクエストの処理は終了します。

Iron.ioはジョブを受け取り、それを登録したURLへPOSTで通知します。

登録したURLに対するルートで、Queueクラスのmarshal()が実行され、送られてきたジョブを実行します。(その後、たぶんIron.ioへ処理済みを通知し、Iron.ioは未処理のジョブを処理済みにします。)

やっていることは面倒ですが、実現するためにはIron.ioへの登録と受け取り先ルートでのQueue::marshal()のリターンですので、とてもお手軽にキューが利用できます。

キューメール使用時の注意

同じアドレスに送るという事はないため、メールアドレスはクロージャーの中にuse文を使用して渡すのが通常です。

 \Mail::queue( array( 'text' => $this->viewName ), $data,
                      function ($m) use ($email, $subject)
            {
                $m->to( $email )->subject( $subject );
            } );

Mail::queueを使用する場合、useのあとの括弧の間に、必ずスペースを一つ入れてください。sendメソッドを使用する場合は、スペースがなくても動作します。

キューを使用する場合、ここでは「定義」のみを行なっており、実行されるのは別のコンテキストです。別のコンテキストという事は、$mとか$emailとか、$subjectは存在しなくなります。 そこで、シリアライズし実行時まで保存されるようになっているのですが、その時の構文解析が決め打ちのポジションで取り扱われているからのようです。そして、メール以外の場合はfunctionとその後の括弧の間のスペースは無しのようです。eval()でエラーが表示されますので、その時は組み合わせをいろいろ変えてみてください。もしかしたら、将来のバージョンで直るかも知れません。