点评的缓存问题

本文最后更新于 2025年4月18日 晚上

目前处于烂尾状态。。。我很抱歉。。

缓存更新

  • 等redis内存满了自己淘汰
  • 超时
  • 主动更新
主动更新
  • 更新数据库时更新缓存
  • 缓存与数据库整合
  • 调用者只操作缓存,由其他线程异步将缓存持久化到数据库中,保证最终一致
先删缓存还是先操作数据库?

先操作数据库,再更新缓存。

缓存操作比数据库操作快;数据库读比写快:所以先删缓存,在数据库update完成前,可能会导致期间的查询不一致。

缓存穿透

客户端的请求在缓存和数据库中都不存在缓存永远不会生效,请求都会打到数据库上,次次查询数据库然后返回不存在。

解决方法

  • 布隆过滤器
    • 不存在就一定不存在,存在可能会误判
    • 快速
  • 缓存空对象
    • 实现简单,但是内存消耗且可能不一致
    • 需要给空对象设置TTL(Time To Live)

缓存雪崩

同一时段,大量缓存的有效期到期失效,或者redis宕机,导致大量请求短期内集中打到数据库。

解决方法

  • TTL随机:N+n模式,n随机
  • 集群
  • 多级缓存

缓存击穿

热点key问题,一个被高并发访问并且缓存重建复杂的key突然失效,在瞬间给数据库带来巨大压力。

解决方法

  • 互斥锁
  • 逻辑过期

互斥锁业务流程

逻辑过期业务流程

逻辑过期需要缓存预热

注意到逻辑过期如果一开始缓存未命中,就会直接空。

因为是防止打到数据库,所以也不会因为缓存未命中而去查数据库。

所以需要缓存预热,一开始的缓存中需要存在所需的数据,否则就会一直缓存击穿未遂而返回空对象。

缓存预热

黑马的视频里没有,自己试着加了一个,与逻辑过期一起使用。

由于缓存预热的数据具有特殊性,所以不写在CacheClient这样的通用工具类中,而是写在ShopServiceImpl类里。

写好缓存预热方法如下,逻辑很简单,设置一个缓存标记,如果没有标记,证明缓存不存在,则预热:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 缓存预热
*/
public void warmupCache() {
// 查询缓存内是否有内容
String key = CACHE_SHOP_KEY;
String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_FLAG);
// 没有内容则预热缓存
if(StrUtil.isBlank(shopJson)) {
// 先放flag
stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_FLAG, RedisConstants.CACHE_SHOP_EXIST);
List<Shop> shops = list();
for(Shop shop : shops) {
cacheClient.setWithLogicalExpire(CACHE_SHOP_KEY + shop.getId(), shop, 30L, TimeUnit.MINUTES);
}
}
}

然后新建一个CacheWarmupRunner类,需要实现ApplicationRunner接口,加上@Component注解,然后注入ShopService,在类里面重写run方法,在run里调用warmupCache

ApplicationRunnerCommendLineRunner

CommandLineRunner 和 ApplicationRunner 都是 Spring Boot 应用程序启动后要执行的接口,它们都允许我们在应用启动后执行一些自定义的初始化逻辑,例如缓存预热。

CommandLineRunnerApplicationRunner 区别如下:

方法签名不同

  • CommandLineRunner接口有一个run(String... args)方法,它接收命令行参数作为可变长度字符串数组。
  • ApplicationRunner接口则提供了一个run(ApplicationArguments args)方法,它接收一个ApplicationArguments对象作为参数,这个对象提供了对传入的所有命令行参数(包括选项和非选项参数)的访问。

参数解析方式不同

  • CommandLineRunner接口更简单直接,适合处理简单的命令行参数。
  • ApplicationRunner接口提供了一种更强大的参数解析能力,可以通过 ApplicationArguments 获取详细的参数信息,比如获取选项参数及其值、非选项参数列表以及查询是否存在特定参数等。

使用场景不同

  • 当只需要处理一组简单的命令行参数时,可以使用CommandLineRunner
  • 对于需要精细控制和解析命令行参数的复杂场景,推荐使用 ApplicationRunner

点评的缓存问题
https://novelyear.github.io/2024/10/10/点评学习记录/
作者
Leoo Yann
发布于
2024年10月10日
更新于
2025年4月18日
许可协议