Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why use redis in a chat application? [closed]

I just recently built a chat, it's working pretty well, but I think I need to hook it up to redis.

From what I understand I need redis for scaling and holding some data if a client refreshes or a server goes down.

A core component of the 1on1 chat is that I store the users, and associate a socket.id to those users

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

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

});

Now on the client side I can say hey I want to chat with "Jack", as long as that is a valid user then I can pass that data to the server, i.e the user name and message just to jack like so.

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

My question is, why should I use redis? What should I store in redis? How should I interact with that data?

I am using an io.adapter

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

Also reading code from an example app I see when a socket connects they save the socket data into redis like so.

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

They use it when entering a room, to get information about the room.

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

      });

    });

  });

});

So again, I am asking because I am kind of confused if I need to store anything inside redis/why I need to, for instance we may have a few hundred thousand users and the node.js server "Jack" and "Mike" are chatting on goes down, it then changes to point to a new node.js instance.

Obviously I want the chat to still remember "Jack's" socket id is "12333" and "Mike's" socket id is "09278" so whenever "Jack" says hey I want to send "Mike/09278" a message the server side socket will direct it properly.

Would storing the username as a key and socket ID as a value be a wise use case for redis, would that socket.id still work?

like image 304
Michael Joseph Aubry Avatar asked Jul 22 '15 21:07

Michael Joseph Aubry


People also ask

Is Redis good for chat app?

Real-time chat app is an online communication channel that allows you to conduct real-time conversations. More and more developers are tapping into the power of Redis as it is extremely fast & due to its support for variety of rich data structure such as Lists, Sets, Sorted Sets, Hashes etc.

When should I use Redis?

Redis can be used with streaming solutions such as Apache Kafka and Amazon Kinesis as an in-memory data store to ingest, process, and analyze real-time data with sub-millisecond latency. Redis is an ideal choice for real-time analytics use cases such as social media analytics, ad targeting, personalization, and IoT.

What is Redis cache and how it works?

So, what is Redic cache? When it comes to Redis, Redis is short for Remote Dictionary Server. Redis is a caching system that works by temporarily storing information in a key-value data structure. Redis cache is popular because it is available in almost all major programming languages.

Can we use Redis as database?

Not only that, you can use it as a multi-model primary database, enabling you to build modern applications, as well as low-latency microservice-based architectures, all on top of Redis.


1 Answers

Redis is a pretty good choice as a database for a chat as it provides a couple of data structures that are not only very handy for various chat use cases but also processed in a really performant way. It also comes along with a PubSub messaging functionality that allows you to scale your backend by spawning multiple server instances.

Scaling socket.io with the socket.io-redis adapter

When you want to run multiple instances of your server - be it because of one server not being able to handle increasing users any more or for setting up a high availablility cluster - then your server instances must communicate with each other in order to be able to deliver messages between users who are connected to different servers. The socket.io-redis adapter solves this by using the redis PubSub feature as a middleware. This won't help you if you are using only a single server instance (in fact I assume it will be slightly less performant) but as soon as you spawn a second server this will work out just fine without any headaches.

Want to get a feeling and some insight on how it's working? Monitor your dev redis while using it and you'll see the internal socket.io messages that are pushed through redis.

redis-cli
monitor

Use cases and their according redis data types

Save active conversations in a SET

A redis set is a collection of unique strings. I don't think storing socket.io id's would work out well as you can't assume that a user will get the same id on a reconnect. Better store his rooms and rejoin him on connect. You add every chat room (btw. direct messages can be defined as a room with two participiants so the handling is the same in both cases) that a user enters to their room set. On a server restart, a client reconnect or second client instance you can retrieve the whole set and rejoin users to their rooms.

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

Save the last 10 messages of a conversation using a redis LIST

A redis list is somewhat of an persistent array of strings. You push a new message into a list and pop the oldest when the list size reaches your threshold. Conveniently the push command returns the size right away.

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

To get the message history use lrange:

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

Save some basic user details in a HASH

A hash is a key/value collection. Use it to store the online status along with avatar urls or whatever.

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

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

Maintain a "recent conversations" list in a SORTED LIST

Sorted sets are similar to SETs but you can assign a score value to every element and retrieve the set ordered by this value. Simply use a timestamp as score whenever there is an interaction between two users and that's it.

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

References

  • socket.io-redis: https://github.com/socketio/socket.io-redis
  • redis PubSub docs: https://redis.io/topics/pubsub
  • redis data types: https://redis.io/topics/data-types-intro
like image 185
sui Avatar answered Oct 21 '22 13:10

sui