想象你是一座城市的总规划师。道路网络就是系统间的通信链路,供水系统是数据流动的管道,供电网络是服务可用的保障,应急预案是容灾与降级策略。好的城市规划让城市在暴雨(流量洪峰)中不内涝(不宕机),在管道爆裂(节点故障)时有备用供水(故障转移)。
关键指标详解
| 指标 | 全称 | 含义 | 典型值 |
|---|---|---|---|
| QPS | Queries Per Second | 每秒查询数(读请求为主) | 知乎 ~30K, 淘宝双11 ~数百万 |
| TPS | Transactions Per Second | 每秒事务数(写操作为主) | MySQL 单机 ~3K-8K |
| Latency | 响应延迟 | 请求发出到收到响应的时间 | P99 < 200ms 为佳 |
| Availability | 可用性 | 系统正常运行时间占比 | 通常要求 ≥ 99.9% |
| Throughput | 吞吐量 | 单位时间处理的数据量 | MB/s 或 GB/s |
QPS 偏向"查询",一个页面可能包含多个 Query(接口调用)。TPS 偏向"事务",一个事务可能包含多个操作(扣库存 + 创建订单 + 扣余额)。面试中两个术语经常混用,但严格来说 TPS 更重,通常 TPS ≤ QPS。
可用性对照表 — "几个9" 意味着什么?
| 可用性 | 年宕机时间 | 日宕机时间 | 可视化 | 等级 |
|---|---|---|---|---|
| 99% | 3.65 天 | 14.4 分钟 | 基本可用 | |
| 99.9% | 8.77 小时 | 1.44 分钟 | 较高可用 | |
| 99.99% | 52.6 分钟 | 8.64 秒 | 高可用 | |
| 99.999% | 5.26 分钟 | 0.86 秒 | 极高可用 | |
| 99.9999% | 31.5 秒 | 0.086 秒 | 电信级 |
公式: 年宕机时间 = (1 - 可用性) × 365 × 24 × 60 分钟
面试官问"设计一个系统"时,先澄清 QPS 量级、可用性要求、延迟要求。不同量级的方案天差地别:日活 1 万和日活 1 亿的系统设计完全不同。
冗余策略
Master 故障时提升 Slave
两节点同时提供服务
无状态节点水平扩展
故障转移 (Failover)
| 类型 | 方式 | 切换速度 | 优缺点 |
|---|---|---|---|
| 自动故障转移 | 心跳检测 + 自动切换 | 秒级 | 快,但可能误判(脑裂风险) |
| 手动故障转移 | 运维人员确认后切换 | 分钟~小时级 | 安全,但停机时间长 |
当两个节点之间的网络断开,各自都认为对方已故障,都提升自己为 Master。解决方案:仲裁节点 (Quorum) — 至少 3 个节点,少数服从多数。类似 Kubernetes 的 etcd 集群要求奇数节点。
健康检查与熔断器 (Circuit Breaker)
熔断器的思想来源于电路中的保险丝:当下游服务异常率超过阈值时,自动"断开",避免级联故障。
统计失败率
等待超时窗口
成功则 Closed,失败则 Open
限流算法 (Rate Limiting)
请求需消耗令牌
允许突发流量(桶内有积累)
典型: Guava RateLimiter
以固定速率流出处理
平滑输出流量
典型: Nginx leaky bucket
| 算法 | 突发流量 | 平滑性 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| 固定窗口 | ❌ 窗口边界问题 | 差 | 低 | 简单限流 |
| 滑动窗口 | ⚠️ 有改善 | 中 | 中 | 精准限流 |
| 令牌桶 | ✅ 允许 | 中 | 中 | API 网关、一般业务 |
| 漏桶 | ❌ 不允许 | 高 | 低 | 流量整形、日志写入 |
优雅降级策略
- 读降级:读缓存/兜底数据,而非实时查询数据库
- 写降级:先写缓存/MQ,异步落盘(如点赞先存 Redis)
- 功能降级:关闭非核心功能(如评论、推荐),保核心链路
- 页面降级:返回静态页面或精简版 UI
场景:某电商平台日常 QPS ~5K,双11 零点峰值达 500K QPS。
方案:
- 限流:网关层令牌桶限流,超出直接返回"活动太火爆"
- 降级:关闭猜你喜欢、商品评价等非核心功能,释放资源
- 扩容:提前 2 小时自动扩容 Kubernetes Pod 数量 10 倍
- 预热:提前将热卖商品数据加载到缓存,避免冷启动
- 异步:下单走消息队列削峰,用户看到"排队中"
10.0.0.1:8080
10.0.0.2:8080
10.0.0.3:8080
负载均衡算法对比
| 算法 | 原理 | 优点 | 缺点 | 场景 |
|---|---|---|---|---|
| Round Robin | 依次轮询 | 简单公平 | 不考虑服务器差异 | 服务器配置一致 |
| Weighted RR | 按权重分配 | 适配不同配置 | 权重需手动调 | 异构服务器集群 |
| IP Hash | 对 IP 取模 | 会话保持 | 节点变更时大量重分配 | 需要 Session 粘滞 |
| Least Conn | 选连接数最少的 | 动态均衡 | 需实时统计连接数 | 长连接 / 请求耗时不均 |
L4 vs L7 负载均衡
| 维度 | L4 (传输层) | L7 (应用层) |
|---|---|---|
| 工作层 | TCP/UDP | HTTP/HTTPS/gRPC |
| 路由依据 | IP + Port | URL, Header, Cookie |
| 性能 | 高(只需看包头) | 稍低(需解析应用层) |
| 灵活性 | 低 | 高(可基于路径路由) |
| 代表 | LVS, AWS NLB | Nginx, HAProxy, AWS ALB |
Nginx 配置示例
# Nginx upstream 配置 - Weighted Round Robin
upstream backend {
server 10.0.0.1:8080 weight=5; # 高配服务器,分配更多流量
server 10.0.0.2:8080 weight=3;
server 10.0.0.3:8080 weight=2; # 低配服务器
# 健康检查(开源版需第三方模块)
# max_fails=3 fail_timeout=30s
# 保持长连接
keepalive 32;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# IP Hash 会话保持
# location /api/ {
# ip_hash;
# proxy_pass http://backend;
# }
}
问题:三台服务器分别 16C/8C/4C,但 Round Robin 分配了等量流量,导致低配机器 CPU 打满。
解决:切换为 Weighted Round Robin,权重比设为 4:2:1。同时在 Nginx 配置 max_fails 和 fail_timeout 实现自动摘除故障节点。
CAP 定理
就像找工作不可能同时满足"钱多、事少、离家近",分布式系统不可能同时满足 C(一致性)、A(可用性)、P(分区容错)。网络分区是必然的(P 必选),所以实际只能在 CP 和 AP 之间选择。
Consistency
Availability
Partition Tolerance
只能选一个
网络一定分区
ZooKeeper, etcd
HBase, MongoDB
Cassandra, DynamoDB
CouchDB, Eureka
BASE 理论
| 字母 | 含义 | 说明 |
|---|---|---|
| BA | Basically Available | 基本可用 — 允许响应时间增加或功能降级 |
| S | Soft State | 软状态 — 允许中间状态(数据副本可暂时不同) |
| E | Eventually Consistent | 最终一致性 — 经过一段时间后,数据最终一致 |
BASE 是对 CAP 中 AP 方向的延伸,大多数互联网系统采用 BASE + 局部强一致的混合策略。
一致性模型
Write x=5
x=5 ✅
x=5 ✅
Read x=5 ✅
写入后,任何副本的读取都能看到最新值。代价:延迟高。
Write x=5
x=5 ✅
⏳ 延迟
x=3 ❌
Read x=3 ⚠️
短暂时间内可能读到旧值,但最终会一致。性能好,体验差。
Write x=5
通知 B
Read x=5 ✅
有因果关系的操作按顺序可见,无因果关系的操作可乱序。折中方案。
问题:电商秒杀,10 件商品被 100 人抢购,不加控制可能卖 20 件。
方案对比:
| 方案 | 一致性 | 性能 | 适用 |
|---|---|---|---|
| 数据库悲观锁 (SELECT FOR UPDATE) | 强一致 | 低 | 低并发秒杀 |
| Redis 原子操作 (DECR) | 强一致 (Redis 单线程) | 高 | 高并发秒杀 |
| 分布式锁 (Redisson) | 强一致 | 中 | 中等并发 |
| 最终一致 (先扣后异步校验) | 最终一致 | 极高 | 可容忍超卖后退款 |
你从 A 银行转 1000 元到 B 银行。A 扣钱成功了,B 加钱却失败了 — 钱凭空消失了!分布式事务就是确保"要么都成功,要么都回滚"的协调机制。
2PC — 两阶段提交
锁定资源
锁定资源
执行提交 ✅
执行提交 ✅
TCC — Try-Confirm-Cancel
| 阶段 | 动作 | 示例(转账场景) |
|---|---|---|
| Try | 资源预留/冻结 | 冻结账户 A 的 1000 元余额 |
| Confirm | 确认执行 | A 扣除 1000 元,B 增加 1000 元 |
| Cancel | 取消/回滚 | 解冻 A 的 1000 元 |
优点:不锁资源(Try 只冻结),性能好。缺点:业务侵入性强,每个操作都要写三个方法。
Saga 模式
本地消息表 + MQ
原理:将分布式事务拆解为本地事务 + 消息通知。
- 服务 A 执行本地事务时,同时写入一张本地消息表(状态=待发送)
- 后台线程定时扫描消息表,将未发送的消息投递到 MQ
- 服务 B 消费 MQ 消息,执行本地事务,成功后 ACK
- 如果服务 B 失败,MQ 会重试(需保证幂等性)
优点:实现简单,性能好。缺点:消息表需定期清理,有短暂不一致窗口。
分布式事务方案对比
| 维度 | 2PC | TCC | Saga | 本地消息表 |
|---|---|---|---|---|
| 一致性 | 强一致 | 强一致 | 最终一致 | 最终一致 |
| 性能 | ❌ 低 | ⚠️ 中 | ✅ 高 | ✅ 高 |
| 业务侵入 | 低 | ❌ 高 (3个方法) | ⚠️ 中 (需补偿) | ⚠️ 中 |
| 实现复杂度 | 中 | 高 | 高 | 低 |
| 适用场景 | 传统数据库 | 资金/支付 | 长流程业务 | 异步通知 |
| 典型框架 | XA 协议 | Hmily, ByteTCC | Seata Saga | RocketMQ 事务消息 |
场景:用户下单,订单服务创建了订单(step 1 ✅),但调用库存服务扣减库存时超时失败(step 2 ❌)。
Saga 补偿方案:
- 触发补偿 → 执行
cancelOrder(orderId),将订单状态设为 CANCELLED - 通知用户"库存不足,订单已取消"
- 如果库存扣减实际成功但响应超时 → 幂等校验,不重复扣减
关键:每个步骤必须有幂等性(同一操作执行多次结果一致),否则补偿可能出问题。
缓存模式 (Cache Patterns)
命中? ✅ 直接返回
查询并写入缓存
读:先缓存后 DB。写:先 DB 再删缓存。应用层控制缓存逻辑。
自动加载
缓存层代理了读取逻辑,应用只需问缓存要数据。类似 Spring Cache 的 @Cacheable。
同步写 DB
写入时同步更新缓存和 DB,保证一致性但写入延迟高。
写入即返回
批量刷盘
写入缓存即返回,异步批量写入 DB。性能极高但可能丢数据(宕机未刷盘)。
缓存失效策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| TTL 过期 | 设置过期时间,到期自动删除 | 所有缓存(兜底策略) |
| 主动删除 | 更新 DB 后立即删除缓存 | Cache Aside 模式 |
| LRU | 最近最少使用,淘汰最久未访问的 | Redis maxmemory-policy |
| LFU | 最不经常使用,淘汰访问频率最低的 | 热点数据集中的场景 |
缓存三大问题
id=-1
❌ 没有
❌ 也没有
不缓存
解决方案:① 缓存空值 (TTL 5min) ② 布隆过滤器前置校验 ③ 接口参数校验
过期 ⏰
同时 miss
承受 10K QPS
解决方案:① 热点 Key 永不过期 + 异步刷新 ② 互斥锁 (只放一个请求回源) ③ 提前续期
同时过期 ⏰
全部 miss
压力暴增
解决方案:① TTL 加随机偏移 (避免同时过期) ② 多级缓存 (L1 + L2) ③ 熔断降级 ④ 缓存预热
热点 Key 处理
识别:Redis hotkeys 命令 (Redis 4.0+),或通过 MONITOR 命令统计(生产慎用)。
处理:
- 本地缓存:在应用层使用 Caffeine/Guava Cache 作为 L1,Redis 作为 L2
- 读写分离:热点 Key 复制到多个从节点分散读压力
- Key 打散:将 hot_key 拆成 hot_key_1, hot_key_2... hot_key_N,随机读取
场景:某社交平台在晚上 8 点高峰期,缓存集群因网络抖动重启,所有缓存同时失效。
经过:缓存失效后所有流量直接打到数据库,数据库 CPU 100%,连接池耗尽,整个服务不可用,持续 30 分钟。
事后改进:
- 缓存 TTL 加随机值 (30min ± 5min),避免集中过期
- 引入本地缓存 Caffeine 作为 L1,Redis 作为 L2
- 网关层限流 + 熔断,缓存故障时返回降级数据
- 缓存预热:服务启动时自动加载热数据
- Redis Sentinel 哨兵模式,主节点故障 30 秒自动切换
读写分离
写入
读取
读取
⚠️ 注意主从复制延迟 — 写完立刻读可能读到旧数据(解决方案:强制路由到 Master)
分库分表
id | name | email | avatar | bio | ...
id | name | email
id | avatar | bio
将不常用字段拆到扩展表
id | user_id | amount | ...
user_id % 3 = 0
user_id % 3 = 1
user_id % 3 = 2
将数据分散到多张表/库
分片键 (Sharding Key) 选择
| 分片键 | 优点 | 缺点 | 适用 |
|---|---|---|---|
| user_id | 用户维度查询快 | 数据可能不均匀(大V) | C 端应用 |
| order_id | 数据均匀分布 | 用户维度查询需跨片 | 订单系统 |
| create_time | 时间范围查询方便 | 热点问题(最近数据集中) | 日志/报表 |
| hash(id) | 数据最均匀 | 范围查询困难 | 通用场景 |
原则:分片键应满足 ① 数据分布均匀 ② 查询尽量命中单分片 ③ 避免跨片 JOIN
分布式 ID 生成 — 雪花算法 (Snowflake)
每台机器每毫秒可生成 4096 个不重复 ID,全局唯一,趋势递增
症状:订单表 5000 万行,按 user_id 查询耗时从 10ms 涨到 500ms,索引已优化。
方案:
- 短期:读写分离 + 热数据缓存(Redis),将读压力分走
- 中期:按
user_id % 16水平分 16 张表,每表 ~300 万行 - 长期:引入 TiDB / CockroachDB 等 NewSQL 数据库,自动分片
分片策略:用 ShardingSphere-JDBC 中间件,应用层无感知分片。订单 ID 使用 Snowflake 生成(包含机器信息和时间戳)。
OEE (Overall Equipment Effectiveness, 设备综合效率) 衡量工厂产线的健康程度。就像体检报告有血压、心率、血氧,OEE 由三个指标组成:可用率(设备有没有在运转)、性能率(运转得够不够快)、质量率(产出的产品合不合格)。实时 OEE 看板就是让这些指标像心跳监护仪一样实时显示。
Step 1: 需求澄清
| 维度 | 需求 |
|---|---|
| 功能 | 实时展示多产线 OEE 指标,支持历史趋势查询、告警通知 |
| 数据源 | PLC/SCADA 设备,每秒上报 10-50 个采集点(温度、速度、良品数等) |
| 用户量 | 内部系统,~200 并发用户(看板 + 移动端) |
| 实时性 | 数据延迟 < 5 秒 |
| 数据保留 | 秒级数据 7 天,分钟聚合 1 年 |
Step 2: 容量估算
# 假设: 5 条产线 × 20 设备 × 30 采集点 = 3000 个数据点/秒
# 写入 QPS: 3000 点/秒 × 1KB/点 ≈ 3 MB/s ≈ 260 GB/天
# 7 天原始数据: ~1.8 TB
# 200 并发用户, 每人每 5 秒刷新一次 → 读 QPS ≈ 40
# 结论:
# - 写入压力大 (3K QPS), 需要时序数据库
# - 读取压力小 (40 QPS), 普通缓存即可
# - 存储量中等, SSD 够用
Step 3: 系统架构
(MQTT)
(消息队列)
(流处理)
(时序数据)
(实时缓存)
(业务数据)
(React + ECharts)
Step 4: 技术选型与理由
| 组件 | 技术 | 为什么选它 |
|---|---|---|
| 消息接入 | EMQX | 工业设备原生支持 MQTT,EMQX 百万级连接 |
| 消息队列 | Kafka | 高吞吐写入 (3K QPS 轻松), 持久化, 回溯消费 |
| 流计算 | Flink | 窗口聚合 (5s/1min), 实时计算 OEE 指标 |
| 时序数据库 | TimescaleDB | 基于 PostgreSQL, SQL 友好, 自动压缩旧数据 |
| 实时推送 | WebSocket | 双向通信, 服务端主动推送更新 |
| 缓存 | Redis | 缓存最新 OEE 值, 支撑高频轮询 |
| 前端 | React + ECharts | ECharts 图表丰富, 支持实时数据更新 |
Step 5: 数据流详解
# 完整数据流:
PLC 设备
→ MQTT 上报 (每秒)
→ EMQX 接收
→ Kafka (topic: raw-telemetry)
→ Flink 消费
→ 计算 OEE = 可用率 × 性能率 × 质量率
→ 结果写入 TimescaleDB (持久化)
→ 结果写入 Redis (实时缓存)
→ 通过 WebSocket 推送到前端看板
→ 告警判断 → 企业微信通知
# OEE 计算公式:
# 可用率 = 实际运行时间 / 计划运行时间
# 性能率 = 实际产量 / 理论产量
# 质量率 = 良品数 / 总产量
# OEE = 可用率 × 性能率 × 质量率 (0~100%)
Step 6: 扩展性设计
- Kafka 分区:按产线 ID 分区,增加分区数即可线性扩容
- Flink 并行度:增加 TaskManager 数量,并行处理各分区
- TimescaleDB:hypertable 自动分片,支持分布式部署 (MST)
- API 服务:无状态设计,Kubernetes HPA 自动扩缩容
- WebSocket:引入 Redis Pub/Sub 做跨节点消息广播
① 需求澄清 — 问清楚 QPS、用户量、功能范围
② 容量估算 — 计算 QPS、存储、带宽
③ 高层设计 — 画出核心架构图,说明组件职责
④ 深入设计 — 聚焦瓶颈,讨论缓存、分片、高可用等
举例:电商系统日常 QPS ~5K,双11 峰值 ~500K;一个 TPS(下单)内部可能触发 5+ 个 QPS(查库存、查价格、扣积分...)。
实现手段:
① 冗余 — 多节点部署,消除单点故障
② 自动故障转移 — 健康检查 + 自动切换(Keepalived、K8s)
③ 灰度发布 — 新版本逐步放量,降低发布风险
④ 混沌工程 — 主动注入故障,验证系统韧性(Chaos Monkey)
三个状态:
① Closed (关闭) — 正常放行请求,统计失败率
② Open (开启) — 快速失败,直接返回错误,不调用下游
③ Half-Open (半开) — 超时后放行少量请求探测,成功则 Closed,失败则 Open
典型实现:Hystrix、Sentinel、Resilience4j
漏桶:请求进入桶中排队,以固定速率流出处理。输出平滑,不允许突发。适合流量整形。
关键区别:令牌桶允许短时间内处理大量请求(突发),漏桶严格控制处理速率。
Nginx 默认使用 Round Robin(轮询)。如果服务器配置不同,用 Weighted RR。
需要会话保持时用 IP Hash。长连接场景用 Least Connections。
L7 (应用层):基于 HTTP URL/Header/Cookie 做路由,灵活但性能稍低。代表:Nginx、HAProxy、AWS ALB。
生产环境通常 L4 + L7 组合使用:LVS 做最前端四层负载,Nginx 做七层路由。
CA 不存在的原因:在分布式系统中,网络分区(P)是必然发生的(网络延迟、交换机故障等)。所以 P 必选,只能在 C 和 A 之间权衡。
CP 系统(保证一致性):ZooKeeper、etcd — 分区时拒绝部分请求
AP 系统(保证可用性):Cassandra、Eureka — 分区时可能返回旧数据
关系:BASE 是 CAP 中 AP 方向的工程实践。大多数互联网系统采用 BASE 理论 — 牺牲强一致性,换取高可用和高性能,通过异步机制保证最终一致性。
例如:点赞数不需要实时精确,用户看到的是缓存的近似值,最终会同步到数据库。
最终一致性:短暂时间内可能读到旧值,但最终(通常毫秒到秒级)所有副本一致。
因果一致性:有因果关系的操作保证顺序(如先发帖后评论),无因果关系的操作可乱序。是强一致和最终一致的折中。
问题:
① 阻塞 — Prepare 后资源被锁定,直到 Commit/Abort,期间其他事务等待
② 单点故障 — 协调者宕机则所有参与者永远阻塞
③ 性能差 — 两次 RPC 往返 + 同步阻塞,TPS 很低
实际中很少直接用 2PC/XA,更多用 TCC、Saga 或消息最终一致。
→ 适合资金、支付等对一致性要求高的短事务。
Saga:将长事务拆成多个本地事务,每个本地事务有对应的补偿操作。失败时反向执行补偿。
→ 适合长流程业务(如旅行预订:机票→酒店→租车),每个步骤涉及不同服务。
关键区别:TCC 在 Try 阶段就预留资源(隔离性好),Saga 不预留(可能脏读)。
① 唯一 ID + 去重表:每笔操作携带唯一 ID,执行前查去重表是否已处理
② 乐观锁 (版本号):UPDATE ... SET val=new, version=version+1 WHERE version=old
③ 状态机:只有特定状态才能转换(PENDING → PAID,重复支付时状态已是 PAID 则忽略)
④ 数据库唯一约束:利用 UNIQUE KEY 防止重复插入
② 后台定时任务扫描消息表,将未发送的消息投递到 MQ
③ 服务 B 消费 MQ 消息,执行本地事务
④ 如果服务 B 失败,MQ 重试(需保证幂等性)
优点:实现简单,不依赖 XA 协议。缺点:消息表需定期清理,有短暂不一致窗口。
① 线程 A 删除缓存
② 线程 B 读缓存 miss,从 DB 读到旧值
③ 线程 B 将旧值写入缓存
④ 线程 A 更新 DB
→ 结果:缓存中是旧值,永久不一致!
先更新 DB 再删缓存:即使删缓存失败,可以靠 TTL 兜底。更严格的方案是用延迟双删:更新 DB → 删缓存 → 延迟 500ms → 再删一次。
解决:① 缓存空值 (TTL 短) ② 布隆过滤器 ③ 参数校验
击穿:热点 Key 过期瞬间 → 大量请求同时打到 DB。
解决:① 热点 Key 永不过期 + 异步刷新 ② 互斥锁(只放一个请求回源)
雪崩:大量 Key 同时过期 + 或缓存集群宕机 → DB 瞬时承受巨大压力。
解决:① TTL 加随机偏移 ② 多级缓存 ③ 熔断降级 ④ 缓存预热
① Cache Aside + 延迟双删:更新 DB → 删缓存 → 延迟再删。简单但非严格一致。
② 订阅 Binlog:通过 Canal 监听 MySQL Binlog,异步删除/更新缓存。更可靠。
③ Read Through + Write Through:缓存层代理读写逻辑,对应用透明。
经验:大多数场景用方案①就够了。一致性要求极高时用方案②(Canal + MQ + 消费者删缓存)。
redis-cli --hotkeys,或通过 MONITOR 统计(生产慎用)。处理:
① 本地缓存:应用层使用 Caffeine/Guava Cache 作为 L1 缓存
② 读写分离:将热点 Key 复制到多个从节点分散读压力
③ Key 打散:hot_key → hot_key_1, hot_key_2...hot_key_N,随机读取
解决方案:
① 强制路由:写完后的短时间内,读操作强制路由到 Master
② 中间件感知:ShardingSphere 等 ORM 中间件可自动感知 GTID 位置
③ 业务容忍:在 UI 上显示"数据更新中,可能有延迟"
④ 半同步复制:至少一个 Slave 确认收到 Binlog 才返回成功
① 垂直分表:按列拆分(将不常用字段移到扩展表)
② 水平分表:按行拆分(按分片键取模/范围分散数据)
分片键选择原则:
① 数据分布均匀(避免热点)
② 查询尽量命中单分片(避免跨片查询)
③ 避免跨片 JOIN 和排序
常用分片键:user_id(C 端应用)、order_id(订单系统)、hash(id)(通用)
特点:
① 全局唯一 — 机器 ID 保证不同节点不重复
② 趋势递增 — 时间戳在高位,ID 按时间递增(有利于 B+ 树索引)
③ 高性能 — 每台机器每毫秒可生成 4096 个 ID
缺点:依赖时钟 — 时钟回拨会导致 ID 重复(解决方案:回拨检测 + 等待)
方案:
① 前端:按钮置灰 + 验证码防刷 + 请求随机丢弃(只放 10% 到后端)
② 网关层:令牌桶限流 + 用户级频率限制
③ 服务层:Redis
DECR 原子扣库存(单线程保证不超卖)④ 异步下单:扣完库存后发 MQ,消费者异步创建订单
⑤ 兜底:数据库层用
UPDATE stock SET count=count-1 WHERE count>0 做最终防线
短码生成:
① 哈希 (MD5/MurmurHash) + Base62 编码,取前 6-7 位
② 自增 ID + Base62 编码 (1 → "1", 10000 → "2Bi")
③ 预生成短码池(提前生成一批存 Redis,用完再生成)
存储:MySQL 存映射关系,Redis 缓存热点短链
读取流程:短链请求 → 301/302 重定向到长链
扩展:分库分表(按短码 hash 分片)+ 布隆过滤器防重复
架构:
① Producer → 写入 Broker 集群 → Consumer 消费
② Broker 集群:分区 (Partition) + 多副本 (Replica) + Leader/Follower
③ 不丢消息:Producer 等待 ISR 副本确认 (acks=all),Consumer 手动提交 offset
④ 不重复:消费者端幂等(去重表/唯一 ID)
⑤ 高可用:分区多副本 + 自动 Leader 选举(如 Kafka 的 Controller)
⑥ 顺序消费:同一 Key 的消息路由到同一分区
① 前端/网关:CDN 是否命中?静态资源是否压缩?
② 应用层:接口响应时间分布(P50/P95/P99),是否慢 SQL?GC 是否频繁?
③ 数据库层:慢查询日志、EXPLAIN 分析、索引是否生效?连接池是否够?
④ 基础设施:CPU、内存、磁盘 IO、网络带宽是否打满?
常见优化手段:
• 慢 SQL → 加索引 / 优化查询 / 读写分离
• 热点查询 → 加缓存 (Redis) / 本地缓存 (Caffeine)
• 数据量大 → 分库分表 / 归档历史数据
• CPU 高 → 异步化 (MQ) / 水平扩容
• 连接池满 → 增大连接池 / 引入连接复用
系统设计与架构 | Backend Interview Prep #05
全栈面试备战库 — 持续更新中