为啥在聊天应用程序中使用 redis? [关闭]

Posted

技术标签:

【中文标题】为啥在聊天应用程序中使用 redis? [关闭]【英文标题】:Why use redis in a chat application? [closed]为什么在聊天应用程序中使用 redis? [关闭] 【发布时间】:2015-10-12 23:09:50 【问题描述】:

我最近刚刚建立了一个聊天,它运行得很好,但我想我需要将它连接到 redis。

据我了解,如果客户端刷新或服务器出现故障,我需要 redis 来扩展和保存一些数据。

1on1 聊天的一个核心组件是我存储用户,并将socket.id 关联到这些用户

var users = ;
io.sockets.on('connection', function (socket) 

  // store the users & socket.id into objects
  users[socket.handshake.headers.user.username] = socket.id;

);

现在在客户端我可以说,嘿,我想和“杰克”聊天,只要那是一个有效的用户,我就可以将该数据传递给服务器,即用户名和消息只是为了杰克.

var chattingWith = data.nickname; // this is Jack passed from the client side
io.to(users[chattingWith]).emit();

我的问题是,我为什么要使用redis?我应该在redis中存储什么?我应该如何与这些数据进行交互?

我正在使用io.adapter

io.adapter(redisIo( 
  host: 'localhost', 
  port: 6379,
  pubClient: pub,
  subClient: sub
));

还从示例应用程序中读取代码,我看到当套接字连接时,它们将套接字数据保存到 redis 中,就像这样。

// store stuff in redis
redisClientPublish.sadd('sockets:for:' + userKey + ':at:' + room_id, socket.id, function(err, socketAdded) 
  if(socketAdded) 
    redisClientPublish.sadd('socketio:sockets', socket.id);
    redisClientPublish.sadd('rooms:' + room_id + ':online', userKey, function(err, userAdded) 
      if(userAdded) 
        redisClientPublish.hincrby('rooms:' + room_id + ':info', 'online', 1);
        redisClientPublish.get('users:' + userKey + ':status', function(err, status) 
          io.sockets.in(room_id).emit('new user', 
            nickname: nickname,
            provider: provider,
            status: status || 'available'
          );
        );
      
    );
  
);

他们在进入房间时使用它来获取有关房间的信息。

app.get('/:id', utils.restrict, function(req, res) 

  console.log(redisClientPublish);

  utils.getRoomInfo(req, res, redisClientPublish, function(room) 

    console.log('Room Info: ' + room); 

    utils.getUsersInRoom(req, res, redisClientPublish, room, function(users) 

      utils.getPublicRoomsInfo(redisClientPublish, function(rooms) 

        utils.getUserStatus(req.user, redisClientPublish, function(status) 
          utils.enterRoom(req, res, room, users, rooms, status);
        );

      );

    );

  );

);

所以,我再次问,因为我有点困惑是否需要在 redis 中存储任何东西/为什么需要,例如我们可能有几十万用户和 node.js 服务器“Jack”和“ Mike" are chatting on 掉线了,然后它变成了指向一个新的 node.js 实例。

显然我希望聊天仍然记得“杰克”的套接字 id 是“12333”和“迈克的”套接字 id 是“09278”所以每当“杰克”说嘿我想向服务器发送“迈克/09278”一条消息侧插座会正确引导它。

将用户名存储为键并将套接字 ID 存储为值是否是 redis 的明智用例,socket.id 是否仍然有效?

【问题讨论】:

【参考方案1】:

Redis 作为聊天数据库是一个不错的选择,因为它提供了一些数据结构,这些数据结构不仅对各种聊天用例非常方便,而且以非常高效的方式处理。它还附带一个 PubSub 消息传递功能,允许您通过生成多个服务器实例来扩展后端。

使用 socket.io-redis 适配器扩展 socket.io

当您想要运行服务器的多个实例时 - 无论是因为一台服务器无法再处理不断增加的用户还是为了设置高可用性集群 - 那么您的服务器实例必须相互通信才能能够在连接到不同服务器的用户之间传递消息。 socket.io-redis 适配器通过使用 redis PubSub 功能作为中间件解决了这个问题。如果您只使用单个服务器实例(实际上我认为它的性能会稍差),这对您没有帮助,但是一旦您生成第二台服务器,这将不会有任何麻烦。

想了解一下它的工作原理吗?在使用时监控你的开发 redis,你会看到通过 redis 推送的内部 socket.io 消息。

redis-cli
monitor

用例及其对应的redis数据类型

将活动对话保存在 SET 中

redis 集是唯一字符串的集合。我不认为存储 socket.io id 会很好,因为您不能假设用户在重新连接时会获得相同的 id。最好存储他的房间并在连接时重新加入他。您添加用户进入他们的房间集的每个聊天室(顺便说一句。直接消息可以定义为一个有两个参与者的房间,因此在两种情况下处理都是相同的)。在服务器重新启动、客户端重新连接或第二个客户端实例时,您可以检索整个集合并将用户重新加入他们的房间。

/* note: untested pseudo code just for illustration */
io.sockets.on('connection', function (socket) 
    rooms = await redis.smembers("rooms:userA");
    rooms.foreach (function(room) 
        socket.join(room);
    

    socket.on('leave', room) 
        socket.leave(room);
        redis.srem("rooms:userA", room);
     

    socket.on('join', room) 
        socket.join(room);
        redis.sadd("rooms:userA", room);
    

使用 redis LIST 保存对话的最后 10 条消息

redis 列表在某种程度上是一个持久的字符串数组。您将新消息推送到列表中,并在列表大小达到您的阈值时弹出最旧的消息。方便的是 push 命令立即返回大小。

socket.on('chatmessage', room, message) 
    if (redis.lpush("conversation:userA:userB", "Hello World") > 10) 
        redis.rpop("conversation:userA:userB");
    
    io.to(room).emit(message);

要获取消息历史记录,请使用 lrange:

msgHistory = redis.lrange("conversation:userA:userB", 0, 10)

在 HASH 中保存一些基本的用户详细信息

哈希是键/值集合。使用它来存储在线状态以及头像网址或其他任何内容。

io.sockets.on('connection', function (socket) 
    redis.hset("userdata:userA", "status", "online");

    socket.on('disconnect', function () 
        redis.hset("userdata:userA", "status", "offline");
    

在 SORTED LIST 中维护一个“最近的对话”列表

排序集类似于 SET,但您可以为每个元素分配一个分值并检索按此值排序的集合。只要两个用户之间有交互,只需使用时间戳作为分数即可。

 socket.on('chatmessage', room, message) 
      io.to(room).emit(message);
      redis.zadd("conversations:userA", new Date().getTime(), room);
 

 async function getTheTenLatestConversations() 
     return await redis.zrange("conversations:userA", 0, 10);
 

参考文献

socket.io-redis:https://github.com/socketio/socket.io-redis redis PubSub 文档:https://redis.io/topics/pubsub redis 数据类型:https://redis.io/topics/data-types-intro

【讨论】:

最佳答案!谢谢! 我正在使用 socket.io-redis 进行开发。它确实跟踪用户订阅的房间。我不确定在哪里。我还会从使用集合来跟踪它中受益吗?我可以对用户房间使用 socket.io-chat 命令并将其发送到我想的 Redis 集?

以上是关于为啥在聊天应用程序中使用 redis? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

为啥 MongooseIM 会在 60 秒后关闭 websocket 连接?

为啥 Redis 会允许客户端关闭服务器?

为啥Redis要关闭THP?

实时 Web 应用程序使用啥以及为啥?需要解释和小型现场演示[关闭]

聊天应用程序:将 Django 与 sockjs-tornado 和 redis 一起使用

在 PHP 中使用 Node.js、Socket.io、Redis 的私人聊天消息