免费完整指南

即时通讯系统

设计即时通讯系统。请覆盖WebSocket connection management at scale: a connection registry mapping user/device to the gateway instance holding their socket, Message delivery semantics: at-least-once delivery with...

00

练习检查点

面试节奏保持精简,让页面能把注意力花在真正的设计决策上。

  1. 01
    澄清范围
  2. 02
    需求 + 规模
  3. 03
    API + 数据模型
  4. 04
    画出架构
  5. 05
    深入探讨
  6. 06
    取舍决策
01

塑造设计的需求

不要只是陈述需求,要主动问出来。每张卡把设计约束和一句你可以在画架构前说出口的澄清问句配成一组。

功能需求

01只有一对一聊天,还是也有群组——群组最大能到多大?

用户在一对一和群组聊天中收发消息——群组上限约 100 名参与者,让每条消息的 fan-out 有界。

02我离线时发来的消息会怎样?

离线期间的消息会在设备重连时送达,最多保留 30 天——服务器是带有限缓冲的中继站,不是永久档案库。

03每位用户一部手机,还是每台设备都同步?

用户可以附加媒体,而用户拥有的每台设备都保持同步——每台设备各自追踪自己的送达状态。

04已读回执和输入中指示器——在范围内吗?

回执要:它们搭上与消息相同的每设备送达路径。输入中指示器则是短暂的——尽力而为,永不存储。

05用户可以为所有人删除一条已发出的消息吗?

为所有人删除是一个 tombstone 事件,传播方式和消息一模一样;已经渲染它的客户端就地替换掉它。编辑不在范围内。

06有人在对话中途加入或离开群组——他们看到什么?

成员变更是聊天中的有序事件:加入者从加入点之后开始收到,离开者在离开事件停止收到——绝不会是片段历史。

范围外语音和视频通话 · 商业消息和聊天机器人 · 注册和个人资料管理

非功能需求

01一条消息要多快抵达一个在线的人?

端到端 500 ms 以内——聊天要么感觉即时,要么感觉坏掉。

02一条消息有可能悄悄消失吗?

不会——送达有保证:消息在任何送达尝试之前就已持久写入,所以断线只会延迟它,永不丢失。

03我们是为什么规模设计?

200M 日活跃用户、每人每天约 20 条消息,任一时刻约有一半在线。

04服务器保留消息内容多久?

不会超过必要时间:未送达收件箱有 30 天 TTL(time-to-live),然后就没了。

05一台聊天服务器挂掉时会怎样?

它的用户在别处重新连接并从收件箱同步——因为送达状态存在存储里、不在服务器内存里,所以没有消息丢失。

持续追问 —— 面试是一场对话

真实面试探得比一份整齐清单深得多。这些范围问句,区分出真正拷问问题的人和只是背诵的人。

  • 群组大小上限——数百还是数千?fan-out 设计全看它。
  • 发件人看到的 ack 是「已送达服务器」还是「已送达设备」?
  • 每个账号要有几台设备保持同步?
  • 消息历史是永久归档,还是 30 天的中继缓冲?
  • 端到端加密在范围内吗?它会改变服务器能存储什么。
02

逼出架构决策的数字

把每个估算都当成一种压力,用来合理化一个组件:缓存、队列、分片、副本、worker pool 或退路。

01

消息写入率

2 亿活跃用户 × 每人每日约 20 条消息200M × 20 = 每天 4B 条;4B ÷ 86,400 秒 ≈ 每秒发出 46K 条;群组扇出 ×2-3 ≈ 每秒 100K 次写入

Message 和 Inbox 用写入优化的存储(LSM log-structured merge trees/wide-column);fan-out 工作量随参与者数扩张,这正是群组要设上限的原因。

02

同时在线连接数

200M 日活跃,任一时刻约一半在线200M × ~50% ≈ 1 亿条打开的 WebSocket 连接

持久连接意味着决定 gateway 集群规模的是连接数——不是请求速率。

03

每个 gateway 的连接数

每台强力 gateway 机器约 100 万条并发 WebSocket 连接100M 同时在线 ÷ 1M ≈ 100+ gateways

用户哈希到 gateway(consistent hashing + registry),让消息路由能找到正确的机器。

04

离线收件箱大小

30 天 TTL,每条消息引用约 1KB即使 1,000 条未送达消息 ≈ 每位休眠用户 1 MB

TTL 让后盾有界;长期休眠的设备改从 Message store 重新同步历史。

决策示例

数字

每秒十万次写入、一亿人同时在线,还有一个承诺:半秒以内,而且永远不会悄悄弄丢任何东西。

我的选择

我会让 gateway 保持无状态:gateway 只握着 socket 并转发——每条消息在任何推送之前,都先持久写入 Message store 与每位收件人的 Inbox。跨服务器投递走以 user ID 分区的 pub/sub,每台 gateway 只订阅自己连着的用户。顺序以服务器收件时间为准——聊天里“快”胜过“完美有序”。

避免

我不会做的事:把未送达消息留在 gateway 内存里——一次崩溃它们就没了,这会打破核心承诺。我不会按 chat ID 分割 pub/sub——一个忙碌的群组就变成热分区;改按 user 分割。而且在测量实际的每分区消息率之前,我不会加分片层——过早分片加的是失效模式,不是容量。

何时改变

如果端到端加密进到范围内,服务器会退化成一个盲目转发密文的路由器:收件箱和多设备同步必须改成每设备的消息副本和客户端持有的密钥。

03

架构路径

先看一张完整的图,再把每条路径各自画成一张图 —— 写入路径与读取路径承载不同流量,合理化不同的组件。

完整全貌

总览 —— 每个组件

Client(WebSocket)ConnectionGatewayMessage ServiceMessage + InboxStoreRecipientGatewaysPub/Sub (peruser)路由到各 gatewayConnectionRegistryuser → gateway 查询PushNotification离线设备

持久写入优先,送达其次。Gateway 是无状态的 socket 持有者;registry 知道谁连在哪里,离线设备收到的是 push 而非 socket frame。

路径 1

发送路径——持久优先,再 fan out

Sender ClientGatewayMessage ServiceMessage + InboxStorePub/Sub →Gateways

发件人的 ack 在持久写入之后才触发;pub/sub 接着抵达每个持有收件人设备的 gateway。漏掉一次 publish 是安全的——收件箱已经有了它。

路径 2

重新连接路径——排空收件箱

Client(reconnect)GatewayInbox(undelivered)Message StoreClient synced

重新连接时客户端排空它的收件箱并比对 sequence number;任何在传输中漏掉的都会被拉取,然后恢复实时送达。

04

API 与数据模型

在优化之前,先让契约可被检视:端点、实体、所有权、重试与状态。

WSsendMessage { chat_id, body, attachments[] }

响应{ message_id, status } — ack after durable write

这个 ack 的意思是「已存储」,不是「已送达」——持久性优先,送达其次。

WSnewMessage → client { chat_id, sender, body }

响应client replies RECEIVED

通过持久连接推送给每位参与者的每台在线设备。

POST/attachments

请求{ body: presigned upload }

响应200 { attachment_id, url }

媒体经由 presigned URL 进到 blob 存储;消息只带那个不透明的引用。

WSheartbeat every 10-30 s (piggybacks last sequence)

响应client compares and syncs gaps

心跳检测死掉的连接,同时兼作缺口检测的通道。

核心实体

Chat

chat_id (PK) · participants (≤100) · name

Message

message_id (PK) · chat_id · sender_id · body · attachments · server_ts

按服务器收到的时间戳排序和显示——用户宁可快点看到消息,也不要完美的发送顺序。

Inbox

user_id (PK) · message_id · ttl_30d

持久性的后盾:在任何 push 尝试之前就写入,重新连接时排空。

Client

user_id (PK) · client_id · last_seen

一位用户,数台设备(上限约 3)——送达是按 client 追踪,不是按 user。

05

深入方向

在面试最后三分之一挑一条路线。每条路线给你主题、它该回答的面试官问题,以及要避免的失败模式。

重点

一亿条开着的 socket

数亿用户如何持有持久连接,一条消息又如何找到持有其收件人的那一个 gateway?

回答

每台 gateway 握约 1M 条 socket;registry 记录 user → gateway 对应,以 user ID 分区的 pub/sub 只把消息送到握着收件人的那台 gateway。

避免

把每条消息广播给每个 gateway——只让 gateway 订阅自己连上的用户。

重点

收件人离线

消息在哪里等、等多久,他们重新连接的那一刻又会发生什么?

回答

消息在收件人的持久 inbox 里等(30 天 TTL);重连时客户端把积压拉完,其间 push 通知服务会提醒闲置设备。

避免

靠 pub/sub 来保证持久性——它是 at-most-once;收件箱写入必须先发生。

重点

两部手机,一个账号

用户在笔记本上读了一条消息。他们的手机需要知道什么,每设备状态又如何追踪?

回答

投递与已读状态按设备跟踪、不是按用户;每个设备从自己的 cursor 同步,手机刚好拉到它还没看过的部分。

避免

按 user 而非按 client 追踪送达——第二台设备会悄悄漏掉消息。

重点

消息乱序抵达

两条消息穿过不同 gateway 赛跑。收件人看到什么顺序,为什么那可以接受?

回答

顺序以每个会话的服务器收件时间为准,客户端照该时间戳渲染;gateway 之间的短暂竞速实际上看不出来,为全局顺序而阻塞反而付出用户有感的延迟。

避免

为了强制全局顺序而阻塞送达——用户偏好快,胜过完美排序。

重点

一个 gateway 在对话中途挂掉

一万名用户同时掉线。他们失去什么,又多快恢复完整?

回答

什么都不会丢——未送达消息存在持久 inbox、不在 gateway 内存。客户端重连到别台 gateway,10-30 秒的心跳带着最新序号,让每个客户端发现缺口并补拉。

避免

任何让 gateway 内存是未送达消息唯一副本的设计。

准备好练习了吗?

把 即时通讯系统 大声讲一遍,让 AI 为你的说明评分。

用 AI 练习这题 →