Redis缓存穿透、击穿、雪崩
Redis目前是非常流行的缓存数据库,缓存穿透、缓存击穿、缓存雪崩是常见的面试题,也是非常重要的问题。
缓存穿透
缓存穿透指的是客户端请求的数据既不在缓存中,也不在数据库中,这样导致请求访问缓存,发现缺失,再去访问数据库时发现也没有数据,当大量的请求到来,数据库的压力就会突然增加。
解决方案:
缓存空对象,对于一些在数据库查找不到记录的,我们将其缓存key的value设置成NULL,设置一个过期时间,这样下次请求访问这个不存在的数据,就不需要再次查询数据库了。
布隆过滤器,布隆过滤器主要用的是哈希的思想,通过一个庞大二进制数和映射函数组成的。
布隆过滤器可能出现判断结果存在的时候不一定存在,但是判断结果为不存在的时候一定不存在,有误判的可能,可以添加元素,不可以删 除元素。
除上面这些外,还可以增强id的复杂度,避免被猜测id规律,做好数据的基础格式校验,热点参数的限流。
缓存雪崩
缓存雪崩指的是同一时间段大量的缓存key失效或者Redis宕机,导致大量请求访问数据库。
解决方案:
均匀设置缓存key的过期时间,可以添加随机值,这样就可以保证数据不会都在同一时间过期。
给Redis集群提高服务的可用性
给缓存服务添加服务熔断或请求限流机制
采用互斥锁,当数据不在Redis,加一个互斥锁,等缓存重新构建完成后再释放锁。
采用双锁策略,一个是主key,设置过期时间,一个是备份key,不会设置过期时间,对value做一个副本。
缓存击穿
缓存击穿问题也称为热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无效的请求会在瞬间给数据库带来冲击,数据库的压力会速增。
解决方案:
互斥锁方案,保证在同一时间只有一个线程可以更新缓存,未能获取到互斥锁的请求,需要等待锁的释放,或者返回空值。
给数据设置逻辑过期时间,当知道数据已经过期时,可以通知后台线程更新缓存以及重新设置过期时间。
代码实战部分
缓存击穿实战代码封装
对于需要添加逻辑过期时间,我们需要数据和逻辑过期时间封装到RedisData中,其中在需要重建缓存的数据需要使用到互斥锁来限制只有一个线程进行重建,并且这个线程是新开的线程,返回已经过期的数据,后面的请求访问过来也都是先返回过期数据,直到新线程重建完缓存数据才是一致性,会出现短暂性的缓存和数据的不一致问题。
private static final ExecutorService CACHE_REBUILD_EXECUTOR;
static {
CACHE_REBUILD_EXECUTOR = newFixedThreadPool(10);
}
public <R, ID> R queryWithLogicalExpire(
String keyPrefix, ID id, Class<R> type, Function<ID, R> dbCallBack, Long time, TimeUnit timeUnit) {
String key = keyPrefix + id;
String json = stringRedisTemplate.opsForValue().get(key);
// 1. 如果不存在
if (StrUtil.isBlank(json)) {
// 2. 不存在,直接返回
return null;
}
// 3. 如果存在, 先把json反序列化为对象
RedisData data = JSONUtil.toBean(json, RedisData.class);
R r = JSONUtil.toBean((JSONObject) data.getData(), type);
LocalDateTime expireTime = data.getExpireTime();
// 4. 判断是否过期
if (expireTime.isAfter(LocalDateTime.now())) {
// 5.1 未过期,直接返回店铺信息
return r;
}
// 5.2 已过期,开始重建
// 6. 缓存重建
// 6.1 尝试获取互斥锁
String lockKey = RedisConstants.LOCK_SHOP_KEY + id;
boolean isLock = tryLock(lockKey);
if (isLock) {
// 6.3 成功,开启另外一个线程将进行缓存重建
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
R r1 = dbCallBack.apply(id);
this.setLogicalExpire(key, r1, time, timeUnit);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// 释放锁
unlock(lockKey);
}
});
}
// 6.4 返回过期数据
return r;
}
public void setLogicalExpire(String key, Object value, Long time, TimeUnit timeUnit) {
// 逻辑过期
RedisData redisData = new RedisData();
redisData.setData(value);
redisData.setExpireTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)));
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
}
互斥锁解决缓存击穿问题:
在进行重建的时候需要申请一把锁,这里才有的是Redis的setnx命令,只有key不存在的时候才会申请成功,申请失败,我们就重复申请。当然我们也可以使用Lock,或者synchronized关键字来同步代码快,同步代码块的时候锁是字符串的时候,需要锁的对象是字符串的.intern()方法,如果是同步方法的话,效率太低了。
public <R, ID> R queryWithMutex(
String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
String key = keyPrefix + id;
// 1.从redis查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
// 2.判断是否存在
if (StrUtil.isNotBlank(shopJson)) {
// 3.存在,直接返回
return JSONUtil.toBean(shopJson, type);
}
// 判断命中的是否是空值
if (shopJson != null) {
// 返回一个错误信息
return null;
}
// 4.实现缓存重建
// 4.1.获取互斥锁
String lockKey = LOCK_SHOP_KEY + id;
R r = null;
try {
boolean isLock = tryLock(lockKey);
// 4.2.判断是否获取成功
if (!isLock) {
// 4.3.获取锁失败,休眠并重试
Thread.sleep(50);
return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);
}
// 4.4.获取锁成功,根据id查询数据库
r = dbFallback.apply(id);
// 5.不存在,返回错误
if (r == null) {
// 将空值写入redis
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
// 返回错误信息
return null;
}
// 6.存在,写入redis
this.set(key, r, time, unit);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
// 7.释放锁
unlock(lockKey);
}
// 8.返回
return r;
}
缓存穿透解决
将缓存查询不到和数据库查询不到的数据写入到Redis中。
public <R, ID> R queryWithPassThrough(
String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit timeUnit) {
String key = keyPrefix + id;
String json = stringRedisTemplate.opsForValue().get(key);
// 如果是空值
if (StrUtil.isNotBlank(json)) {
return JSONUtil.toBean(json, type);
}
// 判断是否是空值
if (json != null) {
return null;
}
R r = dbFallback.apply(id);
if (r == null) {
stringRedisTemplate.opsForValue().set(key, "" ,RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
return null;
}
this.set(key, r, time, timeUnit);
return r;
}
上面是学习Redis课程使用到的一些解决方案,还有很多的方案没有列举出来,布隆过滤器可以使用hutool或者一些开源库的,比较稳定,那就到最后啦,需要大家可以多多支持持…
Redis缓存穿透、击穿、雪崩的更多相关文章
- Redis中几个简单的概念:缓存穿透/击穿/雪崩,别再被吓唬了
Redis中几个“看似”高大上的概念,经常有人提到,某些好事者喜欢死扣概念,实战没多少,嘴巴里冒出来的全是高大上的名词,个人一向鄙视概念党,呵呵! 其实这几个概念:缓存穿透/缓存击穿/缓存雪崩,有一个 ...
- NoSQL & Redis 介绍、缓存穿透 & 击穿 & 雪崩
1. NoSql 简介 2. Redis 简介 2.1 Redis 的起源 2.2 缓存过期 & 缓存淘汰 3. 缓存异常 1)缓存穿透 2)缓存击穿 3)缓存雪崩 4)总结 1. NoSQL ...
- Redis缓存穿透和雪崩
缓存穿透 用户想要查询一个数据 在redis缓存数据库中没有获取到 就会向后端的数据库中查询. 当用户很多 都去访问后端数据库的话,这就会给数据库带来很大的压力. 常见场景:秒杀活动 等 解决方法: ...
- redis缓存穿透,缓存击穿,缓存雪崩
概念解释 redis 缓存穿透 key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源.比如用一个不存在的用户id获取用户信息,不论缓存还是数据库 ...
- Redis缓存穿透,缓存击穿,缓存雪崩,热点Key
导读 使用Redis难免会遇到Redis缓存穿透,缓存击穿,缓存雪崩,热点Key的问题.有些同学可能只是会用Redis来存取,基本都是用项目里封装的工具类来操作.但是作为开发,我们使用Redis时可能 ...
- 【干货!!】三句话搞懂 Redis 缓存穿透、击穿、雪崩
前言 如何有效的理解并且区分 Reids 穿透.击穿和雪崩之间的区别,一直以来都挺困扰我的.特别是穿透和击穿,过一段时间就稀里糊涂的分不清了. 为了有效的帮助笔者自己,以及拥有同样烦恼的朋友们区分这三 ...
- Redis缓存穿透、缓存雪崩、redis并发问题 并发竞争key的解决方案 (阿里)
阿里的人问我 缓存雪崩(大量数据在同一时间过期了)了如何处理,缓存击穿了如何处理,回答的很烂,做了总结: 把redis作为缓存使用已经是司空见惯,但是使用redis后也可能会碰到一系列的问题,尤其是数 ...
- Redis缓存穿透和缓存雪崩以及解决方案
Redis缓存穿透和缓存雪崩以及解决方案 Redis缓存穿透和缓存雪崩以及解决方案缓存穿透解决方案布隆过滤缓存空对象比较缓存雪崩解决方案保证缓存层服务高可用性依赖隔离组件为后端限流并降级数据预热缓存并 ...
- 预防Redis缓存穿透、缓存雪崩解决方案
最近面试中遇到redis缓存穿透.缓存雪崩等问题,特意了解下. redis缓存穿透: 缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有.这样就导致用户查询的时候,在缓存中找不到,每次都要去 ...
- redis与mysql性能对比、redis缓存穿透、缓存雪崩
写在开始 redis是一个基于内存hash结构的缓存型db.其优势在于速读写能力碾压mysql.由于其为基于内存的db所以存储数据量是受限的. redis性能 redis读写性能测试redis官网测试 ...
随机推荐
- the third change day
2022.5.9 今日名言:青春是一个短暂的美梦,当你醒来的时候,它早已消失的无影无踪.----莎士比亚 早起听了一堂听力课,感觉他教的挺好,准备跟着试试,快考试了,别来不及了. 目录 听力技巧 阅读 ...
- linux离线安装插件包
1.下载插件包(联网的linux环境下) # 检查是否安装了vim(vim-minimal是vi) [root@localhost opt]# rpm -qa | grep vim vim-minim ...
- Leecode 3.无重复字符的最大字串长度(Java 暴力拆解)
想法: 1.暴力解法,遇到重复字符就重新开辟空间,最后比较字串长度. 2.指针,但思路不太清晰 ----查看答案和思路,重新整理 滑动窗口: 1.设left,right用于下标值,length ...
- 7 Free Energies: 7.4 Umbrella Sampling Example
7.4 Umbrella Sampling Example计算丙氨酸二肽 Phi/Psi 旋转的 PMF http://ambermd.org/tutorials/advanced/tutori ...
- Linux基础——操作系统
1. 操作系统(Operation System,OS) 操作系统作为接口的示意图 如果想在裸机上运行自己所编写的程序,就必须用机器语言书写程序如果计算机上安装了操作系统,就可以在操作系统上安装支持的 ...
- bootstrapv4轮播图去除两侧阴影及线框
一.前提条件: 在使用bootstrap v4中的轮播图组件时,两侧默认出现阴影,且轮播组件示例不一致! 二.bootstrap文档组件展示与实际应用 1.官方文档展示如下:没有阴影 2.实际应用情 ...
- group by和union,Laravel分页
$res3 = DB::table('users') ->join('user_folow_boutiques', 'user_folow_boutiques.user_id', '=', 'u ...
- Linux下apache日志(按日期存放)分析与状态查看方法
转载网址: https://blog.csdn.net/weixin_42272246/article/details/125602258
- python内置函数len()
len() len()函数用于返回对象(字符串.元组.列表和字典等)的长度或元素个数 len()函数的语法: len(s) 代码示例 print(len(range(10))) print(len([ ...
- 文心一言,通营销之学,成一家之言,百度人工智能AI大数据模型文心一言Python3.10接入
"文心"取自<文心雕龙>一书的开篇,作者刘勰在书中引述了一个古代典故:春秋时期,鲁国有一位名叫孔文子的大夫,他在学问上非常有造诣,但是他的儿子却不学无术,孔文子非常痛心 ...