当前位置: 首页 > news >正文

【遇见青山】项目难点:缓存击穿问题解决方案

【遇见青山】项目难点:缓存击穿问题解决方案

  • 1.缓存击穿
    • 互斥锁🔒方案
    • 逻辑过期方案
  • 2.基于互斥锁方案的具体实现
  • 3.基于逻辑过期方案的具体实现

1.缓存击穿

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

在这里插入图片描述

常见的解决方案有两种:

  • 互斥锁
  • 逻辑过期

互斥锁🔒方案

给线程添加互斥锁🔒

在这里插入图片描述

优点:

  • 没有额外的内存消耗
  • 保证一致性
  • 实现简单

缺点:

  • 线程需要等待,性能受影响
  • 可能有死锁风险

逻辑过期方案

缓存中维护一个expire字段,代表逻辑过期时间(并非TTL值)

例如:

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

优点:

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

缺点:

  • 不保证一致性
  • 有额外内存消耗
  • 实现复杂

2.基于互斥锁方案的具体实现

需求:修改根据id查询商铺的业务,基于互斥锁方式来解决缓存击穿问题

架构流程图:

在这里插入图片描述

首先自定义获取锁和释放锁的方法:

/*** 尝试获取锁,解决缓存击穿问题方案** @param key key*/
private boolean tryLock(String key) {Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(aBoolean);
}/*** 删除锁,解决缓存击穿问题方案** @param key key*/
private void unlock(String key) {stringRedisTemplate.delete(key);
}

核心代码:

/*** 缓存击穿解决方案** @param id 商户id* @return 商户对象*/
public Shop queryWithMutex(Long id) {// 从redis查询商户缓存String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);// 判断商户缓存是否存在if (StringUtils.isNotBlank(shopJson)) {// 此商户缓存存在,直接返回结果return JSONUtil.toBean(shopJson, Shop.class);}// 判断命中的是否为空值 "",防止缓存穿透if ("".equals(shopJson)) {return null;}Shop shop = null;try {// 实现缓存重建// 获取互斥锁boolean isLock = tryLock(LOCK_SHOP_KEY + id);// 判断是否取锁成功if (!isLock) {// 失败,则进入休眠并重试Thread.sleep(50);return queryWithMutex(id);}// 缓存中商户信息不存在,查询数据库shop = getById(id);// 模拟重建的延时Thread.sleep(200);// 查询数据库不存在,返回错误if (shop == null) {// 将null值写入Redis,防止缓存穿透问题stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}// 查询数据库存在,写入数据到Redis中stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {// 释放互斥锁unlock(LOCK_SHOP_KEY + id);}// 返回数据给前端return shop;
}

使用JMeter做压力测试:

这里开1000个线程延时5秒:

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

结果:

全部请求成功!

在这里插入图片描述

平均QPS 200:

在这里插入图片描述


3.基于逻辑过期方案的具体实现

需求:修改根据id查询商铺的业务,基于逻辑过期方式来解决缓存击穿问题

设计架构图:

在这里插入图片描述

这里,我们如何将过期时间字段加入Redis中呢,为了不对原有的代码进行修改,最好的解决办法是封装一个带有过期时间的实体类:

/*** 逻辑过期时间的实体支持*/
@Data
public class RedisData {private LocalDateTime expireTime;private Object data;
}

创建一个将商户预热的方法:

/*** 将热点商户加入到缓存中,进行预热** @param id            商户id* @param expireSeconds 逻辑过期时间*/
public void saveShopToRedis(Long id, Long expireSeconds) {// 查询商户数据Shop shop = getById(id);// 封装逻辑过期时间对象RedisData redisData = new RedisData();redisData.setData(shop);redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));// 写入RedisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}

进行测试:

@SpringBootTest
@RunWith(SpringRunner.class)
public class QingShanApplicationTests {@Resourceprivate ShopServiceImpl shopService;/*** 添加热点商户到Redis缓存的测试类*/@Testpublic void testSaveShop() {shopService.saveShopToRedis(1L, 10L);}
}

添加热点缓存成功!

在这里插入图片描述

核心实现:

// 生成线程池
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);/*** 逻辑过期解决缓存击穿问题* @param id 商户id* @return 商户对象*/
public Shop queryWithLogicalExpire(Long id){// 组装keyString key = RedisConstants.CACHE_SHOP_KEY + id;// 去redis中读取数据String value = stringRedisTemplate.opsForValue().get(key);// 如果缓存未命中,说明访问的不是热点数据,直接返回空if(StrUtil.isBlank(value)){return null;}// 缓存命中后,还需要先判断数据有没有过期RedisData redisData = JSONUtil.toBean(value, RedisData.class);// 获取数据信息,由于数据存进去时是object类型,这里需要做一下处理Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);// 获取数据的逻辑过期时间LocalDateTime expireTime = redisData.getExpireTime();// 如果数据没有过期,就直接返回if(expireTime.isAfter(LocalDateTime.now())){return shop;}// 如果数据已过期,则尝试获取互斥锁String lockKey = RedisConstants.LOCK_SHOP_KEY+id;boolean tryLock = tryLock(lockKey);if(tryLock){// 获取锁成功则开辟一条独立线程执行缓存重建CACHE_REBUILD_EXECUTOR.submit(()->{try {// 再次检查缓存有没有过期,防止在高并发环境下缓存多次重建LocalDateTime time = JSONUtil.toBean(stringRedisTemplate.opsForValue().get(key), RedisData.class).getExpireTime();if(time.isAfter(LocalDateTime.now())){// 数据没过期则直接结束return;}// 调用重建缓存的方法saveShopToRedis(id,10l);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放锁操作unlock(lockKey);}});}// 返回过期数据return shop;
}

相关文章:

【遇见青山】项目难点:缓存击穿问题解决方案

【遇见青山】项目难点:缓存击穿问题解决方案1.缓存击穿互斥锁🔒方案逻辑过期方案2.基于互斥锁方案的具体实现3.基于逻辑过期方案的具体实现1.缓存击穿 缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效…...

2023Flag具体实施计划(短期)

重新看了flag ,要做的事情太多,太杂,上周一周时间都在纠结和琢磨,该怎么下手。如何达成小目标。特别是沟通,汇报,演讲能力, 以及整体体系化的思维能力的训练。如何做到多思考,而不是瞎搞。这边重…...

研一寒假C++复习笔记--左值和右值的理解和使用

目录 1--左值和右值的定义 2--简单理解左值和右值的代码 3--非const引用只能接受左值 1--左值和右值的定义 左值:L-Value,L理解为 Location,表示可寻; 右值:R-Value,R理解为 Read,表示可读&a…...

Android 11.0 动态修改SystemProperties中ro开头系统属性的值

需求: 在11.0的产品开发中,对于定制功能的需求很多,有些机型要求可以修改系统属性值,对于系统本身在10.0以后为了系统安全性,不允许修改ro开头的SystemProperties的值,所以如果要求修改ro的相关系统属性&am…...

为什么分库分表

系列文章目录 文章目录系列文章目录前言一、什么是分库分表二、分库分表的原因分库分表三、如何分库分表3.1 垂直拆分1.垂直分库2、垂直分表3.2 水平拆分水平分库水平分表水平分库分表的策略hash取模算法range范围rangehash取模混合地理位置分片预定义算法四、分库分表的问题分…...

1625_MIT 6.828 stabs文档信息整理_下

全部学习汇总: GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 继续之前的学习笔记,整理一下最近看过的一点stabs资料。 这一页中有一半的信息是Fortran专用的,直接跳过。参数的符号修饰符是p&#xff0c…...

论文阅读 | Rethinking Coarse-to-Fine Approach in Single Image Deblurring

前言:ICCV2021图像单帧运动去糊论文 论文地址:【here】 代码地址:【here】 Rethinking Coarse-to-Fine Approach in Single Image Deblurring 引言 图像去糊来自与物体或相机的运动。现有的deblur领域的深度学习方法大多都是coarse-to-fin…...

Mysql 增删改查(二)—— 增(insert)、删(delete)、改(update)

目录 一、插入 1、insert 2、replace(插入否则更新) 二、更新(update) 三、删除 1、delete 2、truncate(截断表,慎用) 一、插入 1、insert (1) 单行 / 多行插入 全列插入:…...

JSD2212复习串讲

1. Java语言基础阶段 这一部分主要是练,给一些题目还有讲解一些最基础的语法,做一些额外的补充 1.1 基本概念 1.2 变量 1.2.1 数据类型 4类8种 基本类型:整形、浮点型、字符型、布尔型 整形:byte -》short-》int-》long 浮点…...

sphinx 升级到6.x后的Jquery问题

sphinx 升级到6.0 后&#xff0c;以前对于jquery的默认引用方式发生了改变以前在编译后的html中jquery是如下引用的&#xff1a;<script src"_static/jquery.js"></script>而升级到6.0后&#xff0c;对于jquery 是一个googleapi的远程jquery调用&#xf…...

NSSCTF Round#8 Basic

from:http://v2ish1yan.top MyDoor 使用php伪协议读取index.php的代码 php://filter/readconvert.base64-encode/resourceindex.php<?php error_reporting(0);if (isset($_GET[N_S.S])) {eval($_GET[N_S.S]); }if(!isset($_GET[file])) {header(Location:/index.php?fi…...

多传感器融合定位十二-基于图优化的建图方法其一

多传感器融合定位十二-基于图优化的建图方法其一1. 基于预积分的融合方案流程1.1 优化问题分析1.2 预积分的作用1.3 基于预积分的建图方案流程2. 预积分模型设计3. 预积分在优化中的使用3.1 使用方法3.2 残差设计3.3 残差雅可比的推导3.3.1 姿态残差的雅可比3.3.2 速度残差的雅…...

RockChip MPP编码

概述瑞芯微提供的媒体处理软件平台&#xff08;Media Process Platform&#xff0c;简称 MPP&#xff09;是适用于瑞芯微芯片系列的通用媒体处理软件平台。该平台对应用软件屏蔽了芯片相关的复杂底层处理&#xff0c;其目的是为了屏蔽不同芯片的差异&#xff0c;为使用者提供统…...

【学习笔记】NOIP暴零赛2

细思极恐&#xff0c;我的能力已经退步到这个地步了吗&#xff1f; 数据结构 这题的修改是强行加进去迷惑你的。 考虑怎么求树的带权重心。 完了我只会树形dp 完了完了 结论&#xff1a;设uuu的子树和为szusz_uszu​&#xff0c;所有点权值和为sss&#xff0c;那么树的带…...

linux基本功系列之hostname实战

文章目录前言一. hostname命令介绍二. 语法格式及常用选项三. 参考案例3.1 显示本机的主机名3.2 临时修改主机名3.3 显示短格式的主机名3.4 显示主机的ip地址四. 永久修改主机名4.1 centos6 修改主机名的方式4.2 centos7中修改主机名永久生效总结前言 大家好&#xff0c;又见面…...

Easy-Es框架实践测试整理 基于ElasticSearch的ORM框架

文章目录介绍&#xff08;1&#xff09;Elasticsearch java 客户端种类&#xff08;2&#xff09;优势和特性分析&#xff08;3&#xff09;性能、安全、拓展、社区&#xff08;2&#xff09;ES版本及SpringBoot版本说明索引处理&#xff08;一&#xff09;索引别名策略&#x…...

【数据结构】双向链表的模拟实现(无头)

目录 前言&#xff1a; 1、认识双向链表中的结点 2、认识并创建无头双向链表 3、实现双向链表当中的一些方法 3.1、遍历输出方法&#xff08;display&#xff09; 3.2、得到链表的长度&#xff08;size&#xff09; 3.3、查找关键字key是否包含在双链表中(contains) 3.…...

vue自定义指令---处理加载图片失败时出现的碎图,onerror事件

目录 一、自定义指令 1、局部注册和使用 2、全局注册和使用 二、自定义指令处理图片加载失败&#xff08;碎图&#xff09; 一、自定义指令 vue中除v-model、v-show等内置指令之外&#xff0c;还允许注册自定义指令&#xff0c;获取DOM元素&#xff0c;扩展额外的功能。 1、局…...

加盟管理系统挑选法则,看完不怕被坑!

经营服装连锁店铺究竟有多难&#xff1f;小编已经不止一次听到身边的老板&#xff0c;抱怨加盟连锁店铺难以管理了&#xff0c;但同时呢&#xff0c;也听到了很多作为加盟商的老板&#xff0c;抱怨总部给的支持和管理不到位。服装加盟店铺管理&#xff0c;到底有哪些难点呢&…...

alertmanager笔记

1 prometheus的思想 所有告警都应该立刻处理掉&#xff0c;不应该存在长时间未解决的告警。所以具体的表现就是高频的数据采集&#xff0c;和告警的自动恢复&#xff08;默认5分钟&#xff09; 2 alertmanager API调用 使用如下命令即可手工制造告警&#xff0c;注意startsA…...

51单片机定时器初值计算与Proteus仿真

51单片机定时器初值计算方法详解1. 定时器基础原理1.1 单片机定时器工作模式51系列单片机内置的定时器/计数器模块是嵌入式系统中实现精确时间控制的核心部件。定时器本质上是一个特殊功能的寄存器&#xff0c;通过累加时钟脉冲实现计时功能。根据位数不同&#xff0c;51单片机…...

Windows右键菜单终极管理指南:3步告别臃肿,打造高效桌面体验

Windows右键菜单终极管理指南&#xff1a;3步告别臃肿&#xff0c;打造高效桌面体验 【免费下载链接】ContextMenuManager &#x1f5b1;️ 纯粹的Windows右键菜单管理程序 项目地址: https://gitcode.com/gh_mirrors/co/ContextMenuManager 你是否曾因Windows右键菜单过…...

TSMaster与珠海创芯CAN卡的集成指南

1. 珠海创芯CAN卡与TSMaster的基础认知 第一次接触珠海创芯CAN卡时&#xff0c;我和很多工程师一样好奇&#xff1a;这个硬件到底有什么特别之处&#xff1f;实测下来发现&#xff0c;它最大的优势在于高性价比和兼容性。珠海创芯的CAN卡采用标准USB接口&#xff0c;支持CAN2.0…...

说说你对spring的IOC的理解

面试 IOC指的就是控制反转&#xff0c;指的就是创建对象的控制权的转移&#xff0c;简单来说&#xff0c;由之前的手动new对象&#xff0c;转换成了由spring自动生产&#xff0c;spring利用java的反射机制&#xff0c;根据配置文件或注解在运行时动态创建并管理对象。...

避坑指南:关系数据库设计中90%人会犯的完整性约束错误(附真实案例)

避坑指南&#xff1a;关系数据库设计中90%人会犯的完整性约束错误&#xff08;附真实案例&#xff09; 在电商大促期间&#xff0c;某平台突然出现大量"幽灵订单"——用户支付成功后订单消失&#xff0c;而库存却异常扣减。技术团队紧急排查发现&#xff0c;问题根源…...

GNN实战:Cora、Citeseer、PubMed三大文献数据集保姆级使用指南(附代码)

GNN实战&#xff1a;Cora、Citeseer、PubMed三大文献数据集深度解析与工程实践 引言&#xff1a;为什么这三个数据集成为GNN研究的"黄金标准"&#xff1f; 在探索图神经网络&#xff08;GNN&#xff09;的浩瀚宇宙中&#xff0c;Cora、Citeseer和PubMed如同三颗璀璨的…...

N诺机试题

2.整除&#xff08;末尾无空格用printf“ ”&#xff09;#include<stdio.h>int main(){int count0;for(int i100;i<1000;i){if(i%50&&i%60){printf("%d",i);count;if(count%100) printf("\n");else printf(" "); }}return 0;…...

5大核心功能重塑Sketch效率:RenameIt批量命名工具的流程优化实践

5大核心功能重塑Sketch效率&#xff1a;RenameIt批量命名工具的流程优化实践 【免费下载链接】RenameIt Keep your Sketch files organized, batch rename layers and artboards. 项目地址: https://gitcode.com/gh_mirrors/re/RenameIt 在现代UI/UX设计工作流中&#x…...

staticFunctional:嵌入式零堆内存的std::function替代方案

1. staticFunctional&#xff1a;嵌入式系统中零动态内存开销的 std::function 替代方案1.1 设计动因与工程痛点在资源受限的嵌入式系统&#xff08;如 ARM Cortex-M0/M4、AVR、ESP32、Teensy 系列&#xff09;中&#xff0c;std::function的标准实现存在根本性兼容障碍。其典型…...

告别IE时代:手把手教你用allWebPlugin在Chrome/Firefox中运行ActiveX控件(附多插件配置)

企业级ActiveX迁移实战&#xff1a;基于allWebPlugin的现代浏览器兼容方案 当某省级政务系统在2023年进行浏览器兼容性升级时&#xff0c;技术团队发现核心OA模块因依赖ActiveX控件无法在Chrome中运行。这个场景正在全国范围内重复上演——据行业调研显示&#xff0c;超过67%的…...