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. 微信支付服务商api对接

    引入官方sdk <!--微信v3支付sdk {https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient}--> < ...

  2. zyb

  3. Net异步委托-泛型委托Action<T>与Func<T,TResult>及 异步调用AsyncCallback

    1.相同点 Func<ReqMode,ResultModel> 与 Action<ReqMode> 1).都是Net3.5 之后内置的委托方法,作用几乎一致 2).都支持lam ...

  4. [扫描工具]dirsearch简单使用

    [扫描工具]dirsearch简单使用 dirsearch是一个python开发的目录扫描工具.和我们平时使用的dirb.御剑之类的工具一样,就是为了扫描网站的敏感文件和目录从而找到突破口. 安装: ...

  5. gogetssl申请的域名证书私钥文件丢了,可以重新申请个吗?

    因为gogetssl是不保存我们使用浏览器生成的KEY文件的,CSR文件倒是可以有办法再找到 也就是说Certificate Signing Request(CSR)可以想想办法,但是Your Pri ...

  6. Axure RP Extension for Chrome 0.6.2安装和详解

    1.当我们用网页访问一个本地页面时就会出现这种问题,提示你要安装浏览器的扩展程序包,以下有搜狗浏览器如何安装,qq浏览器 ​

  7. Jx9嵌入式脚本语言基本用法

    Jx9是一种嵌入式脚本语言,可用于编写嵌入式脚本和扩展. 以下是一些使用Jx9的示例: 1. 计算器 Jx9可以轻松实现一个计算器.例如: ```print("Enter two numbe ...

  8. Cryptanalyzing and Improving a Novel Color Image Encryption Algorithm Using RT-Enhanced Chaotic Tent Maps

    Cryptanalyzing and Improving a Novel ColorImage Encryption Algorithm Using RT-EnhancedChaotic Tent M ...

  9. Web For Pentester靶场搭建 - XSS

    Web For Pentester是集成了一些简单的Web常见漏洞的靶场,其中有常见的XSS 文件上传 SQL注入 文件包含等常见漏洞,类似于DVWA Web For Pentester搭建 Web ...

  10. svn提交规范

    本文档参考了Git提交规范,旨在规范使用SVN进行代码版本管理时的提交操作. 提交前的准备 1. 检查代码 在提交代码前,请先进行必要的代码检查,确保代码的正确性.可读性和可维护性.可以使用代码质量管 ...