如何构建延迟任务调度系统

一、需求目标

1.需求描述
之前笔者接触过一些营销业务场景,比如说:

用户注册未登录过APP第二天早上10点发一条营销短信促活
红包过期前两天短信通知,下午16:00发送
等等定时任务处理业务。
采用的技术方案是定时任务扫数据汇总表,分页读取一定数量然后处理
然而随着业务的发展,业务多元化,遇到了以下场景:

拼团砍价活动过期前半小时提醒
订单提交半小时内没有完成支付,订单自动取消,库存退还
用户几天内没有操作过系统,发放激活短信
以上场景处理时间不是固定的某个点,而是业务发生的时间推迟一段时间,针对以上的业务场景,我们考虑可以根据不同业务建表,然后每隔一段时间去定时扫表,各自处理业务。
但是随着业务增加,表泛滥,而且此类业务其实有很多相同的地方,那么我们可以考虑把相同逻辑抽离出来,利用延迟队列来处理任务

2.延时队列设计目标
可靠性:任务进入延时队列之后,必须被执行一次
高可用性:支持多实例部署
实时性:允许一定时间误差,当然误差越小越好
可管理:支持消息删除
高性能:数据量大的情况下也能保证高性能

二、技术调研

延时队列实现的几种方式
java.util.Timer + java.util.TimerTask
java.util.concurrent.ScheduledExecutorService
Quartz
java.util.concurrent.DelayQueue
数据库轮询
redis过期键通知
rocketMQ中的延时队列
1. Timer+TimerTask
使用 Timer 实现任务调度的核心类是 Timer 和 TimerTask。其中 Timer 负责设定 TimerTask 的起始与间隔执行时间。使用者只需要创建一个 TimerTask 的继承类,实现自己的 run 方法,然后将其丢给 Timer 去执行即可

Timer 的设计核心是一个 TaskList 和一个 TaskThread。Timer 将接收到的任务丢到自己的 TaskList 中,TaskList 按照 Task 的最初执行时间进行排序。TimerThread 在创建 Timer 时会启动成为一个守护线程。这个线程会轮询所有任务,找到一个最近要执行的任务,然后休眠,当到达最近要执行任务的开始时间点,TimerThread 被唤醒并执行该任务。之后 TimerThread 更新最近一个要执行的任务,继续休眠。

实现思想:应用维护一个全局的Timer调度器,延时任务实现TimerTask,run方法中实现逻辑。计算好具体的延迟执行时间,交给Timer去调度。

选型评估:简单易用,但是缺点较多,单线程调度,所有任务都是串行的,性能低,前一个任务的延迟或异常都将会影响到之后的任务,影响实时性,同时也不具备延时队列的几点能力

3.2 ScheduledExecutorService
基于Timer的缺陷,JDK5推出了基于线程池设计的 ScheduledExecutor,原理是
每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。

选型评估:引入多线程,解决了Timer的一些缺点,但是只适合单机,分布式环境不支持。也不具备延时队列的几点能力,需要考虑跟别的技术结合使用评估是否可以满足延时队列的能力。

3.3 Quartz
Quartz是个轻量级的任务调度框架,可以跟多个应用集成,并且具有容错机制,重启服务的时候内存中丢失的任务可以被持久化

选型评估:

Quartz满足了我们需要的延时队列的可靠性: 持久化任务,避免了服务重启的时候内存中的任务丢失,高可用:执行任务的节点挂了,另外的节点会继续执行
集群分布式并发环境中使用QUARTZ定时任务调度,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务,Quartz的任务触发只能在单个节点运行,其它节点不执行任务,性能低,浪费资源
3.4 DelayQueue
DelayQueue 是一个支持延时获取元素的阻塞队列, 内部采用优先队列 PriorityQueue 存储元素,同时元素必须实现 Delayed 接口;在创建元素时可以指定多久才可以从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素。

选型评估:

效率高,任务触发时间延迟低
适用于单机,需要结合其他技术运用,
数据是保存在内存,需要自己实现持久化
不具备分布式能力,需要自己实现高可用
3.5 数据库轮询
每隔一段时间去查询数据库,处理好的记录标记状态

选型评估:定期轮询数据量大的时候会消耗太多IO资源,效率低

3.6 redis过期键通知
需要DBA做一些额外的配置,开启这个功能

选型评估:Redis的发布/订阅目前是即发即弃(fire and forget)模式的,因此无法实现事件的可靠通知。如果发布/订阅的客户端断链之后又重连,则在客户端断链期间的所有事件都丢失了

7. rocketMQ中的延时队列
选型评估:rocketMQ中消息延迟时间为固定时间段:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,粒度不够,不能很好支持业务

总结
延时队列的技术点还有很多,比如说时间轮之类的方案,要满足延时队列的几点特性,实现高可用,可靠性,我们需要结合多个技术去实现。

三、架构设计

功能设计
系统功能:
延迟任务调度系统提供统一的任务操作接口给业务方调用,业务方可以提交任务,取消任务,查询任务状态。
调度服务属于底层应用,因此采用MQ的方式解耦,所有触发的延迟任务都通过消息的方式发送给业务消费方,
由消费方控制流量,业务幂等。同时也保证了任务的重试机制。

采用技术:elastic-job + db + delayQueue + mq

整体架构

业务调用方

业务方在需要延迟任务的时候调用延迟任务服务操作任务
触发的延迟任务会放到MQ消息队列里面,由业务方自行消费
业务方消费消息处理完成之后,调用延迟任务服务通知处理结果
延迟任务节点

以dubbo方式提供延迟任务接口供业务方操作,用于添加延迟任务,取消任务,反馈任务处理结果。
集成elastic-job提供数据分片功能,每个节点按照对应分片从数据库加载即将触发的延迟任务放到内存中
任务调度触发的延迟任务发送到MQ消息队列中
接收业务调用的延迟消息处理结果反馈
Zookeeper

elastic-job注册中心,存储作业信息
elastic-job

高可用的分布式任务调度系统
注册任务实例信息和分片信息到zk上
数据分片

elastic-job作业数据分片
节点添加/删除,主节点选举,重新分片
任务加载作业

由elastic-job实现,使用数据分片功能,提升系统总吞吐量
将未来N分钟内要触发的任务加载到内存中
任务在内存中的存储和调度

任务加载作业将未来N分钟内触发的任务加载到内存队列DelayQueue
任务调度依靠DelayQueue精确触发
数据库

延迟任务持久化,存储任务数据
延迟任务状态

    INIT(1, "初始化"),
    LOAD(2, "任务已加载"),
    SENDING(3, "消息已发放"),
    SUCCESS(4, "业务处理成功"),
    FAIL(5, "业务处理失败"),
    CANCEL(6, "业务取消");

数据库设计

CREATE TABLE `delay_task` (
  `delay_task_id` bigint(20) NOT NULL COMMENT ‘任务ID‘,
  `sharding_id` tinyint(4) NOT NULL COMMENT ‘分片ID‘,
  `topic` varchar(100) NOT NULL COMMENT ‘消息topic‘,
  `tag` varchar(100) NOT NULL COMMENT ‘消息tag‘,
  `params` varchar(1000) NOT NULL COMMENT ‘参数‘,
  `trigger_time` bigint(19) NOT NULL COMMENT ‘执行时间‘,
  `status` tinyint(4) NOT NULL COMMENT ‘任务状态:1.初始化 2.任务已加载 3.消息已发放 4.业务处理成功 5.业务处理失败‘,
  `extend_field` varchar(100) NOT NULL COMMENT ‘扩展属性‘,
  `create_time` bigint(20) NOT NULL COMMENT ‘创建时间‘,
  `op_time` bigint(20) NOT NULL COMMENT ‘最近一次更新时间‘,
  `last_ver` int(10) NOT NULL COMMENT ‘版本号‘,
  `is_valid` tinyint(2) NOT NULL DEFAULT ‘1‘ COMMENT ‘是否有效 0-失效 1-有效‘,
  PRIMARY KEY (`delay_task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘延迟任务表‘

数据库设计就一张表delay_task,用来存储延迟任务的数据,包括业务方要消费的消息的tag,topic,以及消息体内容

源码:https://github.com/caisl/delay-task

原文地址:https://www.cnblogs.com/cnndevelop/p/12311189.html

时间: 2024-10-30 08:04:42

如何构建延迟任务调度系统的相关文章

从构建分布式秒杀系统聊聊限流的多种实现

前言 俗话说的好,冰冻三尺非一日之寒,滴水穿石非一日之功,罗马也不是一天就建成的.两周前秒杀案例初步成型,分享到了中国最大的同×××友网站-码云.同时也收到了不少小伙伴的建议和投诉.我从不认为分布式.集群.秒杀这些就应该是大厂的专利,在互联网的今天无论什么时候都要时刻武装自己,只有这样,也许你的春天就在明天. 在开发秒杀系统案例的过程中,前面主要分享了队列.缓存.锁和分布式锁以及静态化等等.缓存的目的是为了提升系统访问速度和增强系统的处理能力:分布式锁解决了集群下数据的安全一致性问题:静态化无疑

从构建分布式秒杀系统聊聊限流特技

前言 俗话说的好,冰冻三尺非一日之寒,滴水穿石非一日之功,罗马也不是一天就建成的.两周前秒杀案例初步成型,分享到了中国最大的同性交友网站-码云.同时也收到了不少小伙伴的建议和投诉.我从不认为分布式.集群.秒杀这些就应该是大厂的专利,在互联网的今天无论什么时候都要时刻武装自己,只有这样,也许你的春天就在明天. 在开发秒杀系统案例的过程中,前面主要分享了队列.缓存.锁和分布式锁以及静态化等等.缓存的目的是为了提升系统访问速度和增强系统的处理能力:分布式锁解决了集群下数据的安全一致性问题:静态化无疑是

SpringBoot开发案例从0到1构建分布式秒杀系统

前言 最近,被推送了不少秒杀架构的文章,忙里偷闲自己也总结了一下互联网平台秒杀架构设计,当然也借鉴了不少同学的思路.俗话说,脱离案例讲架构都是耍流氓,最终使用SpringBoot模拟实现了部分秒杀场景,同时跟大家分享交流一下. 秒杀场景 秒杀场景无非就是多个用户在同时抢购一件或者多件商品,专用词汇就是所谓的高并发.现实中经常被大家喜闻乐见的场景,一群大妈抢购打折鸡蛋的画面一定不会陌生,如此场面让服务员大姐很无奈,赶上不要钱了. 业务特点 瞬间高并发.电脑旁边的小哥哥.小姐姐们如超市哄抢的大妈一般

从入门到放弃,.net构建博客系统(二):依赖注入

文章目录:<从入门到放弃,.net构建博客系统> 从入门到放弃,.net构建博客系统(一):系统构建 从入门到放弃,.net构建博客系统(二):依赖注入 上一篇中有讲到项目启动时会进行ioc的依赖注入,但具体是怎么注入的呢?我们先一步步往下走 一.注册autofac配置 首先bootstraper会进行初始化,接着将当前mvc控制器工厂改为AutofacControllerFactory. 1 public class AutofacConfig 2 { 3 /// <summary&g

linux 任务调度 系统任务调度

linux  at 针对运行一次的任务 crontab   控制计划任务的命令 crond系统服务 crond是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程, 与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务工具, 并且会自动启动crond进程,crond进程每分钟会定期检查是否有要执行的任务, 如果有要执行的任务,则自动执行该任务. 系统任务调度:系统周期性所要执行的工作,比如写缓存数据到硬盘.日志清理等.在/etc目录下有一个crontab文件

20.31 expect脚本同步文件;20.32 expect脚本指定host和要同步的文件;20.33 构建文件分发系统;20.34

20.31 expect脚本同步文件 自动同步文件 1. 同步远程机器hao2上/tmp/12.txt文件 到本机/tmp/下: [[email protected] ~]# vim 4.expect 添加内容: #!/usr/bin/expect set passwd "admin" spawn rsync -av [email protected]192.168.211.129:/tmp/12.txt /tmp/ expect { "yes/no" { send

spark内核揭秘-04-spark任务调度系统个人理解

spark的任务调度系统如下所示: 从上图中科院看出来由RDDObject产生DAG,然后进入了DAGScheduler阶段,DAGScheduler是面向state的高层次的调度器,DAGScheduler把DAG拆分成很多的tasks,每组的tasks都是一个state,每当遇到shuffle就会产生新的state,可以看出上图一共有三个state:DAGScheduler需要记录那些RDD被存入磁盘等物化动作,同时需勋勋task的最优化调度,例如数据本地性等:DAGScheduler还要监

ELK+kafka构建日志收集系统

ELK+kafka构建日志收集系统 原文  http://lx.wxqrcode.com/index.php/post/101.html 背景: 最近线上上了ELK,但是只用了一台Redis在中间作为消息队列,以减轻前端es集群的压力,Redis的集群解决方案暂时没有接触过,并且Redis作为消息队列并不是它的强项:所以最近将Redis换成了专业的消息信息发布订阅系统Kafka, Kafka的更多介绍大家可以看这里: 传送门 ,关于ELK的知识网上有很多的哦, 此篇博客主要是总结一下目前线上这个

利用开源架构ELK构建分布式日志系统

本文介绍了如何使用成熟的经典架构ELK(即Elastic search,Logstash和Kibana)构建分布式日志监控系统,很多公司采用该架构构建分布式日志系统,包括新浪微博,freewheel,畅捷通等. 背景日志,对每个系统来说,都是很重要,又很容易被忽视的部分.日志里记录了程序执行的关键信息,ERROR和WARNING信息等等.我们可以根据日志做很多事情,做数据分析,系统监控,排查问题等等 .但是,任何一个中大型系统都不可能是单台Server,日志文件散落在几十台甚至成千上万台Serv