AOP+Redisson 延时队列,实现缓存延时双删策略
一、缓存延时双删
关于缓存和数据库中的数据保持一致有很多种方案,但不管是单独在修改数据库之前,还是之后去删除缓存都会有一定的风险导致数据不一致。而延迟双删是一种相对简单并且收益比较高的实现最终一致性的方式,即在删除缓存之后,间隔一个短暂的时间后再删除缓存一次。这样可以避免并发更新时,假如缓存在第一次被删除后,被其他线程读到旧的数据更新到了缓存,第二次删除还可以补救,从而时间最终一致性。
实现延时双删的方案也有很多,有本地用 Thread.sleep(); 睡眠的方式做延时,也有借助第三方消息中间件做延时消息等等,本文基于 Redisson 中的延时队列进行实验。
在 Redisson 中提供了 RDelayedQueue 可以迅速实现延时消息,本文所使用的 Redisson 版本为 3.19.0 。
二、Redisson 实现延时消息
新建 SpringBoot 项目,在 pom 中加入下面依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.19.0</version>
</dependency>
在 yum 配置中,增加 redis 的信息:
spring:redis:timeout: 6000password:cluster:max-redirects:nodes:- 192.168.72.120:7001- 192.168.72.121:7001- 192.168.72.122:7001
声明 RedissonClient :
@Configuration
public class RedissonConfig {@Beanpublic RedissonClient getRedisson(RedisProperties redisProperties) {Config config = new Config();String[] nodes = redisProperties.getCluster().getNodes().stream().filter(StringUtils::isNotBlank).map(node -> "redis://" + node).collect(Collectors.toList()).toArray(new String[]{});ClusterServersConfig clusterServersConfig = config.useClusterServers().addNodeAddress(nodes);if (StringUtils.isNotBlank(redisProperties.getPassword())) {clusterServersConfig.setPassword(redisProperties.getPassword());}clusterServersConfig.setConnectTimeout((int) (redisProperties.getTimeout().getSeconds() * 1000));clusterServersConfig.setScanInterval(2000);return Redisson.create(config);}
}
延时队列实现延时消息:
@Slf4j
@Component
public class MsgQueue {@ResourceRedissonClient redissonClient;public static final String QUEUE_KEY = "DELAY-QUEUE";// 发送消息public void send(String msg, Long time, TimeUnit unit) {// 获取队列RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(QUEUE_KEY);// 延时队列RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);// 添加数据delayedQueue.offer(msg, time, unit);}// 消息监听@PostConstructpublic void listen() {CompletableFuture.runAsync(() -> {RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(MsgQueue.QUEUE_KEY);log.info("延时消息监听!");while (true) {try {consumer(blockingQueue.take());} catch (InterruptedException e) {throw new RuntimeException(e);}}});}// 消费消息public void consumer(String msg) {log.info("收到延时消息: {} , 当前时间: {} ", msg, LocalDateTime.now().toString());}}
测试延时消息:
@Slf4j
@RestController
@RequestMapping("/msg")
public class MsgController {@ResourceMsgQueue queue;@GetMapping("/test")public void test() {String msg = "你好";queue.send(msg, 5L, TimeUnit.SECONDS);}}
上面发送了延时5秒的消息,运行后可以看到日志:

三、AOP+延时队列,实现延时双删策略
缓存延时删除队列:
@Slf4j
@Component
public class CacheQueue {@ResourceRedissonClient redissonClient;public static final String QUEUE_KEY = "CACHE-DELAY-QUEUE";// 延时删除public void delayedDeletion(String key, Long time, TimeUnit unit) {RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(QUEUE_KEY);RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);log.info("延时删除key: {} , 当前时间: {} ", key, LocalDateTime.now().toString());delayedQueue.offer(key, time, unit);}// 消息监听@PostConstructpublic void listen() {CompletableFuture.runAsync(() -> {RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue(CacheQueue.QUEUE_KEY);while (true) {try {consumer(blockingQueue.take());} catch (InterruptedException e) {throw new RuntimeException(e);}}});}// 消费消息public void consumer(String key) {log.info("删除key: {} , 当前时间: {} ", key, LocalDateTime.now().toString());redissonClient.getBucket("key").delete();}
}
定义缓存和删除缓存注解:
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface Cache {String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface DeleteCache {String name() default "";
}
缓存AOP逻辑:
@Aspect
@Component
public class CacheAspect {@ResourceRedissonClient redissonClient;private final Long validityTime = 2L;@Pointcut("@annotation(com.bxc.retrydemo.anno.Cache)")public void pointCut() {}@Around("pointCut()")public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {Cache ann = ((MethodSignature) pjp.getSignature()).getMethod().getDeclaredAnnotation(Cache.class);if (Objects.nonNull(ann) && StringUtils.isNotBlank(ann.name())) {Object proceed = redissonClient.getBucket(ann.name()).get();if (Objects.nonNull(proceed)){return proceed;}}Object proceed = pjp.proceed();if (Objects.nonNull(ann) && StringUtils.isNotBlank(ann.name())) {redissonClient.getBucket(ann.name()).set(proceed, validityTime, TimeUnit.HOURS);}return proceed;}
}
延时双删 AOP 逻辑:
@Aspect
@Component
public class DeleteCacheAspect {@ResourceRedissonClient redissonClient;@ResourceCacheQueue cacheQueue;private final Long delayedTime = 3L;@Pointcut("@annotation(com.bxc.retrydemo.anno.DeleteCache)")public void pointCut() {}@Around("pointCut()")public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {// 第一次删除缓存DeleteCache ann = ((MethodSignature) pjp.getSignature()).getMethod().getDeclaredAnnotation(DeleteCache.class);if (Objects.nonNull(ann) && StringUtils.isNotBlank(ann.name())) {redissonClient.getBucket(ann.name()).delete();}// 执行业务逻辑Object proceed = pjp.proceed();//延时删除if (Objects.nonNull(ann) && StringUtils.isNotBlank(ann.name())) {cacheQueue.delayedDeletion(ann.name(), delayedTime, TimeUnit.SECONDS);}return proceed;}
}
伪业务逻辑使用:
@Slf4j
@Service
public class MsgServiceImpl implements MsgService {@Cache(name = "you key")@Overridepublic String find() {// 数据库操作// ....// 数据库操作结束return "数据结果";}@DeleteCache(name = "you key")@Overridepublic void update() {// 数据库操作// ....// 数据库操作结束}
}相关文章:
AOP+Redisson 延时队列,实现缓存延时双删策略
一、缓存延时双删 关于缓存和数据库中的数据保持一致有很多种方案,但不管是单独在修改数据库之前,还是之后去删除缓存都会有一定的风险导致数据不一致。而延迟双删是一种相对简单并且收益比较高的实现最终一致性的方式,即在删除缓存之后&…...
Hive中left join 中的where 和 on的区别
目录 一、知识点 二、测试验证 三、引申 一、知识点 left join中关于where和on条件的知识点: 多表left join 是会生成一张临时表。on后面: 一般是对left join 的右表进行条件过滤,会返回左表中的所有行,而右表中没有匹配上的数…...
LaTeX教程(001)-LaTeX文档结构(01)
LaTeX教程(001)- LaTeX \LaTeX LATEX文档结构(01) 说在前面 这是我本人学习《The LaTeX Companion》第三版的笔记,但并不是翻译。 书籍的第一章对 LaTeX \LaTeX LATEX及其历史进行了相当长的介绍,这是几乎每一本关于 LaTeX \LaTeX LATEX的书都会…...
SV-7041T 多媒体教学广播IP网络有源音箱
SV-7041T是深圳锐科达电子有限公司的一款2.0声道壁挂式网络有源音箱,具有10/100M以太网接口,可将网络音源通过自带的功放和喇叭输出播放,可达到功率30W。同时它可以外接一个30W的无源副音箱,用在面积较大的场所。5寸进口全频低音喇…...
Linux文本三剑客awk经典案例
前言: AWK是一种专门用于文本处理的编程语言,它被广泛用于数据提取和报告生成,也是企业笔试面试常考的内容,以下34题是awk的用法案例,希望可以帮到你! 1.查看TCP连接状态 [rootnode1 ~]# netstat -nat | a…...
如何使用 Mermaid、GitHub 和 VSCode 用代码创建关系图三
Mermaid 系列 如何使用 Mermaid、GitHub 和 VSCode 用代码创建关系图一如何使用 Mermaid、GitHub 和 VSCode 用代码创建关系图二 1.如何创建甘特图 Gantt 甘特图以条形图的形式用作可视化表示。它有效地展示了项目的时间表,揭示了各个项目组件完成所需的持续时间…...
考研经验总结——政治篇
文章目录 一、前言二、学习情况三、最后 一、前言 不要提前,不要提前,不要提前, 我曾在暑假的时候上了7天左右的政治课,讲真话是很有趣的,并且对于自身的世界观、人生观和价值观的改善也是相当不错的,把我…...
春招秋招,在线测评到底难不难?
现在很多企业在春招的时候,都会有一个在线测评的环节,目的当然就是希望更加了解清楚毕业生的综合能力以及其他方面的素质,好让HR可以根据岗位筛选出能力达标的人才。所以,现在不少即将面对春招的大学毕业生,比较关心的…...
数学建模比赛中,使用大语言模型如chatgpt、文心一言该如何写Prompt(提示)?
在大型语言模型中,"prompt"(中文常译为“提示”或“引导”)是指提供给模型的输入文本,用于指示或引导模型产生特定的输出。它的作用主要是告诉模型用户想要得到什么样的信息或完成什么样的任务。 例如,在使…...
tcpdump 抓包无法落盘
文章目录 问题背景解决办法 问题背景 在嵌入式设备中(Linux系统),为了分析两个网络节点的通讯问题,往往需要用到tcpdump,抓一个.pcap的包在PC端进行分析。博主在实际操作中发现,抓包无法实时落盘。 解决办法 # 下面的命令是写在…...
【网站项目】066农家乐信息平台
🙊作者简介:拥有多年开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板ÿ…...
idea/webstorm 创建Vue实例 Unresolved type Vue 处理方法
1.电脑本地安装node.js 官网下载 2. 其他: 未排除变量,前期试错(以下步骤配置了,但不确定对解决问题是否有帮助)...
C++ 11新特性之语法甜点2
概述 C 11中引入了许多简化编程工作的语法上的新特性,我们暂且美其名曰:“语法甜点”。书接上篇,我们继续介绍C 11中的这些“语法甜点”,也是第二篇关于“语法甜点”的文章。 语法甜点6:模板右边双括号 在C 03中&#…...
【芯片设计- RTL 数字逻辑设计入门 番外篇 8.1 -- memory repair 详细介绍】
文章目录 memory repair 详细介绍Memory Repair 方法Memory Repair 过程举例memory repair 详细介绍 SoC (System on Chip) 的 Memory Repair 是一种技术,用于检测和修复内存中的损坏单元。由于SoC内部集成了大量的逻辑和存储单元,包括RAM(随机访问存储器)、ROM(只读存储…...
2023强网杯复现
强网先锋 SpeedUp 要求2的27次方的阶乘的逐位之和 在A244060 - OEIS 然后我们将4495662081进行sha256加密 就得到了flag flag{bbdee5c548fddfc76617c562952a3a3b03d423985c095521a8661d248fad3797} MISC easyfuzz 通过尝试输入字符串判断该程序对输入字符的验证规则为9…...
IP代理协议有哪些?爬虫代理如何被合理使用?
随着互联网的普及和发展,IP代理作为一种网络代理方式,越来越受到人们的关注。IP代理协议是网络代理的一种规范,它规定了代理服务器与客户端之间进行通信的规则。了解IP代理协议对于使用代理的人来说非常重要,因为它可以帮助我们更…...
Vue学习笔记(二)快速入门
Vue学习笔记(二)快速入门 vue小试牛刀 hello-vue3.html <body><div id"app"><h1>{{msg}}</h1></div><script type"module">import {createApp} from https://unpkg.com/vue3/dist/vue.esm-b…...
在Vue中@click方法不起效
问题描述: 在跟项目的时候,我们可能会遇到我们click点击时,需要执行多个操作,如:调用方法,修改变量等。举个例子,像这样,我们在管理项目中想要编辑某一值,编辑好后&…...
服装行业ERP系统解决方案
我国的服装企业大多属于劳动密集型,主要有三种类型:自有品牌服装生产销售企业、接订单生产型企业及处于产业链下游的零售分销企业。在经营过程中,服装行业面临诸多挑战,如流行周期短、季节性强,市场变化快;…...
AI绘画探索人工智能的未来
🌈个人主页: Aileen_0v0 🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 💫个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-8fL64RHWVzwpzR6m {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…...
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度
一、引言:多云环境的技术复杂性本质 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,基础设施的技术债呈现指数级积累。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...
