多级缓存

3 阅读4分钟

多级缓存可以理解成:不是只用 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 通知其他实例删除本地缓存。这样才能避免某些实例继续返回旧数据。

常见追问

  1. 为什么不用 Redis 就够了?
    Redis 也有网络开销,高并发热点请求下 Redis 本身也可能成为瓶颈。本地缓存可以把最热的数据直接挡在应用内存里。

  2. 本地缓存有什么缺点?
    每个服务实例都有一份,数据容易不一致;而且占用 JVM 内存,容量不能无限大。

  3. 多级缓存适合什么数据?
    适合读多写少、热点明显、允许短暂最终一致的数据,比如店铺详情、优惠券列表。

  4. 数据更新时怎么保证一致性?
    一般是先更新数据库,再删除缓存。多级缓存下还要删除本地缓存,并通过 MQ 广播通知其他实例清理本地缓存。