试着使用Redis在多个进程中共享会话,来扩展Socket.io(v1.2)
今天是Node.js Advent Calender的第6天,进展顺利呢。
在东京Node学院2014期间,我坐在@mizchi先生和@upgrade_ayp先生的旁边,好像在这里(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
环境
-
- Mac OS X 10.9
-
- Node.js 0.10.26
socket.io 1.2.1
socket.io-redis 0.1.4
redis 2.8.8
モジュール準備
$ mkdir socketApp
$ cd socketApp
$ npm i socket.io
$ npm i socket.io-redis
准备适合进行测试的进程数量。
這次為A、B做好了準備。
该源代码基于这篇文章。
-> 使用Node.js + Socket.IO + jQuery构建的最基本的聊天系统。
进程A,端口3001。
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()`的内容显示。
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。
为Mac OS X准备Redis(安装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
请修改源代码。请在之前的代码中添加两行代码。
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_a.jsと同様に2行追記)
我会重新启动Node.js,并尝试使用与之前相同的方式进行访问。

我认为你可以从这里看出,在多个进程间可以共享会话。
总结
如果要将Node.js/Socket.io扩展,我认为你将走上这样一条道路。如果在AWS上操作,由于ELB的限制,可能需要更多的配置,但基本的思路是这样的。
最近我一直在使用Milkcocoa,所以很久没有碰Socket.io了,但自己搭建服务器还是很有乐趣的。
Automattic 公司与 Guillermo 的先生合作非常荣幸。
下一个是@t_wada先生。
被一群厉害的人包围着,太让人吃惊了。