1. 缓存跟新策略
-
缓存失效策略:当数据更新时,直接删除缓存中的对应数据,下次访问时重新加载最新数据到缓存中。这种策略简单直接,但可能会导致缓存雪崩问题。
-
定期刷新策略:定期刷新缓存中的数据,保持缓存数据与数据库中的数据同步。这种策略可以减轻缓存失效带来的压力,但可能导致缓存中的数据不是最新的。
-
基于事件驱动的主动更新策略:当数据更新时,发布一个事件通知缓存系统,使缓存系统能够及时更新对应的缓存数据。这种策略能够保证缓存数据的及时更新,但需要在系统中引入事件机制。
-
读写分离策略:将缓存中的数据与数据库中的数据分开存储,读操作优先从缓存中获取数据,写操作则更新数据库并删除或更新缓存中的数据。这种策略可以有效减轻数据库的压力,但需要保证缓存中的数据与数据库中的数据保持一致。
-
淘汰策略:当缓存空间不足时,根据一定的策略淘汰部分缓存数据,以腾出空间存储新的数据。常见的淘汰策略包括最近最少使用(LRU)和最少使用(LFU)等
对于低一致性需求的场景,可以使用Redis自带的内存淘汰机制;而对于高一致性需求的场景,可以结合多种缓存更新策略,以确保数据的一致性和系统的高效运行
在使用Redis缓存时,通常推荐的做法是先更新数据库,再删除缓存。这种方法可以减少数据不一致的风险
先更新数据库,再删除缓存原因如下:
-
先更新数据库,再删除缓存:
- 优点:在高并发场景下,虽然会有短暂的数据不一致,但最终数据会保持一致。因为数据库更新完成后,缓存中的旧数据会被删除,新的读取请求会从数据库中获取最新数据并更新缓存。
- 缺点:在数据库更新和缓存删除之间的短暂时间内,可能会有读取到旧数据的情况。
-
先删除缓存,再更新数据库:
- 优点:操作简单,直接删除缓存中的旧数据。
- 缺点:在高并发场景下,可能会导致数据不一致。例如,在缓存删除后但数据库更新前,有新的读取请求进来,这些请求会从数据库中读取旧数据并重新写入缓存,导致缓存和数据库数据不一致。
2. 缓存穿透
缓存穿透是指当用户请求的数据在缓存和数据库中都不存在时,每次请求都会直接访问数据库,导致缓存失效,增加数据库的压力。
-
布隆过滤器:
- 原理:布隆过滤器是一种概率型数据结构,用于快速判断一个元素是否存在于集合中。它可以有效地过滤掉不存在的数据请求。
- 优点:占用内存小,查询速度快。
- 缺点:存在一定的误判率,即可能会误认为不存在的数据存在。
-
缓存空对象:
- 原理:当查询结果为空时,将空结果也缓存起来,设置一个较短的过期时间。
- 优点:可以有效防止缓存穿透。
- 缺点:会占用一定的缓存空间。
-
参数校验:
- 原理:在接口层增加参数校验,对于明显非法的请求直接拦截。
- 优点:简单有效,减少无效请求。
- 缺点:需要根据具体业务场景设计校验规则。
3. 缓存雪崩
缓存雪崩是指在同一段时间大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库带来巨大压力。
以下是一些常见的解决办法:
-
服务熔断和请求限流:在Redis故障时,启动服务熔断机制,暂停业务应用对缓存服务的访问,或者启动请求限流机制,只允许少部分请求访问数据库1。
-
多级缓存策略:结合本地缓存(如Ehcache)和分布式缓存(如Redis),在分布式缓存失效时,本地缓存可以作为备份2。
4. 缓存击穿
缓存击穿是指在高并发情况下,某个热点数据在缓存中失效,导致大量请求同时访问数据库,从而对数据库造成巨大压力。
解决方案:
互斥锁、逻辑过期
使用 jmeter 压力测试
基于互斥锁方式解决缓存击穿:
逻辑过期方式解决缓存击穿:
5. 解决缓存穿透
ShopServiceImpl中:
注入编写的cacherClient类调用 queryWithPassThrough 方法(解决缓存穿透)
CacheClient中:
queryWithPassThrough传入参数分别是
key前缀、id、value类型、
Function<ID, R> dbFallback :是一个函数式接口,用于定义一个从数据库中查询数据的方法。它接受一个类型为 ID 的参数,并返回一个类型为 R 的结果。传入
Function<ID, R> dbFallback = id -> getById(id);
或者
Function<ID, R> dbFallback = this::getById;
过期时间、时间单位
.apply
是 Function
接口中的一个固定方法。Function
是Java 8引入的一个函数式接口,定义了一个抽象方法 apply
,用于将一个输入参数转换为一个输出结果。
6. 解决缓存击穿
互斥锁解决缓存击穿
ShopServiceImpl中:
CacheClient中:
queryWithMutex:参数同上
tryLock和unlock
tryLock 获得锁通过向Redis中通过setIfAbsent存入key,value来维持 若存在则 false 不存在则 true
unlock 释放锁,即删除 Redis 中的这个数据
获得锁失败则休眠然后递归
获得成功后写入 Redis,最终释放锁
逻辑过期解决缓存击穿
ShopServiceImpl中:
CacheClient中:
queryWithLogicalExpire参数同上
创建一个固定大小为10的线程池,用于执行缓存重建任务。
ExecutorService是Java中的一个接口,用于管理和控制一组异步任务的执行。它提供了线程池的功能,避免了手动创建和管理线程的复杂性。