2、缓存分层模型:客户端缓存、应用层缓存、分布式缓存、数据库缓存
说到缓存,很多人的第一反应就是 Redis。但说实话,一个成熟的券商交易系统,缓存绝不是单点作战。我这些年拆解过的生产事故里,有一半以上都是因为「缓存分层没做对」。
今天我们就来聊聊缓存的分层模型。我个人习惯把它分成四层:客户端缓存、应用层缓存、分布式缓存、数据库缓存。每一层都有自己的使命,也有自己的坑。
核心观点:缓存不是越底层越好,也不是越上层越好。关键是要让每一层各司其职,形成合力。
2.1 客户端缓存:离用户最近的一层
客户端缓存,说白了就是数据存在用户的浏览器或 App 里。你想想看,如果每次刷新页面都要去服务器拉数据,那服务器早被压垮了。
我在项目中遇到过这样一个场景:行情页面上的 K 线图,用户每切换一次时间周期就发一次请求。后来我们在前端加了本地缓存,把最近 10 次查询的结果存下来。效果立竿见影,后端压力降了 40%。
客户端缓存常用的手段有:
- HTTP 缓存头:Cache-Control、ETag、Last-Modified
- 浏览器 LocalStorage / SessionStorage:适合存储用户偏好、临时数据
- Service Worker:适合离线场景,比如交易 APP 的首页快照
避坑指南:我曾经在某个项目中,把用户的交易密码 hash 值存在了 LocalStorage 里。后来安全审计被直接打回。记住:客户端缓存永远不要存敏感信息!
2.2 应用层缓存:进程内的「快车道」
应用层缓存,就是跑在应用进程里的缓存。比如 Java 里的 Caffeine、Guava Cache,或者 Go 里的 sync.Map。这一层的特点是:快,非常快。因为数据就在本地内存里,没有网络开销。
但快是有代价的——容量有限,而且进程重启就丢了。所以它适合存什么?
- 热点数据:比如某只股票的实时行情,一秒内被查询上万次
- 配置信息:交易规则、费率表,这些几乎不变
- 临时计算结果:比如风控校验的中间结果
我记得有一次线上事故,就是因为应用层缓存没设过期时间。一个用户的持仓信息被缓存了 30 分钟,结果他平仓了,系统还显示他有持仓。嗯,这里要注意:应用层缓存一定要设置合理的 TTL。
// 以 Caffeine 为例,设置最大容量和过期策略
Cache<String, StockPrice> stockCache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.SECONDS)
.recordStats()
.build();
2.3 分布式缓存:系统的「共享内存」
当应用层缓存不够用了,或者多个服务需要共享数据时,就该分布式缓存登场了。Redis、Memcached 是这里的常客。
分布式缓存解决了什么问题?
- 数据共享:多个服务实例可以访问同一份缓存数据
- 容量扩展:通过集群模式,可以存下几十 GB 甚至 TB 级的数据
- 高可用:主从切换、哨兵模式,保证缓存不丢
但分布式缓存也有自己的坑。我曾经在项目中遇到过「缓存雪崩」——大量缓存同时过期,请求直接打到数据库,数据库瞬间被打爆。后来我们做了两件事:
- 给过期时间加一个随机偏移量,避免集体过期
- 对热点数据做「永不过期 + 后台异步刷新」
警告:分布式缓存不是万能的。网络延迟、序列化开销、连接池耗尽,这些都是真实存在的坑。我见过一个团队把 Redis 当数据库用,存了上亿条记录,结果每次查询都要几十毫秒——那还不如直接用 MySQL。
2.4 数据库缓存:最后的防线
数据库缓存,指的是数据库自带的缓存机制。比如 MySQL 的 Buffer Pool、InnoDB 的 Change Buffer,还有查询缓存(虽然 MySQL 8.0 已经废弃了)。
这一层往往被很多人忽略。你想想看,如果应用层和分布式缓存都 miss 了,最后扛住压力的就是数据库缓存。Buffer Pool 的大小设置是否合理,直接决定了数据库的读写性能。
我个人的经验是:
- Buffer Pool 大小:建议设置为物理内存的 60%-80%
- 预热策略:系统启动后,手动加载热点表到 Buffer Pool
- 监控指标:关注 Buffer Pool 命中率,低于 95% 就要警惕了
-- 查看 MySQL Buffer Pool 命中率
SHOW STATUS LIKE 'Innodb_buffer_pool_read_requests';
SHOW STATUS LIKE 'Innodb_buffer_pool_reads';
-- 命中率 = (read_requests - reads) / read_requests * 100%
2.5 四层模型如何协同工作?
说了这么多,四层缓存到底怎么配合?我画了一张图,你看完就明白了。
从这张图可以看得很清楚:越往上,速度越快但容量越小;越往下,容量越大但速度越慢。一个合理的缓存策略,应该是让请求尽量在上层命中,实在不行再往下走。
我举个例子:用户查询某只股票的实时行情。
- 先看客户端缓存有没有(比如 5 秒内的数据)
- 没有的话,去应用层缓存找(进程内,微秒级)
- 还没有,去 Redis 查(毫秒级)
- Redis 也没有,最后去数据库 Buffer Pool 碰运气
- 如果 Buffer Pool 也 miss,那就只能走磁盘 IO 了
我的经验:在券商系统里,90% 以上的查询应该在前两层命中。如果分布式缓存的命中率低于 70%,说明你的缓存策略有问题——要么过期时间太短,要么缓存的数据不对。
2.6 分层缓存的核心原则
最后,我总结几条原则,你在设计缓存分层时可以拿来参考:
| 原则 | 说明 | 我的踩坑记录 |
|---|---|---|
| 就近原则 | 数据尽量放在离用户最近的地方 | 曾经把所有缓存都放 Redis,忽略了客户端缓存,导致带宽浪费 |
| 分层隔离 | 每一层独立管理,不要跨层依赖 | 应用层缓存依赖 Redis 做数据源,Redis 挂了应用层也崩了 |
| 逐级降级 | 上层 miss 时,下层要能兜底 | 没做降级策略,缓存雪崩时数据库直接被打死 |
| 监控全覆盖 | 每层都要有命中率、延迟、容量监控 | 漏了应用层缓存的监控,内存泄漏了三天才发现 |
嗯,缓存分层这个话题,说起来简单,做起来全是细节。我见过太多团队一上来就上 Redis,结果应用层缓存和客户端缓存完全没用上。说白了,缓存不是越底层越高级,而是越合适越好。
下一层我们聊聊缓存穿透、缓存击穿、缓存雪崩——这三个「缓存杀手」在券商系统里有多可怕,以及怎么防。到时候我会分享几个我亲手处理过的线上事故,保证让你印象深刻。