如何在socket.io中重用redis连接?

40

这是我的代码,使用socket.io作为WebSocket和后端使用pub/sub redis。

var io = io.listen(server),
    buffer = [];

var redis = require("redis");

var subscribe = redis.createClient();  **<--- open new connection overhead**

io.on('connection', function(client) {

    console.log(client.request.headers.cookie);

    subscribe.get("..", function (err, replies) {

    });

    subscribe.on("message",function(channel,message) {

        var msg = { message: [client.sessionId, message] };
        buffer.push(msg);
        if (buffer.length > 15) buffer.shift();
        client.send(msg);
    });

    client.on('message', function(message){
    });

    client.on('disconnect', function(){
        subscribe.quit();
    });
});

每个新的io请求都会创建一个新的redis连接。如果有人用100个标签打开浏览器,那么redis客户端就会打开100个连接。这看起来不太好。

如果cookie相同,是否可能重用redis连接?因此,如果有人打开多个浏览器选项卡,也被视为打开1个连接。


2
我刚刚写了一个可扩展的Socket.IO示例,你可能想看一下。 - Trantor Liu
这里有一个好的链接 - zangw
5个回答

64

实际上,只有在“connection”事件上实例化客户端时,才会为每个连接创建一个新的Redis客户端。在创建聊天系统时,我喜欢创建三个Redis客户端:一个用于发布,一个用于订阅,另一个用于将值存储到Redis中。

例如:

var socketio = require("socket.io")
var redis = require("redis")

// redis clients
var store = redis.createClient()
var pub = redis.createClient()
var sub = redis.createClient()

// ... application paths go here

var socket = socketio.listen(app)

sub.subscribe("chat")

socket.on("connection", function(client){
  client.send("welcome!")

  client.on("message", function(text){
    store.incr("messageNextId", function(e, id){
      store.hmset("messages:" + id, { uid: client.sessionId, text: text }, function(e, r){
        pub.publish("chat", "messages:" + id)
      })
    })
  })

  client.on("disconnect", function(){
    client.broadcast(client.sessionId + " disconnected")
  })

  sub.on("message", function(pattern, key){
    store.hgetall(key, function(e, obj){
      client.send(obj.uid + ": " + obj.text)
    })
  })

})

2
只是为了明确,无论连接多少用户,总共只创建三个Redis客户端。添加另一个节点进程显然会导致更多的Redis客户端。 - sintaxi
4
不错的问题。您会注意到,因为我们在套接字“连接”关闭中订阅了一个Redis频道,这就足以发送消息给所有人,因为在sub对象上的“message”事件将会触发每个已连接的客户端。如果我们使用client.broadcast(),则每个人都会看到房间中的人数乘以消息数。 - sintaxi
4
为了澄清一下,我们可以使用广播,但是我们需要在“连接”闭包之外绑定侦听器,以便事件只触发一次。我们还需要将其更改为socket.broadcast(),因为客户端对象对我们不可用。根据情况,这可能会更好。干得好 :) - sintaxi
21
我知道这里已经有一个相当长的时间间隔了,但我认为这个问题的答案有些危险。将事件监听器绑定到子消息事件将会在每次新客户端加入聊天时发生。这个事件监听器将在客户端断开连接后仍然存在。这将导致积累过期的事件监听器来处理已经离开的客户端的消息。 - tabdulla
4
我知道这个回复来得更晚了,但是我已经想出了如何解决“悬挂侦听器”问题的方法。https://dev59.com/jWgu5IYBdhLWcg3wDS6j#11617812 - hrdwdmrbl
显示剩余3条评论

2

我认为可以将Redis客户端存储到sessioncookie中。因此,当下次使用相同的cookie进行调用时,可以重复使用Redis连接并发布消息。例如:var sessioncookie = redis.createClient();因此最好在服务器端执行此操作。 - user717166

1

当客户端断开连接时,您需要移除监听器。

var io = io.listen(server),
    buffer = [];

var redis = require("redis");

var subscribe = redis.createClient();  

io.on('connection', function(client) {

    console.log(client.request.headers.cookie);

    subscribe.get("..", function (err, replies) {

    });

    var redis_handler = function(channel,message) {

        var msg = { message: [client.sessionId, message] };
        buffer.push(msg);
        if (buffer.length > 15) buffer.shift();
        client.send(msg);
    };

    subscribe.on("message", redis_handler);


    client.on('message', function(message){
    });

    client.on('disconnect', function(){
        subscribe.removeListerner('message', redis_handler)
        //subscribe.quit();
    });
});

请查看Redis、Node.js和Socket.io:跨服务器身份验证和node.js理解


1
我曾经遇到这个问题,还有一个额外的要求,即客户端必须能够订阅私有频道,并且发布到这些频道的内容不应该发送给所有的听众。为了解决这个问题,我尝试编写了一个迷你插件。该插件具有以下特点:
  • 只使用两个redis连接,一个用于发布,一个用于订阅
  • 仅总共订阅一次“message”(而不是每个redis连接都订阅一次)
  • 允许客户端订阅他们自己的私有频道,而不会将信息发送给所有其他正在收听的客户端。
如果您在redis连接限制的地方进行原型设计(例如redis-to-go),则特别有用。 SO链接:https://dev59.com/D2Up5IYBdhLWcg3w8LF4#16770510

主要问题是 Redis 会为每个订阅频道的客户端占用内存。如果有 50k 并发用户,实现这个功能不是一个好主意。 - user717166
非常好的观点,在我的情况下是为了一个游戏,需要将一些客户端数据发送到组,其他数据则只发送给单个玩家。但我猜想如果有50k用户的话,这可能不会被批准。 - Josh Mc

0

自从有人问答了这个问题,使用redis作为存储的方式变得更加简单了。现在它已经内置了

请注意,如果您正在使用redis,因为您正在使用新的节点集群功能(利用多个CPU),则必须在每个群集分叉中创建服务器并附加侦听器(这在任何文档中都没有得到解释;))。我在网上找到的唯一一个好的代码示例是用CoffeeScript编写的,我看到很多人说这种事情“根本行不通”,如果您做错了,那肯定是不行的。 这是一个“正确实现”的示例(但是它是用CoffeeScript编写的)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接