Designing a Scalable One-to-One Chat System: Architecture & Data Strategies
This article breaks down the functional requirements, data models, layered architecture, push‑pull messaging choices, and message flow for building a robust one‑to‑one instant‑messaging service using Redis, MySQL and asynchronous queues.
Function Decomposition
The one‑to‑one instant‑messaging (IM) module consists of four functional components:
Conversation list ordered by the timestamp of the latest message.
Chronological chat window that displays messages in order.
Unread‑message counters for each conversation.
User profile data (avatar, nickname) displayed alongside the conversation.
These components map to five storage groups: conversation list, message records, offline‑message list, unread‑count data, and user profiles.
Data Structures
Redis sorted sets (SortedSet) are used for ordered collections, while MySQL tables provide durable persistence for message records.
1. Conversation List
Key : prefix_xxx:{uid} Value : {conversationId} Score : {msgId} (ID of the latest message)
The user ID forms the Redis key; each member is a conversation ID, and the score is the latest message ID, which automatically keeps the list sorted by recency.
2. Single‑Chat Message List
Key : prefix_session_list:{sessionId} Value : {msgId} Score : {msgId}
Messages can also be persisted in MySQL for long‑term storage:
create table t_msg_record_list (
id bigint not null primary key,
sessionId bigint not null comment 'Conversation ID',
msgId bigint not null comment 'Message ID',
isRead tinyint not null default 0 comment 'Read flag',
recordStatus smallint not null default 0 comment 'Message status',
createTime datetime not null,
key `sessionId` (`sessionId`)
) engine=innodb;Paginated retrieval example (newest first):
SELECT msgId FROM t_msg_record_list
WHERE sessionId = 1 AND recordStatus = 0 AND msgId > 1
ORDER BY id DESC
LIMIT 10;3. Offline Messages
Two Redis structures are required: an index of senders and a list of message IDs per sender.
Sender index key : prefix_xxx:{uid} Value : {senderUid}
Message list key : prefix_offline_msg:{uid}:{senderUid} Value : {msgId}
4. Unread Count
Unread = total received – read
Store both totals per conversation per user:
Total count key : prefix_session_count:{conversationId}:{uid} Value : total message count
Read count key : prefix_session_read_count:{conversationId}:{uid} Value : read message count
5. User Profile
Design a MySQL table for user information (e.g., avatar URL, nickname) and synchronize updates to Redis when needed.
Layered Architecture
The system can be divided into five logical layers:
Client Layer : SDKs for web, mobile, etc., hide low‑level details from the application.
Connection Layer : Maintains long‑lived TCP/WebSocket connections, tracks online status, and isolates connection handling from business logic.
Business Layer : Split into long‑connection services (message send/receive, conversation list, unread push) and short‑connection services (profile queries, updates).
Service Layer : Micro‑services that provide core capabilities such as sensitive‑content filtering, data read/write, and message persistence.
Data Layer : Physical storage using MySQL, Redis, or other databases.
Push‑Pull Mode Selection
A hybrid approach balances bandwidth and latency:
Use pull for device initialization and historical pagination.
Use push for real‑time delivery and unread‑notification pushes.
Message Flow
Sending
Message filtering in the logic layer according to defined rules.
Enrich the message with sender information (e.g., senderId, avatar, nickname).
Enqueue the message to an asynchronous task queue.
The asynchronous task performs four actions:
Persist the chat record in MySQL.
Update the total message count for unread‑count calculation.
Push the message to online recipients or store it in the offline list if the recipient is offline.
Refresh the conversation‑list score in Redis.
Queues can be separated into real‑time, delayed, and retry queues, each with dedicated thread pools.
Receiving
Client appends the message to the chat UI and marks the conversation as having unread messages.
If the chat window is open, the client sends an ACK to the server to mark the message as read.
Server updates the read‑count data in Redis.
When the client pulls offline messages, the server clears the corresponding offline entries.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
IT Architects Alliance
Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
