需求介绍:构建一个分布式短信发送系统,应对双十一活动需向1000万用户快速推送营销短信的挑战,每条数据的业务处理逻辑为0.1s。对于普通任务来说,只有一个线程来处理 可能需要10万秒才能处理完,业务则严重受影响。
常见的一些定时任务介绍:
1. java自带的java.util.Timer类:
优点:
简单易用:Timer类提供了简单直观的API,使得开发者能够快速上手,实现基本的定时任务需求。创建一个定时任务只需几行代码即可完成。
灵活性:Timer允许你安排任务在指定的延迟后执行一次,或者以固定的间隔重复执行。这对于很多简单的定时任务场景来说已经足够灵活。
集成方便:作为Java标准库的一部分,Timer无需引入额外的依赖,可以直接在任何Java项目中使用,便于维护和移植
缺点:
单线程执行:Timer使用一个后台线程来执行所有安排的任务,这意味着所有任务都是串行执行的。如果一个任务执行时间过长,可能会阻塞其他任务的执行,导致整体性能下降或任务延迟。
资源限制于异常处理:由于所有任务共享同一个线程,如果任务执行中抛出未捕获的异常,整个Timer线程将会终止,导致后续所有任务都不会被执行。此外,没有内置机制来优雅地管理或回收长时间运行或失败的任务所占用的资源
功能有限:相较于更复杂的调度框架如ScheduledExecutorService或第三方库如Quartz,Timer的功能较为基础,不支持复杂的调度策略(如cron表达式)、任务优先级管理或动态任务调整。
2. SchduledExecutorService
ScheduledExecutorService是Java并发包(java.util.concurrent)中的一个接口,继承自ExecutorService,专门用于定时和周期性任务的执行。它是基于线程池的,相较于java.util.Timer,提供了更多的灵活性和更高的可靠性。
优点:
线程池支持:ScheduledExecutorService基于线程池执行任务,可以有效管理线程资源,避免了因任务堆积导致的资源耗尽问题。线程池可以重用线程,减少了线程创建和销毁的开销。
并发执行:任务在独立的工作线程中并行执行,不会因为一个任务的执行阻塞其他任务,提高了执行效率,尤其适合需要同时处理多个定时任务的场景。
异常处理:当任务执行中抛出异常时,不会导致整个定时服务停止,仅影响当前任务线程。通过Future或自定义的RejectedExecutionHandler可以更好地处理异常情况。
缺点:
资源管理:虽然线程池提供了灵活性,但其配置(如核心线程数、最大线程数、队列容量等)需要根据任务特性和系统资源仔细调优,否则可能导致资源浪费或任务积压。
复杂度:相比Timer,ScheduledExecutorService的使用和配置相对复杂,特别是对于初学者或简单应用场景,可能需要更多学习成本。
没有内置的corn表达式支持:ScheduledExecutorService不直接支持复杂的cron表达式来定义执行时间,需要自行实现或借助外部库来实现更复杂的定时逻辑。
3. SpringBoot自带的定时任务
优点:
易用性:使用极其简单,只需在配置类上添加@EnableScheduling,然后在需要定时执行的方法上加上@Scheduled注解,配置相应的执行策略即可。
集成度高:作为Spring Boot的一部分,定时任务能够自然地与Spring的依赖注入、事务管理等特性集成,便于在任务中使用Spring管理的Bean。
灵活:支持cron表达式、固定延迟、固定速率等多种执行策略,满足大多数定时任务需求。同时,可以在运行时动态调整任务的执行计划。
缺点:
资源管理:Spring Boot的定时任务默认使用的是单线程执行器,这意味着所有定时任务是串行执行的。如果任务执行时间较长或者任务数量较多,可能会相互阻塞,影响执行效率。虽然可以通过自定义TaskScheduler来使用线程池,但这需要额外的配置。
并发控制:缺乏对高并发场景的原生支持,需要开发者手动配置线程池或使用其他并发工具来提高并发处理能力。
功能局限:对于非常复杂的定时任务调度逻辑(如依赖调度、分布式调度等),Spring Boot自带的定时任务功能可能显得力不从心,这时可能需要集成更专业的任务调度框架如Quartz或使用Spring Cloud Task。
4.分布式调度Quartz
优点:
调度功能强大:Quartz支持灵活的时间触发规则,包括cron-like表达式,可以满足各种复杂调度需求。同时支持一次性任务、重复任务及按需触发任务。
分布式支持:通过JobStore(如JobStoreTX)和集群配置,Quartz可以实现任务的分布式调度和执行,保证高可用性。在集群环境中,任务调度信息存储在共享数据库中,各节点之间能协调作业执行,避免重复执行。
高度可配置与扩展:Quartz提供了丰富的配置选项,可以根据应用需求定制调度策略。同时,Quartz的设计鼓励扩展,允许开发者自定义Job、Trigger和JobStore等组件,以适应特定场景。
缺点:
复杂:相对于简单的定时任务解决方案(如Spring的@Scheduled),Quartz的配置和使用相对复杂,特别是涉及到分布式部署和高级特性时,需要深入理解其内部机制和配置细节。
资源消耗:由于Quartz支持的功能丰富,其运行时会占用一定的系统资源,包括内存和数据库连接。在大规模任务调度场景下,资源管理成为重要考量。
学习难度大:对于新手来说,Quartz的学习曲线较陡峭,特别是如何配置和管理分布式调度环境,需要花费一定时间去掌握。
5.分布式调度xxl-job
优点:
易用性高:XXL-Job提供了友好的Web界面,使得任务的增删改查、执行状态监控变得直观易操作,降低了使用门槛。同时,通过简单的API或注解就可以实现任务的开发和接入。
分布式支持:XXL-Job天然支持分布式部署,任务调度器(Admin)与执行器(Executor)分离,执行器可以部署在多台机器上,实现了任务的分布式执行和高可用。
灵活性强:支持Cron表达式、调度中心主动调用、API调用等多种触发方式,满足不同场景的定时任务需求。同时,任务类型多样,包括HTTP任务、Shell任务、Java任务等。
监控与告警:提供详细的执行日志、失败重试、阻塞处理、邮件告警等功能,便于运维和故障排查,确保任务的稳定执行。
缺点:
社区相对较小:相比于Quartz等成熟框架,XXL-Job的社区和资料相对较少,遇到一些特殊问题时,可能需要深入阅读源码或自行解决。
功能深度有限:虽然XXL-Job覆盖了大部分基础定时任务需求,但在某些高级功能上,如复杂工作流编排、大数据量任务的高效处理等方面,可能不如一些商业化调度工具完善。
依赖关系:XXL-Job的执行器需要依赖Spring环境,对于非Spring应用的集成可能会有一定难度。此外,调度器和执行器之间的通信依赖于数据库或Redis,对这些中间件有一定的依赖。
通过以上的定时调度的介绍,我们最终选择xxx-job来实现以上功能。
Xxl-job介绍
官网地址:分布式任务调度平台XXL-JOB (xuxueli.com)
设计思想
将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求。
将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑。
因此,“调度”和“任务”两部分可以相互解耦,提高系统整体稳定性和扩展性;
系统组成
调度中心
负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。
调度系统与任务解耦,提高了系统可用性和稳定性,同时调度系统性能不再受限于任务模块;
支持可视化、简单且动态的管理调度信息,包括任务新建,更新,删除,GLUE开发和任务报警等,
执行器
负责接收调度请求并执行任务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效;
接收“调度中心”的执行请求、终止请求和日志请求等。
以下是Xxl-job的架构图
Xxl-job的特性
调度中心HA(中心式):调度采用了中心式进行设计,“调度中心”支持集群部署,可保证调度中心HA
执行器HA(分布式):任务分布式的执行,任务执行器支持集群部署,可保证任务执行HA
触发策略:有Cron触发、固定间隔触发、固定延时触发、API事件触发、人工触发、父子任务触发
路由策略:执行器在集群部署的时候提供了丰富的路由策略,如:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用LFU、最久未使用LRU、故障转移等等
故障转移:如果执行器集群的一台机器发生故障,会自动切换到一台正常的执行器发送任务调度
Rolling实时日志的监控:支持rolling方式查看输入的完整执行日志
脚本任务:支持GLUE模式开发和运行脚本任务,包括Shell、python、node.js、php等等类型脚本
Xxl-job项目目录介绍
doc:xxl-job的文档资料,包括了数据库的脚本
xxl-job-core:公共jar包依赖
xxl-job-admin:调度中心,项目源码,是Springboot项目,可以直接启动
xxl-job-executor-samples:执行器,是Sample实例项目,里面的Springboot工程可以直接启动,也可以在该项目的基础上进行开发,也可以将现有的项目改造成为执行器项目
数据库介绍(doc/db目录下)
xxl_job_group:执行器信息表,用于维护任务执行器的信息
xxl_job_info:调度扩展信息表,主要是用于保存xxl-job的调度任务的扩展信息,比如说像任务分组、任务名、机器的地址等等
xxl_job_lock:任务调度锁表
xxl_job_log:日志表,主要是用在保存xxl-job任务调度历史信息,像调度结果、执行结果、调度入参等等
xxl_job_log_report:日志报表,会存储xxl-job任务调度的日志报表,会在调度中心里的报表功能里使用到
xxl_job_logglue:任务的GLUE日志,用于保存GLUE日志的更新历史变化,支持GLUE版本的回溯功能
xxl_job_registry:执行器的注册表,用在维护在线的执行器与调度中心的地址信息
xxl_job_user:系统的用户表
Xxl-job UI界面介绍
任务报表
任务数量:能够看到调度中心运行的任务数量
调度次数:调度中心所触发的调度次数
执行器数量:在整个调度中心中,在线的执行器数量有多少
任务管理
基础配置:
- 执行器:任务的绑定的执行器,任务触发调度时将会自动发现注册成功的执行器, 实现任务自动发现功能; 另一方面也可以方便的进行任务分组。每个任务必须绑定一个执行器, 可在 "执行器管理" 进行设置;
- 任务描述:任务的描述信息,便于任务管理;
- 负责人:任务的负责人;
- 报警邮件:任务调度失败时邮件通知的邮箱地址,支持配置多邮箱地址,配置多个邮箱地址时用逗号分隔;
触发配置:
- 调度类型:
无:该类型不会主动触发调度;
CRON:该类型将会通过CRON,触发任务调度;
固定速度:该类型将会以固定速度,触发任务调度;按照固定的间隔时间,周期性触发;
固定延迟:该类型将会以固定延迟,触发任务调度;按照固定的延迟时间,从上次调度结束后开始计算延迟时间,到达延迟时间后触发下次调度;
- CRON:触发任务执行的Cron表达式;
- 固定速度:固定速度的时间间隔,单位为秒;
- 固定延迟:固定延迟的时间间隔,单位为秒;
任务配置:
- 运行模式:
BEAN模式:任务以JobHandler方式维护在执行器端;需要结合 "JobHandler" 属性匹配执行器中任务;
GLUE模式(Java):任务以源码方式维护在调度中心;该模式的任务实际上是一段继承自IJobHandler的Java类代码并 "groovy" 源码方式维护,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器里中的其他服务;
GLUE模式(Shell):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "shell" 脚本;
GLUE模式(Python):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "python" 脚本;
GLUE模式(PHP):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "php" 脚本;
GLUE模式(NodeJS):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "nodejs" 脚本;
GLUE模式(PowerShell):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "PowerShell" 脚本;
- JobHandler:运行模式为 "BEAN模式" 时生效,对应执行器中新开发的JobHandler类“@JobHandler”注解自定义的value值;
- 执行参数:任务执行所需的参数;
高级配置:
- 路由策略:当执行器集群部署时,提供丰富的路由策略,包括;
FIRST(第一个):固定选择第一个机器;
LAST(最后一个):固定选择最后一个机器;
ROUND(轮询):;
RANDOM(随机):随机选择在线的机器;
CONSISTENT_HASH(一致性HASH):每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。
LEAST_FREQUENTLY_USED(最不经常使用):使用频率最低的机器优先被选举;
LEAST_RECENTLY_USED(最近最久未使用):最久未使用的机器优先被选举;
FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度;
BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度;
SHARDING_BROADCAST(分片广播):广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;
- 子任务:每个任务都拥有一个唯一的任务ID(任务ID可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务ID所对应的任务的一次主动调度。
- 调度过期策略:
- 忽略:调度过期后,忽略过期的任务,从当前时间开始重新计算下次触发时间;
- 立即执行一次:调度过期后,立即执行一次,并从当前时间开始重新计算下次触发时间;
- 阻塞处理策略:调度过于密集执行器来不及处理时的处理策略;
单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行;
丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;
- 任务超时时间:支持自定义任务超时时间,任务运行超时将会主动中断任务;
- 失败重试次数;支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;
调度日志
可以根据条件筛选执行器具体执行的日志记录,例如调度机器,触发类型,路由策略等。
执行器管理
这里是配置执行器,等待执行器启动的时候都会被调度中心监听加入到地址列表,AppName要与我们项目配置的appName一致,不然服务注册不上去。
用户管理
就是对用的操作,默认账户是admin/123456,后续如果添加账户都可以在这里进行添加。
经过对比分析各种定时任务解决方案,XXL-Job凭借其易用性、分布式支持、灵活性以及强大的监控管理能力,成为满足此需求的理想选择。XXL-Job不仅能够通过其分布式架构确保任务的高效并行执行,减少单点故障,而且其友好的Web界面极大简化了任务的管理和监控,使得运维工作更加直观高效。通过集成XXL-Job,我们可以快速构建起一个既能够应对短时间高并发需求,又便于维护和扩展的分布式短信发送系统,从而确保营销活动的顺利进行,提升用户体验,同时也为未来的业务扩展和技术迭代打下坚实的基础。总之,XXL-Job以其全面的功能集和轻量级设计,为应对大规模数据处理和定时任务调度提供了强有力的支撑,是构建高效、可靠分布式系统的得力助手。
更多内容请关注微信公众号