【问题标题】:What should I be using? Socket.io rooms or Redis pub-sub?我应该使用什么? Socket.io 房间还是 Redis pub-sub?
【发布时间】:2013-02-02 11:51:18
【问题描述】:

很简单的问题。我正在使用 nodejs 作为我的后端构建一个实时游戏,我想知道是否有任何可用信息说明哪个更可靠,哪个更有效? 我在整个代码中大量使用 Redis 和 Socket.io。所以我想知道我是否应该使用 Socket.io 的 Rooms 还是使用 redis 的 pub-sub 会更好?

更新: 刚刚意识到有一个非常重要的原因,您可能希望通过 socket.io 房间使用 redis pub/sub。使用 Socket.io 房间,当您发布到侦听器时,(浏览器)客户端接收消息,使用 redis 实际上是(redis~on 服务器)客户端接收消息。出于这个原因,如果您想通知所有(服务器)客户端特定于每个客户端的信息,并且可能在传递给浏览器客户端之前进行一些处理,那么您最好使用 redis。使用 redis 你可以触发一个事件来生成每个用户的个人数据,而与 socket.io 一样,你必须一次真正地生成所有用户的唯一数据,然后遍历它们并将他们的个人数据发送给他们,这几乎打败了房间的用途,至少对我来说是这样。

不幸的是,出于我的目的,我现在被 redis 困住了。

更新 2: 最终开发了一个插件以仅使用 2 个 redis 连接,但仍允许单独的客户端处理,请参阅下面的答案....

【问题讨论】:

  • 有趣的问题,我也想知道。也许这篇文章会有所帮助:stackoverflow.com/questions/10167206/…
  • 感谢您的链接,最后一篇文章提出了一个很好的观点。使用 socket.io 的可扩展性可能较低,因为(进程)范围可能更加有限。
  • 谁能解释这些差异?细节会很好。

标签: node.js redis socket.io publish-subscribe


【解决方案1】:

Redis pub/sub 非常适合所有客户端都可以直接访问 redis。如果您有多个节点服务器,一个可以向其他节点推送消息。

但是如果您在浏览器中也有客户端,则需要其他东西将数据从服务器推送到客户端,在这种情况下,socket.io 很棒。

现在,如果您将 socket.io 与 Redis 存储一起使用,socket.io 将在后台使用 Redis pub/sub 在服务器之间传播消息,而服务器将向客户端传播消息。

因此,使用带有 Redis 存储的 socket.io 配置的 socket.io 房间对您来说可能是最简单的。

【讨论】:

  • 在我当前的设置中,我将 redisStore 用于 socket.io 和 redis pub-sub,但结果是每个通过 socket.io 连接的客户端,我都需要创建一个相应的 redis 连接。如果我切换到 Socket.io 房间,它是否仍会在后台为每个用户使用单独的 redis 连接? (我认为它会?)
  • RedisStore 每个节点实例总共使用 3 个连接(客户端)。它不会为每个客户端连接或每个房间创建新的,而是自己分派到正确的房间。
  • 对,所以在我的情况下使用房间会大大提高效率,因为我目前为每个客户端使用一个 redis 客户端(在 socket.io onconnection 回调中)?
  • 看起来会的,是的。您可以实现逻辑来重用相同的连接,但是利用 socket.io 实现看起来更简单,而且代码应该已经很可靠了。
【解决方案2】:

我最终编写了一个节点插件来允许许多 pub-sub 客户端,但只需要 2 个 redis 连接,而不是每个 socketio 连接上的一个新连接,它应该可以正常工作,认为其他人可能会发现它有用。

此代码假设您运行和设置了 socket.io,基本上在这个示例中,任何数量的 socket.io 客户端都可以连接,并且它始终只使用 2 个 redis 连接,但所有客户端都可以订阅自己的频道。在此示例中,所有客户端都会收到一条消息“甜蜜的消息!” 10 秒后。

socket.io 示例(使用 redis pub-sub):

var
    RPubSubFactory = require('rpss.js');

var 
    redOne = redis.createClient(port, host),
    redTwo = redis.createClient(port, host);

var pSCFactory = new RPubSubFactory(redOne);

io.sockets.on('connection', function(socket){
    var cps = pSCFactory.createClient();
    cps.onMessage(function(channel, message){
        socket.emit('message', message);
    });
    io.sockets.on('disconnect', function(socket){
        // Dont actually need to unsub, because end() will cleanup all subs, 
        // but if you need to sometime during the connection lifetime, you can.
        cps.unsubscribe('cool_channel');
        cps.end();
    });
    cps.subscribe('cool_channel')
});

setTimeout(function(){
    redTwo.publish('cool_channel', 'sweet message!');
},10000);

实际插件代码:

var RPubSubFactory = function(){

    var 
        len,indx,tarr;
    var
        dbcom = false,
        rPubSubIdCounter = 1,
        clientLookup = {},
        globalSubscriptions = {};

    // public
    this.createClient = function()
    {
        return new RPubSupClient();
    }

    // private
    var constructor = function(tdbcom)
    {
        dbcom = tdbcom;
        dbcom.on("message", incommingMessage);
    }
    var incommingMessage = function(rawchannel, strMessage)
    {
        len = globalSubscriptions[rawchannel].length;
        for(var i=0;i<len;i++){
            //console.log(globalSubscriptions[rawchannel][i]+' incomming on channel '+rawchannel);
            clientLookup[globalSubscriptions[rawchannel][i]]._incommingMessage(rawchannel, strMessage);
        }
    }

    // class
    var RPubSupClient = function()
    {
        var 
            id = -1,
            localSubscriptions = [];

        this.id = -1;
        this._incommingMessage = function(){};

        this.subscribe = function(channel)
        {
            //console.log('client '+id+' subscribing to '+channel);
            if(!(channel in globalSubscriptions)){
                globalSubscriptions[channel] = [id];
                dbcom.subscribe(channel);
            }
            else if(globalSubscriptions[channel].indexOf(id) == -1){
                globalSubscriptions[channel].push(id);
            }
            if(localSubscriptions.indexOf(channel) == -1){
                localSubscriptions.push(channel);
            }
        }
        this.unsubscribe = function(channel)
        {
            //console.log('client '+id+' unsubscribing to '+channel);
            if(channel in globalSubscriptions)
            {
                indx = globalSubscriptions[channel].indexOf(id);
                if(indx != -1){
                    globalSubscriptions[channel].splice(indx, 1);
                    if(globalSubscriptions[channel].length == 0){
                        delete globalSubscriptions[channel];
                        dbcom.unsubscribe(channel);
                    }
                }
            }
            indx = localSubscriptions.indexOf(channel);
            if(indx != -1){
                localSubscriptions.splice(indx, 1);
            }
        }
        this.onMessage = function(msgFn)
        {
            this._incommingMessage = msgFn;
        }
        this.end = function()
        {
            //console.log('end client id = '+id+' closing subscriptions='+localSubscriptions.join(','));
            tarr = localSubscriptions.slice(0);
            len = tarr.length;
            for(var i=0;i<len;i++){
                this.unsubscribe(tarr[i]);
            }
            localSubscriptions = [];
            delete clientLookup[id];
        }        
        var constructor = function(){
            this.id = id = rPubSubIdCounter++;
            clientLookup[id] = this;
            //console.log('new client id = '+id);
        }        
        constructor.apply(this, arguments);
    }    
    constructor.apply(this, arguments);
};

module.exports = RPubSubFactory;

我四处乱转,并试图尽可能提高效率,但在做了一些不同的速度测试后,我得出结论这是我能达到的最快速度。

最新版本:https://github.com/Jezternz/node-redis-pubsub

【讨论】:

    猜你喜欢
    • 2012-04-27
    • 2012-08-12
    • 1970-01-01
    • 1970-01-01
    • 2012-06-08
    • 1970-01-01
    • 2011-06-23
    • 2014-05-05
    相关资源
    最近更新 更多