ホーム
採用情報
テックレポート - TechReport
テックレポート詳細
テックレポート - TechReport
Socket.IO, Redisを使用し各ゲーム間でプッシュ通知するシステム
執筆者
- 執筆者:
- 古谷 薫
- 所属部署:
- アメーバ事業本部 ピグ部門
- 業務経歴:
- アメーバ事業本部にて「アメーバピグ」の機能開発リーダー。認証、通知システムなど、アメーバピグ関連サービスが横断的に利用するサブシステムの開発にも従事。
概要
オンライン通知システム(開発コード「homing」)に関する研究内容です。
homingは、アメーバピグ、ピグライフ、ピグアイランド、ピグカフェ、ピグワールドにおいて各サービスの通知情報をログインしているユーザに横断的にプッシュ通知するためのシステムです。
homingでは、クライアントーサーバ間はSocket.IO、サーバサイドにNode.js、データストアにRedisを利用しています。
本研究では、homingのアーキテクチャ、およびNode.js、Socket.IO、Redisの利用においての問題点およびワークアラウンドについて説明します。
目次
1. 序論
アメーバピグは、異なるドメイン(別システム)において、複数のサービスを提供おり、以下のようなアーキテクチャの違いがあります。
このように異なるシステム間で相互にプッシュ通知を送るためのシステムとして、homingを開発しました。
表1-1. アメーバピグ関連サービスの違い
サービス名 |
サーバ |
サーバ-クライアント間プロトコル |
クライアント |
プラットフォーム |
---|---|---|---|---|
アメーバピグ |
Java |
独自バイナリプロトコル |
Flash |
PC |
ピグライフ |
Node.js |
WebSocket |
Flash |
PC |
ピグアイランド |
Node.js |
WebSocket |
Flash |
PC |
ピグカフェ |
Node.js |
WebSocket |
Flash |
PC |
ピグワールド |
Node.js |
WebSocket |
Flash |
PC |
アメーバピグSP |
Java |
HTTP |
HTML, JavaScript |
SP |
どこでもピグライフ |
Node.js |
socket.io(xhr-polling) |
HTML, JavaScript |
SP |
2. システム概要
homingは、アメーバピグ関連サービスのいずれかにログインしているユーザに対して、各サービスの通知情報を横断的にプッシュ通知します。
システムの構成は以下の通りです。
2-1. APIサーバ
ユーザへの通知要求を受け付けるREST fullなAPIを備えたWebサーバです。
Node.jsのClusterによる、master/worker構成で稼働します。
通知要求はワーカプロセスで処理します。
2-2. Redisサーバ(データストア用)
一時的なデータをストアするためのRedisサーバです。
sentinelプロセスによる、master/slave構成で稼働します。
ストアされたデータはメモリ上のみで管理し、ディスクへの書き込みは行いません。
以下の情報を管理します。
- ユーザのオンライン情報
- ユーザIDの索引情報
2-3. Redisサーバ(Pub/Sub用)
publish/subscribe処理のみを行うRedisサーバです。
sentinelプロセスによる、master/slave構成で稼働します。
通知処理のpub/subで主に利用されます。
負荷状態に応じて、この構成を複数に増設可能です。
2-4. Redisサーバ(Socket.IO用)
本システムは、socket.IOのセッション管理用に利用するRedisサーバです。
sentinelプロセスによる、master/slave構成で稼働します。
2-5. フロントサーバ
ユーザとの接続を管理し、ユーザへ通知データを送信します。
Node.jsのClusterによる、master/worker構成で稼働します。
サーバ、クライアント間の接続は、Socket.IOを使用しています。
通知データの送信処理はワーカプロセスで処理します。
3. 利用した技術
3-1. Node.js
Node.jsはサーバサイドJavaScriptの実装の1つで、以下のような特徴を持っています。
- V8エンジン上の実行環境
- シングルスレッドベースの非同期処理
- ノンブロッキングI/O
本システムでは、特性上I/Oバウンドな処理が多いためNode.jsを採用しました。
Node.jsで利用した主要なモジュールは以下の通りです。
モジュール |
バージョン |
備考 |
---|---|---|
Node.js |
0.10.21 |
0.10.21に以下の修正を適用したものを利用しています。 |
socket.io |
0.9.16 |
0.9.16に以下の修正を適用したものを利用しています。 |
express |
3.2.6 |
|
redis |
0.8.3 |
|
redis-sentinel |
0.0.5 |
|
cookie |
0.1.0 |
|
log4js |
0.6.6 |
|
uglify-js |
1.3.5 |
|
heapdump |
0.2.0 |
|
3-2. Socket.IO
Socket.IOは、クライアントとサーバ間の通信プロトコルを複数実装したクライアント/サーバのライブラリで、プロトコルを意識することなくリアルタイム通信を利用できます。
本システムは、クライアント環境にPC、およびSPを想定しており、複数の通信プロトコルをサポートする必要があるためSocket.IOを採用しました。
3-3. Redis
Redisは、オープンソースのkey-valueデータストアです。
以下のような特徴を持っています。
- 格納するValueがリスト、セット、ハッシュといったデータ構造
- データセットはメモリ内に読み込まれるため高速
- publish / subscribeが備わっている
本システムでは、大量なユーザ接続情報、およびユーザへの通知データを複数プロセス間で高速に処理する必要があるため、Redisを採用しました。
利用しているバージョンは以下です。
version : 2.6.16
4. アーキテクチャ
4-1. クライアントの接続
4-1-1. 接続分散
本システムでは、クライアント接続は各プロセスへ分散されます。
-
ロードバランサによる各サーバへの分散
ラウンドロビン方式により、クライアント接続は各サーバへ分散されます。 -
Node.jsのCluster構成による各プロセスへの分散
Cluster構成により、クライアント接続は各ワーカプロセスへ分散されます。
※本システムの場合、接続済みソケット共有型によりラウンドロビン方式で分散されます。
図4-1. Node.js Cluster 接続済みソケット共有型(ラウンドロビン方式による分散)
4-1-2. 接続処理
本システムでは、フロントサーバ、クライアント間の接続は、Socket.IOを利用しています。
また、フロントサーバはマルチプロセスで稼動するため、プロセス間のセッション共有にSocket.IOのRedisStoreを使用しています。
-
ハンドシェイク
socket.IOは、永続的な接続を確立する前にハンドシェイクを行います。
この際に、ユーザ認証処理を行っています。 -
ハンドシェイク共有
ハンドシェイクを行ったプロセスは、ハンドシェイク結果をRedisのpub/subにより他プロセスへ共有します。 -
接続
ハンドシェイク済みの場合、接続を確立します。
※本システムの場合、ハンドシェイク結果が未共有の場合、接続状態を維持したままハンドシェイク結果の共有を一定期間待ちます。 -
接続情報の維持
接続完了後、本システムで利用するユーザの接続情報をRedisへ期限付きで格納します。
接続が維持されている間、ユーザの接続情報は定期的に更新し、期限を延長し続けます。 -
切断
クライアントとの接続が切断された場合、ユーザの接続情報を破棄します。
4-2. 通知の送信
4-2-1. 通知処理
通知処理には、Redisのpub/subを利用しています。
-
通知先のフィルタ処理
APIサーバは、Redis上の接続情報を元に通知先のユーザが接続しているサーバ・プロセスを特定します。 -
Redisにpublish
サーバ・プロセスごとにsubscribeしているチャネルへ通知データをパブリッシュします。
※本システムの場合、通知処理により大量なpub/subが行われるため、pub/sub専用のRedisサーバを複数台用意し分散処理しています。 -
サーバ・プロセスが受信
各サーバ・プロセスは、Redisのslaveからsubscribeし、自プロセス宛のpublishのみ受信します。 -
送信
各サーバ・プロセスは、接続されているユーザへ通知データを送信します。
4-2-2. 通知データをFlashで扱う
本システムでは、サーバ、クライアント間の通信はSocket.IOを利用しています。
アメーバピグの場合、クライアントサイドはFlash(ActionScript)で実装されているため、Socket.IO(JavaScript)で受信した通知データをFlashのExternal Interfaceを使用し、Flashへと受け渡ししています。
4-3. 冗長化
4-3-1. Redis
本システムでは、master/slave構成で利用しています。また、Redis Sentinelによる自動フェールオーバーを導入しています。
Redis Sentinelによるフェールオーバー
本システムでは、Redisサーバごとにsentinelプロセスを起動しています。
sentinelプロセスは、masterプロセス、およびslaveプロセスに対して、PINGコマンド、およびINFOコマンドを定期的に発行し死活監視を行います。
閾値以上のsentinelプロセスがmasterプロセスのダウンを検知した場合にフェールオーバー処理が発動します。
図4-7. Redis Sentinelによるmaster/slave構成
Redis Sentinel Clientによるフェールオーバー検知
本システムでは、「redis-sentinel」というNode.js用モジュールを利用しています。
redis-sentinelを利用することでsentinelプロセスが把握しているmaster、slaveプロセスの情報を元にRedisへの接続を管理することができます。
Redisにてフェールオーバーが発生した場合、sentinelクライアントによりsentinelプロセスからのフェールオーバ通知を受信し、昇格したmasterプロセスへ再接続を行います。
※「redis-sentinel」をそのまま使った場合、RedisクライアントとRedisとの切断が発生した場合のみ再接続を行う仕様です。sentinelプロセスからの通知では、再接続は行いません。
図4-8. Redis Sentinel Clientによるフェールオーバ自動検知
4-4. 負荷分散
4-4-1. Redis
本システムは、Redisを中心にシステムが稼動するため、Redisへの負荷を考慮して3系統のRedis構成を導入し、役割に応じてそれぞれに分散しています。
本システムのRedis利用方法のほとんどが、pub/subによる通信であるためpub/subによる負荷が高くなります。そのため、pub/sub用のRedis構成を別途用意しています。また、subscribeするサーバプロセスの数が300以上になるため、subscribeは基本的にslaveプロセスから行うようにし、負荷に応じてslaveをスケールするようにしています。
APIサーバ - Redisサーバ
APIサーバのワーカープロセスは、Redisとの接続を以下の3種類保持します。
APIサーバ自体は、Publishを除き、Redisへデータの書き込みは行わないためslaveプロセスのみに接続しています。
用途 |
接続先 |
備考 |
---|---|---|
データ参照 |
データストア用Redisサーバ Slave |
※どのSlaveに接続するかは、Redis Sentinel Clientによるランダム振り分けによる。 |
Publish |
Pub/Sub用Redisサーバ Master |
※pub/sub用Redisサーバが複数系統存在する場合は、系統分接続する。 |
Subscribe |
Pub/Sub用Redisサーバ Slave |
※どのSlaveに接続するかは、Redis Sentinel Clientによるランダム振り分けによる。 |
フロントサーバ - Redisサーバ
フロントサーバのワーカープロセスは、Redisとの接続を以下の6種類保持します。
フロントサーバは、Publish、およびRedisへのデータ書き込みを行うため、Masterへの接続も行います。また、Socket.IOを利用しているため、Socket.IO用のRedisにも接続します。
用途 |
接続先 |
備考 |
---|---|---|
データ書込/参照 |
データストア用Redisサーバ Slave |
※どのSlaveに接続するかは、Redis Sentinel Clientによるランダム振り分けによる。 |
Publish |
Pub/Sub用Redisサーバ Master |
※pub/sub用Redisサーバが複数系統存在する場合は、系統分接続する。 |
Subscribe |
Pub/Sub用Redisサーバ Slave |
※どのSlaveに接続するかは、Redis Sentinel Clientによるランダム振り分けによる。 |
データ書込/参照 |
Socket.IO用Redisサーバ Master |
|
Publish |
Socket.IO用Redisサーバ Master |
|
Subscribe |
Socket.IO用Redisサーバ Slave |
※どのSlaveに接続するかは、Redis Sentinel Clientによるランダム振り分けによる。 |
4-4-2. APIサーバ、フロントサーバ
APIサーバ、フロントサーバについては、ロードバランサによる分散です。
5. キャパシティ
5-1. 想定
アメーバピグでの運用を想定し負荷をかけ、本システムのキャパシティを計測しました。
キャパシティ測定の基準としては以下です。
- 各サーバ・プロセスがダウンしないこと。
- ユーザの接続が可能なこと。
- APIサーバが処理待ちのリクエスト数が増え続けないこと。(リクエストをさばけていること)
- フロントサーバの処理待ちの送信数が増え続けないこと(送信処理をさばけている)
アメーバピグでは、「ピグとも」というユーザ同士の繋がり情報を持っており、ユーザのアクションを他ピグともへ通知しています。
現状ピグともは、最大で300人まで登録でき、全ユーザの平均をとると1人あたり10名程度のピグともを保持しています。
この状況下における運用を想定して、負荷をかけました。
5-2. 試験環境
本番で利用を想定している構成にて試験を実施しました。
表5-1. キャパシティ計測時の条件
項目 |
数値 |
---|---|
APIサーバ数 |
5 server |
プロセス数 / APIサーバ |
16 process |
Redisサーバ数 (データストア用) |
Master × 1, Slave × 2 |
Redisサーバ数 (pub/sub用) |
Master × 1, Slave × 2 |
Redisサーバ数 (Socket.IO用) |
Master × 1, Slave × 2 |
フロントサーバ数 |
20 server |
プロセス数 / フロントサーバ |
16 process |
5-3. 接続処理 試験結果
接続ユーザ200,000人が接続した際の負荷を計測した結果です。
試験結果から、本システムで利用しているSocket.IOによるpub/subの負荷が高いため、ユーザの接続数に応じてRedisサーバ(Socket.IO用)をスケールする必要があることが分かります。
表5-2. 接続内訳
項目 |
数値 |
---|---|
接続ユーザ |
200,000 user |
秒間接続数 |
1200 user / sec |
ユーザの切断 |
無し |
各サーバの負荷状態は以下の通り。
※全ユーザが接続されるまでの間における、ピーク時の値です。
表5-3. フロントサーバの負荷
CPU / process |
MEM |
---|---|
80 % |
3GB |
表5-4. Redisサーバ(データストア用、兼pub/sub用) Master
operation |
CPU |
MEM |
---|---|---|
mset : 1,200 / sec |
60 % |
1.5GB |
表5-5. Redisサーバ(データストア用、兼pub/sub用) Slave
operation |
CPU |
MEM |
---|---|---|
- |
20 % |
0.5GB |
表5-6. Redisサーバ(Socket.IO用) Master
operation |
CPU |
MEM |
---|---|---|
publish : 1,200 / sec |
80 % |
8.0GB |
表5-7. Redisサーバ(Socket.IO用) Slave
operation |
CPU |
MEM |
---|---|---|
message : 720,000 / sec |
100 % |
10GB |
5-4. 通知処理 試験結果
接続ユーザ200,000人が接続完了した状態で、以下のAPIリクエストをコールし計測した結果です。
試験結果から、通知処理によるpub/subの負荷が高いため、ユーザの接続数に応じてRedisサーバ(pub/sub用)をスケールする必要があることが分かります。
表5-8. APIリクエスト内訳
項目 |
数値 |
---|---|
計測時間 |
15 分 |
APIリクエスト数 |
1,200 req / sec |
1リクエストあたりの通知対象ユーザ |
300 user / req |
1リクエストあたりの通知送信されるユーザ |
150 user / req |
通知メッセージサイズ |
200 Byte |
1秒間に送信される通知数 |
180,000 message / sec |
各サーバの負荷状態は以下の通り。
※計測時間中のピーク時の値です。
表5-9. APIサーバの負荷
CPU / process |
MEM |
処理待ちリクエスト数 |
---|---|---|
60 % |
3GB |
1 ~ 10 |
表5-10. Redisサーバ(データストア用、兼pub/sub用) Master
operation |
CPU |
MEM |
---|---|---|
publish : 90,000 / sec |
70 % |
5GB |
表5-11. Redisサーバ(データストア用、兼pub/sub用) Slave
operation |
CPU |
MEM |
---|---|---|
mget : 2,400 / sec |
90 % |
1.8GB |
表5-12. Redisサーバ(pub/sub用) Master
operation |
CPU |
MEM |
---|---|---|
publish : 90,000 / sec |
80 % |
6GB |
表5-13. Redisサーバ(pub/sub用) Slave
operation |
CPU |
MEM |
---|---|---|
message : 90,000 / sec |
50 % |
0.5GB |
表5-14. フロントサーバの負荷
CPU / process |
MEM |
処理待ち送信数 |
---|---|---|
50 % |
5GB |
1 ~ 10 |
6. まとめ
オンライン通知システム(開発コード「homing」)の開発に以下を導入したが、それぞれに課題があったためここにまとめます。
-
Node.js Cluster
本システムで利用したバージョンでは、プロセスへの分散が均等に行われないため、接続を維持するようなシステムでは利用は避けたほうがよいです。
次期バージョンにて、本システムで利用した接続済みソケット共有型によるラウンドロビン分散が組み込まれているようなので、利用するなら次期バージョン以降をお勧めします。 -
Socket.IO
本システムで利用したバージョンでは、RedisStoreによるセッション共有の仕組みが高負荷時に大量のリソースを消費したり、接続率が低下したりと実際のサービスに利用するにはいろいろと工夫が必要だということが分かりました。
次期バージョンではRedisStoreに変わる実装が検討されているようなので、そちらに期待したいところです。 -
Redis
データをメモリ上で扱うため書込み速度が高速であり、本システムのような揮発性の高いデータを大量に扱うような場合は利用価値があると思われます。ただし、pub/subに関しては便利な反面多くのリソースを消費するため、利用方法は検討したほうが良いと思いました。
本システムは、通知データをユーザへ届けるためのハブのようなシステムであったためIOバウンドな処理が多く、Node.js、Socket.IO、Redisの組み合わせとしては相性が良かったように思います。
本システムは、アメーバピグの裏側で稼動中です。現在は、UIの表示は行っていませんが、今後アメーバピグ関連サービスから利用される予定です。運用を通して発生した問題点などはフィードバックしたいと思います。
7. ソースコード
-
Node.js(patched) : https://github.com/tico8/node/tree/v0.10.15-patched
-
Socket.io(patched) : https://github.com/tico8/socket.io/tree/0.9.16-patched
8. 参考文献
-
Node.js : http://nodejs.org/
-
Socket.io : http://socket.io/
-
Redis.io : http://redis.io/