高并发下缓存失效问题-缓存穿透、缓存击穿、缓存雪崩、Redis分布式锁简单实现、Redisson实现分布式锁

文章目录

  • 缓存基本使用范式暴露的几个问题
  • 缓存失效问题---缓存穿透
  • 缓存失效问题---缓存击穿
    • 一、单机锁
      • 正确的锁粒度
      • 不正确的锁粒度无法保证查询数据库次数是唯一
    • 二、分布式锁
      • getCatalogJsonData()
      • 分布式锁演进---基本原理
      • 分布式锁(加锁)演进一:删锁失败导致死锁
      • 分布式锁(加锁)演进二:给‘锁’设置过期时间防止死锁
      • 分布式锁(加锁)演进三:必须保证过期时间和占锁动作原子性
      • 分布式锁(解锁)演进一:业务逻辑执行时间大于‘锁’的过期时间
      • 分布式锁(解锁)演进二:UUID保证删除的是自己的‘锁’
      • 分布式锁(解锁)演进三:lua脚本保证删‘锁’原子性
    • 三、锁的自动续期
    • 四、Redis简单实现分布式锁的完整代码
  • 缓存失效问题---缓存雪崩
  • 分布式锁---Redisson

缓存基本使用范式暴露的几个问题

{1、先查询缓存2、if(缓存没有命中){2.1、查询数据库2.2、查询结果放入缓存2.3、同时return结果}3、缓存命中直接return缓存数据
}

如下;使用缓存高效的查询‘三级分类’数据,就完全遵循上面提到的范式

    public Map<Long, List<Catalog2VO>> getCatalogJsonBaseMethod() {String key = ProductConstant.RedisKey.INDEX_CATEGORY_JSON;// 1、从缓存中获取数据String categoryListFromCache = redisTemplate.opsForValue().get(key);if (!StringUtils.hasText(categoryListFromCache)) {// 2.1、缓存没有命中,查询数据库Map<Long, List<Catalog2VO>> catalogJsonFromDB = getCatalogJsonFromDB();// 2.2、将查询结果放入缓存redisTemplate.opsForValue().set(key,JSON.toJSONString(catalogJsonFromDB));return catalogJsonFromDB;}// 3、缓存命中便直接returnreturn JSON.parseObject(categoryListFromCache, new TypeReference<>() {});}

该范式在高并发、分布式下会暴露以下几个问题,这也是本章需要解决和讨论的点

  • 高并发缓存失效之缓存穿透
  • 高并发缓存失效之缓存击穿
  • 高并发缓存失效之缓存雪崩
  • 分布式架构下的分布式锁

缓存失效问题—缓存穿透

请求查询一个百分百不存在的数据

假设id=idooy这条记录在数据库中压根不存在;按照请求处理逻辑先查询缓存,但因为这本就是一条不存在的记录(假设成立),因此缓存也不可能命中,缓存不命中接着就会查询数据库;如果没有将这一次请求查询的null写入缓存,这将导致id=idooy这条请求每次都要去数据库,直接失去了缓存的意义

风险: 利用不存在的数据发送大量请求,数据库瞬时压力增大,最终导致数据库崩溃
解决: 将null结果进行缓存,并加入短暂的过期时间;有时查询固定的值,不需要请求携带参数,这种情况本身就不会出现缓存穿透

缓存失效问题—缓存击穿

某一个Key在高并发请求期间刚好过期失效

对于一个设置了过期时间的Key,如果这个Key在将来的某个时间被高并发访问期间刚好过期失效,那么高并发的请求压力直接给到数据库
解决: 加锁;对同一个Key的高并发请求保证只有一个请求打给数据库;其他请求等待并最终从缓存中获取;下面讨论单机锁分布式锁

一、单机锁

单机锁是指在单体应用中或同一个进程中利用锁的排他性保证高并发期间某个Key失效时只有一个请求去数据库进行查询来避免缓存击穿

代码实现如下所示:

    @Overridepublic Map<Long, List<Catalog2VO>> getCatalogJson() {String key = ProductConstant.RedisKey.INDEX_CATEGORY_JSON;// 1、从缓存中获取数据String categoryListFromCache = redisTemplate.opsForValue().get(key);if (!StringUtils.hasText(categoryListFromCache)) {// 2、缓存没有命中,查询数据库,加锁保证数据库只查询一次// 因为当前this实例为单例,故可以作为锁资源使用synchronized (this) {// 2.1、高并发下必然有N个请求同时等待竞争锁,所以竞争到锁的第一件事就是再查一遍缓存String result = redisTemplate.opsForValue().get(key);if (StringUtils.hasText(result)) {return JSON.parseObject(result, new TypeReference<>() {});}// 2.2、缓存依旧没有命中的情况下查询数据库Map<Long, List<Catalog2VO>> catalogJsonFromDB = getCatalogJsonFromDB();// 2.3、将查询结果放入缓存redisTemplate.opsForValue().set(key,JSON.toJSONString(catalogJsonFromDB),2,TimeUnit.HOURS);return catalogJsonFromDB;}}// 3、缓存命中便直接returnreturn JSON.parseObject(categoryListFromCache, new TypeReference<>() {});}

正确的锁粒度

在这里插入图片描述

不正确的锁粒度无法保证查询数据库次数是唯一

在这里插入图片描述
在这里插入图片描述

二、分布式锁

上面单机锁本质就是使用当前进程中的某个单例对象充当锁资源;在微服务架构分布式部署下,同一个商品服务可能部署N多个,此时每个服务进程之间相互隔离。

在这里插入图片描述
因此;本地锁,只能锁住当前进程,分布式架构下需要分布式锁

getCatalogJsonData()

    private Map<Long, List<Catalog2VO>> getCatalogJsonData() {String key = ProductConstant.RedisKey.INDEX_CATEGORY_JSON;String result = redisTemplate.opsForValue().get(key);if (!StringUtils.hasText(result)) {Map<Long, List<Catalog2VO>> catalogJsonFromDB = getCatalogJsonFromDB();redisTemplate.opsForValue().set(key,JSON.toJSONString(catalogJsonFromDB),2,TimeUnit.HOURS);return catalogJsonFromDB;}return JSON.parseObject(result, new TypeReference<>() {});}

分布式锁演进—基本原理

所有的‘商品服务’可以同时去一个地方“占坑”,如果占到就执行逻辑,否则就必须等待,直到释放锁。
“占坑”可以去Redis,也可以去数据库,可以去任何只要“商品服务”都能访问到的地方
在这里插入图片描述

分布式锁(加锁)演进一:删锁失败导致死锁

在这里插入图片描述
如上图;执行业务逻辑出现异常或者在删锁前系统宕机(kill -9);直接导致没有执行删锁操作。那么其他请求就无法"成功占锁",造成死锁。

接下来给"锁"设置过期时间防止死锁。即使删锁失败也会自动删除

分布式锁(加锁)演进二:给‘锁’设置过期时间防止死锁

在这里插入图片描述
所以,“占锁+设置过期时间”必须保证原子性

分布式锁(加锁)演进三:必须保证过期时间和占锁动作原子性

Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "ok",3,TimeUnit.SECONDS);

在这里插入图片描述

分布式锁(解锁)演进一:业务逻辑执行时间大于‘锁’的过期时间

业务逻辑执行时间超过‘锁’的过期时间;这也就意味着业务逻辑执行完毕以后删的就不是自己的锁。
试想如下高并发场景下, 假设‘锁’的过期时间为10s,业务的执行时间为15s;

①号请求执行到第10s,‘锁’自动过期;②号请求立马占锁成功执行业务逻辑。
在第15s①号业务逻辑执行完毕,成功删除锁。很显然此时①号删除的就不是自己的锁(自己的锁在第10s的时候已自动删除了),而是②号的锁。
同时在15s这一时刻①号删了②号的锁;接着3号占锁成功,如此情况下‘锁永久失效’

在这里插入图片描述
该况下暴露的问题本质就是锁删除了他人的锁;那么接下来就通过唯一ID保证线程删除的是自己的锁

分布式锁(解锁)演进二:UUID保证删除的是自己的‘锁’

在占锁的时候,值指定为uuid,每个人匹配是自己的锁才删除
在这里插入图片描述
如图;问题还是暴露了出来。get(“lock”)并且equals成立,此时锁刚好自动过期删除了,另一个线程占锁成功了,此时再执行delete删锁同样删除的不是自己的锁。
所以这个问题的本质就是删锁的过程不能保证原子性

分布式锁(解锁)演进三:lua脚本保证删‘锁’原子性

如下图;官方提供了‘解锁’的建议和保证解锁过程原子性的lua脚步

  • 锁的值不要设置固定字符串,而是设置一个不可猜测的大随机字符串,称为token。
  • 不是用DEL释放锁,而是发送一个脚本,仅在值匹配时才删除键
    在这里插入图片描述

根据官方提示;解锁的核心业务代码片段

// 解锁
redisTemplate.execute(new DefaultRedisScript<>(getLuaScript(),Long.class),Arrays.asList("lock"),uuid);private String getLuaScript(){return "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +"then\n" +"    return redis.call(\"del\",KEYS[1])\n" +"else\n" +"    return 0\n" +"end";}

在这里插入图片描述

三、锁的自动续期

业务执行时间超长;业务逻辑还未执行完毕‘锁’自动过期了,最简单的方式就是给‘锁’设置足够长的时间。
但完美的解决该问题,自己写代码实现还是很困难的,所以这个问题就抛出Redisson,它提供的分布式锁会解决上面提到的所有问题;包括锁的自动续期

四、Redis简单实现分布式锁的完整代码

  • 加锁原子性命令;保证’设置过期时间和占锁’是原子性操作
  • 解锁原子性命令;uuid保证删的是自己的锁;lua脚本保证了删锁的原子性
  • 设置‘锁’的过期时间足够长,确保业务逻辑执行时间不会超过过期时间这种简单粗暴的方式来解决‘锁’过期自动续期的问题
private Map<Long, List<Catalog2VO>> getCatalogJsonWithRedisLock() {// 所有的请求进来先占坑,即抢占锁String uuid = UUID.randomUUID().toString();// 原子性命令;保证'设置过期时间和占锁'是原子性操作Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3,TimeUnit.SECONDS);if (lock) {// "占坑"成功,执行业务逻辑Map<Long, List<Catalog2VO>> result;try {result = getCatalogJsonData();} finally {// 解锁:uuid保证删的是自己的锁;lua脚本保证了删锁的原子性redisTemplate.execute(new DefaultRedisScript<>(getLuaScript(),Long.class),Arrays.asList("lock"),uuid);}return result;}else {// "占坑"失败,自旋try {// 防止栈溢出TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {throw new RuntimeException(e);}return getCatalogJsonWithRedisLock();}}private String getLuaScript(){return "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +"then\n" +"    return redis.call(\"del\",KEYS[1])\n" +"else\n" +"    return 0\n" +"end";}private Map<Long, List<Catalog2VO>> getCatalogJsonData() {String key = ProductConstant.RedisKey.INDEX_CATEGORY_JSON;String result = redisTemplate.opsForValue().get(key);if (!StringUtils.hasText(result)) {Map<Long, List<Catalog2VO>> catalogJsonFromDB = getCatalogJsonFromDB();redisTemplate.opsForValue().set(key,JSON.toJSONString(catalogJsonFromDB),2,TimeUnit.HOURS);return catalogJsonFromDB;}return JSON.parseObject(result, new TypeReference<>() {});}

缓存失效问题—缓存雪崩

某一时刻大量的Key同时失效

假设缓存中大量的Key使用了相同过期时间,这直接导致在将来的某个时刻这些Key同时失效;此时再大量请求这些Key压力都来到了数据库,使数据库瞬时压力过大可能出现崩溃
解决: 再原有的失效时间上增加一个随机值,这样每个缓存的过期时间的重复率就会很低,也就很难出现Key大面积同时失效导致缓存雪崩问题

// 再原有的失效时间基础上添加随机时间片
// 这里没有增加随机时间片,因为Key的数量有限,足以保证失效时间的离散分布
redisTemplate.opsForValue().set(key,JSON.toJSONString(catalogJsonFromDB),2,TimeUnit.HOURS);

分布式锁—Redisson

上面基于Redis的setnx命令简单的实现了一个分布式锁,并在实现的过程中暴露出许多问题,也都一一的解决了。但是官方建议还是使用redlock来实现分布式锁

注意:Redlock算法实现起来稍微复杂一点,但提供了更好的保证和容错能力
在这里插入图片描述
这里边就存在针对Java的实现Redisson
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/189956.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

牛客小白月赛82(A~C)

目录 A.谜题&#xff1a;质数 输入描述 输出描述 输入 输出 解析 B.Kevin逛超市 2 (简单版本) 输入描述 输出描述 输入 输出 思路 C.被遗忘的书籍 题目描述 输入描述 输出描述 输入 输出 输入 输出 思路 比赛链接 牛客小白月赛82_ACM/NOI/CSP/CCPC/ICPC算…

C++基础 -28- 友元

友元用于访问类中的所有数据成员 类中的私有成员&#xff0c;类外不可访问 定义友元的格式&#xff08;友元函数必须要在类内&#xff0c;声明&#xff09; friend void show(person &b); 使用友元访问类的所有成员 #include "iostream"using namespace std…

Istio可观测性

Istio可观测性 image-20231129072302901 前言 Istio 为网格内所有的服务通信生成详细的遥测数据。这种遥测技术提供了服务行为的可观测性&#xff0c;使运维人员能够排查故障、维护和优化应用程序&#xff0c;而不会给开发人员带来其他额外的负担。通过 Istio&#xff0c;运维…

好用的桌面管理软件推荐

随着电脑的普及&#xff0c;桌面管理软件已经成为我们日常生活和工作中不可或缺的一部分。一个好的桌面管理软件可以帮助我们更高效地组织和管理电脑上的文件和应用程序&#xff0c;提高我们的工作效率。下面&#xff0c;我将为大家推荐几款好用的桌面管理软件。 1、腾讯桌面整…

Nacos 注册中心下载到搭建详细步骤【微服务】

文章目录 一、下载与安装二、Nacos 服务注册1. 引入依赖2. 修改配置文件3. 开启 Nacos 注解4. 启动项目 三、Nacos 服务集群1. 模拟多实例部署2. 配置集群属性3. 服务权重配置 四、Nacos 环境隔离五、Nacos 注册中心原理1. Nacos 与 Eureka 比较2. 配置非临时实例 一、下载与安…

LeNet对MNIST 数据集中的图像进行分类--keras实现

我们将训练一个卷积神经网络来对 MNIST 数据库中的图像进行分类&#xff0c;可以与前面所提到的CNN实现对比CNN对 MNIST 数据库中的图像进行分类-CSDN博客 加载 MNIST 数据库 MNIST 是机器学习领域最著名的数据集之一。 它有 70,000 张手写数字图像 - 下载非常简单 - 图像尺…

MMdetection3.0 问题:DETR验证集上AP值全为0.0000

MMdetection3.0 问题&#xff1a;DETR验证集上AP值全为0.0000 条件&#xff1a; 1、选择的是NWPU-VHR-10数据集&#xff0c;其中训练集455张&#xff0c;验证、测试相同均为195张。 2、在Faster-rcnn、YOLOv3模型上均能训练成功&#xff0c;但是在DETR训练时&#xff0c;出现验…

《地理信息系统原理》笔记/期末复习资料(7. 空间分析)

目录 7. 空间分析 7.1 空间分析的内容与步骤 7.2 数据检索及表格分析 7.2.1 属性统计分析 7.2.2 布尔逻辑查询 7.2.3 空间数据库查询语言 7.2.4 重分类&#xff0c;边界消除与合并 7.3 叠置分析 7.3.1 栅格系统的叠加分析 7.3.2 矢量系统的叠加分析&#xff08;拓扑叠…

视频后期特效处理软件 Motion 5 mac中文版

Motion mac是一款运动图形和视频合成软件&#xff0c;适用于Mac OS平台。 Motion mac软件特点 - 精美的效果&#xff1a;Motion提供了多种高质量的运动图形和视频效果&#xff0c;例如3D效果、烟雾效果、粒子效果等&#xff0c;方便用户制作出丰富多彩的视频和动画。 - 高效的工…

设计模式-结构型模式之桥接设计模式

文章目录 三、桥接模式 三、桥接模式 桥接模式&#xff08;Bridge&#xff09;是用于把抽象化与实现化解耦&#xff0c;使得二者可以独立变化。它通过提供抽象化和实现化之间的桥接结构&#xff0c;来实现二者的解耦。 这种模式涉及到一个作为桥接的接口&#xff0c;使得实体类…

Windows本地搭建Emby媒体库服务器并实现远程访问「内网穿透」

文章目录 1.前言2. Emby网站搭建2.1. Emby下载和安装2.2 Emby网页测试 3. 本地网页发布3.1 注册并安装cpolar内网穿透3.2 Cpolar云端设置3.3 Cpolar内网穿透本地设置 4.公网访问测试5.结语 1.前言 在现代五花八门的网络应用场景中&#xff0c;观看视频绝对是主力应用场景之一&…

jQuery选择器、操作DOM、事件处理机制、动画、ADJX操作知识点梳理

jQuery 核心理念就是写的更少&#xff0c;做的更多实现的代码更加简洁有效的提高开发效率 jQuery跟JavaScript的用法是不一样的 跟jQuery相继诞生的JavaScript库还有很多&#xff0c;不包括node.js 关于代码$("li").get(0)&#xff0c;获取DOM对象 jQuery对象声…

前缀和 LeetCode1094 拼车

1094. 拼车 车上最初有 capacity 个空座位。车 只能 向一个方向行驶&#xff08;也就是说&#xff0c;不允许掉头或改变方向&#xff09; 给定整数 capacity 和一个数组 trips , trip[i] [numPassengersi, fromi, toi] 表示第 i 次旅行有 numPassengersi 乘客&#xff0c;接…

Tomcat 漏洞修复

1、去掉请求响应中Server信息 修复方法&#xff1a; 在Tomcat的配置文件的Connector中增加 server" " &#xff0c;server 的值可以改成你任意想返回的值。

Elasticsearch:什么是自然语言处理(NLP)?

自然语言处理定义 自然语言处理 (natural language processing - NLP) 是人工智能 (AI) 的一种形式&#xff0c;专注于计算机和人们使用人类语言进行交互的方式。 NLP 技术帮助计算机使用我们的自然交流模式&#xff08;语音和书面文本&#xff09;来分析、理解和响应我们。 自…

一进三出宿舍限电模块的改造升级

一进三出宿舍限电模块改造升级石家庄光大远通电气有限公司智能模块功能特点&#xff1a; 电能控制功能&#xff1a;可实施剩余电量管理&#xff0c;电量用完时将自动断电&#xff1b; 剩余电量可视报警提示功能&#xff1a;剩余电量可视&#xff0c;并当电量剩余5度时&#xff…

C#和MySQL技巧分享:日期的模糊查询

文章目录 前言一、EF Core 模糊查询二、MySql 日期模糊查询分析和优化2.1 测试环境准备2.1.1 创建数据库2.1.2 查看测试数据 2.2 查询日期的运行效率对比2.3 运行效率优化 三、EF Core 模糊查询优化3.1 字符串转日期3.2 使用日期格式查询 四、优化建议总结 前言 在处理数据库查…

go开发之个人微信号机器人开发

简要描述&#xff1a; 下载消息中的文件 请求URL&#xff1a; http://域名地址/getMsgFile 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 参数&#xff1a; 参数名必选类型…

Linux Spug自动化运维平台本地部署与公网远程访问

文章目录 前言1. Docker安装Spug2 . 本地访问测试3. Linux 安装cpolar4. 配置Spug公网访问地址5. 公网远程访问Spug管理界面6. 固定Spug公网地址 前言 Spug 面向中小型企业设计的轻量级无 Agent 的自动化运维平台&#xff0c;整合了主机管理、主机批量执行、主机在线终端、文件…