CDPを実際に使ってみる

今回の投稿は「CDP Advent Calender 2012」12/16分の記事です。


題名の通り実際にサービスを運用する際の問題点をCDPを参考に、サービスにあったパターンを組み込んだ時の話をしたいと思います。

参考にしたパターンはCDP:Job Observerパターンです。


解決したい課題

スマートフォンで利用されるPush通知を配信する時に、登録ユーザーが数十万人存在するアプリが複数同時に通知しようとすると、1台のサーバだけだとすべての通知送信処理を終えるまで数十分掛かってしまう。
Pushの登録は、各プラットフォームのサーバへSocketを開いて登録するか、httpでpostする方式で、一度に登録できる対象が限られているために、送信する対象が増えれば増えるほど、各プラットフォームとの通信オーバヘッドがバカにならなくなってきます。


実装

CDP:Job Observerパターンでは、キューの登録状況を監視してオートスケールの機能を使ってサーバを追加しますが、今回の場合は事前に必要となるサーバ数が分かることもあり、自前でサーバを起動することにしました。

おおまかな流れは以下の様な感じ

  • 送信するメッセージや、送信相手の情報は、SQSに登録する。
  • サーバの起動は、送信管理サーバから行う。
  • 送信サーバからSQSのメッセージを取得して処理する
  • サーバの停止は、SQSに送信するメッセージが無くなったら、シャットダウンスクリプトを実行する。

構造


利点

  • 送信最大数を考慮してサーバを起動しておく必要がないので、コストが安く済む
  • 並列で処理するので、送信完了まで短くなる。

注意点

起動したサーバは数分で処理が終わったとしても、1時間分の費用が掛かってしまう。


実装の説明

送信サーバのAMI作成

送信サーバは、事前に送信プログラム以外などを登録したAMIを作成しておきます。
この時、サーバが起動した時に初期化と、キューの監視を行うプログラムの起動を実行する起動スクリプトを組み込んでおきます。
作成したスクリプトは「chkconfig add」などで、起動時に実行されるように登録します。

ただし、AMIをメンテするために起動する場合も、起動スクリプトが実行されてしまうと、色々面倒な事になってしまうので、起動スクリプトの中でUserDataが設定されているかをチェックして、一致した場合だけ実行するようにしておくと良いです。

例えば、

#!/bin/sh
user_data=`curl http://169.254.169.254/latest/user-data/`

if $user_data != "send_server" ; then
	# 送信サーバとして起動されていないのでスクリプトを終了
	exit;
fi


AMIを作成した後にプログラムの変更があると、その都度AMIを更新するのも手間なので、この起動スクリプトの中で最新のプログラムをソース管理サーバから取得するようにしておくのがオススメ。



サーバの起動

管理サーバでは送信対象のメッセージが登録されたら、送信対象数をチェックして、必要なサーバ台数を起動します。

以下のようなメソッドを用意して、必要な台数分ループでメソッドを呼ぶか、run_instancesの引数の$min_count、$max_countに必要な台数を指定して一気に起動します。

public function start_server($zone){
$opt = array(
"SecurityGroup" => "hoge_security",
"KeyName" => "hoge.key",
"UserData" => base64_encode("send_server"),
"InstanceType" => "m1.small",
'Placement' => array('AvailabilityZone' => $zone),
);
$amazon_ec2 = new AmazonEC2();
$amazon_ec2->set_region(AmazonEC2::REGION_APAC_NE1);
$response = $amazon_ec2->run_instances($config["ami_id"], 1, 1, $opt);
if($response->isOK()){
return true;
}

return False;
}

オプションの説明

  • SecurityGroup:任意のセキュリティーグループ
  • KeyName:任意の鍵
  • UserData:送信用サーバと分かるような情報
  • InstanceType:起動するインスタンスの種類
  • Placement:どこのゾーンに起動するかを指定。


起動を実行したら、SQSに送信する情報を登録します。
あとは、起動した送信サーバが、起動スクリプトから実行された送信プログラム(デーモンとして登録)で、SQSからメッセージを取り出して、各プラットフォームへ登録していきます。

送信サーバの終了スクリプト

インスタンスは一時間単位で課金されるので、送信サーバの送信プログラムの中で、起動から一定時間の間はSQSのメッセージが空となっても、一定間隔で新しいメッセージが登録されたかを繰り返しチェックします。
新しいメッセージが存在しなく、課金単位の時間(1時間とか)になるようであれば、終了スクリプトを実行して、自分のインスタンスを終了させます。


最後に

CDPには多くの素晴らしいアーキテクチャが公開されていて、読んでいるだけでも面白くて、実際のサービスを構築する上でのヒントをいっぱいくれます。

CDP Advent Calender 2012もあと数日ですが、引き続き楽しもうと思います!