2013年03月14日

WebSocket アプリの負荷分散

はてなブックマークに登録

最近 SPDY と WebSocket がアツいですね。 再来週の SPDY & WS 勉強会 も、定員100名に対して 参加者が 247 名とかなりアツいことになっています。

その予習というわけでもないですが、最近 WebSocket を実サービスへの 導入方法を考えながら遊んでいたので、 WebSocket の負荷分散方法について 考えていることを書いておこうと思います。

ステートフルな WebSocket アプリケーション

HTTP サービスは基本的にステートレスな実装になっており、リクエストが来るたびに DBサーバーや memcached などのバックエンドから情報を取得して返していました。

この構成では Web アプリ自体は完全にステートレス化することができているので、 負荷分散機はラウンドロビン等のアプリケーションを無視した負荷分散をすることができました。

しかし、 WebSocket が登場してリアルタイム Web が普及し、それがリッチ化してくると、 ステートレス型では効率が悪くなる事があります。

たとえば複数のチャットルームを持つチャットアプリを作る場合、チャットの内容を Redis に格納し、 Redis の pubsub 機能を使って他の Web サーバーに更新を通知することができますが、 Redis は subscribe している Web サーバー全てに通知を送り、チャットログを取得するリクエストを さばかなければなりません.

MMO ゲームのようにアプリサーバーのメモリ上にチャットログを格納し、同じチャットルームにいる ユーザーが全員同じアプリサーバーに接続するようにすればどうでしょうか? アプリサーバーはバックエンドから通知を受け取ったりメッセージを取得しなくても全員に メッセージを配信できます。これならバックエンドとの通信はログを保存する1回で済みます。

正攻法でこの構成を組む場合、アプリをステートレスなフロントWebアプリとステートフルな バックエンドアプリの2段構成にすることができます。 でも、この程度ならリバースプロキシで直接リクエストの振り分けをやってしまったほうが 楽だし低コストですよね。

redis を使った動的な負荷分散

そこで、 redis に URL のパスごとに分散先を格納しておき、それを使って動的に リバースプロキシする構成を考えてみました。

効率や応答速度のことを考えたら、リバースプロキシは WebSocket のデコード&エンコードをする よりも、HTTP サーバーが Upgrade ヘッダを見つけると TCP プロキシとして動作するほうがいいはずです。

Go

最初に目をつけたのは Go です。nginx よりはコードを読み書きしやすいからです。 (node.js でも良いのですが個人的には JavaScript よりも Go の方が好きです)

Go の標準ライブラリ "net/http" にはそのものズバリな revserseproxy があります。 ただしこれは WebSocket に対応していないので、ちょっと改造して Upgrade ヘッダを見つけると TCP プロキシになる機能を追加しました。 (methane/rproxy)

あとは redigo という Redis クライアント(名前がカッコイイ!) と組み合わせればお手軽に賢いリバースプロキシの完成です。

nginx + lua

そういえば nginx も最近 WebSocket に対応したって話題になってたなーと思って調べてみると、 これも Upgrade ヘッダを見て TCP プロキシとしてふるまうようです。 さらに調べてみたら Redis + Lua を使って動的な負荷分散するサンプルもいくつかみつかりました。

ただし、 nginx のサブリクエストは基本的に HTTP 用であまり memcached や redis に格納した 生の文字列を扱うのに向いていないので、 lua-resty-redis という pure Lua のライブラリを使って書きなおしてみました。

これもちゃんと nginx の非同期機能を使ってくれるので Redis に問い合わせしている間 ブロックしたりはしませんし、コネクションプールも可能です。

せっかく作ったので Go 版を紹介したものの, nginx が使えるならもう nginx でいいですね。 でも、毎回 Redis に問い合わせるのではなく expire 付きの LRU キャッシュをインメモリでやるとか、 Socket.IO のセッションを解析して自分で Redis に格納するとか複雑なことをするなら Go の強みが生きてくるかもしれません。

サンプル

お待たせしました。サンプルはこちらになります。

KLab/websocket-reverseproxy-demo

このサンプルでは、 Tornado を使ったオンメモリで動くチャットアプリを2つのポートで立ち上げ、 Redis にチャットルームごとのバックエンドを格納した上で、 nginx と go によるリバースプロクシの動作を確認しています。

Go も nginx も開発版を使っているので少しハードルが高いかもしれませんが、 SPDY & WS 勉強会の 参加者はぜひ予習として試してみてください。


@methane
klab_gijutsu2 at 18:33│Comments(2)TrackBack(0)golang 

トラックバックURL

この記事へのコメント

1. Posted by 通りすがり   2014年08月20日 11:04
websocket は、プレーンテキスト垂れ流しですが、セキュアにするのに、ssl しか選択はないのでしょうか?
2. Posted by methane   2014年09月03日 20:48
WebSocket にはフレームのマスク処理があって、クライアント側は利用が必須になっていますし、サーバー側もマスクを有効にすることが許されています。
このマスクはフレーム毎にランダムな4バイトでフレーム内のデータを XOR します。
なので、一応プレーンテキストを避ける事はできるのですが、とても簡単な難読化でしかないので、セキュアではありません。

特殊な proxy に邪魔されないためにも、 WebSocket を使う場合は TLS を利用することが推奨されます。

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント