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缓存穿透、击穿、雪崩的更多相关文章

  1. Redis中几个简单的概念:缓存穿透/击穿/雪崩,别再被吓唬了

    Redis中几个“看似”高大上的概念,经常有人提到,某些好事者喜欢死扣概念,实战没多少,嘴巴里冒出来的全是高大上的名词,个人一向鄙视概念党,呵呵! 其实这几个概念:缓存穿透/缓存击穿/缓存雪崩,有一个 ...

  2. NoSQL & Redis 介绍、缓存穿透 & 击穿 & 雪崩

    1. NoSql 简介 2. Redis 简介 2.1 Redis 的起源 2.2 缓存过期 & 缓存淘汰 3. 缓存异常 1)缓存穿透 2)缓存击穿 3)缓存雪崩 4)总结 1. NoSQL ...

  3. Redis缓存穿透和雪崩

    缓存穿透 用户想要查询一个数据 在redis缓存数据库中没有获取到 就会向后端的数据库中查询. 当用户很多 都去访问后端数据库的话,这就会给数据库带来很大的压力. 常见场景:秒杀活动 等 解决方法: ...

  4. redis缓存穿透,缓存击穿,缓存雪崩

    概念解释 redis 缓存穿透 key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源.比如用一个不存在的用户id获取用户信息,不论缓存还是数据库 ...

  5. Redis缓存穿透,缓存击穿,缓存雪崩,热点Key

    导读 使用Redis难免会遇到Redis缓存穿透,缓存击穿,缓存雪崩,热点Key的问题.有些同学可能只是会用Redis来存取,基本都是用项目里封装的工具类来操作.但是作为开发,我们使用Redis时可能 ...

  6. 【干货!!】三句话搞懂 Redis 缓存穿透、击穿、雪崩

    前言 如何有效的理解并且区分 Reids 穿透.击穿和雪崩之间的区别,一直以来都挺困扰我的.特别是穿透和击穿,过一段时间就稀里糊涂的分不清了. 为了有效的帮助笔者自己,以及拥有同样烦恼的朋友们区分这三 ...

  7. Redis缓存穿透、缓存雪崩、redis并发问题 并发竞争key的解决方案 (阿里)

    阿里的人问我 缓存雪崩(大量数据在同一时间过期了)了如何处理,缓存击穿了如何处理,回答的很烂,做了总结: 把redis作为缓存使用已经是司空见惯,但是使用redis后也可能会碰到一系列的问题,尤其是数 ...

  8. Redis缓存穿透和缓存雪崩以及解决方案

    Redis缓存穿透和缓存雪崩以及解决方案 Redis缓存穿透和缓存雪崩以及解决方案缓存穿透解决方案布隆过滤缓存空对象比较缓存雪崩解决方案保证缓存层服务高可用性依赖隔离组件为后端限流并降级数据预热缓存并 ...

  9. 预防Redis缓存穿透、缓存雪崩解决方案

    最近面试中遇到redis缓存穿透.缓存雪崩等问题,特意了解下. redis缓存穿透: 缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有.这样就导致用户查询的时候,在缓存中找不到,每次都要去 ...

  10. redis与mysql性能对比、redis缓存穿透、缓存雪崩

    写在开始 redis是一个基于内存hash结构的缓存型db.其优势在于速读写能力碾压mysql.由于其为基于内存的db所以存储数据量是受限的. redis性能 redis读写性能测试redis官网测试 ...

随机推荐

  1. 贪心算法(Java)

    贪心算法 文章目录 贪心算法 0.写在前面 1.贪心算法的基本要素 1.1 贪心选择性质 1.2 最优子结构性质 1.3 贪心算法与动态规划算法的差异 2.贪心算法的特点 3.贪心法的正确性证明 4. ...

  2. 在Mac上不常用但会用到的命令

    文章目录 1. chflags 2. rename 1. chflags chflags hidden filename 给文件添加隐藏属性 chflags nohidden filename 去掉文 ...

  3. DVWA-XSS (DOM) DOM型跨站脚本攻击

    XSS(Cross Site Scripting),跨站脚本攻击,能使攻击者在页面嵌入一些脚本代码,用户再访问,被诱导点击时,执行恶意脚本,常见为javascript,也有Flash.VBscript ...

  4. Android笔记--文本输入

    编辑框EditText 相关内部部件取下: inputType的类型如下: 具体实现: 不同边框的实现: 焦点变更监听器 具体实现: 文本变化监听器 具体实现:

  5. 对Android关联SDK后,还是无法显示那俩图标的解决

    显示出来!!!! 可以这么解决: 步骤一: 步骤二: 找到这个,在上方的栏里面: 步骤三: 将这四个选中: 步骤四: 然后选中这个栏: 步骤五: 选中Android: 步骤六: 最后,点击右下角的Ap ...

  6. 使用sync.Once实现高效的单例模式

    1. 简介 本文介绍使用sync.Once来实现单例模式,包括单例模式的定义,以及使用sync.Once实现单例模式的示例,同时也比较了其他单例模式的实现.最后以一个开源框架中使用sync.Once实 ...

  7. 基于机器学习的语音编解码器声网Agora Silver:支持超低码率下的高音质语音互动

    从 1860 年电话发明,到现如今通过网络进行语音互动,语音始终是最自然.最基础的实时互动方式.过去几年,语音实时互动成为越来越多人日常生活的一部分.但是每个人都会遇到弱网环境,这会直接影响语音通话体 ...

  8. [AIGC]GPT模型概述

    2 Open AI: ChatGPT 2.0 ChatGPT 官网 https://openai.com/ https://platform.openai.com/ 原 : https://beta. ...

  9. 开源项目audioFlux: 针对音频领域的深度学习工具库

    目录 时频变换 频谱重排 倒谱系数 解卷积 谱特征 音乐信息检索 audioFlux是一个Python和C实现的库,提供音频领域系统.全面.多维度的特征提取与组合,结合各种深度学习网络模型,进行音频领 ...

  10. LeeCode 318周赛复盘

    T1: 对数组执行操作 思路:模拟 public int[] applyOperations(int[] nums) { int n = nums.length; for (int i = 0; i ...