试着使用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先生。

被一群厉害的人包围着,太让人吃惊了。

bannerAds