6 聊天系统设计
聊天系统设计
设计微信:
-
Work Solution
-
Real-time Service
-
Online Status: Pull vs Push
相关设计题:
-
Design Facebook Messenger
-
Design WhatsApp
-
Design Facebook Live Comments
Scenario#
设计微信
基本的功能:
-
用户登录注册
-
通讯录
-
两个用户互发消息
-
群聊
-
朋友圈
-
其他功能
-
群聊历史消息
-
限制多机登录/支持多机登录
-
用户在线状态
-
用户登录注册和通讯录可以看: 用户系统设计
朋友圈可以看: 新鲜事系统设计
Service#
Message Service 负责信息相关的存取
Realtime Service 负责信息的实时推送
设计 Message Table 的一个例子, 这么设计缺少什么信息?
Message Table | ||
---|---|---|
id | int | primary key |
from_user_id | int | 谁发的 |
to_user_id | int | 发给谁 |
content | text | 发了啥 |
created_at | timestamp | 啥时候发的 |
如果根据以上表单, 想要查询 A 和 B 之间的对话:
select * from message_table
where from_user_id = A and to_user_id = B OR from_user_id = B and to_user_id = A
order by created_at DESC;
这样会导致两个问题:
-
where 语句过于复杂, 导致 SQL 效率低下.
-
如果是多人聊天群, 这种结构不可扩展
为了解决以上问题, 我们引入一个 Thread Table (会话).
Inbox has a list of Threads. Thread has a list of Messages.
Thread Table | ||
---|---|---|
id | int | primary key |
participant_user_ids | text (json) | {"userid1", "userid2", userid3} 表示是用户 1, 2, 3 之间的对话. |
created_at | timestamp | |
updated_at | timestamp |
Message Table | ||
---|---|---|
id | int | primary key |
thread_id | int | foreign key |
user_id | int | 谁发的 |
content | text | 内容 |
created_at | timestamp | 啥时候发的 |
这样设计的表意思就是: 哪个用户发消息给哪个对话, 而不是哪个用户发消息给哪个用户.
那么这么设计如何获取某个 Thread 下的所有 Messages 呢?
select * from message_table where thread_id=xxx order by created_at DESC limit xxx;
但是这么设计也同样会导致一些功能无法实现:
-
一些消息 A 用户看过了, 但是 B 用户还没有看过
-
未读消息数
解决方法1 拆分成为多张表
Thread Table 存储公有的信息, UserThread 存储私有的信息.
Thread Table | ||
---|---|---|
id | int | primary key |
participant_user_ids | text(json) | {"userid1", "userid2", userid3} 表示是用户 1, 2, 3 之间的对话. |
created_at | timestamp | |
updated_at | timestamp |
UserThread Table | ||
---|---|---|
user_id | foreign key | |
thread_id | foreign key | |
unread_count | int | 未读消息数 |
is_muted | boolen | 是否已静音 |
joined_at | timestamp | 加入会话时间 |
如何查询 Thread id?
当用户 A 给用户 B 发送消息时, 可能不知道他们之间的 thread_id 是什么, 如何在服务器上查询?
如果就现在的表来说, 只能先查询 User Table, 查到所有的 Thread id, 然后再根据 Thread id 查询 Thread Table, 解序列化 participant_user_ids 字段, 找是否用户为 A 和 B. 如果找不到, 那就在 Thread Table 新建一行字段. 在 User Table 新建多行字段.
这样查找由于 join 操作和解序列化操作, 就会非常慢. 所以我们可以在 Thread Table 增加一个字段, 名叫 participant_user_ids_hashcode, 值是将字段 participant_user_ids 取 hash 值, 这样我们在查找的时候直接将用户 A 和用户 B 按照一定的身份来序列化, 然后 hash, 用得到的值查找 Thread Table 表即可.
Thread Table | ||
---|---|---|
id | int | primary key |
participant_user_ids | text(json) | {"userid1", "userid2", userid3} 表示是用户 1, 2, 3 之间的对话. |
participant_user_ids_hashcode | hash(participant_user_ids) | hash值用于快速查找 |
created_at | timestamp | |
updated_at | timestamp |
信息推送方式
使用 Socket 来进行双向通信. Socket 是很早就出现的技术, Web Socket 是在 H5 之后才诞生的技术, 专门用于让浏览器支持被服务器推送消息所用.
如果 Push Server 挂了怎么办, 那么就只能让 Client 每十秒一次来刷新数据.
Storage#
Message Table (NoSQL)
数据量很大, 不需要修改, 一条聊天信息就像一条日志.
RowKey 怎么选择?
首先明确查询需求和数据如何唯一: 查询特定会话的消息, 消息需要按照时间顺序倒叙排列.
Message Table 的 RowKey 使用 thread_id 和 created_at 合成.
Thread Table (NoSQL)
不需要事务, 数据量适中
RowKey 怎么选择?
首先明确查询需求和数据如何唯一:
-
根据参与的用户信息, 比如用户A用户B会话, 查询会话的信息.
-
根据会话的 id, 查询会话的信息.
多条件查询的设计可以看: RowKey 设计笔记
RowKey: participant_user_ids_hashcode~id
UserThread Table(NoSQL)
不需要事务, 数据量适中
RowKey 怎么选择
首先明确查询需求和数据如何唯一:
-
根据 userid 查询用户的所有会话
-
根据 userid 和 threadid 查询特定会话
userid 和 threadid 可以唯一确定一条记录, 所以 RowKey: user_id~thread_id
如果以上表都用 SQL 呢
建议使用 NoSQL, 非要使用 SQL 的话那么 Sharding Key 就是怎么取怎么设置. 如果多种取法, 那么就多个表或者建立像 NoSQL 一样的索引.
Scale#
如何更好的支持群聊?
需要解决发送 500 次重复请求设计, 非常浪费资源.
push-server 知道群聊都有哪些用户, 并标记已经 socket 连接成功的用户, 只给他们发消息.
如何限制多机登录
考虑我们的使用场景:
-
不允许两个手机登录微信
-
允许手机和桌面客户端同时登录
解决方案: 在 Session 表中记录用户登录的客户端信息和用户状态即可.
如何支持用户在线状态显示
方案1: 用 Push Server 中 Socket 连接情况来代表用户是否在线, .
缺点:
- 如果用户的网络不稳定, 会造成连接时断时续
方案2: 让用户的客户端向 Web-server 发送在线信息, web-server 维持一个表, 里面写着用户最后一次更新在线信息的时间.
告诉服务器在线时, 还可以顺带更新所有好友的在线状态.
如果系统对实时状态要求不高的时候, pull 更好.
作者:Kohn
出处:https://www.cnblogs.com/geraldkohn/p/17091103.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人