Node.js、Socket.io、Redis pub/sub 大容量、低延迟困难
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10557826/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Node.js, Socket.io, Redis pub/sub high volume, low latency difficulties
提问by lebear
When conjoining socket.io/node.js and redis pub/sub in an attempt to create a real-time web broadcast system driven by server events that can handle multiple transports, there seems to be three approaches:
当将 socket.io/node.js 和 redis pub/sub 结合起来,试图创建一个由可以处理多种传输的服务器事件驱动的实时网络广播系统时,似乎有三种方法:
'createClient' a redis connection and subscribe to channel(s). On socket.io client connection, join the client into a socket.io room. In the redis.on("message", ...) event, call io.sockets.in(room).emit("event", data) to distribute to all clients in the relevant room. Like How to reuse redis connection in socket.io?
'createClient' a redis connection. On socket.io client connection, join the client into a socket.io room and subscribe to relevant redis channel(s). Include redis.on("message", ...) inside the client connection closure and on receipt of message call client.emit("event", data) to raise the event on the specific client. Like the answer in Examples in using RedisStore in socket.io
Use the RedisStore baked into socket.io and 'broadcast' from the single "dispatch" channel in Redis following the socketio-spec protocol.
'createClient' 一个 redis 连接并订阅频道。在 socket.io 客户端连接上,将客户端加入 socket.io 房间。在 redis.on("message", ...) 事件中,调用 io.sockets.in(room).emit("event", data) 分发给相关房间的所有客户端。比如如何在 socket.io 中重用 redis 连接?
'createClient' 一个 redis 连接。在 socket.io 客户端连接上,将客户端加入 socket.io 房间并订阅相关的 redis 频道。在客户端连接关闭中包含 redis.on("message", ...) 并在收到消息调用 client.emit("event", data) 以在特定客户端上引发事件。就像在 socket.io 中使用 RedisStore 的例子中的答案一样
使用嵌入 socket.io 的 RedisStore 并按照 socketio-spec 协议从 Redis 中的单个“调度”通道“广播”。
Number 1 allows handling the Redis sub and associated event once for all clients. Number 2 offers a more direct hook into Redis pub/sub. Number 3 is simpler, but offers little control over the messaging events.
编号 1 允许为所有客户端处理一次 Redis 子和关联事件。数字 2 提供了一个更直接的钩子到 Redis 发布/订阅。数字 3 更简单,但对消息传递事件几乎没有控制。
However, in my tests, all exhibit unexpectedly low performance with more than 1 connected client. The server events in question are 1,000 messages published to a redis channel as quickly as possible, to be distributed as quickly as possible. Performance is measured by timings at the connected clients (socket.io-client based that log timestamps into a Redis list for analysis).
但是,在我的测试中,所有连接的客户端都表现出意外的低性能。有问题的服务器事件是尽快发布到 redis 通道的 1,000 条消息,以便尽快分发。性能是通过连接客户端的时间来衡量的(socket.io-client 基于将时间戳记录到 Redis 列表中以供分析)。
What I surmise is that in option 1, server receives the message, then sequentially writes it to all connected clients. In option 2, server receives each message multiple times (once per client subscription) and writes it to the relevant client. In either case, the server doesn't get to the second message event until it's communicated to all connected clients. A situation clearly exacerbated with rising concurrency.
我推测在选项 1 中,服务器接收消息,然后将其依次写入所有连接的客户端。在选项 2 中,服务器多次接收每条消息(每个客户端订阅一次)并将其写入相关客户端。在任何一种情况下,服务器都不会到达第二个消息事件,直到它与所有连接的客户端进行通信。随着并发性的增加,情况明显恶化。
This seems at odds with the perceived wisdom of the stacks capabilities. I want to believe, but I'm struggling.
这似乎与堆栈功能的感知智慧不一致。我想相信,但我在挣扎。
Is this scenario (low latency distribution of high volume of messages) just not an option with these tools (yet?), or am I missing a trick?
这种情况(大量消息的低延迟分发)是不是这些工具的选项(还?),还是我错过了一个技巧?
回答by Mark Essel
I thought this was a reasonable question and had researched it briefly a while back. I spent a little time searching for examples that you may be able to pick up some helpful tips from.
我认为这是一个合理的问题,并在不久前对其进行了简短的研究。我花了一些时间搜索示例,您可以从中获取一些有用的提示。
Examples
例子
I like to begin with straight forward examples:
我喜欢从直接的例子开始:
The light sample is a single page (note you'll want to replace redis-node-client with something like node_redisby Matt Ranney:
轻量级示例是单个页面(请注意,您需要将 redis-node-client替换为 Matt Ranney 的node_redis 之类的内容:
/*
* Mclarens Bar: Redis based Instant Messaging
* Nikhil Marathe - 22/04/2010
* A simple example of an IM client implemented using
* Redis PUB/SUB commands so that all the communication
* is offloaded to Redis, and the node.js code only
* handles command interpretation,presentation and subscribing.
*
* Requires redis-node-client and a recent version of Redis
* http://code.google.com/p/redis
* http://github.com/fictorial/redis-node-client
*
* Start the server then telnet to port 8000
* Register with NICK <nick>, use WHO to see others
* Use TALKTO <nick> to initiate a chat. Send a message
* using MSG <nick> <msg>. Note its important to do a
* TALKTO so that both sides are listening. Use STOP <nick>
* to stop talking to someone, and QUIT to exit.
*
* This code is in the public domain.
*/
var redis = require('./redis-node-client/lib/redis-client');
var sys = require('sys');
var net = require('net');
var server = net.createServer(function(stream) {
var sub; // redis connection
var pub;
var registered = false;
var nick = "";
function channel(a,b) {
return [a,b].sort().join(':');
}
function shareTable(other) {
sys.debug(nick + ": Subscribing to "+channel(nick,other));
sub.subscribeTo(channel(nick,other), function(channel, message) {
var str = message.toString();
var sender = str.slice(0, str.indexOf(':'));
if( sender != nick )
stream.write("[" + sender + "] " + str.substr(str.indexOf(':')+1) + "\n");
});
}
function leaveTable(other) {
sub.unsubscribeFrom(channel(nick,other), function(err) {
stream.write("Stopped talking to " + other+ "\n");
});
}
stream.addListener("connect", function() {
sub = redis.createClient();
pub = redis.createClient();
});
stream.addListener("data", function(data) {
if( !registered ) {
var msg = data.toString().match(/^NICK (\w*)/);
if(msg) {
stream.write("SERVER: Hi " + msg[1] + "\n");
pub.sadd('mclarens:inside', msg[1], function(err) {
if(err) {
stream.end();
}
registered = true;
nick = msg[1];
// server messages
sub.subscribeTo( nick + ":info", function(nick, message) {
var m = message.toString().split(' ');
var cmd = m[0];
var who = m[1];
if( cmd == "start" ) {
stream.write( who + " is now talking to you\n");
shareTable(who);
}
else if( cmd == "stop" ) {
stream.write( who + " stopped talking to you\n");
leaveTable(who);
}
});
});
}
else {
stream.write("Please register with NICK <nickname>\n");
}
return;
}
var fragments = data.toString().replace('\r\n', '').split(' ');
switch(fragments[0]) {
case 'TALKTO':
pub.publish(fragments[1]+":info", "start " + nick, function(a,b) {
});
shareTable(fragments[1]);
break;
case 'MSG':
pub.publish(channel(nick, fragments[1]),
nick + ':' +fragments.slice(2).join(' '),
function(err, reply) {
if(err) {
stream.write("ERROR!");
}
});
break;
case 'WHO':
pub.smembers('mclarens:inside', function(err, users) {
stream.write("Online:\n" + users.join('\n') + "\n");
});
break;
case 'STOP':
leaveTable(fragments[1]);
pub.publish(fragments[1]+":info", "stop " + nick, function() {});
break;
case 'QUIT':
stream.end();
break;
}
});
stream.addListener("end", function() {
pub.publish(nick, nick + " is offline");
pub.srem('mclarens:inside', nick, function(err) {
if(err) {
sys.debug("Could not remove client");
}
});
});
});
server.listen(8000, "localhost");
Documents
文件
There's a ton of documentation out there, and the apis are rapidly changing on this type of stack so you'll have to weigh the time relevance of each doc.
那里有大量文档,而且这种类型的堆栈上的 api 正在迅速变化,因此您必须权衡每个文档的时间相关性。
- node activity streams
- cloud foundry example
- how to node redis pubsub
- redis latency
- redis cookbook Using Pub/Sub for Asynchronous Communication
- linkedin's generic tips
- node redis bindings
- google groups nodejs question
Related Questions
相关问题
Just a few related questions, this is a hot topic on stack:
只是一些相关的问题,这是堆栈上的热门话题:
- Redis pub/sub for chat server in node.js
- How to design redis pub/sub for an instant messaging system?
Notable tips (ymmv)
值得注意的提示 (ymmv)
Turn off or optimize socket pooling, use efficient bindings, monitor latency, and make sure you're not duplicating work (ie no need to publish to all listeners twice).
关闭或优化套接字池,使用高效绑定,监控延迟,并确保您没有重复工作(即不需要向所有侦听器发布两次)。

