为什么在聊天应用程序中使用redis?

时间:2015-07-22 21:02:23

标签: 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;

});

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

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服务器“杰克”和“迈克“正在聊天,然后更改为指向新的node.js实例。

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

将用户名作为密钥和套接字ID存储为值是redis的明智用例,socket.id是否仍然可以工作?

1 个答案:

答案 0 :(得分:3)

Redis是一个不错的聊天数据库,因为它提供了两个数据结构,这些数据结构不仅在各种聊天用例中都很方便,而且可以真正有效地进行处理。它还带有PubSub消息传递功能,使您可以通过生成多个服务器实例来扩展后端。

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

如果您要运行服务器的多个实例-是因为一台服务器不再能够处理增加的用户,或者是因为要设置高可用性群集-那么您的服务器实例必须相互通信才能能够在连接到不同服务器的用户之间传递消息。 socket.io-redis适配器通过使用redis PubSub功能作为中间件来解决此问题。如果您仅使用单个服务器实例(实际上我认为它的性能会稍差),这将无济于事,但是一旦您生成第二个服务器,它就可以正常工作而没有任何麻烦。

想了解一下它的工作原理并获得一些见解吗?监视使用中的dev 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.srem("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");
    }
}

在“已排序列表”中维护“近期对话”列表

排序的集合与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);
 }

参考