在 WhatsApp 中,广播列表(Broadcast List)允许用户向多个联系人一次性发送消息,而无需创建群组。与群聊不同,广播列表的接收者之间是互相不知道的,他们收到的消息看起来就像是一对一的私聊。因此,存储广播列表及其成员需要一套独特的设计,既要支持高效的消息分发,又要维护这种“一对一”的隐私特性。
1. 核心存储:广播列表表 (BroadcastLists Table)
首先,需要一个表来存储每个用户创建的广播列表的基本信息。
表结构示例:
SQL
CREATE TABLE BroadcastLists (
list_id BIGINT PRIMARY KEY, -- 广播列表的唯一ID
owner_user_id BIGINT NOT NULL, -- 广播列表创建者的用户ID (外键 Users.user_id)
list_name VARCHAR(255) NOT NULL, -- 广播列表的名称 (用户自定义)
created_at BIGINT NOT NULL, -- 创建时间戳
updated_at BIGINT NOT NULL, -- 最后更新时间戳
-- 其他元数据:例如,是否激活等
-- FOREIGN KEY (owner_user_id) REFERENCES Users(user_id)
);
字段说明:
list_id: 广播列表的唯一标识符,通常是一个分布式 ID。
owner_user_id: 指明哪个用户拥有这个广播列表。这是关键,因为广播列表是用户私有的。
list_name: 用户为该列表设置的名称,便于管理。
2. 存储成员:广播列表成员表 (BroadcastListMembers Table)
为了记录每个广播列表包含哪些成员,需要一个关联表。
表结构示例:
SQL
CREATE TABLE BroadcastListMembers (
list_id BIGINT NOT NULL, -- 外键,关联 BroadcastLists.list_id
member_user_id BIGINT NOT NULL, -- 外键,关联 Users.user_id (列表中的成员)
added_at BIGINT NOT NULL, -- 成员被添加到列表的时间戳
-- 其他元数据,例如,是否处于活跃状态等
PRIMARY KEY (list_id, member_user_id) -- 复合主键,确保一个成员在一个列表中只有一条记录
-- INDEX idx_member_lists (member_user_id, list_id) -- 用于快速查找用户所属的广播列表
-- FOREIGN KEY (list_id) REFERENCES BroadcastLists(list_id),
-- FOREIGN KEY (member_user_id) REFERENCES Users(user_id)
);
字段说明:
list_id: 指向所属的广播列表。
member_user_id: 指向列表中的一个接收者用户。
PRIMARY KEY (list_id, member_user_id): 确保组合的唯一性,一个成员不会被添加两次。
3. 消息存储和分发机制
这是广播列表与群聊消息存储方式的根本不同之处。
消息表 (Messages Table) 的处理:
当用户 A 通过广播列表发送一条消息 M 时,这条消息在 Messages 表中会表现为多条“一对一”消息。
对于列表中的每个成员(B, C, D...),服务器会生成 开曼群岛 whatsapp 数据库 一个独立的逻辑消息实例(尽管其内容相同),并将其路由到每个接收者的收件箱。
在 Messages 表中,这些消息的 sender_id 是用户 A,而 receiver_id 分别是 B, C, D 等。
通常,消息表中会有一个字段(例如 broadcast_list_id)来关联原始的广播列表,以便追踪。
<!-- end list -->
SQL
CREATE TABLE Messages (
message_id BIGINT PRIMARY KEY,
chat_id BIGINT NOT NULL, -- 一对一聊天会话ID (sender_id, receiver_id 组合)
sender_id BIGINT NOT NULL, -- 消息发送者 (广播列表的创建者)
receiver_id BIGINT NOT NULL, -- 消息接收者 (广播列表中的一个成员)
**broadcast_list_id BIGINT, -- 关联 BroadcastLists.list_id (如果消息来自广播) **
content BLOB, -- 加密消息内容
timestamp BIGINT NOT NULL,
message_type VARCHAR(50) NOT NULL,
status VARCHAR(20) NOT NULL,
-- ... 其他字段
INDEX idx_chat_time (chat_id, timestamp)
);
分发流程:
用户 A 在客户端选择一个广播列表,输入消息 M,点击发送。
客户端将消息 M 和 list_id 发送给 WhatsApp 服务器。
服务器根据 list_id 从 BroadcastListMembers 表中查询所有成员(B, C, D...)。
对于每个成员,服务器会像处理普通一对一消息一样,将消息 M(封装为对 B, C, D 的私聊消息)路由到每个成员的收件箱。
消息成功递送后,服务器上的临时副本被删除。
每个成员 B, C, D 在他们的设备上收到这条消息时,它看起来就像是用户 A 单独发给他们的一条普通私聊消息。他们看不到这条消息还被发送给了其他广播列表成员。
已读状态:
由于每条广播消息在接收者端都被视为一对一消息,因此其已读状态也遵循一对一消息的逻辑。
发送者 A 无法像群聊那样看到一个统一的“谁已读”列表,而是只能看到每条“一对一”消息的独立已读状态(例如,消息 M 发送给 B,A 只能看到 B 是否已读这条消息)。
WhatsApp 通常不会提供广播消息的“消息详情”来显示所有成员的已读状态,因为这违背了广播列表的隐私设计。
4. 可扩展性与隐私
分片: BroadcastLists 和 BroadcastListMembers 表通常会与 Users 表一起进行分片,分片键通常是 owner_user_id。这样,一个用户的所有广播列表及其成员数据都存储在同一个分片上,优化了用户管理自身列表的查询效率。
隐私保护: 广播列表的关键在于接收者之间相互隔离。数据库设计必须严格维护这种隔离性。BroadcastListMembers 表仅供列表的 owner_user_id 和服务器内部使用,不向列表成员之间共享。
性能: 当广播列表中的成员数量很多时,发送一条广播消息会产生大量独立的“一对一”消息,这会给消息服务器带来较大的分发和写入压力。高效的消息队列和异步处理是必要的。
通过这种“一对多内部转换成多条一对一”的机制,WhatsApp 巧妙地实现了广播功能,同时严格遵守了用户之间的隐私界限。