Вопрос по node.js, socket.io, publish-subscribe, low-latency, redis – Node.js, Socket.io, Redis Pub / Sub большой объем, трудности с низкой задержкой

63

При соединении 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.

Номер 1 позволяет обрабатывать подпункт Redis и связанное с ним событие один раз для всех клиентов. Номер 2 предлагает более прямой доступ к Redis Pub / Sub. Номер 3 проще, но предлагает мало контроля над событиями обмена сообщениями.

Однако в моих тестах все показали неожиданно низкую производительность с более чем 1 подключенным клиентом. Рассматриваемые события сервера - это 1000 сообщений, опубликованных на канале Redis как можно быстрее, чтобы распространяться как можно быстрее. Производительность измеряется по времени на подключенных клиентах (на основе socket.io-client, которые записывают метки времени в список Redis для анализа).

Я предполагаю, что в варианте 1 сервер получает сообщение, а затем последовательно записывает его всем подключенным клиентам. В варианте 2 сервер получает каждое сообщение несколько раз (один раз для клиентской подписки) и записывает его соответствующему клиенту. В любом случае сервер не получает второе событие сообщения до тех пор, пока оно не будет передано всем подключенным клиентам. Ситуация явно обостряется с ростом параллелизма.

Это, кажется, противоречит воспринятой мудрости возможностей стеков. Я хочу верить, но я изо всех сил.

Этот сценарий (распределение большого количества сообщений с низкой задержкой) просто не подходит для этих инструментов (пока?) Или я упускаю хитрость?

Вы пробовали pub-sub с серверами обмена сообщениями, такими как 0MQ / AMQ, и получили аналогичную производительность? ali haider
Привет lebear, у меня точно такая же проблема, и нашел эту ссылку после осмотра. Если вы нашли ответ на свой вопрос, я бы тоже хотел его услышать. andyk
желаю удачи, получая больше ответов, мне любопытно, чтобы в них принимали участие мнения экспертов. Мой ответ был основан главным образом на документах, плавающих в Интернете. Mark Essel

Ваш Ответ

1   ответ
30

Я подумал, что это разумный вопрос, и некоторое время назад исследовал его. Я потратил немного времени на поиск примеров, из которых вы можете получить некоторые полезные советы.

Examples

Мне нравится начинать с простых примеров:

Простой образец - это одна страница (обратите внимание, что вы захотите заменить redis-node-client чем-то вроде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

Там тонна документации, и API быстро меняются в этом типе стека, поэтому вам придется взвесить временную значимость каждого документа.

Related Questions

Просто несколько связанных вопросов, это горячая тема в стеке:

Notable tips (ymmv)

Отключите или оптимизируйте пул сокетов, используйте эффективные привязки, следите за задержкой и убедитесь, что вы не дублируете работу (т. Е. Нет необходимости публиковать все слушатели дважды).

Похожие вопросы