LoginSignup
111

More than 5 years have passed since last update.

Socket.io(v1.2)のスケールアウト。Redisを使って複数プロセスでのセッション共有してみる。

Posted at

Node.js Advent Calender の6日目です。順調に続いてますね。
この間の東京Node学園祭2014@upgrade_aypさんセッションで@mizchiさんと隣の席でしたがここでもmizchiさんの隣(次の日)みたいです笑

はじめに

今回のネタはNode.js/Socket.ioをスケールアウトするときに初心者向け記事です。
(実際にAWS上で複数サーバーを立ててNode.jsプロセスのロードバランシングをしたときにつまづいた経験から、おさらいを兼ねてやってみます。)

こちらの記事(Node.jsってなんじゃ?(Socket.IOとELBのまとめ))にあるように複数プロセスでSocket.ioを共有する場合はRedisを使います。

Socket.ioの0.9系以前と1系以降とではけっこう中身が変わっている印象ですが、 書籍や国内技術ブログなどは未だに0.9系以前で記述されたものが多い です。
(↑でリンクを貼った記事もやはりSocket.ioが1系になる前の記事です)

今では1系が主流なので1系の書き方で書いてみます。

ちなみに1系ではsocket.io-redisを使う必要があります。 0.9系まではsocket.ioだけで出来たみたいですけど、1系では外部ライブラリ化したみたいですね。

この辺りは@nulltaskさんのExpress / Socket.IO をスケールアウトしてみようを読むと勉強になります。

ちなみに、socket.ioを利用していない部分からsocket.ioへ接続させるためのsocket.io-emitterも公開されています。
socket.io-emitter

環境

モジュール準備

$ mkdir socketApp
$ cd socketApp
$ npm i socket.io
$ npm i socket.io-redis

テストするプロセス数分の用意

今回はA,Bを用意

ソースコードはこちらの記事をもとにしています。
-> Node.js + Socket.IO + jQuery で最小構成チャット

プロセスA ポート3001

process_a.js
var fs = require('fs');
var app = require('http').createServer(function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write("<h1>プロセスA</h1>");
  res.end(fs.readFileSync('index.html'));
}).listen(3001);

var io = require('socket.io').listen(app);
io.sockets.on('connection', function(socket) {
  socket.on('msg', function(data) {
    io.sockets.emit('msg', data);
    console.log('receive:', data);
  });
});

プロセスB ポート3002

process_a.jsと違うのはポート番号(3002)とres.write()の表示内容だけです。

process_b.js
var fs = require('fs');
var app = require('http').createServer(function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write("<h1>プロセスB</h1>");
  res.end(fs.readFileSync('index.html'));
}).listen(3002);

var io = require('socket.io').listen(app);
io.sockets.on('connection', function(socket) {
  socket.on('msg', function(data) {
    io.sockets.emit('msg', data);
   console.log('receive:', data);
  });
});

フロント側

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>chat</title>
</head>
<body>
<script src="/socket.io/socket.io.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
$(function() {
  var socket = io.connect();
  $('form').submit(function() {
    socket.emit('msg', $('input').val());
    $('input').val('');
    return false;
  });

  socket.on('msg', function(data) {
        console.log(data);
    $('div').prepend(data + '<br>');

  });
});
</script>
<form><input></form><div></div>
</body>
</html>

ディレクトリ構成

socketApp/
|
|---node_modules/
|      |---socket.io/
|      |___socket.io-redis/
|
|---index.html
|---process_a.js (ポート3001)
|___process_b.js (ポート3002)

一度起動してみる

ターミナルでnodeプロセスAを起動します。

$ node process_a.js

ブラウザからhttp://localhost:3001にアクセスします。

別タブか別ウィンドウのターミナルでプロセスBを起動します。

$ node process_b.js

Socket.ioがプロセス毎で動いているのが分かると思います。

これをAとBのプロセスで共有するためにRedisを使います。

redisの準備 (Mac OS XでRedisインストール)

Redisインストール

$ brew install redis
==> Downloading https://downloads.sf.net/project/machomebrew/Bottles/redis-2.8.8.mavericks.bottle.tar.gz
######################################################################## 100.0%
==> Pouring redis-2.8.8.mavericks.bottle.tar.gz
==> Caveats
To have launchd start redis at login:
    ln -sfv /usr/local/opt/redis/*.plist ~/Library/LaunchAgents
Then to load redis now:
    launchctl load ~/Library/LaunchAgents/homebrew.mxcl.redis.plist
Or, if you don't want/need launchctl, you can just run:
    redis-server /usr/local/etc/redis.conf
==> Summary
?  /usr/local/Cellar/redis/2.8.8: 10 files, 1.3M

Redis起動。デフォルトで6379ポートで起動します。

$ redis-server
[41618] 26 Nov 10:20:26.224 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
[41618] 26 Nov 10:20:26.226 * Increased maximum number of open files to 10032 (it was originally set to 256).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 2.8.8 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in stand alone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 41618
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

[41618] 26 Nov 10:20:26.227 # Server started, Redis version 2.8.8
[41618] 26 Nov 10:20:26.227 * The server is now ready to accept connections on port 6379

socket.io-redisを利用

ソースコードを書き換えます。先ほどのコードに2行追記してください。

process_a.js
var fs = require('fs');
var app = require('http').createServer(function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write("<h1>プロセスA</h1>");
  res.end(fs.readFileSync('index.html'));
}).listen(3001);

var io = require('socket.io').listen(app);
var redis = require('socket.io-redis'); //++追記
io.adapter(redis({ host: 'localhost', port: 6379 })); //++追記

io.sockets.on('connection', function(socket) {
  socket.on('msg', function(data) {
    io.sockets.emit('msg', data);
    console.log('receive:', data);
  });
});
process_b.js
(省略 process_a.jsと同様に2行追記)

Node.jsを再起動して先ほどと同様にアクセスしてみます。

これで複数プロセス間でセッションが共有できたことが分かると思います。

まとめ

Node.js/Socket.ioをスケールアウトさせようとするとこんな感じの道を通ることになると思います。AWSでやる場合はELBの都合上もう少し設定をごにょごにょする必要ありですけど、基本的な考え方はこんな感じです。

最近はMilkcocoaばかり使っていたので久しぶりにSocke.ioを触りましたがやっぱり自分でサーバー側作るのは楽しいですね。

Automattic社とGuillermo氏さまさまです。

次は@t_wadaさんです。

なんかすごい人たちに囲まれてたんですな汗

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
111