首页 > 数据库 >Redis缓存穿透、击穿、雪崩

Redis缓存穿透、击穿、雪崩

时间:2023-02-04 13:37:03浏览次数:51  
标签:缓存 return String 过期 Redis 雪崩 key id


文章目录

  • ​​缓存穿透​​
  • ​​缓存雪崩​​
  • ​​缓存击穿​​
  • ​​代码实战部分​​
  • ​​缓存击穿实战代码封装​​
  • ​​缓存穿透解决​​

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或者一些开源库的,比较稳定,那就到最后啦,需要大家可以多多支持持…


标签:缓存,return,String,过期,Redis,雪崩,key,id
From: https://blog.51cto.com/u_15559794/6037136

相关文章

  • 【缓存策略及实践】前端如何配置 HTTP 缓存机制
    缓存的目的主要作用是可以加快资源获取速度,提升用户体验,减少网络传输,缓解服务端的压力。强缓存不需要发送请求到服务端,直接读取浏览器本地缓存,显示的HTTP状态码是200......
  • 使用 Python 操作 Redis 数据库
    1.简介Redis是是一个高性能的key-value数据库。Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。Redis不仅仅支持简单的key-......
  • Redis下载和安装(Windows系统)
    在Windows系统下安装Redis要比Linux系统安装稍微复杂一些,本节详细介绍如何在Windows系统上如何安装Redis。打开上述的下载链接,Redis支持32位和64位的Window......
  • Spring Boot 集成 Redis 实现数据缓存
    SpringBoot集成Redis实现缓存主要分为以下三步:加入Redis依赖加入Redis配置演示Redis缓存加入依赖首先创建一个项目,在项目中加入Redis依赖,项目依赖如下......
  • 二级缓存
      ......
  • 磁盘IO爆满?Redis关闭数据持久化功能
    今天把一些站点迁移到了阿里云上去,然后发现性能不太够。开启了redis后因为redis的持久化功能将我服务器的磁盘IO使用爆满了。后面形成了链式反应,因为磁盘IO爆满,导致数据库......
  • 【Azure Cache for Redis】Python Djange-Redis连接Azure Redis服务遇上(104, 'Connec
    问题描述使用Python连接AzureRedis服务,因为在代码中使用的是Djange-redis组件,所以通过如下的配置连接到AzureRedis服务:CACHES={"default":{"BACKEND":"dj......
  • 虹科产品 | 使用Redis企业版数据库为MySQL增添魅力!
    MySQL读取数据慢?难以轻松扩展?数据搜索效率低?无法实时分发数据集?针对以上问题,虹科Redis企业版数据库的解决方案来了!企业如果将Redis企业版数据库与MySQL一起使用,可以实现......
  • Redis数据结构
    1.SDSstructsdshdr{//记录buf数组中已使用字节的数量//等于SDS所保存字符串的长度intlen;//记录buf数组中未使用字节的数量intfree;/......
  • K8s Helm部署redis高可用
    一、HelmHelm是Kubernetes应用的包管理工具,主要用来管理Charts,类似Linux系统的yum。HelmChart是用来封装Kubernetes原生应用程序的一系列YAML文件。可以在你部署......