SocketIO で HTTP Long Polling を使う場合、ひとつのコネクションが複数のプロセスにまたがることを許容しない。
Node の cluster
モジュールは sticky-session
を使うと同じ IP アドレスからの接続を固定のサーバープロセスでクライアントからの要求を処理できる (スティッキーセッション)。だが PM2 の cluster
モードは sticky-session
に対応していない。そのため、SocketIO を使っている場合は PM2 の cluster
モードが使えない。
cluster
モードを使わないとひとつのプロセスですべての処理をさせることになり、サーバー資源を余らせてしまう。なのでどうにかして対処したい。
fork
モードを使う
PM2 の fork
モードを使うと複数のプロセスを起動させられる。
PM2 は fork
モードでプロセスを起動させると環境変数「NODE_APP_INSTANCE
」(process.env.NODE_APP_INSTANCE
) にインスタンス ID を入れる。インスタンス ID は 0
からプロセスの数に応じて増える。そのため、基準となるポート番号に環境変数「NODE_APP_INSTANCE
」の値を加えることにより、複数のポート番号でサーバープロセスを起動できる。
// ...
var app = express();
var server = http.createServer(app);
var io = socketIO(server);
// ...
var port = 8080 + Number(process.env.NODE_APP_INSTANCE || 0);
server.listen(port);
H2O からリバースプロキシする
H2O の Proxy Directives は同一ホスト名で異なるポート番号のサーバーにリバースプロキシできない。
そのため、Nginx を介さなければならない。
Nginx の upstream
ディレクティブに羅列させ、ip_hash
ディレクティブを使うと cluster
モジュールと sticky-session
を使ったときと同様に同じ IP アドレスからの接続を固定のサーバープロセスで処理させられる。
upstream example-app {
ip_hash;
server 127.0.0.1:8080;
server 127.0.0.1:8081;
server 127.0.0.1:8082;
server 127.0.0.1:8083;
}
server {
listen 10000;
location / {
proxy_pass http://example-app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
あとは Nginx に対して H2O からリバースプロキシするだけで良い。
hosts:
"example.com:443":
listen:
port: 443
ssl:
certificate-file: /etc/letsencrypt/live/example.com/fullchain.pem
key-file: /etc/letsencrypt/live/example.com/privkey.pem
paths:
/:
proxy.reverse.url: http://127.0.0.1:10000/
proxy.timeout.io: 10000
proxy.websocket: ON
構成図
+-------------------+
| |
| Redis (port 6379) |
| |
+---------+---------+
|
+------------------------+------------+-----------+------------------------+
| | | |
+---------+---------+ +---------+---------+ +---------+---------+ +---------+---------+
| | | | | | | |
| Node (port 8080) | | Node (port 8081) | | Node (port 8082) | | Node (port 8083) |
| | | | | | | |
+---------+---------+ +---------+---------+ +---------+---------+ +---------+---------+
| | | |
+------------------------+------------+-----------+------------------------+
|
+---------+---------+
| |
| Nginx (port 10000)|
| |
+---------+---------+
|
+---------+---------+
| |
| H2O (port 443) |
| |
+-------------------+
わたしは HTTP/2 でサーバープッシュが使いたかったのと、brotil を使いたかったため H2O にポート 443 番を返してもらっている。
ALPN に対応している OpenSSL 1.0.2 以降で Nginx をビルドできる環境で、かつサーバープッシュが不要であるのならば、H2O ではなく Nginx にポート 443 番を返してもらえば良いだろう。そのほうが構成もシンプルになり、管理の手間も減る。