← 返回画廊

后端系统设计速查:从单机到千万级的关键决策

用法:先定位场景(第 8 节对照表)→ 回到对应章节看权衡与坑。所有数字为数量级参考,面试/评审时报数量级即可,不要报精确值。


1. 估算能力(Back-of-envelope)

1.1 必背常数

常数记法
一天秒数86,400 ≈ 10^5估算统一按 10^5
一月秒数≈ 2.6 × 10^6
1 亿请求/天1,150 QPS(均值)「亿/天 ≈ 千级 QPS」
峰值系数均值 × 2~5(常规);秒杀/热点 × 10+面试默认 ×3
2^10≈ 10^3(K);2^20 ≈ 10^6(M);2^30 ≈ 10^9(G)换算桥梁
1 字符1B(ASCII)/ 3B(UTF-8 中文)
long/时间戳8B;UUID 16B;MD5 16B

1.2 必背延迟数字(数量级)

操作延迟对比感受
L1 缓存访问~1 ns基准
内存随机访问~100 ns
内存顺序读 1MB~50 μs顺序 >> 随机
SSD 随机读~100 μs比内存慢 10^3
SSD 顺序读 1MB~1 ms
机械盘寻道~10 ms避免随机 IO 的根源
同机房 RTT~0.5 ms一次 RPC 的底价
同城跨机房 RTT~1~3 ms同城双活可行的原因
跨国 RTT~150 ms异地多活难做强一致的原因
Redis GET~0.1~1 ms(含网络)
MySQL 索引点查~1~10 ms

1.3 单机容量参考(数量级,用于判断「要不要拆」)

组件单机能力(参考)
Nginx数万~十万 QPS(转发)
Redis 单实例~10 万 QPS
MySQL 单实例读 ~5k QPS / 写 ~1~2k TPS;单表建议 < 千万~5000 万行
Kafka 单 broker数十万 msg/s(顺序写 + 零拷贝)
普通 Java/Go 服务数百~数千 QPS/实例(取决于业务逻辑)
单机连接数数万长连接常规,百万需专门调优

1.4 估算套路(固定四步)

  1. QPS:DAU × 人均请求数 ÷ 10^5 → 均值;×3 → 峰值;读写比(常见 10:1~100:1)拆分读写。
  2. 存储:单条大小 × 日增条数 × 保留天数 × 副本数(通常 ×3)。
  3. 带宽:峰值 QPS × 平均响应体大小。1 万 QPS × 100KB = 1GB/s ≈ 8Gbps → 必须上 CDN。
  4. 机器数:峰值 QPS ÷ 单机能力 × 冗余系数(1.5~2,容忍 N+1 故障)。

:只算均值不算峰值;忘算副本和索引膨胀(MySQL 索引可占数据量 50%+);忘了流量增长(按 1~2 年 ×2~5 预留)。


2. 缓存

2.1 缓存模式选型

模式读写路径何时用代价/坑
Cache-Aside(旁路,默认选它)读:查缓存→miss 查库→回填;写:先更新 DB,再删缓存绝大多数读多写少场景需自己处理一致性;删除失败要重试
Read/Write-Through缓存层代理读写 DB有统一缓存中间件时实现复杂,业务侵入小但基建要求高
Write-Behind(异步写回)先写缓存,异步批量落库写极高频、可容忍丢失(点赞数、计数器)宕机丢数据;顺序难保证
本地缓存(Caffeine/Guava)进程内热点小数据、配置类;作为 Redis 前的 L1多实例不一致;靠短 TTL + 广播失效缓解

2.2 三大经典问题

问题定义对策(按优先级)
穿透不存在的 key,永远打到 DB① 缓存空值(短 TTL 1~5min)② 布隆过滤器拦截 ③ 参数合法性校验(负数 ID 直接拒)
击穿单个热点 key 过期瞬间,并发全打 DB① 互斥锁/singleflight:只放一个请求回源 ② 热点 key 逻辑过期(不设 TTL,异步刷新)③ 热点永不过期 + 主动更新
雪崩大量 key 同时过期或 Redis 整体挂掉① TTL 加随机抖动(base + rand(0, 10%·base))② Redis 高可用(哨兵/集群)③ 服务侧限流 + 降级兜底 ④ 多级缓存

2.3 缓存一致性(面试高频)

其他坑:大 key(>10KB 就要警惕,压缩/拆分);热 key(本地缓存 + 分片打散 key_{1..N});缓存与 DB 字段演进不同步(值里带版本号)。


3. 数据库

3.1 SQL vs NoSQL 选型

维度选 SQL(MySQL/PG)选 NoSQL
数据关系多表关联、事务、强 schema弱关系、schema 多变
一致性需要 ACID(订单、支付、账务)可接受最终一致
查询模式灵活 ad-hoc 查询查询模式固定、按 key 访问
量级单表千万级内舒适天然水平扩展
NoSQL 类型代表典型场景
KVRedis缓存、会话、计数、排行榜(zset)、分布式锁
文档MongoDB内容/商品详情、schema 多变、嵌套结构
宽列HBase/Cassandra海量写入、时序类、消息存储、画像
搜索Elasticsearch全文检索、多维筛选聚合(不当主存储
Neo4j社交关系、风控关联分析

务实原则:默认 MySQL;专门问题上专门组件(搜索→ES,缓存→Redis);NoSQL 是补充不是替代,核心账务数据留在关系库。

3.2 索引

要点说明
最左前缀联合索引 (a,b,c) 支持 a / a,b / a,b,c;跳过 a 不走索引
覆盖索引查询列全在索引里 → 免回表,高频查询优先设计
失效场景索引列上用函数/运算、隐式类型转换(字符串列传数字)、前导 %like%、OR 混用无索引列
区分度低区分度列(性别、状态)单独建索引无意义;可放联合索引尾部
代价每个索引拖慢写入 + 占存储;单表索引建议 ≤ 5~6 个
主键用自增/趋势递增(雪花 ID);随机 UUID 导致页分裂、写放大

explain 看 type(至少 range)和 rows;深分页 limit 100000,10 要改游标(where id > ?)或延迟关联。

3.3 扩展路径(按顺序,别跳步)

参数/索引/SQL 优化 → 读写分离 → 垂直拆分(按业务拆库) → 水平分库分表(最后手段)
手段何时用代价
读写分离读远大于写、读扛不住主从延迟:写后立即读可能读到旧值 → 关键读走主库 / 会话粘性 / 延迟检测
垂直分库业务边界清晰、单库容量/连接数瓶颈跨库无 join、无本地事务
水平分表单表 > 千万~5000 万行、单库写入瓶颈见下

分库分表关键决策

决策点选项与建议
分片键最高频查询维度(C 端通常 user_id);一旦选定极难改
算法哈希取模(分布均匀,扩容需迁移→用一致性哈希/预分片如 1024 片缓解);范围/时间(利于冷热分离,但有热点尾部)
非分片键查询① 基因法(把 userid 片段编入 orderid)② 异构索引表/ES ③ 广播查询(尽量避免)
全局 ID雪花算法(注意时钟回拨)/ 号段模式(Leaf)
跨片问题join 改应用层聚合;分页用 ES/汇总表;事务见第 5 节

:过早分库分表(复杂度巨大,先确认优化空间用尽);分片键选错导致大量跨片查询;扩容方案没提前设计。

3.4 主从复制

模式语义取舍
异步复制主库提交即返回最快;主库挂 → 丢最近事务
半同步至少 1 个从库 ack平衡(默认推荐);超时会退化为异步
全同步/组复制(MGR)多数派确认不丢数据;延迟高、吞吐降

主从延迟来源:大事务、从库单线程回放(开并行复制)、从库机器差。处理写后读:强制读主 / GTID 等待 / 客户端缓存刚写的值。


4. 消息队列

4.1 什么时候引入(三大理由,说不出理由就别加)

目的场景例子
异步非核心步骤移出同步链路,降 RT下单后发短信/积分/推荐
解耦上游不关心下游是谁、有几个订单事件被 N 个系统消费
削峰瞬时流量 >> 下游处理能力秒杀请求排队慢慢消化

引入的代价:系统复杂度 +1(可用性依赖、消息丢失/重复/积压三大新问题)、链路变最终一致、排查跨系统。同步链路上强依赖结果的调用不要改 MQ

4.2 选型速览

MQ优势适用
Kafka吞吐最高、分区顺序、生态(流处理)日志/埋点、大数据管道、事件流
RocketMQ事务消息、延迟消息、业务特性全电商/交易类业务解耦
RabbitMQ路由灵活、低延迟中小规模业务、复杂路由
Pulsar存算分离、多租户云原生、超大规模多业务线

4.3 三大必答问题

① 消息不丢(三段各自保证)

环节手段
生产端同步发送 + 重试;或本地消息表(见 5.3)
Broker刷盘策略 + 多副本(Kafka acks=all + min.insync.replicas>=2
消费端处理成功后再提交 offset / ack(禁止先 ack 后处理)

② 重复消费(一定会发生,别想「恰好一次」)

③ 顺序性

④ 积压处理:先扩消费者(≤ 分区数才有效)→ 消费逻辑批量化/去掉慢操作 → 紧急时新建大分区数 topic 转发分流 → 事后补:消费限速与容量预估。

:死信队列必须有监控与人工处理流程;消息体别放大对象(放 ID,消费端反查)。


5. 一致性与可用性

5.1 CAP 实战理解

5.2 最终一致性常见实现

手段机制适用
可靠消息(本地消息表/事务消息)见 5.3跨服务数据同步的默认方案
订阅 binlog(Canal/Debezium/CDC)DB 变更 → MQ → 下游缓存失效、异构索引、数仓同步;对业务零侵入
定时对账 + 补偿周期比对两边数据修复差异一切最终一致方案的兜底,必须有
重试 + 幂等失败自动重试直到成功所有异步链路的基础件

5.3 分布式事务方案对比

方案一致性侵入性性能何时用
2PC/XA低(DB 层)差(同步阻塞、协调者单点)传统场景、跨库低并发;互联网基本不用
TCC最终(业务层强控制)极高(每个操作写 Try/Confirm/Cancel ×3)资金类、需要资源预留(冻结额度)
Saga最终(可见中间态)中(每步写补偿)长流程多步骤(订票=机票+酒店+车)
本地消息表最终默认首选:业务 DB 事务内写消息表,后台扫表投递 MQ,消费端幂等
事务消息(RocketMQ)最终有 RocketMQ 时替代本地消息表(半消息 + 回查)
最大努力通知通知外部第三方(回调 + 重试 + 对方主动查询)

决策树:能拆成单库事务就别上分布式事务 → 能接受最终一致选本地消息表/事务消息 → 资金预留类选 TCC → 长补偿链选 Saga。空回滚、悬挂、幂等是 TCC/Saga 的三个必处理问题。


6. 限流、熔断、降级

6.1 限流算法

算法原理优缺点适用
固定窗口每窗口计数实现最简;临界突刺(窗口边界 2 倍流量)粗粒度够用时
滑动窗口细分子窗口滑动统计解决突刺;内存略多通用计数限流(Sentinel 默认)
漏桶恒定速率流出绝对平滑;无法应对突发,多余排队/丢弃对下游保护、调用第三方(对方限速)
令牌桶恒速放令牌,可攒允许桶容量内的突发,最常用网关/接口限流默认选它(Guava RateLimiter)

分布式限流:Redis + Lua(原子性)实现集群级配额;单机限流兜底防 Redis 挂。限流维度:全局 QPS / 用户 / IP / 接口分级。被限流的响应:返回 429 + 友好提示/排队页,别返回 500。

6.2 熔断(保护调用方自己)

6.3 降级(弃车保帅)

类型例子
功能降级大促关闭推荐/评论,保下单支付
数据降级实时数据不可用 → 返回缓存/静态兜底数据
写降级同步写改异步写、暂停非核心写入
读降级强一致读降为读缓存

务实要点:降级开关要提前埋好(配置中心一键切换),大促前演练;核心/非核心链路要事先分级,别等出事再决定砍谁。三件套关系:限流防洪(入口),熔断止损(依赖),降级保命(体验)


7. 高可用与扩展

7.1 无状态化(水平扩展的前提)

7.2 负载均衡

层级代表特点
DNS智能 DNS地理调度;生效慢(TTL),不适合故障切换
L4LVS/云 SLB性能极高,按 IP:Port 转发
L7Nginx/Envoy按 URL/Header 路由、灰度;性能次之
客户端微服务框架(gRPC/Dubbo)服务发现 + 客户端选节点,少一跳
算法适用
轮询/加权轮询节点同构/异构的默认选择
最少连接请求耗时差异大
一致性哈希需要亲和性(缓存命中、有状态分片);节点变动只影响 1/N
IP Hash简单会话粘性(慎用:与无状态化冲突,且 NAT 下倾斜)

必配:健康检查(主动探测 + 摘除)+ 慢启动(新节点渐进引流)。

7.3 垂直 vs 水平扩展

垂直(加配置)水平(加机器)
优点零架构改动、立即见效无上限、天然冗余
缺点有硬件天花板、单点、成本超线性要求无状态/可分片,架构复杂
务实早期先垂直,快且便宜有状态组件(DB)垂直扛到不得不拆;无状态服务尽早水平

7.4 幂等设计(分布式系统的地基)

重试无处不在(超时重试、MQ 重投、用户重复点击),所有写接口默认要考虑幂等

手段机制适用
唯一索引业务单号/请求 ID 唯一约束,重复插入报错即忽略创建类操作(最可靠)
Token 机制先取 token,提交时原子校验并删除表单防重复提交
状态机只允许合法状态迁移,非法迁移拒绝订单/工单类流程
乐观锁update ... set v=v+1 where id=? and v=?并发更新
Redis setnx请求 ID 去重(设 TTL)高性能场景(注意 Redis 挂时的兜底)

:只在网关做幂等不够,重试可能发生在任意内部环节;「查询后插入」不加唯一索引在并发下必然重复。

7.5 可用性数字

级别年不可用大致含义
99.9%~8.8 小时单机房、有基本冗余
99.99%~53 分钟同城多机房、自动故障转移
99.999%~5 分钟异地多活,成本陡增

串联链路可用性相乘(0.999^5 ≈ 0.995)→ 依赖越多可用性越低,用异步/缓存/降级切断强依赖。容灾演进:主备(冷/热)→ 同城双活 → 异地多活(按用户分片,单元内闭环,代价极大,非必要不做)。


8. 场景 → 推荐方案速查表

场景推荐方案(骨架)关键点
读多写少(资讯/商品页)CDN + Redis Cache-Aside + MySQL 读写分离缓存一致性用「先更 DB 后删缓存 + binlog 兜底」
秒杀/瞬时高并发写前置拦截(按钮置灰/答题)→ 限流 → Redis 预扣库存(Lua 原子)→ MQ 排队 → DB 异步落单库存防超卖:Redis 原子扣减 + DB 乐观锁双保险;幂等
计数/点赞/浏览量Redis incr + 定时/异步批量落库容忍少量丢失;热 key 分片
排行榜Redis zset大榜单只存 TopN + 定期归档
Feed 流推模式(写扩散,粉丝少)/ 拉模式(读扩散,大 V)/ 推拉结合(在线推、大 V 拉)大 V 是分界点(粉丝 > 万级用拉)
短链系统发号器(号段/雪花)+ 62 进制编码 + Redis 缓存映射301 vs 302(要统计选 302);布隆防穿透
分布式锁Redis SET NX PX + 唯一持有者标识 + Lua 释放;要求可靠用 Redisson(看门狗续期);强一致场景用 etcd/ZK锁超时 vs 业务超时;主从切换丢锁风险(RedLock 争议大,关键场景用 CP 存储)
分布式 ID雪花算法(注意时钟回拨)或号段模式需趋势递增利于 InnoDB 写入
延迟任务(订单超时关闭)RocketMQ 延迟消息 / Redis zset 轮询 / 时间轮兜底:定时扫表补偿
跨服务数据一致(下单+扣库存+积分)本地消息表 / RocketMQ 事务消息 + 消费幂等 + 对账资金冻结类才上 TCC
海量日志/埋点Kafka → Flink → ES/ClickHouse/数仓顺序写 + 批量,别用 MySQL 扛
全文搜索/多维筛选MySQL 主存 + binlog 同步 ESES 不做主存储,接受秒级延迟
单表过大(>5000 万)先归档冷数据/优化 → 再水平分表(user_id 分片 + 基因法/异构索引解决其他维度查询)分片键 = 最高频查询维度
写后立即读要求高读主库 / GTID 等待 / 客户端回填主从延迟无法根除,只能绕
依赖的第三方不稳定超时(必设,且 < 上游超时)+ 重试(带退避 + 幂等)+ 熔断 + 降级兜底超时是第一道防线,比熔断更重要
大促保稳容量压测 → 分级降级预案 → 入口限流 → 全链路监控 + 一键开关预案没演练过 = 没有预案

附:面试/评审答题框架

1. 澄清需求与规模(QPS/存储/读写比/一致性要求)—— 用第 1 节估算
2. 先给最简架构(LB + 无状态服务 + DB + 缓存),能跑通
3. 按瓶颈逐步演进:读瓶颈→缓存/读写分离;写瓶颈→MQ/分库分表
4. 每个组件说清「为什么引入 + 引入后的新问题 + 如何兜底」
5. 收尾谈非功能项:幂等、监控告警、降级预案、容量冗余

核心心法:每引入一个组件,都在用复杂度换某个能力——说不出换来了什么,就不要引入。