1.Introduction
Building a distributed chat system was one of the things i wanted to build, Designing a chat system like slack or messenger has always been among the top questions asked in interview's. I am self taught developer currently in high school building side projects. In this article, I want to share with you my design of Distributed Chat Application.
1.1.Prerequisite
- Basic knowledge of Load balancer
- How Websockets Works
- Understanding Message Queue
- Cassandra
2.Features
At First we must list out the features that we are going to build as this gives us a better understanding at the project.
- One on One message: Two users can chat with each other
- Group Chat: users can send message to a group
- Create Groups: users can create groups
- Join Groups: users can join group with their name
- servers must be able to scale: Multiple web-server instance behind the load balancer.
3.Database Design
We would use NoSQL database such as cassandra. It's easy to scale up and it's very suitable for write-heavy works.
3.1 Database Schema
In Cassandra performing JOINS is not possible, That's a feature🐱💻
In here Timestamp is maintained in each message to retrieve message when user logs in.
4.System Design
One of our core feature will be scalability so We will be deploying 3 instance of our API/WebSocket server and with the help of a load balancer like Nginx/haproxy traffic will be distributed to each server.
4.1.Private Chat
- What are the complexity that we deal when we spin up more than 1 instance?
Suppose user1 has to send a message to user2 but due to the existence of 3 servers User1 establish a websocket connection with S1 and User2 establish a connection with S3. How the heck am i supposed to send message to User2 now?😵
Introducing distributed servers == Increases Complexity
My approach to the solution is to keep a Message Queue, you can choose between kafka, RabbitMQ or Redis. I chose Redis because it's less Complexity and sub millisecond write and read speed🚀. Each server will have it's own topic in redis. So when a message needs to be send to other server it will be send to respective server's topic. Each servers will also consume new message from their topic.
- How do i know which server the receiver is connected to?
We will maintain a User Mapping table with columns userID and SeverID. When users connect to the websocket server their userid and serverid is stored in the User Mapping Table in cassandra and when user disconnects their record is deleted or marked disconnected by introducing an additional field status.
This will be our final solution for private chat below is the data flow of this design👇
Data Flow:
- User1 and User2 sends request to load-balancer
- load-balancer connects User1 to S1 & User2 to S3
- User1 wants to send message to User2
- User1 sends the message to S1
- S1 retrieve's which server User2 is connected to from User Mapping Table
- S1 sends the message to S3 TOPIC in redis Queue
- S3 retrieve's new message from S3 TOPIC from redis Queue
- S3 then sends the message to User2
4.2.Group Chat
Although Group chat follows the same design we can make some tweaks in the websocket servers for better performance.
- What is the performance issue that we are going to encounter in this architecture?
Assume that User1(Connected to S1) needs to send a message to group X with 400 members, These members are distributed among the other two servers S2 & S3 with 200 members connected to each server(assuming all the users are active right now).S1 would get the list of members in the group X then loop through each of them and send the message to the server member are connected to. This would send 200 Duplicate message to S2 & S3, Therefore this is not an optimal case for us.😕
This is how i prevented the sending of duplicate messages, At First we retrieve the group members from cassandra then get the SERVERID of the servers to which they are connected from user mapping table. We then group them according to their SERVERID with SERVERID being the key and array of USERID being the value. We then loop through the keys and send to the respective Topics in Message Queue according to their key with the message.
isgroup field is used to check if the message is being sent to a group or not, group_members is an array of members of group X which are connected to server S2 in this case.
Data Flow:
- User1 connects to S1
- User1 sends a message to group "X"
- S1 fetches the group members of group "X"
- S1 loops through them to get the id of their connected server
- Groups the members based on the server they are connected on
- serverId being the key and array of users being the value
- loops through the keys and sends the message to the Topic in message queue with the same key along with its value (array of members)
- S2 & S3 receives the message with server_id, sender, is_group, group_name, group_members and message
- S2/S3 loops through the group members (users which belong to group X which are connected to S2/S3) and sends the message to them
This is the solution that i came up with and this might not be the perfect solution and lot of other improvements can be made in this. If you have a better suggestion please let me know below I would love to discuss it😁. This was made after i finished making a distributed chat system with golang, redis and cassandra using redis as Message Queue.
Distributed Chat System: https://github.com/leoantony72/go-chat2
Twitter: https://twitter.com/serpico_z
If this post gets good attraction i will do system design of notification system next time🐱💻. See you next time👋
Top comments (3)
good work
Thank you😁
Let me know your thoughts on this