22.缓存雪崩缓存击穿

2024-08-31 10:44
文章标签 缓存 22 击穿 雪崩

本文主要是介绍22.缓存雪崩缓存击穿,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

定义

同一时段大量的缓存key同时失效或者redis服务宕机,导致大量请求到达数据库,带来巨大压力。

解决方案

1.给不同key的TTL添加随机值。

2.利用redis集群提高服务的可用性。

3.给缓存业务添加降级限流策略。

4.给业务添加多级缓存。

缓存击穿

缓存击穿也叫做热点key问题,被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求会在瞬间给数据库带来巨大冲击。

解决方案

1.互斥锁

缺点:线程相互等待,性能差。可能有死锁风险。

优点:保证强一致性,没有额外的内存消耗,不用存储逻辑过期时间字段,实现简单。

2.逻辑过期

优点:线程无需等待,性能较好。不保证一致性,有额外内存消耗,实现复杂。

基于互斥锁方式解决缓存击穿

这里的互斥锁采用setnx命令,当这个key不存在的时候才能设置值,如果key已经存在了,则设置值失败。

127.0.0.1:6379> setnx lock 1
(integer) 1
127.0.0.1:6379> setnx lock 2
(integer) 0
127.0.0.1:6379> get lock
"1"

加锁是 setnx key 命令->赋值

释放锁 del key 命令->删除

如果设置锁,然后迟迟没有去删除,也就是没有释放锁。所以在设置setnx锁的时候,会加一个有效期。

 /*** 尝试加锁* @return*/public boolean tryLock(String key) {//setnx命令Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);//这里会自动拆箱,防止flag为null报空指针异常return BooleanUtil.isTrue(flag);}/*** 释放锁* @param key*/public void unLock(String key) {stringRedisTemplate.delete(key);}
 /*** 防止缓存击穿-带互斥锁* @return*/public Shop queryWithMutex(Long id) {String key = RedisConstants.CACHE_SHOP_KEY + id;//从redis中查询商铺缓存String shopJsonStr = stringRedisTemplate.opsForValue().get(key);//redis中有数据直接返回if(StrUtil.isNotBlank(shopJsonStr)) {return JSONUtil.toBean(shopJsonStr, Shop.class);}//判断命中的是否为空值if(shopJsonStr != null) {//说明命中空字符串,不会去查数据库return null;}Shop shop = null;String lockKey = "lock:shop:"+id;try {//未命中,实现缓存重建//1.获取互斥锁boolean isLock = tryLock(lockKey);//2.判断是否获取互斥锁成功//3.失败则休眠重试if(!isLock) {Thread.sleep(50);return queryWithMutex(id);}//4.成功则,redis中没有数据,继续查询数据库shop = getById(id);if(ObjectUtil.isNull(shop)) {//将空值写入redisstringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);//数据库没有查询到数据,返回错误return null;}//数据库中查询到数据,存入redis,再返回数据;设置超时时间stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);}catch (Exception e) {e.printStackTrace();}finally {//5.释放互斥锁unLock(lockKey);}return shop;}

逻辑过期解决缓存击穿

向redis中添加数据时,额外添加一个时间字段。

逻辑过期,其实旧的数据还存在于redis中,只是是旧数据而已,还是可以在缓存重建的过程中返回旧数据。这样就不至于给数据库带来巨大压力。

 

 缓存数据预热

@Data
public class RedisData {/*** 逻辑过期时间*/private LocalDateTime expireTime;private Object data;
}
/*** 创造热点数据带逻辑过期,提前预热* @param id*/public void saveShop2Redis(Long id, Long expiredSeconds) {//1.查询店铺信息Shop shop = getById(id);//2.封装逻辑过期时间String key = RedisConstants.CACHE_SHOP_KEY + id;RedisData redisData = new RedisData();redisData.setExpireTime(LocalDateTime.now().plusSeconds(expiredSeconds));redisData.setData(shop);//3.写入redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}
@Testvoid testSaveShop() {shopService.saveShop2Redis(1L, 10L);}

redis中存储的店铺带逻辑过期时间

{

"data": {

"area": "北京",

"openHours": "10:00-22:00",

"sold": 4215,

"images": "https://qcloud.dpfile.com/pc/jiclIsCKmOI2arxKN1Uf0Hx3PucIJH8q0QSz-Z8llzcN56-_QiKuOvyio1OOxsRtFoXqu0G3iT2T27qat3WhLVEuLYk00OmSS1IdNpm8K8sG4JN9RIm2mTKcbLtc2o2vfCF2ubeXzk49OsGrXt_KYDCngOyCwZK-s3fqawWswzk.jpg,https://qcloud.dpfile.com/pc/IOf6VX3qaBgFXFVgp75w-KKJmWZjFc8GXDU8g9bQC6YGCpAmG00QbfT4vCCBj7njuzFvxlbkWx5uwqY2qcjixFEuLYk00OmSS1IdNpm8K8sG4JN9RIm2mTKcbLtc2o2vmIU_8ZGOT1OjpJmLxG6urQ.jpg",

"address": "北京王府井19号",

"comments": 3035,

"avgPrice": 80,

"updateTime": 1724455437000,

"score": 37,

"createTime": 1640167839000,

"name": "画画茶餐厅",

"x": 120.149192,

"y": 30.316078,

"typeId": 1,

"id": 1

},

"expireTime": 1725022878389

}

//创建线程池private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);/*** 防止缓存击穿-逻辑过期+互斥锁* @return*/public Shop queryWithLogicalExpired(Long id) {String key = RedisConstants.CACHE_SHOP_KEY + id;//从redis中查询商铺缓存String shopJsonStr = stringRedisTemplate.opsForValue().get(key);if(StrUtil.isBlank(shopJsonStr)) {//不存在,直接返回return null;}//redis中命中,需要将数据返序列化为java对象RedisData redisData = JSONUtil.toBean(shopJsonStr, RedisData.class);//判断是否逻辑过期LocalDateTime expireTime = redisData.getExpireTime();JSONObject data = (JSONObject)redisData.getData();Shop shop = JSONUtil.toBean(data, Shop.class);if(expireTime.isAfter(LocalDateTime.now())) {//未过期,返回店铺信息return shop;}//过期,需要重建缓存//这里可能会有大量的线程在这里等着,只有一个线程能够获取锁去完成缓存重建,完成缓存重建后释放锁,其他线程还会获取锁//所以在锁里面的逻辑需要再判断一次缓存是否过期,防止缓存反复的重建。//1.获取互斥锁String lockKey = RedisConstants.LOCK_SHOP_KEY + id;boolean lock = tryLock(lockKey);if(lock) {//获取锁成功//需要重新从redis中获取数据,再判断一次缓存是否过期,防止缓存反复的重建,DoubleCheckshopJsonStr = stringRedisTemplate.opsForValue().get(key);redisData = JSONUtil.toBean(shopJsonStr, RedisData.class);expireTime = redisData.getExpireTime();if(expireTime.isAfter(LocalDateTime.now())) {//未过期,返回店铺信息return shop;}//使用线程池去完成缓存重建(独立的线程)CACHE_REBUILD_EXECUTOR.submit(() -> {try{saveShop2Redis(id, 10L);}catch (Exception e) {e.printStackTrace();}finally {//在线程里面释放锁unLock(lockKey);}});}//立即返回旧的逻辑过期数据return shop;}
这里可能会有大量的线程在这里等着,只有一个线程能够获取锁去完成缓存重建,完成缓存重建后释放锁,其他线程还会获取锁.所以在锁里面的逻辑需要再判断一次缓存是否过期,防止缓存反复的重建。需要重新从redis中获取数据,再判断一次缓存是否过期,防止缓存反复的重建,DoubleCheck。

这篇关于22.缓存雪崩缓存击穿的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1123678

相关文章

缓存雪崩问题

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。 解决方案: 1、使用锁进行控制 2、对同一类型信息的key设置不同的过期时间 3、缓存预热 1. 什么是缓存雪崩 缓存雪崩是指在短时间内,大量缓存数据同时失效,导致所有请求直接涌向数据库,瞬间增加数据库的负载压力,可能导致数据库性能下降甚至崩溃。这种情况往往发生在缓存中大量 k

Redis中使用布隆过滤器解决缓存穿透问题

一、缓存穿透(失效)问题 缓存穿透是指查询一个一定不存在的数据,由于缓存中没有命中,会去数据库中查询,而数据库中也没有该数据,并且每次查询都不会命中缓存,从而每次请求都直接打到了数据库上,这会给数据库带来巨大压力。 二、布隆过滤器原理 布隆过滤器(Bloom Filter)是一种空间效率很高的随机数据结构,它利用多个不同的哈希函数将一个元素映射到一个位数组中的多个位置,并将这些位置的值置

防止缓存击穿、缓存穿透和缓存雪崩

使用Redis缓存防止缓存击穿、缓存穿透和缓存雪崩 在高并发系统中,缓存击穿、缓存穿透和缓存雪崩是三种常见的缓存问题。本文将介绍如何使用Redis、分布式锁和布隆过滤器有效解决这些问题,并且会通过Java代码详细说明实现的思路和原因。 1. 背景 缓存穿透:指的是大量请求缓存中不存在且数据库中也不存在的数据,导致大量请求直接打到数据库上,形成数据库压力。 缓存击穿:指的是某个热点数据在

PHP APC缓存函数使用教程

APC,全称是Alternative PHP Cache,官方翻译叫”可选PHP缓存”。它为我们提供了缓存和优化PHP的中间代码的框架。 APC的缓存分两部分:系统缓存和用户数据缓存。(Linux APC扩展安装) 系统缓存 它是指APC把PHP文件源码的编译结果缓存起来,然后在每次调用时先对比时间标记。如果未过期,则使用缓存的中间代码运行。默认缓存 3600s(一小时)。但是这样仍会浪费大量C

缓存策略使用总结

缓存是提高系统性能的最简单方法之一。相对而言,数据库(or NoSQL数据库)的速度比较慢,而速度却又是致胜的关键。 如果使用得当,缓存可以减少相应时间、减少数据库负载以及节省成本。本文罗列了几种缓存策略,选择正确的一种会有很大的不同。缓存策略取决于数据和数据访问模式。换句话说,数据是如何写和读的。例如: 系统是写多读少的吗?(例如基于时间的日志)数据是否是只写入一次并被读取多次?(例如用户配

java-redis-雪崩

Redis 雪崩问题 Redis雪崩 是指在 Redis 缓存系统中,当大量缓存同时失效时,所有请求直接打到数据库,导致数据库瞬间压力激增,甚至崩溃的现象。雪崩问题通常出现在高并发的系统中,因为缓存的失效导致后端数据库承受不了巨大的请求量。 具体表现: 大量缓存同时失效后,所有流量直接访问数据库。数据库承载过大的并发量,导致性能急剧下降,甚至崩溃。之后,当 Redis 缓存恢复正常时,由于数

uniapp小程序下载缓存服务器上的图片

1. 使用uni.downloadFile,但是注意下载图片的地址里的域名,需要在微信公众平台里面的downloadFile合法域名进行配置。 export default function downloadAndCacheImage(imageUrl, name) {return new Promise((resolve, reject) => {console.log("imageUrl",

基于canal的Redis缓存双写

canal地址:alibaba/canal: 阿里巴巴 MySQL binlog 增量订阅&消费组件 (github.com)https://github.com/alibaba/canal 1. 准备 1.1 MySQL 查看主机二进制日志 show master status 查看binlog是否开启 show variables like 'log_bin' 授权

插件:清理maven错误缓存.bat

插件:https://pan.baidu.com/s/1nHIxHoo1C4MvFlW7QbZe5Q?pwd=7zenhttps://pan.baidu.com/s/1nHIxHoo1C4MvFlW7QbZe5Q?pwd=7zen没错误缓存时: 有错误缓存时:

Android/Linux 磁盘写入缓存/等待时间 参数修改

Linux系统当进行文件写操作时,并不会将数据立马写入磁盘,而是写写到缓存,等待达到占用内存一定比例或超过一定时间才会批量将这些缓存数据写入磁盘,这样可以减少IO操作,提升性能和磁盘寿命。如果数据还没来得及写入磁盘发生硬件掉电,这些数据就会丢失。应用可以调用sync实时将内容写入磁盘避免丢失。排查丢失问题可以在断电前执行sync命令,看能不能复现,若无法复现,说明就是缓存没有及时写入磁盘导致。