WebSocketでチャットアプリを作る with Redis&Node.js

    どうも。営業部の伊藤です。

    もともとHTML5の仕様の一部として策定が進められていたWebSocketについて勉強するため、Node.jsを利用して簡単なチャットアプリを作ってみました。(目指せブラウザ版LINE!)

    今回はデータベースとしてRedisを採用しましたので、まずはRedisのインストールとコマンドラインを使った簡単な動作確認までを書きます。

    まず、Redisをインストールします。
    yumでもインストールはできますが、最新をインストールするため、ソースからインストールします。
    ※2012年9月頃

    # wget http://redis.googlecode.com/files/redis-2.4.16.tar.gz
    # tar zxvf redis-2.4.16.tar.gz
    # cd redis-2.4.16
    # make
    # make install

    あっという間です。
    RedisはANSI-Cで実装されているため、インストール時に依存ライブラリなどのインストールが不要です。
    よって、コンパイルは上記ステップで完了となります。

    既知の情報だと思いますが、念のため。
    RedisはインメモリベースのKVSです。
    KVSはRDB(リレーショナルデータベース)のJOINのように複雑、且つ高負荷なSQLは発行せず、高速化/拡張性に趣をおいたNoSQLのデータベース管理システムとなります。
    いわゆる分散型データ保管技術の1つです。
    同様のデータベースにはGoogle検索で採用されているBigtable(ビッグテーブル)、Amazonで採用されているDynamo(ダイナモ)、有名どころだとmemcachedなど様々な種類があります。
    また、RDBのような複雑なシステムではないため、サービス(mixi、Amazonなど)や大学の研究で独自のKVSが開発/採用されているケースも多々あります。

    そんなKVSですが、KVSごとの特徴は?ということで、今回選定したRedisの特徴をいくつか記載します。
    1. 永続化機構
    通常メモリ上でデータを管理しているため、memcachedのように障害時、再起動時にはデータを喪失してしまいますが、Redisは定期的にデータのスナップショットをダンプファイルにし、再起動時にこのファイルをメモリに読み込むことでデータを復元する仕組みを備えています。
    定期的という事で不安を抱く方も多いと思いますが、”Append Only File”という仕組みを利用することで全ての更新コマンドを管理しておき、再起動時にこのコマンドを読み込むという方法もあります。これであればデータの喪失は最小限に食い止めれます。
    注意すべきなのは、永続化機構といってもあくまでもインメモリベースのため、最大容量はRAMに依存します。
    RAM容量より多くのデータを保存したい場合はスワップファイルを利用し、一部のデータをHDDに退避させる必要があります。(RedisVM)

    2. 豊富なデータ型とコマンド
    Redisはハッシュ(HASH)以外の型もサポートしており、リスト型(LIST)、集合型(SET)、順序付き集合型(SORTED SET)などのデータ構造が扱えます。
    また、コマンドが非常に豊富、且つアトミックに操作が可能なため、CAS操作などトランザクションを考慮した処理が不要です。
    ただし注意すべき点として、アトミック実行(MULTI/EXEC)は一般的なトランザクションとして実行されている訳ではないため、実行中にエラーが発生した場合にROOLLBACKはされません。残りの処理も実行されます…ここは要注意です!
    (値を設定&取得くらいのアトミック操作であれば”GETSET”という1つのコマンドで対応可能)
    KVSの利用用途としてはデータの整合性よりもスピード重視の利用用途が多いと思いますので、トランザクションを考慮する機会は少ないと思いますが、アトミックに操作が可能な事でデータ管理方法の幅が広がります。

    3. レプリケーション
    Redisはマスター/スレーブのレプリケーションが設定ができ、スケールアウトが非常に容易です。
    設定は簡単で、スレーブ側の設定ファイルに以下項目を追加するだけです。

    # cp redis.conf redis_slave.conf
    # vi redis_slave.conf

    # port 7000 <= スレーブはポート7000番で稼働
    # slaveof localhost 6379 <= slaveofにマスターのホスト名とポート番号を指定

    # redis-server redis_slave.conf

    設定できているか否かの確認はinfoコマンドを実行するか、実際に値を設定して確認します。
    infoコマンドの場合、実行後に「role」を確認してください。

    # redis-cli -p 6379 -h localhost <= マスターに接続
    redis 127.0.0.1:6379>info

    role:master

    役割がマスターになっていますね。次にスレーブに接続します。

    # redis-cli -p 7000 -h localhost <= スレーブに接続
    redis 127.0.0.1:7000>info

    role:slave

    役割がスレーブになっていますね。無事設定できているようです。

    最後に、実際にレプリケーションできているかを確認するため、マスターにデータを登録し、その後スレーブでデータを取得してみます。

    # redis-cli -p 6379 -h localhost
    redis localhost:6379> set test “test-data”
    OK
    redis localhost:6379> get test
    “test-data”
    redis localhost:6379>exit# redis-cli -p 7000 -h localhost
    redis localhost:7000> get test
    “test-data”
    redis localhost:7000>

    問題なさそうです。

    4. Publish/Subscribe型(Pub/Sub型)の通信をサポート
    今回DBにRedisを採用したのはこの「Pub/Sub型通信」をRedisがサポートしているためです。
    送信側と受信側がトピックを介して”多対多”で通信できるモデルのため、Redisを中継サーバとしてWebSocketサーバを冗長化する事ができます。
    Node.jsについて説明できていないため順番が前後しますが、Nodeを利用してPub/Sub通信の動きを確認してみます。

    nodeのコンソールを起動し、以下スクリプトを実行します。

    # node
    > var sys = require(“sys”);
    > var redis = require(“redis”);> var subscriber = redis.createClient(6379, “localhost”);
    > subscriber.subscribe(“I am subscriber”);
    > subscriber.on(“message”, function(channel, message){
    > sys.puts(channel + “に「” + message + “」というメッセージが送信されたようです。”);
    > });

    > var publisher = redis.createClient(6379, “localhost”); //publisherは敢えて作る必要ないです。var subscriberを使って問題無いですが、分かりやすくするため!
    > publisher.publish(“I am subscriber”, “I am publisher”);

    6379番ポートのredisの「I am subscriber」というトピックに対して配信を申し込み(subscribe)、配信があればコンソール上に受信内容を出力するようにします。
    その後、6379番ポートのredisの「I am subscriber」というトピックに対して”I am publisher”というメッセージを配信します。

    上記スクリプトを実行すると、以下メッセージが表示されます。

    > I am subscriberに「I am publisher」というメッセージが送信されたようです。

    Pub/Sub通信のイメージ湧きましたでしょうか?
    分かり辛いですね…なんというか…自作自演ってな感じですね。

    という事で、上記コンソールはそのままにしておき、別コンソールを起動して以下コマンドを実行してみます。

    # redis-cli -p 6379 -h localhost
    redis localhost:6379> publish “I am subscriber” “I am publisher”

    するとnodeコンソール上に先ほどと同様に以下メッセージが表示されます。

    > I am subscriberに「I am publisher」というメッセージが送信されたようです。

    これこそがPub/Sub型の配信モデルです。
    node側ではトピック「I am subscriber」を購読(subscribe)状態のため、
    別イベントで「I am subscriber」にメッセージが登録されたと同時にメッセージをお知らせしてもらっています。

    今回は受信/購読者(Subscriber)、発行/公表者(Publisher)共に単一で確認しましたが、前述したように多対多の通信モデルのため、複数のSubscriber、またはPublisherでも上記通信が可能となります。
    WebSocketサーバーを冗長化した際に各々のサーバーでsubscribeしておき、連携したいデータをpublishすればサーバー間連系はこの中継サーバーに委ねられます。
    単一であればWebSocketでブロードキャストすればいいだけですが、冗長化を考えるとPub/Sub型を利用できるredisは非常に便利です。
    エンドポイントはWebSocketで管理しているため、サーバー間はPub/Sub型じゃなくてTriggerでもいいんじゃない?という方も居ると思いますが、サービス独自の特殊な通信制御が必要でない限りはPub/Sub型通信モデルの方が便利だと思います。

    Redisの準備は一旦ここまでとし、次はNodeの準備を進めます。
    少し長くなってしまったため、Nodeと各種ライブラリ(Socket.IOやExpressなど)のインストールについては
    次回ブログで投稿させていただきます。