- 教程
【免费下载链接】Grokking-System-Design
Systems design is the process of defining the architecture, modules, interfaces, and data for a system to satisfy specified requirements. Systems design could be seen as the application of systems theory to product development.
导读
本文基于 designs/facebook-messenger.md 展开,该文档以「概述图 + 细节图」两张架构图精炼地承载了 Facebook Messenger 类聊天系统的完整设计脉络:先给出用户经聊天服务器互发消息的基础链路,再给出负载均衡、聊天服务器集群、数据库分片与缓存集群组成的分布式扩展架构。读完本文,你将掌握即时消息系统从单点到集群的演进路径、图中每个组件的职责,以及仓库基础文档(负载均衡、分片、缓存、实时通信、队列、冗余、CAP 定理等)在聊天场景中的具体落点,可直接用于系统设计面试的讲解与复盘。
一、文档概览:两张图讲清一个完整设计
与仓库中大多数设计文档(如 designs/short-url.md 的需求清单、容量估算、API 表格、数据模型等长篇结构)不同,facebook-messenger.md采用极简的组织方式:正文仅一个## Summary小节,所有技术内容全部封装在两张架构图中——
- 概述图(img/facebook-messenger-overview.png):展示最基础的聊天系统形态,回答"消息如何从一个人传到另一个人并落盘";
- 细节图(img/facebook-messenger-detail.png):展示分布式扩展后的完整形态,回答"用户量大之后如何拆分与加速"。
这两张图恰好对应了系统设计面试中"先设计最小可行系统,再识别瓶颈、逐步扩展"的标准节奏(参见 README.md 的 Interview Process 一节)。
二、基础架构:单聊天服务器的消息流转
概述图描绘了一个最简单的点对点聊天拓扑,共 4 个组件、4 条消息流向:
| 组件 | 职责(按图示) |
|---|---|
User 1/User 2 | 消息的发送方与接收方 |
Chat Server | 消息转发的核心枢纽 |
Data storage | 消息的持久化存储 |
数据流如下:
User 1向Chat Server发送消息 A(Send Message A);Chat Server将消息 A 转发给User 2(Receive Message A);User 2向Chat Server发送消息 B(Send Message B);Chat Server将消息 B 转发给User 1(Receive Message B);- 在此过程中,
Chat Server与Data storage之间为双向读写,即消息既要落盘持久化,也要在必要时从存储中取回(例如离线消息、历史消息查询)。
2.1 架构含义解读
从这张图可以提炼出聊天系统的两个本质约束:
- 双向实时通信:用户与聊天服务器之间不是一问一答的普通 HTTP 请求,而是需要服务器能主动向客户端推送消息。仓库的 basics/client-server-communication.md 列出了从标准 HTTP、Ajax 轮询、HTTP 长轮询、WebSocket 到 Server-Sent Events(SSE)的演进,其中WebSocket(基于单条 TCP 连接的全双工通道、低通信开销、实时数据传输)最贴合"发送 + 接收"对称的场景;
- 消息必须持久化:
Chat Server与Data storage的双向箭头表明,消息不是转瞬即逝的网络包,而是要落库的"数据对象",这决定了后续数据模型与存储选型(SQL/NoSQL、分片)的设计空间。
2.2 显而易见的瓶颈
基础架构只有一个Chat Server和一个Data storage,两者都是典型的单点故障与性能瓶颈:
- 单台聊天服务器承载所有连接与转发,CPU、内存、文件描述符(每个长连接都会占用)很快耗尽;
- 单一存储无法支撑海量消息的写入与读取;
- 任一组件宕机,整个聊天服务即不可用。
这正是细节图要解决的问题。
三、分布式扩展架构:从单点走向集群
细节图给出了完整的规模化方案,图中组件包括:
- 用户层:
User A、User B、User C(图中以 3 个用户示意海量用户); - 第一级负载均衡:
Load Balancer - User to Chat Server Mapping,负责把用户映射到具体的聊天服务器; - 聊天服务层:
Chat Servers集群,图中含 4 台服务器实例; - 存储层:
DB Shard数据库分片,图中含 4 个分片实例,每台聊天服务器直连分片读写; - 缓存层:
Load Balancer - User to Cache Server Mapping负载均衡 +Cache集群(图中 3 台实例),同时DB Shard整体向缓存层供数,即缓存以数据库为数据源。
3.1 分层数据流串联
把图中的箭头连起来,可以得到完整的请求链路:
- 用户(User A/B/C)首先命中
Load Balancer - User to Chat Server Mapping; - 负载均衡按映射策略把用户分发到
Chat Servers集群中的某一台实例; - 聊天服务器直接读写对应的
DB Shard(消息按分片规则落到不同数据库实例); - 聊天服务器与数据库都接入缓存层:聊天服务器通过
Load Balancer - User to Cache Server Mapping访问Cache集群,数据库分片向缓存层同步/供数,从而把高频读请求从数据库卸载到缓存。
3.2 两个关键设计点
(1)为什么用户到聊天服务器的映射需要专门一个负载均衡?
聊天连接本质上是有状态的:一个用户与某台聊天服务器之间通常维持着 WebSocket 或长轮询连接,服务器侧保存着该用户的会话状态。若负载均衡随机分发,用户每次重连都可能落到不同服务器,导致状态丢失、连接反复重建。因此图中用「User to Chat Server Mapping」明确表达:负载均衡需要按用户维度做映射(可理解为会话保持 / IP hash 之类的粘性策略),让同一用户尽量稳定落在同一台聊天服务器上。
(2)为什么存储要分片、读要缓存?
消息数据具有"按用户/会话天然可分"的特征,适合用DB Shard水平拆分来扩展写入与存储容量;而历史消息、会话列表、用户资料等读取频繁且局部性强,适合引入Cache集群加速,并通过缓存负载均衡把聊天服务器对缓存的访问与数据库解耦。
四、图中关键组件的原理深挖(仓库基础文档佐证)
细节图中的每个组件,在仓库 basics 目录下都有对应的原理文档,下面逐一对应展开。
4.1 负载均衡:聊天的两级 LB
basics/load-balancing.md 指出负载均衡帮助系统"跨不断增多的服务器水平扩展",并给出了三个典型部署位置:
- 用户与 Web 服务器之间;
- Web 服务器与内部平台层(应用服务器、缓存服务器)之间;
- 内部平台层与数据库之间。
细节图正好呈现了前两类:Load Balancer - User to Chat Server Mapping位于用户与聊天服务器之间,Load Balancer - User to Cache Server Mapping位于聊天服务器与缓存之间(此外聊天服务器与 DB 分片之间同样可以再设一层 LB,形成文档所述的第三个位置)。
负载均衡算法方面,仓库列出:最少连接(least connection)、最少响应时间(least response time)、最少带宽(least bandwidth)、轮询(round robin)、加权轮询(weighted round robin)、IP hash。对聊天系统而言,IP hash天然具备"同一来源 IP 落到同一服务器"的特性,可作为用户-聊天服务器映射的简单实现;实现方式上可以是智能客户端、硬件负载均衡器或软件负载均衡器。
4.2 数据库分片:DB Shard 的拆分方式
basics/sharding.md 系统介绍了数据分区方法:
- 水平分区(range-based sharding):按行拆分到不同表/库,缺点是若分区键选取不当会导致服务器负载不均;
- 垂直分区:按功能域拆分到独立服务器,实现直观但对应用扩展有后续限制;
- 目录式分区:通过查找服务抽象分区方案,便于扩库但自身可能成为单点。
分区准则上,聊天消息最常用的是key/hash-based partitioning:对某键属性(如user_id或会话 ID)施加哈希得到分区号;其痛点是新增服务器可能改变哈希函数、需要数据重分布,仓库给出的缓解方案是 consistent hashing——把键与服务器都哈希到环上、顺时针找首个节点,扩容缩容时只需重映射k/n个键,并可引入虚拟副本缓解热点。
分片后还需面对三个常见问题(同样见 basics/sharding.md):跨分片的 join 效率低(可反规范化,但引入数据不一致)、外键等引用完整性难保证(交由应用层维护)、以及数据分布不均时的再平衡。聊天场景通常以用户/会话为分片维度,尽量保证单会话数据落在同一分片内,从而规避跨分片 join。
4.3 缓存层:读多场景的加速器
basics/caching.md 开宗明义:缓存利用局部性原理(最近被请求的数据很可能再次被请求),存在于架构的各层,但通常放在最靠近前端的位置。图中Cache集群被聊天服务器与数据库分片共同引用,属于全局缓存形态——所有请求节点共享一个更快的存储,聊天服务器访问缓存时由缓存服务器处理未命中。
与缓存配套的三个决策点:
- 缓存失效策略:写直达(write-through,缓存与存储同时写,一致性好但写延迟高)、写旁路(write-around,只写存储,最近写入的数据读时易 miss)、写回(write-back,只写缓存、异步落库,写吞吐高但有宕机丢数风险)——聊天历史消息这类读多写少的场景,写旁路/写回与"数据库向缓存供数"的图示语义更契合;
- 淘汰策略:FIFO、LIFO、LRU(最近最少使用)、MRU、LFU、随机替换,仓库文档尤其强调 LRU 适用于"丢弃最久未使用"的语义;
- 分布式缓存 vs 全局缓存:若聊天服务器实例很多,也可用一致哈希把缓存数据分散到各节点(分布式缓存,便于扩容但节点丢失即丢缓存),或像图中这样独立成缓存集群(全局缓存,由缓存服务器统一处理 miss)。
4.4 实时通信:消息推送的协议选型
basics/client-server-communication.md 完整对比了五种通信方式,是"如何让消息实时到达对端"的答案:
- 标准 HTTP 请求:一问一答,无法推送;
- Ajax 轮询:客户端周期性(如每 0.5 秒)请求,缺点是需要不停询问、大量空响应造成 HTTP 开销;
- HTTP 长轮询:服务器延迟响应直到有更新或超时,客户端收到响应后立即发起新请求;每个长轮询请求都有超时,需周期性重连;
- WebSocket:单条 TCP 连接上的持久全双工通道,握手后双方随时可发数据,通信开销低、实时性高;
- SSE:服务器向客户端单向推送的实时流,适合服务器循环产生多个事件下行的场景。
对照概述图中 User 与 Chat Server 之间"发送 + 接收"对称的箭头,WebSocket 是最贴合的选型;在兼容性受限时可用 HTTP 长轮询兜底。需要注意的是,长连接会长期占用聊天服务器的连接资源,这也是必须把Chat Servers扩展为集群并做用户-服务器映射的根本原因之一。
4.5 队列与异步化:聊天的可选扩展手段
图中虽未显式画出消息队列,但 basics/queues.md 指出:大规模分布式系统中不同组件需要异步协作,队列是"客户端请求与实际服务工作"之间的抽象——客户端提交任务后无需等待结果,队列还能在服务故障时提供缓冲保护。在 Messenger 场景中,以下工作天然适合异步化:
- 消息的推送通知(Push Notification)投递;
- 离线消息的批量落库与上线后的补发;
- 消息送达回执与已读状态等非关键路径更新。
也就是说,可以在 Chat Server 与存储/推送服务之间插入队列,把实时转发与后台处理解耦,这也是从两张图推演后续演进时可以补充的一环(文档本身未画,故此处仅作设计思路说明)。
4.6 冗余与高可用
basics/redundancy.md 定义冗余为"对关键数据或服务的复制,以提升系统可靠性",典型手段包括服务器故障转移(failover)与共享无状态(shared-nothing)架构——每个节点独立运行、无中央状态管理、新节点可无特殊条件加入、无单点故障。
细节图中的Chat Servers多实例本身就是服务层冗余:单台实例宕机后,负载均衡可以把映射切换到存活实例(配合会话状态外置到缓存/存储,可实现无缝故障转移)。这正是概述图单服务器形态缺失、而细节图补上的高可用能力。
4.7 一致性权衡:CAP 视角
basics/cap-theorem.md 指出:网络分区发生时,必须在一致性与可用性之间二选一——ACID 数据库选择一致性优先,BASE 系统选择可用性优先;而无分区时两者并不冲突。
聊天系统对一致性有不同的层级要求:
- 消息内容与顺序:同一会话内的消息必须有序、不丢失,通常要求强一致或至少会话级有序;
- 已读状态、在线状态:可以接受短暂不一致(最终一致),换取高可用与低延迟。
因此在存储层选型与复制策略上,需要按数据类别区分对待,这正是 basics/cap-theorem.md 在 Messenger 设计中的具体落点。
4.8 存储选型:SQL vs NoSQL
basics/sql-vs-nosql.md 给出了选型框架:SQL 以固定 schema、ACID 事务、纵向扩展见长;NoSQL 以动态 schema、横向扩展(加机器即可扩容)、牺牲部分 ACID 换取性能见长,常见形态包括键值存储(Redis、Dynamo)、文档库(MongoDB、CouchDB)、宽列库(Cassandra、HBase)与图数据库(Neo4J)。
对聊天消息而言:
- 消息记录天然按会话/用户聚合,文档或宽列模型的"按 key 聚合读取"特性贴合"拉取某会话最近 N 条消息"的查询模式;
- 海量消息需要水平扩展,NoSQL 的廉价横向扩展更契合,且可与 basics/sharding.md 的分片方案叠加;
- 若需要强事务(如转账类业务消息)则 SQL 更合适。
仓库其他设计文档也体现了类似判断——例如 designs/short-url.md 在存储观察中明确写出"NoSQL 更易扩展,SQL 加分片也可行",可作为同仓库内选型思路的参照。
五、回到方法论:如何用这两张图讲透一个系统设计
README.md 将系统设计面试流程归纳为三步:
- 范围界定(Scope the problem):不要假设,通过澄清问题理解约束与用例,包括需求澄清与系统接口定义;
- 抽象设计(Sketch up an abstract design):列出系统的积木与关系,包括粗略估算(back-of-the-envelope estimation)、定义数据模型、高层设计;
- 识别并解决瓶颈(Identify and address the bottlenecks):进入详细设计,找出瓶颈并解决。
facebook-messenger.md的两张图恰好是这套方法的可视化缩影:概述图 = 第 2 步的高层设计(先画出最小可运行系统),细节图 = 第 3 步的详细设计与瓶颈解决(单点 → 集群、单库 → 分片、无缓存 → 缓存层)。
如果想在面试中把这两张图扩展成一份完整的设计稿,可以参照仓库中结构最完整的 designs/short-url.md,它通常覆盖这些章节:功能/非功能/扩展需求、容量估算(QPS、存储、带宽、缓存内存)、系统 API(含参数表)、数据库 schema、核心算法、数据分区与复制、缓存策略(LRU、更新时机)、负载均衡位置、过期数据清扫、遥测统计与安全权限。对 Messenger 而言,对应的推演方向包括:
- 容量估算:假设读多写少(如 100:1 读:写比),估算每秒消息量与存储增长(需自行设定合理假设,文档未给具体数字,不可臆造);
- 数据模型:消息表(消息 ID、发送者、接收者/会话 ID、内容、时间戳)、用户表、会话表;
- 分区:按用户/会话哈希分片(见 4.2);
- 缓存:缓存最近会话与热用户状态,LRU 淘汰(见 4.3);
- 实时通道:WebSocket 为主、长轮询兜底(见 4.4)。
六、小结
facebook-messenger.md用两张图完成了"从 0 到 1、从 1 到 N"的完整叙事:概述图中的User ↔ Chat Server ↔ Data storage是聊天系统不可再简化的核心骨架,细节图则在每个单点上做文章——用「用户-聊天服务器映射负载均衡」解决有状态连接的归属问题,用Chat Servers集群解决连接与转发容量问题,用DB Shard解决存储与写入扩展问题,用Cache集群解决读放大问题。四个方向分别对应仓库基础文档中的负载均衡、冗余、分片(含一致哈希)与缓存原理,而实时通信选型(WebSocket/长轮询)、异步队列与 CAP 权衡则是聊天系统区别于普通 Web 系统的独特维度。掌握这张图背后的组件职责与取舍逻辑,即可在面试中把任何聊天类系统设计题讲得有条理、有依据。
延伸阅读:仓库 basics 目录下的 load-balancing.md、sharding.md、caching.md、client-server-communication.md、queues.md、redundancy.md、cap-theorem.md、sql-vs-nosql.md、consistent-hashing.md,以及 README.md 中的系统设计面试方法论,都是深入这一主题的配套材料。
- 教程
【免费下载链接】Grokking-System-Design
Systems design is the process of defining the architecture, modules, interfaces, and data for a system to satisfy specified requirements. Systems design could be seen as the application of systems theory to product development.
相关推荐
10分钟上手RSpec-Mocks:Ruby开发者必备测试工具快速入门
10分钟上手RSpec Mocks:Ruby开发者必备测试工具快速入门 RSpec Mocks是Ruby生态中最受欢迎的测试替身(test double)框架,
Resticker常见问题解决:从锁冲突到存储连接错误的终极指南
Resticker常见问题解决:从锁冲突到存储连接错误的终极指南 Resticker是一款通过Docker容器运行自动restic备份的强大工具,能够帮助用户轻
运维HunyuanWorld-Mirror vs Fast3R:单向前馈模型效率优势
HunyuanWorld Mirror vs Fast3R:单向前馈模型效率优势 在3D重建领域,传统模型常受限于多阶段优化带来的计算瓶颈,尤其在实时场景重建、
教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考