如何处理消息的已读状态在群聊中的复杂性(谁读了、谁没读)?

Maximize job database potential with expert discussions and advice.
Post Reply
muskanislam99
Posts: 243
Joined: Sat Dec 28, 2024 5:47 am

如何处理消息的已读状态在群聊中的复杂性(谁读了、谁没读)?

Post by muskanislam99 »

处理群聊中消息的已读状态是 WhatsApp 这种应用的复杂特性之一,因为它不仅仅是简单的“已读”或“未读”,而是要精确地追踪群组中的每个成员对特定消息的阅读状态。这意味着要记录“谁在什么时候读了这条消息”的信息。WhatsApp 在其群聊中提供了“消息详情”功能,允许发送者查看哪些成员已收到消息,哪些成员已阅读消息,以及具体的时间。

1. 核心存储结构:MessageReceipts 表
为了处理这种复杂性,最常见的且高效的数据库设计是使用一个专门的**MessageReceipts (消息回执)表**。这个表存储了每条消息对于每个接收者的具体状态。

表结构示例:

SQL

CREATE TABLE MessageReceipts (
message_id BIGINT NOT NULL, -- 外键,关联 Messages.message_id
recipient_id BIGINT NOT NULL, -- 收到/阅读此消息的群组成员的 user_id
delivered_at BIGINT, -- 消息送达接收方设备的 Unix 毫秒时间戳
read_at BIGINT, -- 消息被接收方阅读的 Unix 毫秒时间戳
-- 其他可能的字段:
-- device_id BIGINT, -- 接收方阅读消息的设备ID (如果需要追踪多设备状态)
-- status_flags SMALLINT, -- 可以用位掩码存储其他小状态,如已播放(针对语音/视频)
PRIMARY KEY (message_id, recipient_id) -- 复合主键,确保每条消息每个接收者只有一条记录
-- 索引:
-- INDEX idx_recipient_messages (recipient_id, message_id) -- 用于快速查找某个用户已读/未读哪些消息
);
字段说明:

message_id: 指向 Messages 表中某条群聊消息的 ID。
recipient_id: 指向 Users 表中群组的某个成员的 ID。
delivered_at: 当消息成功送达 recipient_id 所代表 波斯尼亚和黑塞哥维那 whatsapp 数据库 的设备时,服务器收到该设备的回执后,会在此字段记录时间戳。这对应于 WhatsApp 的双灰勾。
read_at: 当 recipient_id 在其设备上实际打开并查看了该消息时,客户端会发送已读回执给服务器,服务器在此字段记录时间戳。这对应于 WhatsApp 的双蓝勾。
2. 工作流程与状态更新
处理群聊消息的已读状态涉及客户端和服务器之间的多次交互:

消息发送 (客户端 → 服务器):

发送者 A 发送一条群聊消息 M 到群组 G。
客户端 M 将 M 存储在本地,初始状态可能是“发送中”。
服务器处理 (服务器内部):

服务器接收到消息 M 后,将其持久化到群聊消息存储中(如前所述)。
服务器从 GroupMembers 表中获取群组 G 的所有活跃成员列表(B, C, D...)。
对于每个成员,服务器会尝试将消息 M 推送到他们的设备。
在 MessageReceipts 表中,可以为每个成员预插入或动态插入一条记录:(message_id, recipient_id, NULL, NULL)。
消息送达 (接收方客户端 → 服务器):

当成员 B 的设备成功收到消息 M 并将其写入本地数据库后,成员 B 的客户端会向服务器发送一个递送回执(Delivery Receipt)。
服务器收到 B 的递送回执后,更新 MessageReceipts 表中对应 (M.message_id, B.user_id) 记录的 delivered_at 字段为当前时间戳。
服务器随后向发送者 A 的设备发送一个通知,告知消息已送达 B。发送者 A 的客户端更新本地 UI(例如,单勾变双灰勾)。
消息已读 (接收方客户端 → 服务器):

当成员 B 在其设备上打开聊天界面并看到消息 M 时,成员 B 的客户端会向服务器发送一个已读回执(Read Receipt)。
服务器收到 B 的已读回执后,更新 MessageReceipts 表中对应 (M.message_id, B.user_id) 记录的 read_at 字段为当前时间戳。
服务器随后向发送者 A 的设备发送一个通知,告知消息已由 B 阅读。
发送者端的聚合显示:

当发送者 A 查看消息 M 的状态时(例如,点击消息 -> “消息详情”),A 的客户端会向服务器请求这条消息在群组 G 中的所有回执信息。
服务器查询 MessageReceipts 表,获取 message_id 为 M.message_id 的所有记录。
服务器还会关联 GroupMembers 表和 Users 表,以获取每个 recipient_id 的用户名。
服务器将这些数据返回给发送者 A 的客户端,A 的客户端会显示一个列表,指明“XXX 已送达 YYYY 时”,“ZZZ 已读 QQQQ 时”。
发送者 A 客户端的 UI 还会根据 MessageReceipts 表中所有群成员的 read_at 状态进行聚合:
如果所有群成员都已送达(delivered_at 非空),则显示双灰勾。
如果所有群成员都已阅读(read_at 非空),则显示双蓝勾。
3. 复杂性和优化
数据量: MessageReceipts 表会非常庞大。如果一个群组有 100 人,每发一条消息就会产生 100 条递送回执和 100 条已读回执(如果每个人都阅读)。这需要高性能的数据库(如 Cassandra)和有效的分片策略(基于 group_id 或 message_id)。
实时性: 状态更新需要低延迟,通常通过 WebSocket 或 MQTT 等实时协议进行。
离线处理: 如果客户端离线,回执会暂存在本地,待上线后批量发送给服务器。
幂等性: 由于网络重试,回执消息可能重复发送,服务器需要设计为幂等处理,避免重复记录或错误更新。
已读回执关闭: WhatsApp 允许用户关闭个人聊天的已读回执,但通常不允许关闭群聊的已读回执,这是为了群组沟通的透明性。
性能优化:
对于大群,实时聚合“所有人都已读”的蓝勾状态可能开销很大。通常,客户端可以只在用户请求“消息详情”时才拉取所有成员的详细状态,而平时仅显示基于服务器汇总的整体状态。
使用 NoSQL 数据库的宽列模型可以很好地适应这种一对多的关系,将一条消息的所有回执作为该消息记录的一个嵌套结构或一个子表,优化读写效率。
通过 MessageReceipts 这种细粒度的追踪,WhatsApp 能够精确地管理群聊中每一条消息的复杂已读状态,为用户提供清晰的反馈。
Post Reply