【Redis】缓存击穿问题及其解决方案

article/2025/8/26 2:00:04

【Redis】缓存击穿问题及其解决方案

文章目录

  • 【Redis】缓存击穿问题及其解决方案
    • 1. 缓存击穿概念
    • 2. 解决方案
      • 2.1 互斥锁
        • 2.1.1 互斥锁的优缺点
        • 2.1.2 互斥锁的代码实现
      • 2.2 逻辑过期
        • 2.2.1 逻辑过期的优缺点
        • 2.2.2 逻辑过期的代码实现

1. 缓存击穿概念

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

如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IMqkJoHL-1673507243265)(C:\Users\zhuhuanjie\AppData\Roaming\Typora\typora-user-images\image-20230111162127504.png)]

线程1缓存未命中,去重建缓存;在线程1重建缓存的时候,线程2缓存又没命中,线程2也去重建缓存;和线程2同时来的线程3,线程4…缓存都没命中,都去重建缓存,给数据库带来了巨大的压力。


2. 解决方案

缓存击穿的常见解决方案有两种:

  • 互斥锁
  • 逻辑过期

2.1 互斥锁

互斥锁的实现思路就是在第一个线程到来的时候获取互斥锁,后面的线程来到之后尝试去获取互斥锁,获取失败,于是进行休眠重试。直到第一个线程缓存重建成功之后,释放互斥锁。之后其余线程在重试过程中就成功查询缓存命中了重建数据。

互斥锁的流程图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-azeAoiqm-1673507243266)(C:\Users\zhuhuanjie\AppData\Roaming\Typora\typora-user-images\image-20230111162937081.png)]


2.1.1 互斥锁的优缺点

优点:

  • 没有额外的内存消耗
  • 保证一致性(数据库和redis数据一致)
  • 实现简单

缺点:

  • 线程需要等待,性能受影响
  • 可能有死锁风险(一个方法里有多个查询操作,另一个方法也有多个重合的查询操作)

2.1.2 互斥锁的代码实现

我们先设定一个场景:假设这是一个电商平台,我们通过id去查询店铺信息。

代码实现流程图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QJe2Cbyn-1673507243267)(C:\Users\zhuhuanjie\AppData\Roaming\Typora\typora-user-images\image-20230112102744196.png)]

首先我们编写获取锁和释放锁的方法,如下所示:

//获取锁
private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);
}//释放锁
private void unLock(String key) {stringRedisTemplate.delete(key);
}

然后编写一个解决缓存击穿问题的方法,最后写一个调用解决方法的业务方法:

@Override
public Result queryById(Long id) {//缓存空对象解决 缓存穿透//Shop shop = queryWithPassThrough(id);//互斥锁解决 缓存击穿Shop shop = queryWithMutex(id);if (shop == null) {return Result.fail("店铺不存在!");}return Result.ok(shop);
}public Shop queryWithMutex(Long id) {//1.从redis查询商铺缓存String key = CACHE_SHOP_KEY + id;String shopJson = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {//3.存在,直接返回return JSONUtil.toBean(shopJson, Shop.class);}//此时 shopJson 不是为null就是为""if (shopJson != null) {//为""直接返回错误信息,为null查询数据库return null;}//4.实现缓存重建//4.1.获取互斥锁String lockKey = "lock:shop:" + id;Shop shop = null;try {boolean isLock = tryLock(lockKey);//4.2.判断是否获取成功while (!isLock) {//4.3.失败,则休眠重试Thread.sleep(50);return queryWithMutex(id);}//4.4.获取锁成功,再次检测缓存释放存在(double check)String cacheShopJson = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(cacheShopJson)) {//4.5.存在,直接返回return JSONUtil.toBean(cacheShopJson, Shop.class);}//5.缓存数据不存在,根据id查询数据库shop = getById(id);//模拟重建的延时Thread.sleep(200);//6.不存在,返回错误if (shop == null) {//缓存空值stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}//7.存在,写入redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {//8.释放锁unLock(lockKey);}return shop;
}

2.2 逻辑过期

逻辑过期就是给缓存的数据添加一个逻辑过期字段,而不是真正的给它设置一个TTL。每次查询缓存的时候去判断是否已经超过了我们设置的逻辑过期时间,如果未过期,直接返回缓存数据;如果已经过期则进行缓存重建。

逻辑过期的流程图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O7g61ksR-1673507243267)(C:\Users\zhuhuanjie\AppData\Roaming\Typora\typora-user-images\image-20230111163351906.png)]

解释:第一个线程到来之后发现逻辑过期,于是获取互斥锁,再开启一个新线程去进行缓存重建。当后续线程到来时,发现缓存已过期,尝试获取互斥锁也失败,但是此时不进行等待重试,而是直接返回过期数据。之后第一个线程成功缓存数据释放互斥锁之后,后面线程继续来访,发现命中缓存并且没有过期,返回重建数据。


2.2.1 逻辑过期的优缺点

优点:

  • 线程无需等待,性能较好

缺点:

  • 不保证一致性(因为会返回过期数据)
  • 有额外的内存消耗(同时缓存了逻辑过期时间的字段)
  • 实现复杂

2.2.2 逻辑过期的代码实现

我们先设定一个场景:假设这是一个电商平台,我们通过id去查询店铺信息。

代码实现流程图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MLB2j8pP-1673507243268)(C:\Users\zhuhuanjie\AppData\Roaming\Typora\typora-user-images\image-20230112143640211.png)]

1)构建存储类

我们想要实现逻辑过期,首先得清楚redis中到底要存储什么样的数据?我们是不是要在每个类中都添加一个逻辑过期的字段?这是不对的,如果我们再每个类中都添加了一个逻辑过期时间字段,这样对原代码就有了 侵入性 ,我们应该使整个系统具有可拓展性,所以我们应该新建一个类来填充要存入redis的数据,代码如下:

@Data
public class RedisData {private LocalDateTime expireTime;private Object data;
}

2)创建线程池

由于我们需要开启独立线程去重建缓存,所以我们可以选择创建一个线程池。

private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

3)编写缓存重建的代码

缓存重建就是直接查询数据库,将查询到的数据缓存到redis中。

public void saveShop2Redis(Long id, Long expireSeconds) throws InterruptedException {//1.查询店铺数据Shop shop = getById(id);//2.封装逻辑过期时间RedisData redisData = new RedisData();redisData.setData(shop);//设置逻辑过期时间redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}

4)编写业务方法并调用缓存击穿方法

@Override
public Result queryById(Long id) {//缓存空对象解决 缓存穿透//Shop shop = queryWithPassThrough(id);//互斥锁解决 缓存击穿//Shop shop = queryWithMutex(id);//逻辑过期解决 缓存击穿Shop shop = queryWithLogicalExpire(id);if (shop == null) {return Result.fail("店铺不存在!");}return Result.ok(shop);
}public Shop queryWithLogicalExpire(Long id) {//1.从redis查询商铺缓存String key = CACHE_SHOP_KEY + id;String shopJson = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在if (StrUtil.isBlank(shopJson)) {//未命中,直接返回空return null;}//3.命中,判断是否过期RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);Shop cacheShop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {//3.1未过期,直接返回店铺信息return cacheShop;}//3.2.已过期,缓存重建//3.3.获取锁String lockKey = LOCK_SHOP_KEY + id;boolean flag = tryLock(lockKey);if (flag) {//3.4.获取成功//4再次检查redis缓存是否过期,做double checkshopJson = stringRedisTemplate.opsForValue().get(key);//4.1.判断是否存在if (StrUtil.isBlank(shopJson)) {//未命中,直接返回空return null;}//4.2.命中,判断是否过期redisData = JSONUtil.toBean(shopJson, RedisData.class);cacheShop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {//4.3.未过期,直接返回店铺信息return cacheShop;}CACHE_REBUILD_EXECUTOR.submit(() -> {//5.重建缓存try {this.saveShop2Redis(id, 20L);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放锁unLock(lockKey);}});}//7.获取失败,返回旧数据return cacheShop;
}

http://chatgpt.dhexx.cn/article/jUiFahvc.shtml

相关文章

Redis 缓存穿透、缓存击穿、缓存雪崩

文章目录 一、缓存穿透1. 概念2. 解决方案 二、缓存击穿1. 概念2. 解决方案 三、缓存雪崩1. 概念2. 解决方案 一、缓存穿透 1. 概念 key 对应的数据在redis中并不存在,每次针对此 key的请求从缓存获取不到,请求转发到数据库,访问量大了可能…

Redis中的缓存穿透、雪崩、击穿的原因以及解决方案(详解)

一、概述 ① 缓存穿透:大量请求根本不存在的key(下文详解) ② 缓存雪崩:redis中大量key集体过期(下文详解) ③ 缓存击穿:redis中一个热点key过期(大量用户访问该热点key,…

Redis——缓存击穿、穿透、雪崩

1、缓存穿透: (1)问题描述:key对应的数据并不存在,每次请求访问key时,缓存中查找不到,请求都会直接访问到数据库中去,请求量超出数据库时,便会导致数据库崩溃。如一个用…

ping端口

1.ping端口: telnet 62.78.63.209 10105 2.如果提示telnet不是内部或外部命令… 则:右击“此电脑”–>选择“控制面板主页”,单击“程序”,如图: 3.完成。

windows下ping端口

上图的操作完成以后 进入dos控制台 输入telnet ip地址 端口号 回车 标识已ping通 ping不通是这种提示 转载于:https://www.cnblogs.com/shianliang/p/8639392.html

windows ping 端口测试

前言 项目中存在能ping通IP地址,但是打不开web应用程序,这时需要测试端口能否ping通。 解决方法 测试是否能够ping通192.168.0.1的80端口 telnet 192.168.0.1 80遇到问题 ‘telnet’ 不是内部或外部命令,也不是可运行的程序 或批处理文件。 …

Windows Server 启用或关闭ping端口

启用或关闭 ping 端口有两种方式,如下 1、通过命令行设置 打开 ping 端口 netsh firewall set icmpsetting 8关闭 ping 端口 netsh firewall set icmpsetting 8 disable2、通过高级安全 Windows 防火墙设置 打开 “高级安全 Windows 防火墙” ,在入…

windows系统ping端口及利用telnet命令Ping 端口

第一步: 第二步:打开telnet client 第三步:telnet ip 端口

Mac系统用命令打开ping端口的方法

电脑上不了网问题相信大家都有遇到过,排查网络故障问题用到ping,之前windows系统上能够轻松打开ping端。但是如果在Mac电脑该如何打开ping端口?其实打开方法并不复杂,接下来小编给大家演示Mac系统用命令打开ping端口的方法。 Mac…

linux ping 某个端口,linux 怎么ping 端口

满意答案 cysdmt 推荐于 2016.12.05 采纳率:54% 等级:8 已帮助:864人 linux命令ping用法详解 功能说明:检测主机。 语 法:ping [-dfnqrrv][-c][-i][-i][-l][-p][-s][-t][主机名称或ip地址] 补充说明:执行…

Win10系统Ping端口及利用telnet命令Ping 端口

启用 telnet 客户端组件为 Ping 端口做准备 在程序界面下,选择“打开或关闭Windows功能”,如下图所示: 在打开的对话框中,找到“Telnet客户端”并勾选。最后点击“确定”,等待几分钟,系统将会为你开启Teln…

计算机ping使用的端口,mac系统ping端口命令怎么使用

最近电脑出问题了,上不了网,想排查下电脑的问题出在哪儿就需要ping,但是该如何打开终端如何ping呢?下面就让学习啦小编教大家MAC系统ping端口命令怎么使用吧。 Mac系统ping端口命令的使用方法 方法一: 在桌面上的DOCK栏中&#x…

w7系统怎么ping服务器,win7系统中如何ping端口命令

正常情况下,win7系统时无法ping端口的,不过要是开启了Telnrt客户端,则可以实现这一功能,启用Telnrt客户端需要用户进入Windows 组件中勾选,当然,如果用户有一个可以专门Ping端口的第三方软件也是可以的。 一…

Win10如何ping端口是否开放

如何ping端口是否开放 前言一、打开Telent Client功能1.打开控制面板2.进入启用Windows功能页面,勾选Telnet客户端3.打开cmd,输入命令 telnet ip 端口4.回车如果进入黑屏页面,则ping通了,否则显示连接失败 前言 ping具体的网址是…

自制python版 在线ping 端口检查工具 python3.5 +docker

自己有几十台服务器需要实现在线检查是否在线和网络延时情况 1.开搞。 docker run -d -p 8000:8000 -i -t -v /pyFile:/usr/src/python python:3.5 /bin/bash 在docker 部署python3.5https://blog.csdn.net/qq_44741568/article/details/120035609 2. 进入容器 &#xff0c…

ping端口的方法 - win下

方法 默认的cmd > ping xx.xx.xx.xx 是无法带端口测试的,需要工具tcping.exe帮忙下载tcping.exe,存放到任意位置,例如D:\tcping小程序下载-tcping工具(tcping.exe)免费版 - 极光下载站tcping工具可以为用户检测非常多的网络问题&#xff…

ping端口神器psping

简介 测试服务端端口是否正常,通过ping命令不能测试端口是否打开、是否可正常连通。当然Telnet也可以解决。本文分享的工具是sysInternals工具集下的一个小工具psPing,这个工具支持Ping:ICMP、TCPorUDP,以及延迟分析和带宽测试。 …

计算机ping使用的端口,Win7系统中如何Ping端口?Ping端口命令的用法

Win7系统中如何Ping端口?正常情况下,Win7系统是无法Ping端口的,不过如果启用了Telnrt客户端,则可以实现这一功能,启用Telnrt客户端需要用户进入Windows 组件中勾选,当然,如果用户有一个可以专门…

计算机ping使用的端口,Windows7系统中怎么Ping端口?利用telnet命令Ping 端口的方法...

通常,Win7系统是无法Ping端口的,不过如果你要开启了telnet客户端即可实现ping端口的功能。Windows7系统中怎么Ping端口?下面装机之家小编教你利用telnet命令Ping 端口的方法。 Windows7系统中怎么Ping端口? 一、启用 telnet 客户端…

Windows如何ping端口

cmd中原生带有ping命令,但ping命令的缺点是无法指定目标端口,如果目标主机只开放了部分端口,则ping命令无法返回正确的结果。 有两种方法可以ping端口 使用tcping.exe工具电脑开启Telnet服务 一,使用tcping.exe工具 1&#xff…