node.js - 为什么在聊天应用程序中使用 redis?

标签 node.js sockets redis socket.io

我最近刚刚建立了一个聊天,它运行得很好,但我想我需要将它连接到 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;

});

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

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 实例。

显然,我希望聊天仍然记得“Jack”的套接字 id 是“12333”,而“Mike 的”套接字 id 是“09278”,所以每当“Jack”说嘿,我想向服务器发送“Mike/09278”一条消息时侧 socket 会正确引导它。

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

最佳答案

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);
 }

引用文献

关于node.js - 为什么在聊天应用程序中使用 redis?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31573918/

相关文章:

node.js - 冲泡安装 npm "npm: command not found"

node.js - NodeJS http-代理 : DEPTH_ZERO_SELF_SIGNED_CERT error when proxying https

c++ - 接收数据包发送后,等待功能等待

c++ - socks.h 中的一个问题

node.js - Node js回调歧义

json - 在nodejs中制作json时想要在mongoose记录中添加额外的键

node.js - MongoDB $addFields 和 $in 总计

C套接字Web服务器不在浏览器中显示html,仅发送HTTP响应

Redis Cross Slot 错误

database - 使用 PHP、MySQL 和 Redis