Redis的缓存更新策略和缓存问题
Redis的三种缓存更新策略、缓存更新时需要考虑的问题、缓存穿透、缓存击穿、缓存雪崩
1.缓存更新
1.1缓存更新策略
- 内存淘汰:
- 不需要自己维护,利用Redis的内存淘汰机制,当内存不足时自动淘汰部分数据,下次查询时更新缓存
- 一致性 : 差
- 维护成本:无
- 超时删除:
- 给缓存数据添加TTL时间,到期后自动删除缓存,下次查询时更新缓存
- 一致性 :一般 (如果数据库中的值发生更新,但是缓存中的数据仍未过期,会出现数据不一致问题)
- 维护成本:低
- 主动更新:
- 在修改数据库的同时,进行更新缓存的操作
- 一致性:好 (也有可能出现数据不一致)
- 维护成本:高
业务场景:
- 低一致性需求:使用内存淘汰机制。如一些更改频率很低的数据的缓存查询
- 高一致性需求:主动更新,并以超时删除作为辅助备用方案。如频繁修改的数据的缓存查询
1.2 主动更新策略需要考虑的问题
1.删除缓存还是更新缓存?
- 更新缓存: 每次更新数据库都更新缓存,无效写操作较多 (比如更新1000次数据库同一个数据,那么缓存也更新了1000次,实际上只有最后一次更新缓存是有效的) 适合于读多写少
- 删除缓存: 更新数据库时让缓存失效(删除缓存 or 让缓存过期 ),查询时再更新缓存 (常用方式)
2.如何保证缓存与数据库的操作同时成功或失败?(原子性)
-
单体系统: 将缓存与数据库操作放在一个事务
-
分布式系统:利用TTC等分布式事务方案
3.先操作缓存还是先操作数据库?
- 先删除缓存,再更新数据库
可能存在的问题:多线程环境下,(操作数据库耗时长于操作缓存)很大概率发生 线程2 在线程1 删除缓存还未来得及更新数据库时,就进行查询操作,然后未命中查询数据库一个旧的数据写入缓存,导致出现数据一致性问题
- 先更新数据库,再删除缓存 (常用方式)
可能出现的问题:在线程1查缓存时,此时缓存恰好失效,然后查数据库,还未来得及写入缓存,线程2进行了更新数据库的操作,然后线程1将旧的数据写入缓存,导致数据不一致性的问题(由于操作缓存效率更高,这种问题发生的概率比较小)
2.缓存问题
2.1缓存穿透
场景: 查询不存在的数据,使得请求一直查询数据库,在大量请求下导致数据库负载过大,甚至宕机
解决方案:
- 缓存空对象
存储层未命中后,仍然将空值存入缓存,再次访问该数据时,缓存直接返回空值 (空值是“”)
优点: 实现简单,维护方便
缺点: 额外的内存消耗 (可以给空值设置TTL过期时间)
可能造成短期的不一致 (在缓存空值之后,数据库新增了这个数据)
- 布隆过滤器
将所有存在的key提前存入布隆过滤器(二进制形式),在访问缓存层之前,先通过过滤器拦截,若请求的是不存在的key,则直接返回空值
优点: 内存占用较少,没有多余key
缺点: 实现复杂 存在误判可能
主动的应对方案:
-
增强id的复杂度,避免被猜测id规律
-
做好数据的基础格式校验
-
加强用户权限校验
-
做好热点参数的限流
2.缓存击穿
场景: 热点数据,高并发访问。在其缓存失效瞬间,大量请求直达存储层,导致数据库压力剧增,服务崩溃
解决方案:
- 加互斥锁
对数据的访问加互斥锁,当一个线程访问该数据时,其他线程只能等待,这个线程访问过后,缓存中的数据将被重建,此时其他线程可以直接从缓存中取值
优点: 保证一致性 实现简单
缺点: 线程需要等待,性能受影响,可能有死锁风险(在多缓存场景下多个锁可能导致死锁)
使用Redis中的setnx模拟互斥锁的过程
private boolean tryLocal(String key) {
// setnx 存入一个key 值可以随意 设置10s的过期时间
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
private void unlock(String key) {
stringRedisTemplate.delete(key);
}
- 逻辑过期
不设置过期时间,添加一个expire属性 值为当前时间+过期时间 转为整数(时间戳)
为每个value设置逻辑过期时间,当发现该值逻辑过期时,使用单独的线程重新缓存,其他线程返回旧的值
优点: 线程无需等待,性能较好
缺点: 不保证一致性(返回旧数据),有额外内存消耗,实现复杂
3.缓存雪崩
场景: 当缓存失效 (如同一时段大量的key过期或者redis挂了),导致所有请求直达存储层,造成存储层宕机
解决方案:
- 避免同时过期
设置过期时间,附加一个随机数,避免大量的key同时过期
- 构建高可用的redis缓存
部署多个redis实列,个别结点宕机,依然可以保持服务的整体可用 (Redis集群)
- 构建多级缓存
增加本地缓存,在存储层前面多加一级屏障,降低请求直达存储层的几率 (比如 caffieine本地缓存 、Nginx缓存)
- 启用限流和降级措施
对存储层增加限流措施,当请求超出限制时,对其提供降级服务
更多推荐
所有评论(0)