Socket.io + Redis PubSubでリアルタイムメッセージ配信
とあるサービスに「チャット機能」を追加しようという話になり、急ピッチで仕組みを用意することになった。 仕様/要件はふわっとしているものの、 2週間後にはリリースというケツは決まっている。 はてさてどうしたものかとその瞬間は思ったものだが、無事仕組みとして載せられたので、備忘記しておく。
リアルタイムチャット機能
要件は以下。
例えば、動画を視聴しているとして、その動画の横に、自分含む視聴者のコメントが流れてくる、 ようなものを想像してもらえれば良い。
Socket.IO
「クライアント - サーバ間のリアルタイムなメッセージのやり取り」ということで、
という方針は早々に決まった。自動的にサーバは Node.js に決定。 ブラウザからのアクセスがあるため、xhr-polling / WebSocket をSocket.IO が両方サポートしていることは有難かったし、WebSocket通信を数行で始められる手軽さも魅力的だった。 ケツが2週間後なので実装コストがなるべく少なく済み、ビジネスロジックに集中できる方法を採択する必要があったため。
Socket.IOのポイントをまとめると、
- WebSocket, xhr-polling をサポート、クライアントの動作環境に応じていずれかを自動採択する
- "1 : 1" や "1 : N" のようなBasicな特定双方向通信、一斉配信 (broadcast)、接続単位を論理的に分離する 名前空間/namespace や 部屋/room 機能 など様々な配信方法をサポート
インストールは npm で一発 。 実装イメージは以下。
server side
var io = require('socket.io').listen(3000); io.sockets.on('connection', function (socket) { ... your logic ... });
client side
<script src="http://your.domain.com:3000/socket.io/socket.io.js"></script> <script> var socket = io('http://your.domain.com:3000'); socket.on('connect', function() { ... your logic ... }); ... and so on... </script>
ネイティブアプリ用のクライアントライブラリもある。 Socket.IOのGithub を見たところ、 C++ の実装もあるようだ。
Redis PubSub
Socket.IO on Node.js なWeb/Appサーバに対して、各クライアントはWebSocket通信で接続する。 接続数増と耐障害性を担保するためWeb/Appサーバの冗長化を考えた時に、一斉配信するメッセージを複数台のサーバにどのように伝達させるかで一瞬悩んだ。 所謂Publish / Subscribe ... ということで思い出したのがRedis。 "危険なほどのスピード" で有名なオンメモリKey-Value データストアで、扱えるデータ構造がMemcached と比べて豊富、ディスクへの永続化もあり人気のKVSだが、このRedis、PubSub も提供している。
1. 購読者 (subscriber) 、任意のkeyをsubscribeする
$ redis-cli 127.0.0.1:6379> SUBSCRIBE hoge Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "hoge" 3) (integer) 1
2. (別のターミナルで) 出版者 (publisher) 、1で指定したkeyでメッセージをpublishする
$ redis-cli 127.0.0.1:6379> PUBLISH hoge "hello pubsub" (integer) 1
3. 購読者 (subscriber) の端末に、2で配信されたメッセージが表示される
$ redis-cli 127.0.0.1:6379> SUBSCRIBE hoge Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "hoge" 3) (integer) 1 1) "message" ←追加 2) "hoge" ←追加 3) "hello pubsub" ←追加
Socket.IO - Redis の連携は容易。Socket.IOのバージョンによりインストール方法が若干変わる点に注意。
- Socket.IO v0.9 系まで
- Socket.IO 内に同梱されているため、特に追加インストールは必要なし。
- 呼出は require('redis')
- Socket.IO v1.0 以降
- 外部ライブラリとして切り離された socket.io-redis をインストール
- 呼出は require('socket.io-redis')
ちなみに、AWSのElasticCacheはRedisをサポートしており、Read Replica & Multi-AZも対応している。
PubSubはReplica Nodeに対しても有効で、PubSub sessionに関しても負荷対策が可能。
- Node.jsからReplica Nodeに対してSubscribe sessionを張っておく
- コメント投稿はMaster Nodeに対してPublish
- Read Replicaに対してSubscribeしている全購読者に配信される
WebSocketとELB
Socket.IO on Node.js サーバを冗長化させた場合の振り分け方も一工夫必要だった。 通常のHTTP通信と異なるため。注意点は以下の2点。
- Cient - Server間の通信は通常のHTTP通信と異なりWebSocketで接続持続される
- Socket.ioによるhandshake処理は必ず同一サーバに接続させなければならない
例えば、ロードバランサーを上段に挟む場合、全てのセッションがロードバランサーに対して張られることとなる。 通常のHTTPと異なりWebSocketのセッションを張り続けるという特性上、接続を集約させるロードバランサーがボトルネックになる危険がある。 また、ロードバランサーによっては、Socket.IO/WebSocketの間に立てない場合がある。例えば、AWSのELB。
- ProtocolはHTTPかTCPか → "あちらを立てればこちらが立たず"
- HTTPを選ぶと、ELBによりHTTP Headerが書き換えられWebSocketのhandshake時に利用するHTTP Upgrade headerが削られWebSocket接続できない (XHR-Pollingが強制される)
- TCPを選ぶと、Sticky Sessionが利用できず接続するたびに別のサーバが応答するためhandshakeに失敗することがある
- 参考: Express / Socket.IO をスケールアウトしてみよう
- どちらのProtocolを選んでも、ELBによりTimeout扱いとされ通信が切断される可能性がある。
AWSのSolution Architectも、ELBは最初のセッション確立時にのみ利用してdispatchしてWebSocketさせるのが良いと言及している。
教えに従い、接続情報を返すAPIをクライアント側で呼出してから、サーバに対して直接WebSocket通信させる方式とした。
システム構成
行き着いたシステム構成は以下。
- Web/Appサーバ: Node.js/Socket.IO, Apache/PHP on Amazon EC2
- Load Balancer: Amazon ELB
- DB: Amazon RDS for MySQL
- KVS: Amazon ElastiCache for Redis
主機能がApache / PHPで動くWebアプリケーションのため、APIは既存資産を流用して実装。 Apahce/PHP を Node.js/Socket.IOと同梱させAPIを提供するようにした。 同梱させた理由は、接続情報管理の実装を省き自分のホスト名を返すようにしたかったため。 本来は、APIサーバは切り離して、ステータス含めて接続情報を管理するようにしたほうが望ましいと思う。
次回は、WebSocket接続のSSL/TLS対応をまとめようと思う。
はじめてのNode.js -サーバーサイドJavaScriptでWebアプリを開発する-
- 作者: 松島浩道
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2013/03/15
- メディア: 大型本
- クリック: 15回
- この商品を含むブログ (5件) を見る
- 作者: Josiah L. Carlson,長尾高弘
- 出版社/メーカー: KADOKAWA/アスキー・メディアワークス
- 発売日: 2013/12/27
- メディア: 大型本
- この商品を含むブログ (4件) を見る
Amazon Web Services クラウドデザインパターン設計ガイド 改訂版
- 作者: 玉川憲,片山暁雄,鈴木宏康,野上忍,瀬戸島敏宏,坂西隆之,日経SYSTEMS
- 出版社/メーカー: 日経BP社
- 発売日: 2015/05/28
- メディア: 単行本
- この商品を含むブログ (3件) を見る
Amazon Web Services パターン別構築・運用ガイド
- 作者: NRIネットコム株式会社,佐々木拓郎,林晋一郎,小西秀和,佐藤瞬
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2015/03/25
- メディア: 大型本
- この商品を含むブログ (2件) を見る