系统设计工具箱
系统设计的公共词汇:负载均衡、多级缓存、消息队列、限流熔断、水平扩展、容量估算。本章是面试与真实架构评审的公共工具箱。
把请求分发到多台机器:
| 策略 | 做法 | 适用 |
|---|---|---|
| 轮询/加权 | 依次分发 | 均匀负载 |
| 最少连接 | 发给连接最少的 | 请求耗时差异大 |
| 一致性哈希 | 按键哈希到节点(→ 哈希表) | 缓存命中敏感(同 key 同节点) |
| 地理 DNS/Anycast | 最近机房(→ DNS) | 全球分布 |
无状态设计是水平扩展的前提:会话状态外置(Redis)、文件外置(对象存储)——任何一台都能处理任何请求。四层(L4,快)与七层(L7,灵活:按路径/头分流)是两个均衡点。
多级缓存与缓存失效
Section titled “多级缓存与缓存失效”缓存失效是两大难题之一("计算机科学只有两大难题:缓存失效与命名")——缓存的三个经典问题:
- 穿透:查询不存在的 key,全部打到数据库——对策:空值缓存、布隆过滤器(先问"可能存在吗");
- 击穿:热点 key 过期瞬间并发重建——对策:互斥重建(单飞 singleflight)、逻辑过期;
- 雪崩:大量 key 同时过期——对策:过期时间加随机抖动、多级缓存。
缓存一致性:旁路缓存(cache-aside)是默认模式——读先查缓存、未命中查库回填;写先更新库、再失效缓存(失效而非更新:更新会引入并发竞态)。**主动失效(invalidation)**用发布订阅(→ 架构与权衡)。
缓存的判断标准:读多写少 + 可容忍陈旧——两者缺一,缓存是负债(一致性问题大于性能收益)。
消息队列的语义
Section titled “消息队列的语义”队列(Kafka/RabbitMQ)的三个语义层:
- 投递语义:至多一次(不重试)、至少一次(重试,可能重复)、精确一次(至少一次 + 幂等,→ 协调服务与实践模式);
- 顺序保证:按 key 分区内有序(Kafka)——全局有序牺牲吞吐;
- 堆积与背压:消费跟不上生产 → 堆积监控、消费者扩容、背压(生产端限速)。
队列的核心价值是削峰与解耦(时间解耦,→ 架构与权衡);代价是复杂度与最终一致。
- 限流保护自己:固定窗口(简单、边界突刺)、滑动窗口、令牌桶(匀速发放、允许突发)是标准答案。按调用方/接口分层限流;
- 熔断保护下游:错误率超阈值就"断开",直接快速失败——给下游喘息、避免级联超时(级联失败是分布式系统的死亡螺旋:超时 → 重试 → 更慢 → 更多重试)。半开探测恢复;
- 舱壁(bulkhead):资源隔离(连接池按下游划分)——一个下游耗尽不拖垮全部。
限流保护自己,熔断保护下游——两者都是为级联失败设计的。
水平扩展与无状态
Section titled “水平扩展与无状态”- 垂直扩展(更强机器)有物理上限与单点风险;
- 水平扩展(更多机器)的前提是无状态:会话外置、请求可路由到任意节点;
- 扩展顺序:先无状态化,再读写分离(→ 复制与分片),再缓存,最后才分片(分片是最痛的一步,能不动就不动)。
先估算 QPS 与数据量,再画架构图——数量级的心算:
- QPS:日活 、人均请求 → 平均 QPS ,峰值 ×3–10;
- 读放大:1 次页面请求 → 10–100 次后端调用 → 100–1000 次数据库查询(N+1 的来源);
- 存储:每条记录大小 × 增速 × 保留期;索引与副本 ×2–3;
- 带宽:QPS × 响应大小。
估算的作用不是精确,是排除不可行:1 万 QPS × 10 KB 响应 = 100 MB/s 出口、每天 TB 级日志——这些数量级决定"要不要缓存、要不要分片",比架构图先算。
- 无状态设计是水平扩展的前提;一致性哈希让缓存命中不因扩容失效
- 缓存三问题:穿透(空值/布隆)、击穿(单飞)、雪崩(过期抖动);写用失效不用更新
- 缓存失效是两大难题之一:读多写少 + 可容忍陈旧才值得缓存
- 队列削峰解耦,代价是重复与乱序(消费端幂等)
- 限流保护自己,熔断保护下游;先估算 QPS 与数据量,再画架构图