定时任务方案实现与对比

定时任务

   定时任务通常用来处理,在某个特定的时间去做某个事情, 并由对应的重试策略来应对部分异常场景。

   定时任务分为分布式定时任务和单机定时任务两个大的方向,他们的适用场景不同。

任务类型 特点 优点 缺点
单机定时任务 在单台计算机上运行,其执行结果和单台机器上的数据有关,如对本地机器的缓存做核对、清理日志等。 1. 简单易用,无需依赖外部组件; 任务一般没有持久化机制,重启后有任务丢失的问题。
分布式定时任务 将任务交由集群中的某个机器来执行,常用于需要在分布式环境下协同执行的任务,例如处理耗时较长的任务、数据同步、消息处理、超时关单等场景 高可靠性、高可扩展性和分布式特性 视具体的组件而定

应用场景

  1. 超时关单场景:每笔订单创建后,10min未推进支付状态,即执行关单操作

    • 生成订单时,提交一个10min后的分布式关单任务,完成关闭内部订单和其他相关通知操作。可对该任务指定不同的执行间隔时间。
    • 使用MQ,将消息投递到延迟消息队列中,指定时间间隔后,再将消息投递给消费者。
  2. 资金回退(FundBack)场景:支付回调接口中,内部订单1已经由钱包B通知支付成功了,但此时收到A钱包通知订单1支付成功,此时需要告知A支付失败且通知A进行 FundBack 操作。

    • 生成 FundBack 任务,指定不同间隔时间来通知A,避免因钱包A发布期间无法收到FundBack信息的问题。
  3. 核对本地缓存数据:分布式多级缓存中,需要扫描缓存和DB中的数据是否一致。

    • 建设本地定时任务,完成每台应用的核对

本地定时任务框架

   单机定时任务有 Timer、ScheduledExecutorService、SpringTask、SpringQuartz 等。Timer、ScheduledExecutorService 都无法使用 Cron 表达式指定任务执行的具体时间,灵活性不够,本文不做展开。

SpringTask

org.springframework.scheduling.annotation.Scheduled提供的属性如下:

/**
	 * A cron-like expression, extending the usual UN*X definition to include triggers
	 * on the second, minute, hour, day of month, month, and day of week.
	 * 

For example, {@code "0 * * * * MON-FRI"} means once per minute on weekdays * (at the top of the minute - the 0th second). *

The fields read from left to right are interpreted as follows. *

    *
  • second
  • *
  • minute
  • *
  • hour
  • *
  • day of month
  • *
  • month
  • *
  • day of week
  • *
*

The special value {@link #CRON_DISABLED "-"} indicates a disabled cron * trigger, primarily meant for externally specified values resolved by a * ${...} placeholder. * @return an expression that can be parsed to a cron schedule * @see org.springframework.scheduling.support.CronSequenceGenerator */ String cron() default ""; /** * A time zone for which the cron expression will be resolved. By default, this * attribute is the empty String (i.e. the server's local time zone will be used). * @return a zone id accepted by {@link java.util.TimeZone#getTimeZone(String)}, * or an empty String to indicate the server's default time zone * @since 4.0 * @see org.springframework.scheduling.support.CronTrigger#CronTrigger(String, java.util.TimeZone) * @see java.util.TimeZone */ String zone() default ""; /** * Execute the annotated method with a fixed period in milliseconds between the * end of the last invocation and the start of the next. * @return the delay in milliseconds */ long fixedDelay() default -1; /** * Execute the annotated method with a fixed period in milliseconds between the * end of the last invocation and the start of the next. * @return the delay in milliseconds as a String value, e.g. a placeholder * or a {@link java.time.Duration#parse java.time.Duration} compliant value * @since 3.2.2 */ String fixedDelayString() default ""; /** * Execute the annotated method with a fixed period in milliseconds between * invocations. * @return the period in milliseconds */ long fixedRate() default -1; /** * Execute the annotated method with a fixed period in milliseconds between * invocations. * @return the period in milliseconds as a String value, e.g. a placeholder * or a {@link java.time.Duration#parse java.time.Duration} compliant value * @since 3.2.2 */ String fixedRateString() default ""; /** * Number of milliseconds to delay before the first execution of a * {@link #fixedRate} or {@link #fixedDelay} task. * @return the initial delay in milliseconds * @since 3.2 */ long initialDelay() default -1; /** * Number of milliseconds to delay before the first execution of a * {@link #fixedRate} or {@link #fixedDelay} task. * @return the initial delay in milliseconds as a String value, e.g. a placeholder * or a {@link java.time.Duration#parse java.time.Duration} compliant value * @since 3.2.2 */ String initialDelayString() default "";

执行原理:

   ScheduledTaskRegistrar#afterPropertiesSet 方法中,默认初始化一个单线程的 ScheduledExecutorService 来执行任务。

   SpringTask 在 ScheduledAnnotationBeanPostProcessor#processScheduled 中解析和收集 Scheduled 注解中的参数;然后向 ScheduledTaskRegistrar 中添加对应类型的任务。

ScheduledExecutorService:

   总结起来就是通过线程池来执行任务;DelayedWorkQueue 作为阻塞队列,并排序任务定时执行;ScheduledFutureTask 记录任务定时信息,。

   ScheduledExecutorService 继承线程池,也是把任务提交给线程池执行,只不过它的任务类进行了扩展。
定时任务方案实现与对比_第1张图片

   ScheduledExecutorService 自定义了阻塞队列 DelayedWorkQueue 给线程池使用,它可以根据 ScheduledFutureTask 的下次执行时间来阻塞 take 方法,并且新进来的 ScheduledFutureTask 会根据这个时间来进行排序,最小的最前面。

   任务类 ScheduledFutureTask 继承 FutureTask 并扩展了一些属性来记录任务下次执行时间和每次执行间隔。同时重写了run方法重新计算任务下次执行时间,并把任务放到线程池队列中。
定时任务方案实现与对比_第2张图片

spring task 的优缺点:
优点:

  • spring框架自带的定时功能,开启和定义定时任务非常容易,支持复杂的 cron 表达式,可以满足绝大多数单机版的业务场景。单线程执行任务时,当前次的调度完成后,再执行下一次任务调度。

缺点:

  • 默认单线程,如果前面的任务执行时间太长,对后面任务的执行有影响。
  • 不支持集群方式部署,提交的任务在单机内存中,可能出现任务丢失的情况

SpringQuartz

优点:

  • 默认是多线程异步执行,单个任务时,在上一个调度未完成时,下一个调度时间到时,会另起一个线程开始新的调度,多个任务之间互不影响。
  • 支持复杂的 cron 表达式
  • 它能被集群实例化,支持分布式部署。

缺点:

  • 相对于spring task实现定时任务成本更高,需要手动配置 QuartzJobBean 、 JobDetail和 Trigger 等。需要引入了第三方的 quartz 包,有一定的学习成本。
  • 没有内置 UI 管理控制台。
  • 不支持并行调度,不支持失败处理策略和动态分片的策略等。

代码案例

分布式定时任务框架

XXL-Job

定时任务方案实现与对比_第3张图片
   将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求。

   将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑。

调度中心:

   自研调度组件(早期调度组件基于Quartz);一方面是为了精简系统降低冗余依赖,另一方面是为了提供系统的可控度与稳定性;

执行器:

   执行器实际上是一个内嵌的Server,在项目启动时,执行器会通过 “@JobHandler” 识别Spring容器中“Bean模式任务”,以注解的value属性为key管理起来。

   “执行器”接收到“调度中心”的调度请求时,如果任务类型为“Bean模式”,将会匹配Spring容器中的“Bean模式任务”,然后调用其execute方法,执行任务逻辑。如果任务类型为“GLUE模式”,将会加载GLue代码,实例化Java对象,注入依赖的Spring服务(注意:Glue代码中注入的Spring服务,必须存在与该“执行器”项目的Spring容器中),然后调用execute方法,执行任务逻辑。

任务注册与发现

   任务注册以 “执行器” 为最小粒度进行注册。

   基于 HTTP 协议的 REST接口(未使用ZK),将执行器注册到 调度中心 DB 的注册表中,再通过对应的 Beat 完成任务注册、执行器注册、动态任务发现、注册信息失效等动作。

优缺点:

优点:

  • 有界面管理定时任务,支持弹性扩容缩容、动态分片、故障转移、失败报警等功能。
  • 支持在运行时动态创建任务( XxlJobHelper#submitCronTask),参见:xxl-job issue277

缺点:

  • 一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行。和 quartz 一样,通过数据库分布式锁,来控制任务不能重复执行
  • 调度中心HA(中心式):调度采用中心式设计,“调度中心”自研调度组件并支持集群部署,可保证调度中心HA。不同于 Elastic-Job 的去中心化设计, XXL-JOB 的这种设计也被称为中心化设计

官网-架构


其他资料

JavaGuide-task

你可能感兴趣的:(系统架构与方案,java)