从实现到原理,总结11种延迟任务的实现方式(上)

news2024/11/22 1:14:59

1 前言

延迟任务在我们日常生活中比较常见,比如订单支付超时取消订单功能,又比如自动确定收货的功能等等。

所以本篇文章就来从实现到原理来盘点延迟任务的11种实现方式,这些方式并没有绝对的好坏之分,只是适用场景的不大相同。

在这里插入图片描述

2 DelayQueue

DelayQueue是JDK提供的api,是一个延迟队列

在这里插入图片描述

DelayQueue泛型参数得实现Delayed接口,Delayed继承了Comparable接口。

在这里插入图片描述
getDelay方法返回这个任务还剩多久时间可以执行,小于0的时候说明可以这个延迟任务到了执行的时间了。

compareTo这个是对任务排序的,保证最先到延迟时间的任务排到队列的头。

2.1 demo

@Getter
public class SanYouTask implements Delayed {

    private final String taskContent;

    private final Long triggerTime;

    public SanYouTask(String taskContent, Long delayTime) {
        this.taskContent = taskContent;
        this.triggerTime = System.currentTimeMillis() + delayTime * 1000;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(triggerTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return this.triggerTime.compareTo(((SanYouTask) o).triggerTime);
    }

}

SanYouTask实现了Delayed接口,构造参数

  • taskContent:延迟任务的具体的内容
  • delayTime:延迟时间,秒为单位

2.2 测试

@Slf4j
public class DelayQueueDemo {

    public static void main(String[] args) {
        DelayQueue<SanYouTask> sanYouTaskDelayQueue = new DelayQueue<>();

        new Thread(() -> {
            while (true) {
                try {
                    SanYouTask sanYouTask = sanYouTaskDelayQueue.take();
                    log.info("获取到延迟任务:{}", sanYouTask.getTaskContent());
                } catch (Exception e) {
                }
            }
        }).start();

        log.info("提交延迟任务");
        sanYouTaskDelayQueue.offer(new SanYouTask("三友的java日记5s", 5L));
        sanYouTaskDelayQueue.offer(new SanYouTask("三友的java日记3s", 3L));
        sanYouTaskDelayQueue.offer(new SanYouTask("三友的java日记8s", 8L));
    }
}

开启一个线程从DelayQueue中获取任务,然后提交了三个任务,延迟时间分为别5s,3s,8s。

测试结果:

在这里插入图片描述

成功实现了延迟任务。

2.3 实现原理

在这里插入图片描述

offer方法在提交任务的时候,会通过根据compareTo的实现对任务进行排序,将最先需要被执行的任务放到队列头。

take方法获取任务的时候,会拿到队列头部的元素,也就是队列中最早需要被执行的任务,通过getDelay返回值判断任务是否需要被立刻执行,如果需要的话,就返回任务,如果不需要就会等待这个任务到延迟时间的剩余时间,当时间到了就会将任务返回。

3 Timer

Timer也是JDK提供的api

3.1 demo

@Slf4j
public class TimerDemo {

    public static void main(String[] args) {
        Timer timer = new Timer();
        
        log.info("提交延迟任务");
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                log.info("执行延迟任务");
            }
        }, 5000);
    }

}

通过schedule提交一个延迟时间为5s的延迟任务

在这里插入图片描述

3.2 实现原理

提交的任务是一个TimerTask

public abstract class TimerTask implements Runnable {
    //忽略其它属性
    
    long nextExecutionTime;
}

TimerTask内部有一个nextExecutionTime属性,代表下一次任务执行的时间,在提交任务的时候会计算出nextExecutionTime值。

Timer内部有一个TaskQueue对象,用来保存TimerTask任务的,会根据nextExecutionTime来排序,保证能够快速获取到最早需要被执行的延迟任务。

在Timer内部还有一个执行任务的线程TimerThread,这个线程就跟DelayQueue demo中开启的线程作用是一样的,用来执行到了延迟时间的任务。

所以总的来看,Timer有点像整体封装了DelayQueue demo中的所有东西,让用起来简单点。

虽然Timer用起来比较简单,但是在阿里规范中是不推荐使用的,主要是有以下几点原因:

  • Timer使用单线程来处理任务,长时间运行的任务会导致其他任务的延时处理
  • Timer没有对运行时异常进行处理,一旦某个任务触发运行时异常,会导致整个Timer崩溃,不安全

4 ScheduledThreadPoolExecutor

由于Timer在使用上有一定的问题,所以在JDK1.5版本的时候提供了ScheduledThreadPoolExecutor,这个跟Timer的作用差不多,并且他们的方法的命名都是差不多的,但是ScheduledThreadPoolExecutor解决了单线程和异常崩溃等问题。

4.1 demo

@Slf4j
public class ScheduledThreadPoolExecutorDemo {

    public static void main(String[] args) {
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2, new ThreadPoolExecutor.CallerRunsPolicy());

        log.info("提交延迟任务");
        executor.schedule(() -> log.info("执行延迟任务"), 5, TimeUnit.SECONDS);
    }

}

结果:

在这里插入图片描述

4.2 实现原理

ScheduledThreadPoolExecutor继承了ThreadPoolExecutor,也就是继承了线程池,所以可以有很多个线程来执行任务。

ScheduledThreadPoolExecutor在构造的时候会传入一个DelayedWorkQueue阻塞队列,所以线程池内部的阻塞队列是DelayedWorkQueue。

在这里插入图片描述
在提交延迟任务的时候,任务会被封装一个任务会被封装成ScheduledFutureTask对象,然后放到DelayedWorkQueue阻塞队列中。

在这里插入图片描述
ScheduledFutureTask实现了前面提到的Delayed接口,所以其实可以猜到DelayedWorkQueue会根据ScheduledFutureTask对于Delayed接口的实现来排序,所以线程能够获取到最早到延迟时间的任务。

当线程从DelayedWorkQueue中获取到需要执行的任务之后就会执行任务。

5 RocketMQ

RocketMQ是阿里开源的一款消息中间件,实现了延迟消息的功能,如果有对RocketMQ不熟悉的小伙伴可以看一下我之前写的RocketMQ保姆级教程和RocketMQ消息短暂而又精彩的一生 这两篇文章。

RocketMQ延迟消息的延迟时间默认有18个等级。

在这里插入图片描述
当发送消息的时候只需要指定延迟等级即可。如果这18个等级的延迟时间不符和你的要求,可以修改RocketMQ服务端的配置文件。

5.1 demo

依赖:

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.1</version>
  
<!--web依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>

配置文件:

rocketmq:
  name-server: 192.168.200.144:9876 #服务器ip:nameServer端口
  producer:
    group: sanyouProducer

controller类,通过DefaultMQProducer发送延迟消息到sanyouDelayTaskTopic这个topic,延迟等级为2,也就是延迟时间为5s的意思。

@RestController
@Slf4j
public class RocketMQDelayTaskController {

    @Resource
    private DefaultMQProducer producer;

    @GetMapping("/rocketmq/add")
    public void addTask(@RequestParam("task") String task) throws Exception {
        Message msg = new Message("sanyouDelayTaskTopic", "TagA", task.getBytes(RemotingHelper.DEFAULT_CHARSET));
        msg.setDelayTimeLevel(2);
        // 发送消息并得到消息的发送结果,然后打印
        log.info("提交延迟任务");
        producer.send(msg);
    }

}

创建一个消费者,监听sanyouDelayTaskTopic的消息。

@Component
@RocketMQMessageListener(consumerGroup = "sanyouConsumer", topic = "sanyouDelayTaskTopic")
@Slf4j
public class SanYouDelayTaskTopicListener implements RocketMQListener<String> {

    @Override
    public void onMessage(String msg) {
        log.info("获取到延迟任务:{}", msg);
    }

}

启动应用,浏览器输入以下链接添加任务

http://localhost:8080/rocketmq/add?task=sanyou

测试结果:

在这里插入图片描述

5.2 实现原理

在这里插入图片描述
生产者发送延迟消息之后,RocketMQ服务端在接收到消息之后,会去根据延迟级别是否大于0来判断是否是延迟消息

  • 如果不大于0,说明不是延迟消息,那就会将消息保存到指定的topic中
  • 如果大于0,说明是延迟消息,此时RocketMQ会进行一波偷梁换柱的操作,将消息的topic改成SCHEDULE_TOPIC_XXXX中,XXXX不是占位符,然后存储。

在BocketMQ内部有一个延迟任务,相当于是一个定时任务,这个任务就会获取SCHEDULE_TOPIC_XXXX中的消息,判断消息是否到了延迟时间,如果到了,那么就会将消息的topic存储到原来真正的topic(拿我们的例子来说就是sanyouDelayTaskTopic)中,之后消费者就可以从真正的topic中获取到消息了。

在这里插入图片描述
RocketMQ这种实现方式相比于前面提到的三种更加可靠,因为前面提到的三种任务内容都是存在内存的,服务器重启任务就丢了,如果要实现任务不丢还得自己实现逻辑,但是RocketMQ消息有持久化机制,能够保证任务不丢失。

6 RabbitMQ

RabbitMQ也是一款消息中间件,通过RabbitMQ的死信队列也可以是先延迟任务的功能。

6.1 demo

引入RabbitMQ的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>

配置文件

spring:
  rabbitmq:
    host: 192.168.200.144 #服务器ip
    port: 5672
    virtual-host: /

RabbitMQ死信队列的配置类,后面说原理的时候会介绍干啥的

@Configuration
public class RabbitMQConfiguration {
    
    @Bean
    public DirectExchange sanyouDirectExchangee() {
        return new DirectExchange("sanyouDirectExchangee");
    }

    @Bean
    public Queue sanyouQueue() {
        return QueueBuilder
                //指定队列名称,并持久化
                .durable("sanyouQueue")
                //设置队列的超时时间为5秒,也就是延迟任务的时间
                .ttl(5000)
                //指定死信交换机
                .deadLetterExchange("sanyouDelayTaskExchangee")
                .build();
    }

    @Bean
    public Binding sanyouQueueBinding() {
        return BindingBuilder.bind(sanyouQueue()).to(sanyouDirectExchangee()).with("");
    }

    @Bean
    public DirectExchange sanyouDelayTaskExchange() {
        return new DirectExchange("sanyouDelayTaskExchangee");
    }

    @Bean
    public Queue sanyouDelayTaskQueue() {
        return QueueBuilder
                //指定队列名称,并持久化
                .durable("sanyouDelayTaskQueue")
                .build();
    }

    @Bean
    public Binding sanyouDelayTaskQueueBinding() {
        return BindingBuilder.bind(sanyouDelayTaskQueue()).to(sanyouDelayTaskExchange()).with("");
    }

}

RabbitMQDelayTaskController用来发送消息,这里没指定延迟时间,是因为在声明队列的时候指定了延迟时间为5s

@RestController
@Slf4j
public class RabbitMQDelayTaskController {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/rabbitmq/add")
    public void addTask(@RequestParam("task") String task) throws Exception {
        // 消息ID,需要封装到CorrelationData中
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        log.info("提交延迟任务");
        // 发送消息
        rabbitTemplate.convertAndSend("sanyouDirectExchangee", "", task, correlationData);
    }

}

启动应用,浏览器输入以下链接添加任务

http://localhost:8080/rabbitmq/add?task=sanyou

测试结果,成功实现5s的延迟任务

在这里插入图片描述

6.2 实现原理

在这里插入图片描述
整个工作流程如下:

  1. 消息发送的时候会将消息发送到sanyouDirectExchange这个交换机上
  2. 由于sanyouDirectExchange绑定了sanyouQueue,所以消息会被路由到sanyouQueue这个队列上
  3. 由于sanyouQueue没有消费者消费消息,并且又设置了5s的过期时间,所以当消息过期之后,消息就被放到绑定的sanyouDelayTaskExchange死信交换机中
  4. 消息到达sanyouDelayTaskExchange交换机后,由于跟sanyouDelayTaskQueue进行了绑定,所以消息就被路由到sanyouDelayTaskQueue中,消费者就能从sanyouDelayTaskQueue中拿到消息了

上面说的队列与交换机的绑定关系,就是上面的配置类所干的事。

其实从这个单从消息流转的角度可以看出,RabbitMQ跟RocketMQ实现有相似之处。

消息最开始都并没有放到最终消费者消费的队列中,而都是放到一个中间队列中,等消息到了过期时间或者说是延迟时间,消息就会被放到最终的队列供消费者消息。

只不过RabbitMQ需要你显示的手动指定消息所在的中间队列,而RocketMQ是在内部已经做好了这块逻辑。

除了基于RabbitMQ的死信队列来做,RabbitMQ官方还提供了延时插件,也可以实现延迟消息的功能,这个插件的大致原理也跟上面说的一样,延时消息会被先保存在一个中间的地方,叫做Mnesia,然后有一个定时任务去查询最近需要被投递的消息,将其投递到目标队列中。

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

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

相关文章

高频前端React面试题汇总

近期整理了一下高频的前端面试题&#xff0c;分享给大家一起来学习。如有问题&#xff0c;欢迎指正&#xff01; 一、组件基础 1. React 事件机制 <div onClick{this.handleClick.bind(this)}>点我</div>React并不是将click事件绑定到了div的真实DOM上&#xff0…

ChatGPT+Mermaid自然语言流程图形化产出小试 | 京东云技术团队

ChatGPTMermaid语言实现技术概念可视化 本文旨在介绍如何使用ChatGPT和Mermaid语言生成流程图的技术。在现代软件开发中&#xff0c;流程图是一种重要的工具&#xff0c;用于可视化和呈现各种流程和结构。结合ChatGPT的自然语言处理能力和Mermaid的简单语法&#xff0c;可以轻…

【北邮国院大三下】Intellectual Property Law 知识产权基础 Week3

北邮国院大三电商在读&#xff0c;随课程进行整理知识点。仅整理PPT和相关法条中相对重要的知识点&#xff0c;个人认为相对不重要的细小的知识点不列在其中。如有错误请指出。转载请注明出处&#xff0c;祝您学习愉快。 如需要pdf格式的文件请私信联系或微信联系 本Week的主…

供应链|多期库存系统中具有销售损失的最优联合补货和转运策略

封面图来源&#xff1a; https://www.pexels.com/photo/aerial-shot-of-cargo-ship-on-sea-3840441/ 作者&#xff1a;Hossein Abouee-Mehrizi, Oded Berman, Shrutivandana Sharma 引用&#xff1a;Abouee-Mehrizi, H., Berman, O., & Sharma, S. (2015). Optimal joint r…

马原第三章复习2.生产力和生产关系

目录 社会基本矛盾和运动规律 社会基本矛盾和运动规律 这两个问题是最重点 生产力和生产关系 经济基础和上层建筑 生产力和生产关系 生产力是生产的能力 生产力是人类在实践中形成的改造自然使其适应自然的能力 生产力包含两个重要的要素:三要素 劳动

年化收益 21%:lightGBM的WFA滚动训练,使用qlib的alpha158因子集

原创文章第242篇&#xff0c;专注“个人成长与财富自由、世界运作的逻辑与投资"。 开始之前&#xff0c;先说说感受。 把整个框架与思路都在社群里开源出来&#xff0c;就是希望大家看懂思路&#xff0c;而不是拿一两个策略。说实话&#xff0c;投资哪有这种高确定性的“…

通义听悟上线,强大的视频会议和学习直播分析能力,人工智能如何改变我们的生活和工作方式?

什么是通义听悟 通义听悟已开启公测&#xff0c;公测期&#xff08;2023年6月1日至30日&#xff09;用户可体验所有AI功能&#xff0c;含全文概要、章节速览、发言总结等高阶AI功能&#xff0c;通过阿里云主账号登录。 官方给的应用场景&#xff1a; 1、实时会议记录&#x…

4.MySQL表的增删改查(进阶)

文章目录 &#x1f36f;1. 数据库约束&#x1f36f;&#x1f34e;1.1 约束类型&#x1f34e;&#x1f34f;1.2 NULL约束&#x1f34f;&#x1f34a;1.3 UNIQUE&#xff1a;唯一约束&#x1f34a;&#x1f34b;1.4 DEFAULT&#xff1a;默认值约束&#x1f34b;&#x1f352;1.5 …

MySQL数据库语言三、DCL语句

&#x1f618;作者简介&#xff1a;正在努力的99年公司职员。 &#x1f44a;宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。 &#x1f64f;创作不易&#xff0c;…

绩效管理的本质是激发员工,而不是扣工资!

绩效管理是企业管理中非常重要的一个环节&#xff0c;通过对员工表现进行评估和奖励&#xff0c;可以提高整个团队的士气和生产力。 然而&#xff0c;在实际操作中&#xff0c;有些企业却将绩效管理变成了惩罚员工的手段&#xff0c;甚至使用绩效扣除员工的薪水。这种做法不仅…

VALSE 2023:版面分析技术如何赋能生产生活?

目录 0 写在前面1 文档版面分析2 版面元素检测3 文档排版引擎总结 0 写在前面 VALSE年度研讨会旨在为中国青年学者在计算机视觉、图像处理、模式识别与机器学习研究领域提供一个具有深度的学术交流平台。VALSE秉持理性批判、勇于探索、实证和创新等科学精神&#xff0c;倡导自…

光伏电池建模及温度光照的影响曲线

光伏电池建模及温度光照的影响MATLAB程序及仿真资源-CSDN文库https://download.csdn.net/download/weixin_56691527/87910193模型介绍&#xff1a; 需要MATLAB2018B及以上的版本&#xff01;&#xff01; 首先根据根据环境修正公式搭建光伏电池仿真模型&#xff1a; 温度变化…

传统机器学习算法解析(opencv实现)

前言 文本主要解析在传统机器学习当中一些小的算法与思想&#xff0c;只是传统机器学习算法当中的一小部分&#xff0c;更多传统机器学习算法可参考我的另外几篇博客 链接1: PCA主成分分析 链接2: Canny边缘检测算法 链接3: K-Means聚类算法 链接4: SIFT算法分析 1. opencv …

MMDeploy安装和pth转ONNX

参考&#xff1a; https://github.com/open-mmlab/mmdeploy/blob/main/README_zh-CN.md MMDeploy安装指导 MMDeploy 是 OpenMMLab 模型部署工具箱&#xff0c;为各算法库提供统一的部署体验。基于 MMDeploy&#xff0c;开发者可以轻松从训练 repo 生成指定硬件所需 SDK&#…

0x80080005 windows更新失败导致的net framework3.5安装失败

缘起 客户安装应用软件提示需要安装net framework3.5 sp1,但是下载了net framework的安装包后一直提示正在下载中&#xff0c;后来发现系统的windows更新功能都用不了&#xff0c;真的是坑啊。 解决方案 按Windows按键&#xff08;田字按键&#xff09;X&#xff0c;选择Powe…

2023-6-15-第六式适配器模式

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f4a5;&#x1f4a5;&#x1f4a5;欢迎来到&#x1f91e;汤姆&#x1f91e;的csdn博文&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f49f;&#x1f49f;喜欢的朋友可以关注一下&#xf…

如何通过java程序获取表的自增主键值?

获取自增主键&#xff1a; 在 Java 程序中&#xff0c;使用 JDBC 插入记录到 MySQL 数据库时&#xff0c;可以通过以下步骤获取自增主键的值&#xff1a; 第一步&#xff1a;在 PreparedStatement 对象中添加 Statement.RETURN_GENERATED_KEYS 常量作为参数&#xff0c;表示希…

Zabbix与信创、云原生、高可用等热点解析|Zabbix大会·上海站

根据信通院调研显示&#xff0c;超过90%的中国金融机构已经引入开源软件。工信部突出强调开源在驱动软件产业发展的重要作用。作为一个完全开源免费的企业级监控解决方案&#xff0c;Zabbix在IT基础监控、网络监控、Server监控和云监控等领域都获得了同行和用户极高的评价。 Za…

html好看的登录页面1(十三种风格登录页面源码)

文章目录 1.登录风格效果说明1.1 背景凹起风登录界面1.2 弹出风登录界面1.3 科技时尚风登录界面1.4 蓝色一夏风登录界面1.5 模糊背景左右风登录界面1.6 上中下介绍风登录界面1.7 深沉科技风登陆界面1.8 舒适简洁风登录界面1.9 网站风登录界面1.10 小框清爽风登录界面1.11 夜色风…

【IoU全总结】GIoU, DIoU, CIoU, EIoUFocal, αIoU, SIoU,WIoU【基础收藏】

&#x1f951; Welcome to Aedream同学 s blog! &#x1f951; 并不存在效果一定优秀的IoU&#xff0c;需要结合自己的网络、数据集试验。 不想深究原理可直接跳转总结。文内公式均为手打&#xff0c;非图片&#xff0c;方便查看 文章目录 L1 Loss&#xff0c;L2Loss&#xff0…