java中延时队列的实现
大家好,我是一名CRUD工程师,最近我朋友突然来问我如何实现延时队列,我脱口而出就是MQ。不过突然想到公司的项目好像用的是java的一个原生类。于是我就想着趁周末的时间好好的去探究一下各方法实现延时队列的优缺点。
延迟消息
延迟消息就是字面上的意思就是当系统接收到消息之后,需要隔一段时间进行处理,不管是几秒,几分钟还是几个小时,在这的消息发生就叫延时消息
在我不断的进行探究下发现一共有5种常见的方法去实现(欢迎补充哈)
DelayQueue
作为Java的原生类DelayQueue
供我们去实现延迟发送。
DelayQueue
是一个无界的BlockingQueue
,用于放置实现了Delayed
接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。
在使用的时候,我们add进去的队列的元素需要实现Delayed接口(同时该接口继承了Comparable接口,所以我们DelayQueue是有序的)
不过这样就会有一个问题,因为DelayQueue
它本身是java里的类吗,它是没有持久化的,一旦服务器重启就会导致数据丢失。并且如果进行了多机部署还需要添加分布式锁,所以在我看来在生产上用这个方法不是一个很好的选择,如果延时发送的消息重要性并不是很高那影响就不大。当然作为java的原生类也是有优点的,就是系统不需要和其他服务进行数据通讯,所有的请求都在项目内容进行,就避免了两个服务之间因为信道的不稳定导致的数据丢失的情况。
时间轮算法
具体的介绍可以自行百度
Netty 包里提供了一种时间轮的实现——HashedWheelTimer,其底层使用了数组+链表的数据结构:
//1996 年 George Varghese 和 Tony Lauck 的论文《Hashed and Hierarchical Timing Wheels:
//Data Structures for the Efficient Implementation of a Timer Facility》中提出了一种时间轮管理 Timeout 事件的方式。其设计非常巧妙,并且类似时钟的运行,
//原始时间轮有 8 个格子,假定指针经过每个格子花费时间是 1 个时间单位,当前指针指向 0,一个 17 个时间单位后超时的任务则需要运转 2 圈再通过一个格子后被执行,放在相同格子的任务会形成一个链表。
public class Test{public static void main(String[] args) {//设置每个格子是 100ms, 总共 256 个格子HashedWheelTimer hashedWheelTimer = new HashedWheelTimer(100, TimeUnit.MILLISECONDS, 256);//加入三个任务,依次设置超时时间是 10s 5s 20sSystem.out.println("加入第一个任务, time= " + LocalDateTime.now());hashedWheelTimer.newTimeout(timeout -> {System.out.println("执行第一个任务, time= " + LocalDateTime.now());}, 10, TimeUnit.SECONDS);System.out.println("加入第二个任务, time= " + LocalDateTime.now());hashedWheelTimer.newTimeout(timeout -> {System.out.println("执行第二个任务, time= " + LocalDateTime.now());}, 5, TimeUnit.SECONDS);System.out.println("加入第三个任务, time= " + LocalDateTime.now());hashedWheelTimer.newTimeout(timeout -> {System.out.println("执行第三个任务, time= " + LocalDateTime.now());}, 20, TimeUnit.SECONDS);System.out.println("等待任务执行===========");}
}
不过这一样会导致数据的丢失,可以在一些不那么重要的情况下使用
Redis
具有存储功能,能够快速读写并具有持久化操作的我一下子就想到了Redis
关于Redis去实现延时队列,我想到了两种方法:
1、Redis里提供了一种数据结构叫做zset
,它是可排序的集合并且Redis支持数据持久化。有赞的延迟队列就是基于通过zset
进行设计和存储的。
概述:将时间作为zset的分值,zrangewithscores可以获取zset种score值最小的元素(也就是即将到期的任务,去判断系统时间和score的大小,如果相等就执行并删除该任务。(如果想要异步可以使用Timer开一个线程去监听Redis的zset)
2、使用Redis存储数据的过期时间,服务端开启一个过期回调。(较为简单,但在Redis的过期回调中无法获取Key值就需要再Value中再存放一个Key)
消息队列(RabbitMQ的延时队列)
RabbitMQ
这个大名我相信大家都听过,在我印象里RabbitMQ
自己本身好像是不支持延时发送的,想要实现这个功能主要还是依靠它的TTL
(Time To Live 消息存活的时间)
简述:生产者通过Key将消息投入到对应的队列中,但并不对该队列进行消费,等队列里的元素触发了过期时(过期时间就是需要延迟的时间)该消息就会进入死信队列中,此时我们可以将该消息再次转发到正常的队列中进行消费,或者直接在该死信队列中进行消费,从而达到延迟队列的效果。
由于RabbitMQ
是专门做消息队列所以它对消息的可靠性会比Redis更加高(消息投递的可靠性、至少处理一次的消费语义,重复投递,手动ACK,投递失败的消息回调等)
消息队列(RocketMQ延时等级)
RocketMQ
还可以通过投递消息的时候设置延迟等级
Message message = new Message("test", ("Hello world").getBytes());
//设置延时等级
message.setDelayTimeLevel(3);
producer.send(message);
默认支持18个延迟等级
messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
当我们设置了延迟等级的消息之后,RocketMQ
不会把消息直接投递到对应的topic,而是转发到对应延迟等级的队列中。在Broker
内部会为每个延迟队列起TimerTask
来进行判断是否有消息到达了时间。如果到期了,则将消息重新存储到CommitLog,转发到真正目标的topic
结论
这次主要是介绍了java中延时队列各种方法的视线方法,就放了一些的代码(详细的代码会在后面几周发出来)
在公司的项目中其实很多时候并不是越复杂越牛逼的技术越好,我们需要根据不同的业务场景,资源情况去做一个选择。
引用掘金的一句话:
很多时候,我们看到的系统很烂,技术栈很烂,发现好多场景都没有用到最佳实践而感到懊恼,在年轻的时候都想有重构的心。但实际上每引入一个中间件都是需要付出成本的,粗糙也有粗糙的好处。
只要业务能完美支持,那就是好的方案。
相关文章:
java中延时队列的实现
大家好,我是一名CRUD工程师,最近我朋友突然来问我如何实现延时队列,我脱口而出就是MQ。不过突然想到公司的项目好像用的是java的一个原生类。于是我就想着趁周末的时间好好的去探究一下各方法实现延时队列的优缺点。 延迟消息 延迟消息就是字…...
基于java的circle buffer的实现
总目录链接==>> AutoSAR入门和实战系列总目录 文章目录 缓冲区示例什么是循环缓冲区?方法 1:使用数组插入元素删除元素方法 2:使用链表插入元素:删除元素:当数据经常从一个地方移动到另一个地方或从一个进程移动到另一个进程或被频繁访问时,它不能存储在永久性内存…...
通用方法——为什么重写equals还要重写hashcode
本文介绍java.lang.Object类中的两个方法:equals和hashCode。这两个方法大家应该都知道,但是这两个方法的作用是什么、为什么重写equals还要重写hashCode、它们之间有什么关系和约定等,今天就来带大家了解一下。 1、hashCode hashCode即散列…...

JavaSE学习进阶day2_01 包和权限修饰符
第一章 包 1.1 包 包在操作系统中其实就是一个文件夹。包是用来分门别类的管理技术,不同的技术类放在不同的包下,方便管理和维护。 在IDEA项目中,建包的操作如下: 这个咱们在基础班就谈到过。 包名的命名规范: 路径…...
Android性能调优 - 省电优化
省电:通过工具Battery Historian查看到:耗电大头: 屏幕、网络、cpuled/oled屏幕显示:降低亮度,开深色模式;锁屏间隔缩短到 ;亮屏需要一直持有唤醒锁,还有gps定位也需要用到唤醒锁;网络: 常用的网络优化措施…...
ElasticSearch - SpringBoot整合ES之全文搜索匹配查询 match
文章目录1. 数据准备2. match 匹配查询1. 全文检索2. 简化查询DSL语句3. match 匹配查询原理官方文档地址:https://www.elastic.co/guide/en/elasticsearch/reference/index.html权威指南:https://www.elastic.co/guide/cn/elasticsearch/guide/current/…...

句子的改写和扩写
目录 1.句子改写 2.句子扩写 (不低于15个句子算是长句子,不能太多长句子) 1.句子改写 我绝不会嫁给你的。 如果你是世界上最后一个男人,我就去寺庙。 If you married me,I would jump into the well. 如果你嫁给我,我…...

DockerFile创建及案例
DockerFile dockerfile是用来构建docker镜像的文件,命令脚本参数脚本! 构建步骤 编写一个dockerfile文件docker build 构建成为一个对象docker run 运行镜像docker push 发布镜像(DockerHub、阿里云镜像仓库) 去官网Docker-Hub…...

第十四届蓝桥杯三月真题刷题训练——第 1 天
目录 题目1:数列求值 代码: 题目2:质数 代码: 题目3:饮料换购 代码: 题目1:数列求值 题目描述 本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出…...
基于容器云提交spark job任务
容器云提交spark job任务 容器云提交KindJob类型的spark任务,首先需要申请具有Job任务提交权限的rbac,然后编写对应的yaml文件,通过spark内置的spark-submit命令,提交用户程序(jar包)到集群执行。 1、创建任务job提交权限rbac …...
Linux系统调用之目录操作函数
前言 如果,想要深入的学习Linux系统调用中mkdir,rmdir,rename,chdir,getcwd等这些有关于目录操作函数,还是需要去自己阅读Linux系统中的帮助文档。 具体输入命令: man 2 mkdir/rmdir/rename/ch…...

设计模式-策略模式
前言 作为一名合格的前端开发工程师,全面的掌握面向对象的设计思想非常重要,而“设计模式”是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的,代表了面向对象设计思想的最佳实践。正如《HeadFirst设计模式》中说的一句话&…...

面试+算法:罗马数字及Excel列名与数字互相转换
概述 算法是一个程序员的核心竞争力,也是面试最重要的考查环节。 试题 判断一个罗马数字是否有效 罗马数字包含七种字符:I,V,X,L,C,D和M,如下 字符数值I1V5X10L50C100D500M1000…...
Connext DDS路由服务Routing Service(1)
1 简介 RTI路由服务是一种开箱即用的解决方案,允许开发人员快速扩展和集成不同或地理位置分散的实时系统。它跨域、LAN和WAN扩展RTI ConnextDDS应用程序,包括防火墙和NAT穿越。 它还支持DDS到DDS的桥接,允许您对数据进行转换。这允许未修改的DDS应用程序进行通信,即使它们是…...

如何使用SaleSmartly进行Facebook Messenger 营销、销售和支持
如何使用SaleSmartly(ss客服)进行Facebook Messenger 营销、销售和支持上篇文章我们讲了什么是Facebook Messenger CRM以及获得Facebook Messenger CRM的注意事项,现在你有更多时间与客户聊天,让我们看看你如何使用SaleSmartly&am…...

教资教育知识与能力中学教学
目录 3.1 教学概述 3.2 教学过程 3.3 教学原则*【简答/辨析重点】 3.4 教学方法 3.5 教学组织形式 3.6 教学工作基本环节 3.7 教学评价 3.1 教学概述 1、教学的意义【14/18辨析】 教学是传授系统知识、促进学生发展的最有效形式; 教学是学校进行全面发展教…...

IDEA中使用Tomcat的两种方式:集成本地Tomcat使用Tomcat Maven插件
一、前言 在IDEA中创建完一个Maven Web项目,并补齐了目录以后,准备使用Tomcat时,就需要在自己创建的项目中去部署Tomcat,前文已经介绍了如何创建Maven Web,所以这里就不多加赘述,直接讲述部署Tomcat的方法…...

IP 地址的简介
IP 地址 Internet 依靠 TCP/IP 协议,在全球范围内实现不同硬件结构、不同操作系统、不同网络系统的主机之间的互联。在 Internet 上,每一个节点都依靠唯一的 IP 地址相互区分和相互联系,IP 地址用于标识互联网中的每台主机的身份,…...
3D动作/动画特效
硕士/博士符合一本高校人才引进条件的硕士、博士,教研能力突出者可签合作高校正式编制本科/硕士成绩优异专业扎实、有创新思维者可在签约工作后在校继续读研读博【产业模式】数字经济→数字孪生→升级转型【细份领域】数字产业、数字工程、数字教研、数字政企【合作…...
python 多线程编程之_thread模块
_thread模块除了可以派生线程外,还提供了基本的同步数据结构,又称为锁对象(lock object,也叫原语锁、简单锁、互斥锁、互斥和二进制信号量)。 下面是常用的线程函数: 函数描述start_new_thread(function,…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...

C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...

车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...
【实施指南】Android客户端HTTPS双向认证实施指南
🔐 一、所需准备材料 证书文件(6类核心文件) 类型 格式 作用 Android端要求 CA根证书 .crt/.pem 验证服务器/客户端证书合法性 需预置到Android信任库 服务器证书 .crt 服务器身份证明 客户端需持有以验证服务器 客户端证书 .crt 客户端身份…...
虚幻基础:角色旋转
能帮到你的话,就给个赞吧 😘 文章目录 移动组件使用控制器所需旋转:组件 使用 控制器旋转将旋转朝向运动:组件 使用 移动方向旋转 控制器旋转和移动旋转 缺点移动旋转:必须移动才能旋转,不移动不旋转控制器…...
用js实现常见排序算法
以下是几种常见排序算法的 JS实现,包括选择排序、冒泡排序、插入排序、快速排序和归并排序,以及每种算法的特点和复杂度分析 1. 选择排序(Selection Sort) 核心思想:每次从未排序部分选择最小元素,与未排…...

生产管理系统开发:专业软件开发公司的实践与思考
生产管理系统开发的关键点 在当前制造业智能化升级的转型背景下,生产管理系统开发正逐步成为企业优化生产流程的重要技术手段。不同行业、不同规模的企业在推进生产管理数字化转型过程中,面临的挑战存在显著差异。本文结合具体实践案例,分析…...

OpenHarmony标准系统-HDF框架之I2C驱动开发
文章目录 引言I2C基础知识概念和特性协议,四种信号组合 I2C调试手段硬件软件 HDF框架下的I2C设备驱动案例描述驱动Dispatch驱动读写 总结 引言 I2C基础知识 概念和特性 集成电路总线,由串网12C(1C、12C、Inter-Integrated Circuit BUS)行数据线SDA和串…...