架构(08):高并发架构——缓存分层、削峰填谷、异步化、无状态化
更新时间:2026-09-02。本文是
architecture/架构领域第 08 篇,接 可观测性设计。高并发不是靠加机器就能解决的,加机器只是其中一步。核心是缓存减少计算、削峰填谷平滑流量、异步化解耦、无状态化水平扩展。理解这四招,大部分高并发场景都能应对。
本文要回答的问题
- 缓存分层怎么设计?CDN → 本地缓存 → 分布式缓存 → 数据库?
- 缓存穿透、击穿、雪崩分别是什么?怎么解决?
- 削峰填谷怎么做?消息队列 + 限流 + 令牌桶?
- 无状态化为什么能水平扩展?Session 怎么处理?
- 读写分离怎么实现?什么时候用?
一、缓存分层策略
缓存分层:
┌──────────────────────────────────────────────────────────┐
│ CDN(静态资源缓存) │
│ CSS、JS、图片、视频 → 离用户最近 │
│ 命中率:高,静态资源很少变 │
├──────────────────────────────────────────────────────────┤
│ 本地缓存(进程内缓存) │
│ LRU/LFU Map、Guava Cache、Caffeine │
│ Redis 的本地缓存不够快时用 │
│ 命中率:中,热点数据 │
├──────────────────────────────────────────────────────────┤
│ 分布式缓存(Redis/Memcached) │
│ 共享缓存,多个服务实例共同访问 │
│ 命中率:中高,大多数数据 │
├──────────────────────────────────────────────────────────┤
│ 数据库(MySQL/PostgreSQL) │
│ 最终数据源,持久化存储 │
└──────────────────────────────────────────────────────────┘缓存查询顺序: CDN → 本地缓存 → Redis → 数据库
缓存更新策略:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| Cache Aside | 读:先查缓存,没有反查数据库再写缓存;写:先更新数据库,再删除缓存 | 最常用,推荐 |
| Read Through | 缓存层自动反查数据库,对应用透明 | 框架支持 |
| Write Through | 写缓存,缓存同步写数据库 | 一致性要求高 |
| Write Behind | 先写缓存,批量异步写数据库 | 性能要求高,可接受延迟 |
Cache Aside 模式示例:
读:读 Redis → 没有 → 读 MySQL → 写 Redis → 返回
写:写 MySQL → 删 Redis(下次读重建)二、缓存三座大山
1. 缓存穿透
问题:查询一个不存在的数据,缓存没有,每次都查数据库
攻击者可以构造不存在的 key 打垮数据库
解决:
1. 缓存空值(null):不存在的数据也缓存,但 TTL 短(1-5 分钟)
2. 布隆过滤器(Bloom Filter):判断 key 是否存在,不存在直接返回
3. 参数校验:非法参数直接拒绝2. 缓存击穿
问题:热点 key 过期,大量请求同时打到数据库
解决:
1. 互斥锁:第一个请求加锁,查数据库并重建缓存,其他等待
2. 热点数据不过期:热点 key 不设 TTL,后台异步更新
3. 提前更新:缓存快过期时,主动更新3. 缓存雪崩
问题:大量 key 同时过期,大量请求打到数据库
解决:
1. 过期时间随机化:TTL 加随机值,避免同时过期
2. 多级缓存:本地缓存 + Redis,Redis 挂了还有本地缓存
3. 限流降级:数据库扛不住时,限流三、削峰填谷
削峰填谷:把突发流量缓冲到消息队列,下游慢慢消费
┌──────────┐ ┌──────────────┐ ┌──────────┐
│ 上游请求 │ →→→ │ 消息队列 │ →→→ │ 下游消费者 │
│ 突发 1万 │ │ 缓冲 1万 │ │ 每秒处理 500│
│ QPS │ │ 慢慢消费 │ │ 不被打垮 │
└──────────┘ └──────────────┘ └──────────┘
优点:保护下游,不被打垮
缺点:延迟增加,需要补偿限流算法
| 算法 | 原理 | 特点 | 适用场景 |
|---|---|---|---|
| 计数器 | 固定窗口,计数,到了拒绝 | 突刺,最后一个窗口边界 | 简单限流 |
| 滑动窗口 | 滑动窗口,精度高 | 平滑,比计数器好 | 精确限流 |
| 漏桶 | 固定速率流出,满了拒绝 | 绝对的平滑,不能处理突发 | 保护下游 |
| 令牌桶 | 生成令牌,请求消耗令牌 | 允许突发,平滑 | 常见,推荐 |
令牌桶(推荐):
每秒生成 N 个令牌 → 桶里最多 N 个
请求消耗 1 个令牌 → 有令牌就通过,没有就拒绝
优点:允许突发流量(桶里积攒的令牌),也平滑了流量四、异步化
同步:请求 → 处理 → 返回,用户等待
异步:请求 → 发消息 → 立即返回
后台消费者处理 → 完成通知
优点:
1. 解耦,不阻塞
2. 削峰填谷
3. 用户体验好(先返回,后台处理)
缺点:
1. 延迟增加
2. 复杂度高
3. 需要补偿
适用场景:
非实时操作(发送邮件、短信、通知)
长耗时操作(图片处理、数据导出)
跨服务协调(订单创建后,扣库存、支付、物流)五、无状态化
无状态化:服务实例不保存会话状态,状态交给外部存储
有状态:
Session 存在本地内存
用户只能请求同一个实例(粘性 Session)
服务挂了,Session 丢了
无状态化:
Session 存在 Redis
用户请求任意实例都行
服务挂了,换个实例继续
可以水平扩展
无状态化是水平扩展的前提:
没有无状态化,加机器也没用
因为 Session 分散在不同实例,用户请求错了实例,Session 不对实现:
1. Session 外置:Redis/Memcached 存储 Session
2. JWT Token:用户信息在 Token 中,不需要服务端存储
3. 分布式 Session:Spring Session 等框架支持六、读写分离
读写分离:主库写,从库读
┌──────────────┐
│ 主库 │ 写 INSERT、UPDATE、DELETE
└──────┬───────┘
│ 异步复制
↓
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 从库 1 │ │ 从库 2 │ │ 从库 3 │
│ 读 │ │ 读 │ │ 读 │
└──────────────┘ └──────────────┘ └──────────────┘
优点:
1. 读扩展:读多写少场景,多加从库
2. 写保护:主库不受读影响
3. 故障隔离:从库挂了不影响写
缺点:
1. 延迟:主从复制有延迟,读可能读到旧数据
2. 复杂度:应用层/中间件层处理读写分离
适用场景:读多写少,容忍短暂不一致七、整体架构图
┌────────────────────────────────────────────────────────────────┐
│ CDN(静态资源) │
│ CSS/JS/图片 │
├────────────────────────────────────────────────────────────────┤
│ Nginx(反向代理 + 负载均衡 + 限流) │
├────────────────────────────────────────────────────────────────┤
│ 应用服务(无状态化,水平扩展) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 本地缓存 → 分布式缓存 → 数据库 │ │
│ │ (热点) (共享) (持久化) │ │
│ └──────────────────────────────────────────────────────────┘ │
├────────────────────────────────────────────────────────────────┤
│ 消息队列(异步化,削峰填谷) │
├────────────────────────────────────────────────────────────────┤
│ 后台消费者(异步处理) │
└────────────────────────────────────────────────────────────────┘八、常见坑对照
| 错误 | 后果 | 对策 |
|---|---|---|
| 缓存穿透 | 数据库被打垮 | 缓存空值 + 布隆过滤器 |
| 缓存雪崩 | 大量 key 同时过期,数据库打垮 | TTL 随机化 |
| 热点 key 不过期 | 缓存一直占着,数据不一致 | 带 TTL,后台异步更新 |
| 粘性 Session | 不能水平扩展 | 无状态化,Session 存 Redis |
| 读多写少不用读写分离 | 数据库读压力大 | 读写分离,加从库 |
| 同步阻塞操作 | 用户体验差,线程阻塞 | 异步化,消息队列 |
相关与延伸
架构领域 8 篇完成。下一个领域:AI tools 系列。分层架构,见 分层架构——整洁架构、六边形架构。
一句话总结
高并发架构:缓存分层 CDN → 本地缓存 → 分布式缓存(Redis)→ 数据库,缓存穿透布隆过滤器+空值缓存,缓存雪崩 TTL 随机化+多级缓存,缓存击穿互斥锁+热点不过期;削峰填谷:消息队列缓冲突发流量,下游慢慢消费,令牌桶限流允许突发;异步化:非实时操作发消息立即返回,后台处理,解耦不阻塞;无状态化:Session 外置 Redis 或 JWT Token,是水平扩展的前提;读写分离:主库写从库读,读多写少场景,容忍短暂不一致。