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 之后才诞生的技术, 专门用于让浏览器支持被服务器推送消息所用.

1 get push-server ip

5 send message to Client1

3 send message

2 websoctet

4 send message

Client1

web-server

push-server

Client2

如果 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人的群

发送500次push请求

Push给少数在线的人

A

web-server

push-server

B

需要解决发送 500 次重复请求设计, 非常浪费资源.

发送消息到500人的群

发送1次push请求

Push给socket连接上的用户

A

web-server

push-server

B

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 国际」许可协议进行许可。

posted @   kohn  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示