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() {// 数据库操作// ....// 数据库操作结束}
}

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

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

相关文章

强化学习原理python篇07——策略梯度法

强化学习原理python篇07——策略梯度法 Average state valueAverage rewardMonte Carlo policy gradient (REINFORCE)REINFORCE示例在torch里面编写这段代码1、用随机权重初始化策略网络2、运行N个完整的片段&#xff0c;保存其(s,a,r,s)状态转移3、对于每个片段k的每一步t&…

Web中的转发与重定向

转发与重定向 一、转发和重定向的概念1.转发2.重定向 二、JavaWeb 中的转发和重定向三、SpringMVC 中的转发和重定向1.转发(1) 默认的方式(2) 完整的方式 2.重定向 四、总结 一、转发和重定向的概念 在 Web 应用中&#xff0c;转发和重定向都是用于将请求从一个页面传递到另一…

故障诊断 | 一文解决,CNN卷积神经网络故障诊断(Matlab)

文章目录 效果一览文章概述专栏介绍源码设计参考资料效果一览 文章概述 故障诊断 | 一文解决,CNN卷积神经网络故障诊断(Matlab) 专栏介绍 订阅【故障诊断】专栏,不定期更新机器学习和深度学习在故障诊断中的应用;订阅

外星人入侵(python)

前言 代码来源《python编程从入门到实践》Eric Matthes 署 袁国忠 译 使用软件&#xff1a;PyCharm Community Editor 2022 目的&#xff1a;记录一下按照书上敲的代码 alien_invasion.py 游戏的一些初始化设置&#xff0c;调用已经封装好的函数方法&#xff0c;一个函数的…

【React】前端项目引入阿里图标

【React】前端项目引入阿里图标 方式11、登录自己的iconfont-阿里巴巴矢量图标库&#xff0c;把需要的图标加入到自己的项目中去&#xff1b;2、加入并进入到项目中去选择Font class 并下载到本地3、得到的文件夹如下4. 把红框中的部分粘贴到自己的项目中&#xff08;public 文…

【百度Apollo】本地调试仿真:加速自动驾驶系统开发的利器

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下…

更改MAC终端样式(美化、易用的提示等)

1. 前言 之前用 Ubuntu、Elementary OS 时觉得其终端既漂亮又好用&#xff0c;购买的云服务器的默认终端也好看&#xff0c;一些牛人的桌面终端也配置得挺好看。虽然 Mac 的默认终端配置已经比 Windows 好看好用很多了&#xff0c;但还是觉得不够。于是灵机一动&#xff0c;想…

springboot139华强北商城二手手机管理系统

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…

idea docker 内网应用实践

文章目录 前言一、服务器端1.1 离线安装docker1.2 开启docker远程访问1.3 制作对应jdk镜像1.3.1 下载jdk171.3.2 Dockerfile 制作jdk17镜像1.3.3 镜像导出1.3.4 服务器引入镜像 二、Idea 配置2.1 Dockerfile2.2 pom 引入docker插件2.3 idea docker插件配置2.4 打包镜像上传2.5 …

第17次修改了可删除可持久保存的前端html备忘录:增加年月日星期,增加倒计时,更改保存区名称可以多个备忘录保存不一样的信息,匹配背景主题:现代深色

第17次修改了可删除可持久保存的前端html备忘录&#xff1a;增加年月日星期&#xff0c;增加倒计时&#xff0c;更改保存区名称可以多个备忘录保存不一样的信息&#xff0c;匹配背景主题&#xff1a;现代深色 备忘录代码&#xff1a; <!DOCTYPE html> <html lang&quo…

前端常见的栈溢出报错

什么是栈溢出&#xff1f; 在前端开发中&#xff0c;栈溢出是指JavaScript引擎执行代码时&#xff0c;调用栈&#xff08;call stack&#xff09;变得太大&#xff0c;超过了浏览器或JavaScript引擎所分配的栈空间&#xff0c;从而导致栈溢出错误。调用栈是一种数据结构&#x…

【Spark系列2】Spark编程模型RDD

RDD概述 RDD最初的概述来源于一片论文-伯克利实验室的Resilient Distributed Datasets&#xff1a;A Fault-Tolerant Abstraction for In-Memory Cluster Computing。这篇论文奠定了RDD基本功能的思想 RDD实际为Resilient Distribution Datasets的简称&#xff0c;意为弹性分…

git clone常见问题一览及解决方法

在使用Ubuntu下&#xff0c;终端运行git clone命令时会遇见许多问题&#xff0c;本文主要针对一些常见的问题进行整理。关于换源问题&#xff0c;推荐使用小鱼的一键换源。 1.git clone 速度过慢 1.1 魔法 这个方法不做过多赘述&#xff0c;ubuntu下个人使用发现体验良好&am…

matlab中的图窗属性和坐标轴的属性

图窗的Position和Outerposition Position 指定窗口的尺寸和窗口在屏幕中的位置。 Outerposition 指定窗口外轮廓的大小和位置。 两者都是用一个4维向量来定义&#xff0c;格式为[左 底 宽 高]。 可通过set函数修改Position和Outerposition&#xff0c;如下&#xff1a;在屏幕左…

【2024全网最详细】Google 搜索命令终极指南

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 你是否尝试过使用 Google 搜索作为免费的 SEO …

Flink 读取 Kafka 消息写入 Hudi 表无报错但没有写入任何记录的解决方法

博主历时三年精心创作的《大数据平台架构与原型实现:数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行,点击《重磅推荐:建大数据平台太难了!给我发个工程原型吧!》了解图书详情,京东购书链接:https://item.jd.com/12677623.html,扫描左侧二维…

设计一个支持并发的前端缓存接口

文章目录 一、概述二、并发缓存2.1、问题2.2、思考2.3、优化 三、总结四、最后 一、概述 缓存池不过就是一个map&#xff0c;存储接口数据的地方&#xff0c;将接口的路径和参数拼到一块作为key&#xff0c;数据作为value存起来罢了&#xff0c;这个咱谁都会。 const cacheMa…

pnpm tauri icon 一键转换 icon

在使用 Electron、Tauri 等框架开发桌面应用时&#xff0c;需要为应用生成不同平台的图标&#xff0c;如&#xff1a;MacOS 中的 icon.icns、Windows 中的 icon.ico、Linux 中的 *.png 等类型图标&#xff0c;这里介绍一种简单快捷一键转换图标的方法。 准备工作 nodejs 环境n…

ElasticSearch 应用实践 笔记

概述 介绍 ES 是一个开源的高扩展的分布式全文搜索引擎&#xff0c;是整个Elastic Stack技术栈的核心。它可以近乎实时的存储&#xff0c;检索数据&#xff1b;本身扩展性很好&#xff0c;可以扩展到上百台服务器&#xff0c;处理PB级别的数据。ElasticSearch的底层是开源库Lu…

c JPEG ZRL (15,0) 的问题

问题&#xff1a;如果量化表全为64个1&#xff0c;编码图片显示非常乱。如果用压缩比较大的量化表&#xff0c;显示基本正常。如果编码过程中不处理ZRL&#xff0c;图片正常。此问题一直没有排除。 下图为两张量化表全为64个1的情况下加了&#xff08;15,0&#xff09;后的不正…