写点什么

定时任务:历史 & 应用

作者:agnostic
  • 2022-10-23
    上海
  • 本文字数:1659 字

    阅读完需:约 5 分钟

在现实生活中,有许多需要定时执行的操作。比如:每日/每月计算利息、每月的财务报表、月底的工资发放等等。在软件中,我们一般通过定时任务来实现。

同时,定时任务和消息队列一样,也可以达到异步化的效果。对于大量的任务,通过定时触发,按计算资源调度的方式,可以避免机器被打爆。


crontab & Quartz

在操作系统层面,定时任务有两种实现方式:轮询和中断。我们可以采用 while 循环,不停判断时钟是否到确定的时刻,来判断是否拉起任务执行。也可以对固定时刻设置一个中断,到时触发。显然的,对于实时性要求不那么苛刻的场景,一定是中断的方式会比较有效。

在 linux 操作系统层中,有一个 crontab 命令支持定时的任务调度。

!每天的早上 6 点到 12 点,每隔 3 个小时执行一次 /usr/bin/backup0 6-12/3 * * * /usr/bin/backup
复制代码


在 java 中,jdk 自身就带 Timer,可以满足定期执行的效果。但是,定时任务除了需要定期触发外,还需要对任务有持久化的能力。所以,在单体系统中,最流行的是实用 Quartz 框架。

Quartz 通常有三部分组成:调度器(Scheduler)、任务(JobDetail)、触发器(Trigger,包括 SimpleTrigger 和 CronTrigger)。

定义执行任务的 Job,这里要实现 Quartz 提供的 Job 接口:

public class PrintJob implements Job {    @Override    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {        System.out.println(new Date() + " : 任务「PrintJob」被执行。");    }}
复制代码

创建 Scheduler 和 Trigger,并执行定时任务:

public class MyScheduler {	public static void main(String[] args) throws SchedulerException {    // 1、创建调度器Scheduler    SchedulerFactory schedulerFactory = new StdSchedulerFactory();    Scheduler scheduler = schedulerFactory.getScheduler();    // 2、创建JobDetail实例,并与PrintJob类绑定(Job执行内容)    JobDetail jobDetail = JobBuilder.newJob(PrintJob.class)            .withIdentity("job", "group").build();    // 3、构建Trigger实例,每隔1s执行一次    Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger", "triggerGroup")            .startNow()//立即生效            .withSchedule(SimpleScheduleBuilder.simpleSchedule()                    .withIntervalInSeconds(1)//每隔1s执行一次                    .repeatForever()).build();//一直执行
//4、Scheduler绑定Job和Trigger,并执行 scheduler.scheduleJob(jobDetail, trigger); System.out.println("--------scheduler start ! ------------"); scheduler.start(); }}
复制代码


分布式

在分布式系统中。我们需要定时执行的多个任务可以分配到集群的不同机器中执行,充分利用资源。所以在分布式系统中的定时任务,我们可以理解为:定时触发+集群调度。

最简单的,我们可以采用 Quartz+YARN 的方式来实现。

但是 Quartz 如果在单机,也可以有稳定性风险,所以可以采用集群 Quartz+选主机制,来保证定时触发的高可用。


定时任务的开发规范

不讨论定时触发,聚焦在定时触发后任务的逻辑上。

首先,定时任务的触发由于延迟、多活等因素,我们不能保证定时任务只被触发一次。所以,我们需要保证触发多次的情况下的不重入

这里有两种做法:

  1. 悲观锁方式:不论是用数据库锁还是分布式锁,在执行任务前先锁定任务,然后判断任务状态。状态为已执行就忽略本次调度。

  2. 乐观锁方式:这种方式下,需要任务的执行基本满足函数式的要求,在执行的过程中不进行写入操作。执行完后,进行幂等的写入。


其次,定时任务的执行可以分为单条数据的处理和数据汇总处理两类。

对于单条数据处理,无论悲观锁和乐观锁的方式,效果都类似。

对于数据汇总处理的,建议采用悲锁的方式。同时,数据的处理如果量比较大,考虑引入 map-reduce。


最后,定时任务触发,需要考虑部分时点触发失效的场景:

  • 任务的捞取逻辑尽量不要依赖调度周期,可以通过状态而不是时间区间。

  • 不要用定时任务实现比较依赖实效性的工作。


发布于: 刚刚阅读数: 3
用户头像

agnostic

关注

还未添加个人签名 2019-02-14 加入

还未添加个人简介

评论

发布
暂无评论
定时任务:历史 & 应用_定时任务_agnostic_InfoQ写作社区