多级缓存可以理解成:不是只用 Redis 一层缓存,而是在请求链路上放多层缓存,越靠近应用,访问越快。
在你这个项目里,可以这样理解:
用户请求
↓
一级缓存:Caffeine 本地缓存
↓
二级缓存:Redis 分布式缓存
↓
数据库:MySQL
为什么要做多级缓存?
因为 Redis 虽然快,但它仍然是一次网络访问;Caffeine 是 JVM 本地内存缓存,速度更快。
所以热点数据,比如:
| 数据 | 适合原因 |
|---|---|
| 店铺详情 | 读多写少 |
| 优惠券列表 | 查询频繁 |
| 热门商户信息 | 高并发访问 |
可以先放到本地缓存里,减少 Redis 压力,也减少数据库压力。
读取流程
以查询店铺详情为例:
1. 先查 Caffeine
命中:直接返回
2. Caffeine 没有,再查 Redis
命中:返回数据,并回填 Caffeine
3. Redis 也没有,再查 MySQL
查到:写入 Redis,再写入 Caffeine,然后返回
4. MySQL 也没有
返回不存在,可以缓存空值防止穿透
一句话:
本地缓存挡住最热请求,Redis 承接跨实例共享缓存,数据库作为最终数据源。
多级缓存的优点
| 优点 | 解释 |
|---|---|
| 性能更高 | Caffeine 是本地内存访问,比 Redis 网络访问更快 |
| Redis 压力更小 | 热点请求大量命中本地缓存 |
| 抗压能力更强 | Redis 短暂抖动时,本地缓存还能撑住一部分流量 |
| 适合热点数据 | 店铺详情、优惠券列表这类读多写少数据很合适 |
它最大的难点:缓存一致性
多级缓存不是简单“多存几份数据”。
因为数据一旦修改,就要考虑:
MySQL 改了
Redis 旧数据怎么办?
每个 JVM 里的 Caffeine 旧数据怎么办?
如果只删 Redis,不删本地缓存,那么其他服务实例的 Caffeine 里还可能返回旧数据。
所以项目里通常要这样处理:
更新数据库
↓
删除 Redis 缓存
↓
删除当前 JVM 的 Caffeine 缓存
↓
通过 MQ 通知其他服务实例删除本地缓存
也就是:
Redis 可以直接删,但本地缓存分散在各个服务实例里,所以需要 RocketMQ 这类消息机制做广播失效。
和普通 Redis 缓存的区别
| 对比点 | Redis 单级缓存 | 多级缓存 |
|---|---|---|
| 访问速度 | 快,但有网络开销 | 本地缓存命中更快 |
| 架构复杂度 | 简单 | 更复杂 |
| 一致性处理 | 主要删 Redis | 还要删各实例本地缓存 |
| 适合场景 | 普通缓存 | 高并发热点读场景 |
面试回答版
多级缓存就是在数据库前面增加多层缓存,比如项目里可以用 Caffeine 作为一级本地缓存,Redis 作为二级分布式缓存。查询店铺详情时,先查 Caffeine,命中就直接返回;没命中再查 Redis,Redis 命中后回填本地缓存;如果 Redis 也没有,再查 MySQL,并把结果写回 Redis 和 Caffeine。这样可以减少 Redis 和数据库压力,提高热点数据的访问性能。
但多级缓存的难点是一致性。因为 Caffeine 是每个 JVM 实例独有的,更新数据时不能只删 Redis,还要删除本机本地缓存,并通过 MQ 通知其他实例删除本地缓存。这样才能避免某些实例继续返回旧数据。
常见追问
-
为什么不用 Redis 就够了?
Redis 也有网络开销,高并发热点请求下 Redis 本身也可能成为瓶颈。本地缓存可以把最热的数据直接挡在应用内存里。 -
本地缓存有什么缺点?
每个服务实例都有一份,数据容易不一致;而且占用 JVM 内存,容量不能无限大。 -
多级缓存适合什么数据?
适合读多写少、热点明显、允许短暂最终一致的数据,比如店铺详情、优惠券列表。 -
数据更新时怎么保证一致性?
一般是先更新数据库,再删除缓存。多级缓存下还要删除本地缓存,并通过 MQ 广播通知其他实例清理本地缓存。