PlayとRedisでスケーラブルWebSocket

本日2本目。

年末から突発的に1人でWebSocketブームです。(^^v
概要レベルの知識はあったんですが、実際に触ってみると思った以上に使える技術で応用範囲も広そうな気がします。
これは楽しい!


さて、スケールアウトするWebSocketアプリを作ろうと思ったらどうやらRedisが必修らしいので、Redisにも手を出してみることにしました。

この記事はそのまとめです。

 

★Redisとは

Memcacheの亜種。というのがこれまでの理解だったんですが、まぁだいたいあってます。基本的にはいわゆるKey-Valueストアです。

ですが実際にはMemcacheよりも遥かに高機能です。
ちゃんとドキュメントを読んだわけではないですが、ざっくりとMemcacheよりも優れている点を上げてみると

  • List, Set, Mapなどの集合を値として扱える
  • キーの一覧が取得できるなど管理系のコマンドがある
  • Pub/Sub機能がある
  • 永続化できる


などがあると思います。
今回、WebSocketで使うのは主にPub/Sub機能ですがListなどの集合の値をアトミックな操作で追加/削除できる機能はスケーラブルなアプリを作る上ではいかにも欲しそうな機能です。

通信プロトコルは多分独自で、ざっとクライアントのソースを眺めた感じではとてもシンプルそうです。可能な限り素のソケット通信に近いプロトコルを使用することで速度を稼いでいるんでしょうね。


★Pub/Subとは

Publisher/Subscriberの略です。日本語だと発信/購読という単語がしっくりきますかね。
任意のキー(チャネルという)を指定して購読を登録しておくと、そのチャネルに対して発信されたメッセージがすべての購読者にプッシュで配信されます。

ひとつのSubscriberで複数のチャネル(またはパターン)を登録することが可能(「hoge」、「fuga」、「room1.*」みたいな感じで指定できる)で、配信されるメッセージにはマッチしたチャネル名も含まれます。
なので、購読者はチャネルで分岐して処理を振り分けることができるわけです。まさにWebSocketのためにあるような機能ですね。(^^v


さて、プッシュをどうやって実現しているかというとその方法は極めて単純で単にソケットを繋ぎっぱにしてるだけです。
なので購読を行っているクライアント(ソケット)はメッセージがこない限りブロックします。(ここではNon Blocking I/Oは考慮せず単純なソケット通信を前提に話しています。)
まぁ、この辺たいていはライブラリがうまいことやってくれるので利用者はあまり気にする必要はないんですが、この事実からPub/Subを使う時の重要な注意点が導き出せます。
それは以下のような点です。

  • 購読中のクライアントは購読登録/解除のメソッド以外を実行してはならない
  • もちろん発信もアウト
  • 購読解除を行わない限りそのコネクションは占有され続ける


などなど。
初めて実装を行う際にはなんとなく同じクライアントを使いまわして購読と発信をやってしまいそうになりますが普通にアウトです。

またサーバーサイドでコネクション数の上限が決まっているので購読解除のし忘れはそのままリソースリークです。(もっとも購読に限らずRedisのクライアントはソケット繋ぎっぱなしにしていることが多いと思います。なので、利用の際にはJDBCのConnectionと同じようにこまめにcloseするかコネクションプールを使用する必要があります。)


★Java/ScalaのRedisクライアント事情

調査を始めた当初はJedisとかSedisとかいう単語がいきなり目に飛び込んできてかなり混乱しました。GitHub時代の弊害で雑多なライブラリが乱立しておりどれが良いんだかさっぱりわからないんですが、以下にいくつか調べたものをあげておきます。

- Jedis
https://github.com/xetorthio/jedis

JavaのRedisクライアント実装です。
多分これがデファクトスタンダード。

JavaのRedisクライアントだからJedisですか。そうですか。。。(--

- Sedis
https://github.com/pk11/sedis

JedisのScalaラッパー。
といっても全部のAPIがラップされているわけではないので、ないものを使おうと思ったら結局JedisのAPIを直接たたくことになる。(Pub/Subとかはない)

ScalaのRedisクライアントだからSedisですか。そうですか。。。(--
なんとなく名前に騙されそうになりますが、多分これを使うくらいなら最初からJedisを直で使う方が良いです。

- scala-redis
https://github.com/debasishg/scala-redis

最初からScalaで実装されたクライアント。
多分これがScalaのデファクト。

元々は他の人が始めたプロジェクトをこの人がフォークしてガンガン作りこんでいつの間にかこっちが本流になったらしい。。。

う~ん、GitHub時代。。。(--
なんか本家と元祖が並んでるたこ焼き屋みたいで何を信じれば良いのか疑心暗鬼にかられますけど、Redisの公式ページのクライアント一覧でもこれに星がついているしMavenセントラルにも登録されているので多分これが本家(?)です。

どうでもいいけどこのクライアント一覧。

 

お前のクライアントをこのリストに載せて欲しいのか?だったらredis-doc repositoryをフォークしてプルリクだしな!

 

とか書いてあるよ。。。
凄いな、GitHub時代。(^^;

- scala-reids-nb
https://github.com/debasishg/scala-redis-nb

Akkaを使用したNonBlocking I/OのRedisクライアント。
作者は上のscala-redisと同じ人。

RedisはNonBlocking I/Oと相性良さそうだけど、残念ながらPub/SubのAPIはない。
多分まだ枯れてないので動向はチェックしつつも様子見な感じ?

- play-plugins/redis
https://github.com/typesafehub/play-plugins/tree/master/redis

Play2系のRedisPlugin。CacheインターフェースのBackendをRedisに置き換えてくれる。
内部ではSedisを使用している


- 結局どれを使えば良いの?
素直にJavaならJedis。Scalaならscala-redisが良いと思います。

Playから使おうと思ったら多分最初に見つかるのはplay-plugins/redisなのでそれをそのまま使いたくなるけど、個人的にはscala-redisの方がお勧めです。(Jedisを直に使うならそれでも良いと思う。つまりSedisの評価が低い。)

あと、scala-redisはソースがとても読みやすいところも良いです。
実のところRedisのドキュメントはほとんど見てないんだけど、これのソース見たおかげでRedisの使い方がなんとなくわかったというのもあります。

読みやすいのはあんまりScalaっぽくないコードのせいかもしれないですけどね。(^^;
個人的には自分型アノテーションの使い方とかこれ見て初めてちゃんとわかった気がするのでそれも良かったです。
いきなりPlay本体のソースとか見ちゃうと多分Scala嫌いになると思うので、最初はこれくらいのライトなライブラリから眺めるのも良いんじゃないですかね。(^^;;;


PlayのCacheインターフェースを使いたいなら、scala-redis版はないんですけどこれは自分で作っても良いと思います。
多分、

  •  Sedis版のソースをコピって、
  •  import置き換えて、
  •  コンパイルエラーがでなくなるまで直す!


とするだけで動くと思う。(^^;
もっとも、無理してCacheに合わせなくても直でRedisを使えば良いと思いますけどね。


★HerokuのRedis事情

ついでにHerokuのRedisアドオンについて。
自分でRedisサーバたてても良いんだけどそれすらもめんどくさいので、localhostから使うテストサーバもHerokuAddon(無料版)で済ませています。(^^;;;

HerokuのAddonページで「redis」を検索するとなんと5つものAddonが引っ掛かります。(2014年1月現在)
うち、4つがCloudでのRedisサーバ提供者。。。
どこに差別化ポイントがあるのか全くわかりませんが、多分見るべきポイントはそれほど多くはないです。

今回の場合

  •  価格
  •  コネクション数(最大接続数)
  •  メモリサイズ


の3つだけを見て、無料で一番メモリサイズが大きかったRedisCloud-25MBを選択しました。
各社Webコンソールの使いやすさとかが違うんだろうと予想しますけど、コマンドライン(CLI)でたいていのことはできるので多分そんなに重要ではないです。(Webコンソールを見たのはRedisCloudだけだけど、まぁ十分です。)

最重要なのは実はメモリサイズよりもコネクション数です。前述の通りPub/Subは購読者の数だけコネクションを消費するので、これが少ないとすぐに接続エラーになってしまいます。
無料で使えるAddonはいくつかありますが、どれもコネクション数は10なので実運用には耐えない。。。というか1人でテストしてるだけでも割と簡単に上限に達します。

まぁ、これがきっかけでリソースリークを気にするようになったので結果オーライではあるんですけどね。(^^;

これが許容できないなら自前でRedisサーバを動かすのも良いでしょう。
CLIを使うために結局のところRedisのインストールは必要ですし、インストールさえすれば設定なしでいきなり起動させることができます。

逆にHerokuアドオン(Paasサービス)を使うことのメリットはWebコンソールがあるということにつきます。
先にWebコンソールは重要ではないと書きましたが、それは80点と85点の差には意味が無いということであって、まったく無いのとあるのとでは大違いなわけです。

今回の場合特にコネクション数がリアルタイムにわかったのが非常にありがたかったです。

□□□□
PlayでのRedis Pub/Subの実装についても書くつもりですけど、全然そこまでたどりつかないですね。。。(^^;
続きはまた次回

コメント(0)