核心积木 I:从一台机器到集群
负载均衡、无状态服务、水平扩展、CDN——这四块积木出现在每一个系统设计答案的开头,也是你公司系统的入口。理解它们,你就有了画第 3 步「高层架构图」的默认骨架,不再是每次现编。
§1 · 起点一台机器的极限
所有系统都从一台服务器开始。撑不住时第一个念头是纵向扩展(vertical scaling):换更强的机器。它简单有效,但有三条天花板:
- 物理上限:CPU 核数、内存插槽是有限的,且越往上单价越贵(非线性);
- 单点故障(single point of failure):一台机器挂了 = 整个服务挂了;
- 原地升级要停机。
横向扩展(horizontal scaling)换一条路:加更多普通机器分摊负载。便宜、可容错、可在线扩——但带来一个新问题:请求怎么分?机器挂了谁知道?这就引出负载均衡器。
§2 · 积木一负载均衡器 Load Balancer
负载均衡器站在所有后端服务器前面,做三件事:分发请求、健康检查、剔除故障节点。它让「一群机器」对外表现为「一台机器」。
四层 vs 七层
| 类型 | 工作在 | 能看到什么 | 典型场景 |
|---|---|---|---|
| L4(传输层) | TCP/UDP | 只有 IP 和端口 | 超高性能转发、TCP 长连接、数据库前置 |
| L7(应用层) | HTTP/HTTPS | URL、Header、Cookie | 按路径分流(/api → 订单服务)、灰度、压缩 |
常见分发算法
| 算法 | 规则 | 适合 |
|---|---|---|
| 轮询 Round Robin | 挨个来 | 机器配置相同、请求耗时均匀 |
| 最少连接 Least Connections | 谁手上活少给谁 | 请求耗时差异大(如混合长短请求) |
| IP/一致性哈希 | 同一来源固定到同一节点 | 需要本地缓存命中/会话保持 |
| 加权轮询 | 按机器配置加权 | 新机器与老机器混布 |
你的 LB 配置里,健康检查(health check)的路径和超时值得专门审一遍:检查太宽松——后端半死不活还在接流量;太严格——部署窗口被误杀。真实事故常出在这。
§3 · 积木二无状态服务 Stateless Service
横向扩展的前置条件。有状态的服务把用户会话存在本机内存里——用户的下一个请求落到另一台机器就「失忆」了。于是要么 L2 会话粘连(sticky session,破坏负载均衡),要么……
解法:把状态赶出去。会话放 Redis、文件放对象存储,服务进程变成「无状态」的:任何请求交给任何实例,结果都一样。收益是三重的:
- LB 随便轮询,无需会话粘连;
- 扩缩容就是加/减实例,弹性伸缩(autoscaling)成为可能;
- 任何一台挂了,用户无感知(流量切走即可)。
评审时问一句:「这个服务真的是无状态吗?」本地磁盘写文件、进程内缓存当真值、定时任务单实例跑——都是伪装成无状态的有状态服务,是扩容时的经典暗雷。
§4 · 积木三CDN:把内容搬到用户门口
Lesson 0002 告诉你跨洋 RTT ~150ms 且受光速限制无法优化。CDN(内容分发网络,Content Delivery Network)的思路不是让光变快,而是别让数据跨洋:把静态内容提前复制到全球几百个边缘节点,用户就近取[1]。
| 方式 | 机制 | 适合 |
|---|---|---|
| Pull(拉) | 边缘节点首次收到请求时回源取,并按 Cache-Control 缓存 | 大多数场景,省事,冷启动慢一点 |
| Push(推) | 内容更新时主动推到边缘 | predictable 的热点(新剧集上线、大促素材) |
三个工作细节:① Cache-Control 头决定缓存时长,配合文件名哈希(app.a3f9.js)实现「永久缓存 + 发布即更新」;② 私有内容用签名 URL(带过期时间的 token)防盗链;③ 刷新(purge)是兜底手段,别当常规操作用。
Lesson 0013 会算给你看:视频平台的出站带宽大到一个量级——自建机房出口根本扛不住,选 CDN 不是优化项,是可行性前提。记住这个推理路径。
§5 · 拼起来一张默认骨架图
用户 ──DNS──▶ [CDN 边缘节点] ←─ 静态资源(JS/CSS/图片/视频)
│ 动态请求
▼
[负载均衡器 L7]
│ 健康检查 + 分发
▼
[无状态服务 ×N](可弹性扩缩容)
│
▼
[缓存 Redis] ─▶ [数据库](下一课起的主角)
这张图是四步框架第 ③ 步的默认起手式:先画它,再根据约束清单增删组件。它不酷,但每一次增删你都说得出理由——这才是资深的标志。
§6 · 随堂检测
§7 · 检索练习
凭记忆回答:① 为什么无状态是横向扩展的前置条件?② L4 和 L7 各举一个适用场景;③ CDN 的 Push 与 Pull 各适合什么?
核对参考答案
① 有状态服务把会话存在实例本机,换实例就丢状态 → 无法随意调度流量;状态外置(Redis/对象存储)后任何实例等价,才能自由扩缩容。② L4:数据库前置/TCP 长连接高性能转发;L7:按 URL 路径把 /api 与 /static 分流。③ Push:可预知热点(新剧集上线)提前推边缘;Pull:一般内容按需回源 + Cache-Control 控制。
§8 · 本周行动
对你公司系统回答四个问题(问同事/看配置都行):
① 入口是 L4 还是 L7?用的什么分发算法?
② 服务真的无状态吗?(本地写文件?进程内缓存当真值?)
③ 静态资源走 CDN 吗?Cache-Control 策略是什么?
④ 健康检查路径是什么?挂掉一台实例,流量多久切走?
§9 · 延伸资源
- 首选精读:System Design Primer 的 Load Balancer 与 CDN 小节[2]
- System Design 101 的图解版(一图看懂 LB 在整条链路的位置)[3]
- 术语词典(本课新增:LB、无状态、CDN、单点故障等)
- 下一课 → Lesson 0004 数据层:SQL vs NoSQL 与复制