Help us understand the problem. What is going on with this article?

Socket.ioで作ったWebSocketサーバをELBでロードバランスする

More than 3 years have passed since last update.

課題

現在、作っているサービスではsocket.ioを使って作ったWebSocketサーバをELB経由で使っています。
開発環境はELB配下にEC2が1台で、本番環境は複数台です。

これ、どちらも正しく動いているように見えていたんですが、最近セットアップした本番環境の方でWebSocketへのUpgradeがうまくいっておらず、pollingで動いていることに今日気がつきました。

サービスとしてはまぁ一応問題はないんですが、WebSocketで動いていると思っていたものが実はPollingだったというのはわりかし衝撃です。

原因

なんでやねんと思ってググるとわりと簡単に原因に行き当たりました。公式ドキュメントで思いっきり説明されています。

http://socket.io/docs/using-multiple-nodes/

日本語だとこの辺でが参考になります。

どういうことかと言うと、socket-ioは最初はhttp(s)で接続して、WebSocketが使えそうだったらWebSocketを使うというアーキテクチャのため、イニシャルの接続でリクエストが2回発行されているんですね。

ロードバランシングしているとこの二つのリクエストが同じサーバに接続するとは限らないので異なるサーバに接続された場合にセッション情報が共有されていないためにWebSocketへのUpgradeが失敗する、と。

だったら、pollingもうまくいかないんじゃ。。。という気がしますがこれが動いているのは多分、Keep-Aliveが効いているためです。

WebSocketとhttp(s)ではそもそもプロトコルが違うので、どうしても接続が分かれてしまうんだろうと推測します。

対策(不採用)

公式では、以下の対応方法が紹介されています。

Sticky Sessionを使う

ロードバランサでSticky Sessionを使えるのであれば、同一クライアントからのリクエストは常に同一サーバに回されるのでこの問題は発生しません。

ただし、ELBの場合は

  • WebSocketを使用する場合はTCPモードを使用する必要がある
    • httpモードではHTTPヘッダがELBによって若干書き換えられるのでWebSocket Upgradeができない
  • TCPモードではSticky Sessionは使えない
    • httpヘッダとか一切見ないで素通ししているだけだから

となるのでこの方法は使えません。

Redisを使ってセッションを共有する

(2015/10/15 追記)同僚からこれはメッセージをシェアするだけでハンドシェイク時には機能しないんじゃないかという指摘を受けました。
socket.io-redisのソース見た感じ多分その通りです。
複数のWebSocketサーバを建てる場合にRedisは有効な解だと思いますが、以下のオリジナルの記述は間違いです。

多分これが正攻法です。
複数立っているsocket.ioサーバのセッション情報をRedisを介して共有することができるようなので、そうすれば複数のリクエストが異なるサーバに繋がっても問題なくなります。

解としてはこれがベストだろうと思うんですが、当たり前ですがRedisサーバを用意する必要があるわけです。
いずれやるのは良いとしても、時期的に今はサーバサイドに大きく手を入れたくはないのです。。。

対策(採用)

なんか方法はないものかと、socket.ioのソースを眺めていた所、かなりあっさりした解決法にたどり着いたのでここに紹介します。

var io = require("socket.io-client");
var url = ...;

var socket = io(url, {
    transports: ["websocket"]
});

いじょ。(^^v

transportsのデフォルトは["polling", "websocket"]ですが、これをwebsocketのみにすることによって最初のhttp(s)での接続をスキップしていきなりWebSocket接続することができます。
この場合(TCP的な)リクエストは1回になるのでセッション問題は発生しません。

ただし、当然ながらWebSocketに対応していないような古いブラウザへの対応はできなくなります。

サービスの対象ブラウザをモダンブラウザに限定できるならこの方法もアリでしょう。

余談ですが、サーバサイドのコードには最初のhttp(s)リクエストに対応するためにCORS対応のコードが入っているんですが、このやり方の場合は多分それも不要になります。(WebSocketにはCORSなんて関係ないから)

まとめ

もはやWebSocket非対応のブラウザなんか使ってはいけませぬ

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away