Redis 缓存学习笔记:商品详情缓存和常见问题

写在前面

Redis 是 Java 后端学习中绕不开的一块。刚开始我对 Redis 的理解很简单:查数据库慢,就把结果放进缓存。

后来做项目和看业务代码时,才发现缓存不是“加上 Redis 就完事”。缓存 key 怎么设计、过期时间怎么定、数据不一致怎么办、缓存穿透和击穿怎么处理,这些都需要结合具体业务判断。

这篇文章是我对商品详情缓存场景的学习整理,不包装成生产经验,只记录我目前能理解和讲清楚的部分。

为什么商品详情适合缓存

商品详情、文章详情、用户基础信息这类接口有一个共同特点:

  • 读多写少
  • 数据结构相对固定
  • 页面访问频率比较高
  • 可以接受短时间缓存

所以这类数据比较适合先查 Redis,缓存没有命中再查 MySQL。

大致流程是:

1
2
3
4
5
6
请求商品详情
-> 查 Redis
-> 命中:直接返回
-> 未命中:查 MySQL
-> 写入 Redis
-> 返回结果

基础代码思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public ProductDetail getProductDetail(Long productId) {
String key = "product:detail:" + productId;

String cache = redisTemplate.opsForValue().get(key);
if (cache != null) {
return JSON.parseObject(cache, ProductDetail.class);
}

ProductDetail detail = productMapper.selectDetailById(productId);
if (detail == null) {
return null;
}

redisTemplate.opsForValue().set(
key,
JSON.toJSONString(detail),
30,
TimeUnit.MINUTES
);

return detail;
}

这段代码能跑,但还不够完善。真正需要考虑的是缓存异常情况。

缓存穿透

缓存穿透指的是查询一个数据库里也不存在的数据。比如有人一直请求不存在的商品 ID,每次缓存查不到,又继续打到数据库。

常见处理方式:

  • 参数校验,明显非法的 ID 直接拦截
  • 缓存空值,但过期时间设置短一点
  • 使用布隆过滤器提前判断 ID 是否可能存在

我在项目练习中更常用的是缓存空值:

1
2
3
4
if (detail == null) {
redisTemplate.opsForValue().set(key, "null", 5, TimeUnit.MINUTES);
return null;
}

这样可以减少重复请求不存在数据时对数据库的压力。

缓存击穿

缓存击穿通常发生在热点 key 失效的一瞬间,很多请求同时查不到缓存,然后一起访问数据库。

可以考虑:

  • 热点数据过期时间设置得更长
  • 提前预热热点数据
  • 查询数据库时加互斥锁
  • 使用逻辑过期方案

我目前更能理解的是互斥锁方案:缓存没命中时,先尝试拿锁,拿到锁的请求去查数据库并回写缓存,其他请求短暂等待或稍后重试。

1
2
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent("lock:product:" + productId, "1", 10, TimeUnit.SECONDS);

这里也要注意:锁一定要有过期时间,避免异常情况下锁无法释放。

缓存雪崩

缓存雪崩是大量 key 在同一时间失效,导致请求集中打到数据库。

比较简单的做法是给过期时间加随机值:

1
2
int ttl = 30 + ThreadLocalRandom.current().nextInt(10);
redisTemplate.opsForValue().set(key, value, ttl, TimeUnit.MINUTES);

这样不同 key 的过期时间会错开一些。

缓存一致性

缓存一致性是我觉得比较难的一块。更新数据库后,缓存应该怎么处理?

常见做法是:

  • 先更新数据库,再删除缓存
  • 不直接更新缓存,避免写入旧值
  • 对一致性要求高的场景,不要完全依赖缓存

例如商品信息更新后:

1
2
productMapper.updateById(product);
redisTemplate.delete("product:detail:" + product.getId());

下一次查询时重新从数据库读取并写入缓存。

这个方案不是绝对完美,但对于很多读多写少的业务已经够用。真正强一致的场景,需要结合事务、消息队列或延迟双删等方案继续设计。

我现在对 Redis 的理解

Redis 的价值不是单纯“让接口快”,而是用内存缓存减少数据库重复查询。但用了 Redis 之后,也会引入新的复杂度:

  • 数据是否一致
  • key 是否合理
  • 过期时间是否合适
  • 异常场景是否兜底
  • 缓存失效时数据库能不能扛住

所以我现在写简历或博客时,不会夸大自己做了多复杂的缓存架构,而是更愿意把具体问题讲清楚。

小结

这篇文章主要整理了 Redis 在商品详情缓存场景下的几个基础问题:穿透、击穿、雪崩和一致性。

对我来说,Redis 不是一个“加分词”,而是一个需要结合业务认真设计的工具。能讲清楚为什么缓存、缓存什么、什么时候失效、失败后怎么兜底,比写“高并发架构”更真实。


Redis 缓存学习笔记:商品详情缓存和常见问题
https://zxyblog.top/2024/09/10/Redis缓存学习笔记-商品详情缓存和常见问题/
作者
zxy
发布于
2024年9月10日
许可协议