Node.js, Socket.io, Redis pub / sub 대용량, 낮은 지연 시간
여러 전송을 처리 할 수있는 서버 이벤트에 의해 구동되는 실시간 웹 브로드 캐스트 시스템을 만들기 위해 socket.io/node.js와 redis pub / sub를 결합 할 때 세 가지 접근 방식이있는 것 같습니다.
'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 채널을 구독하십시오. 특정 클라이언트에서 이벤트를 발생시키기 위해 클라이언트 연결 종료 및 메시지 호출 client.emit ( "event", data) 수신시 redis.on ( "message", ...)을 포함합니다. socket.io에서 RedisStore를 사용 하는 예제 의 답변과 같습니다.
socket.io에 구운 RedisStore를 사용하고 socketio-spec 프로토콜에 따라 Redis의 단일 "dispatch"채널에서 'broadcast'를 사용합니다.
1 번은 모든 클라이언트에 대해 Redis 하위 및 관련 이벤트를 한 번 처리 할 수 있습니다. 2 번은 Redis pub / sub에 대한보다 직접적인 연결을 제공합니다. 3 번은 더 간단하지만 메시징 이벤트를 거의 제어 할 수 없습니다.
그러나 내 테스트에서는 둘 이상의 연결된 클라이언트에서 예기치 않게 성능이 저하되었습니다. 문제의 서버 이벤트는 가능한 한 빨리 배포되도록 redis 채널에 가능한 한 빨리 게시 된 1,000 개의 메시지입니다. 성능은 연결된 클라이언트의 타이밍으로 측정됩니다 (분석을 위해 타임 스탬프를 Redis 목록에 기록하는 socket.io-client 기반).
내가 추측하는 것은 옵션 1에서 서버가 메시지를 수신 한 다음 연결된 모든 클라이언트에 순차적으로 기록한다는 것입니다. 옵션 2에서 서버는 각 메시지를 여러 번 (클라이언트 구독 당 한 번) 수신하고 관련 클라이언트에 기록합니다. 두 경우 모두 연결된 모든 클라이언트와 통신 할 때까지 서버는 두 번째 메시지 이벤트에 도달하지 않습니다. 동시성이 증가함에 따라 상황이 분명히 악화되었습니다.
이것은 스택 기능의 인식 된 지혜와 상충되는 것 같습니다. 믿고 싶지만 고군분투하고 있습니다.
이 시나리오 (고용량 메시지의 짧은 지연 시간 배포)는 이러한 도구를 사용할 수있는 옵션이 아닙니까 (아직?), 아니면 트릭을 놓치고 있습니까?
나는 이것이 합리적인 질문이라고 생각하고 잠시 전에 조사했습니다. 몇 가지 유용한 팁을 얻을 수있는 예를 검색하는 데 약간의 시간을 보냈습니다.
예
저는 간단한 예부터 시작하고 싶습니다.
가벼운 샘플은 단일 페이지입니다 (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");
서류
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.
- 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)
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).
'UFO ET IT' 카테고리의 다른 글
소규모 .NET 오픈 소스 프로젝트를위한 지속적인 통합 (0) | 2020.11.18 |
---|---|
Xcode 프로젝트 형식 : 3.1, 3.2, 6.3 및 8.0의 차이점은 무엇입니까? (0) | 2020.11.18 |
Interface Builder는 스토리 보드를 저하시키고, 뷰의 크기를 조정하고, 조금씩 위치를 변경합니다. (0) | 2020.11.18 |
return 문에서 파이썬 튜플 풀기 (0) | 2020.11.18 |
괄호없이 Python 3 인쇄 (0) | 2020.11.18 |