那些年,我们用过的“定时调度”

栏目: 数据库 · 发布时间: 6年前

内容简介:作为后端开发人员,我们总会遇到这样的业务场景:每周同步一批数据;每半个小时检查一遍服务器运行状况;每天早上八点给用户发送一份包含今日待办事项的邮件,等等。这些场景中都离不开“定时器”,就像一个定好时间规则的闹钟,它会在指定时间触发,执行我们想要定义的调度任务。那么我们今天就来数一下,那些年我们用过的“定时调度”。从刚工作就一直使用oracle数据库,最早接触的定时任务就是oracle数据库的job。job有定时执行的功能,可以在指定的时间点或每天的某个时间点自行执行任务。 而且oracle重新启动后,job

定时调度

作为后端开发人员,我们总会遇到这样的业务场景:每周同步一批数据;每半个小时检查一遍服务器运行状况;每天早上八点给用户发送一份包含今日待办事项的邮件,等等。

这些场景中都离不开“定时器”,就像一个定好时间规则的闹钟,它会在指定时间触发,执行我们想要定义的调度任务。那么我们今天就来数一下,那些年我们用过的“定时调度”。

1. job (oracle)

从刚工作就一直使用oracle数据库,最早接触的定时任务就是oracle数据库的job。job有定时执行的功能,可以在指定的时间点或每天的某个时间点自行执行任务。 而且oracle重新启动后,job会继续运行,不用重新启动。

而且job的机制非常完备,可以查询相关的表或视图,查询job的定时规则和执行情况。缺点是作为oracle数据库层面的工具,自定义功能扩展,二次开发的难度比较大。

1.1 创建job

DECLARE
  job NUMBER;
BEGIN
    sys.dbms_job.submit(job => job,

    what => 'prc_name;',                          --执行的存储过程的名字

    next_date => to_date('22-11-2013 09:09:41', 'dd-mm-yyyy hh24:mi:ss'), --下一次执行时间

    interval =>'sysdate+1/24');            --每天24小时,即每小时运行prc_name过程一次
END;

-- job参数是输出参数,由submit()过程返回的binary_ineger,这个值用来唯一标识一个工作。一般定义一个变量接收,可以去user_jobs视图查询job值。
-- what参数是将被执行的PL/SQL代码块,存储过程名称等。
-- next_date参数指识何时将运行这个工作。
-- interval参数何时这个工作将被重执行

1.2 删除job

DECLARE
BEGIN
  dbms_job.remove(1093);  -- 1093为当前需要删除的 job 值
  COMMIT;
END;

1.3 查询job

-- 查询当前用户的job
select * from user_jobs;
-- 查询所有job
select * from dba_jobs;
-- 查询所有运行中的job
select * from dba_jobs_running;

2. crontab (linux)

crond 是 linux 下用来周期性的执行某种任务或等待处理某些事件的一个守护进程,与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务 工具,并且会自动启动crond进程,crond进程每分钟会定期检查是否有要执行的任务,如果有要执行的任务,则自动执行该任务。

cron是服务名称,crond是后台进程,crontab则是定制好的计划任务表。大部分linux系统默认都安装了cron,可以检查一下。

-- 检查Crontab工具是否安装
crontab -l
-- 检查crond服务是否启动
service crond status

-- centos安装
yum install vixie-cron
yum install crontabs

crontab基本操作命令

-- 列出某个用户cron服务的详细内容
crontab -l
-- 编辑某个用户的cron服务
crontab -e

crontab表达式格式

{minute} {hour} {day-of-month} {month} {day-of-week} {full-path-to-shell-script}

 minute: 区间为 0 – 59
 hour: 区间为0 – 23
 day-of-month: 区间为0 – 31
 month: 区间为1 – 12. 1 是1月. 12是12月
 Day-of-week: 区间为0 – 7. 周日可以是0或7.

在以上各个字段中,还可以使用以下特殊字符:
星号(*):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。
逗号(,):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9”
中杠(-):可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6”
正斜线(/):可以用正斜线指定时间的间隔频率,例如“0-23/2”表示每两小时执行一次。同时正斜线可以和星号一起使用,例如*/10,如果用在minute字段,表示每十分钟执行一次。

推荐一个crontab表达式的校验网站( https://tool.lu/crontab/

3. Timer和ScheduledExecutorService (java)

Timer是jdk中提供的一个定时器工具,使用的时候会在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次。

//只执行一次
public void schedule(TimerTask task, long delay);
public void schedule(TimerTask task, Date time);
//循环执行
// 在循环执行类别中根据循环时间间隔又可以分为两类
public void schedule(TimerTask task, long delay, long period) ;
public void schedule(TimerTask task, Date firstTime, long period) ;
 
public void scheduleAtFixedRate(TimerTask task, long delay, long period)
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)

TimerTask是一个实现了Runnable接口的抽象类,代表一个可以被Timer执行的任务。TimerTask类是一个抽象类,由Timer 安排为一次执行或重复执行的任务。它有一个抽象方法run()方法,该方法用于执行相应计时器任务要执行的操作。因此每一个具体的任务类都必须继承TimerTask,然后重写run()方法。

另外它还有两个非抽象的方法

-- 取消此计时器任务
boolean cancel()
-- 返回此任务最近实际执行的安排执行时间
long scheduledExecutionTime()

当然,一般使用Timer的比较少,因为它的缺点比较明显:

  1. 单线程,当多个timer同时运行时,会等上一个执行完成,再执行下一个。
  2. Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终止。

所以一般使用ScheduledExecutorService替代Timer。

ScheduledExecutorService:也是jdk自带的一个基于线程池设计的定时任务类。其每个调度任务都会分配到线程池中的一个线程执行,所以其任务是并发执行的,互不影响。

4. SpringTask (spring)

Timer和ScheduledExecutorService都是属于jdk层面上实现定时调度的类,功能还不足以让我们满意,那么现在介绍一个比较完善的定时调度工具 - SpringTask,是Spring提供的,支持注解和配置文件形式,支持crontab表达式,使用简单但功能强大。我个人非常喜欢SpringTask,仅仅是因为支持crontab表达式。

在springboot里面使用方式非常简单:

  1. 启动类添加开启定时调度的注解 @EnableScheduling
  2. 在需要定时执行的方法上,增加注解 @Scheduled(cron ="crontab表达式")

默认的简单的使用步骤只有以上两步,但是SpringTask的默认使用方式也有一些不足:

  1. 默认线程池的poolsize为1,可以理解为Timer类似的单线程模式。
  2. 无法动态修改crontab表达式,修改完只能重新部署后,才能生效。

问题1的解决方式,可以通过自定义 TaskExecutor来修改当前的线程池。问题2,则可以直接使用 threadPoolTaskScheduler类实现自定义的定时调度规则。

附解决两个问题的源码 TaskTimer.class

@Component
public class TaskTimer {

    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;
    @Autowired
    private TaskRepo taskRepo;

    /**
     * ***定时引擎***
     *
     * 实例化一个线程池任务调度类
     * 默认 ThreadPoolTaskScheduler 的 poolSize 为1,类似于newSingleThreadExecutor 单线程模式,只能执行完一个调度,再执行其他调度
     * 需要自定义扩展poolSize,允许一定程度的 多线程并行场景
     *
     * @return
     */
    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(100);
        threadPoolTaskScheduler.setThreadNamePrefix("Thread - ");
        threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
        threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
        return threadPoolTaskScheduler;
    }

    /**
     * 启动定时调度
     * @param taskCode
     * @param cron
     * @param runnable
     * @return
     */
    public boolean start(String taskCode, String cron,Runnable runnable){
        ScheduledFuture<?> currentFuture=taskRepo.findTask(taskCode);
        //已存在的调度,无法再创建
        if(currentFuture!=null){
            throw  new RuntimeException("调度\""+taskCode+"\"已存在,无法再创建");
        }
        //创建新的调度,并加入 taskMap
        currentFuture = threadPoolTaskScheduler.schedule(runnable,new CronTrigger(cron));
        if (currentFuture!=null){
            this.taskRepo.addTask(taskCode,currentFuture);
            return true;
        }
        throw  new RuntimeException("任务启动失败!!!");
    }

    /**
     * 暂停定时调度
     * @param taskCode
     * @return
     */
    public boolean stop(String taskCode) {
        //taskId 不存在的,无法停止,只能修改
        ScheduledFuture<?> currentFuture=this.taskRepo.findTask(taskCode);
        if(currentFuture!=null){
            return currentFuture.cancel(true);
        }
        return true;
    }

    /**
     * 删除定时调度
     * @param taskCode
     * @return
     */
    public boolean remove(String taskCode){
        ScheduledFuture<?> currentFuture=this.taskRepo.findTask(taskCode);
        if(currentFuture!=null){
             currentFuture.cancel(true);
             taskRepo.removeTask(taskCode);
             return true;
        }
        return false;
    }

    /**
     * 修改定时调度
     * @param taskCode
     * @param cron
     * @param runnable
     * @return
     */
    public boolean update(String taskCode,String cron,Runnable runnable){
        ScheduledFuture<?> currentFuture=this.taskRepo.findTask(taskCode);
        //已存在的定时调度,先停止,再新增,并更新 新的ScheduledFuture
        if(currentFuture!=null) {
            currentFuture.cancel(true);
        }
        currentFuture= threadPoolTaskScheduler.schedule(runnable,new CronTrigger(cron));
        if(currentFuture!=null){
            this.taskRepo.addTask(taskCode,currentFuture);
            return true;
        }
        return false;
    }

}

5. Quartz (其他产品)

Quartz是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。它是一个功能强大、十分成熟的重量级产品,还支持负载均衡,实现分布式调度。

不过,对于Quartz的安装你要多花点功夫了,从数据库要建哪些表,到应用程序该如何部署。对于这样一个庞大的产品,本篇文章就不附上它的使用说明书了。

参考文档


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Iterative Methods for Sparse Linear Systems, Second Edition

Iterative Methods for Sparse Linear Systems, Second Edition

Yousef Saad / Society for Industrial and Applied Mathematics / 2003-04-30 / USD 102.00

Tremendous progress has been made in the scientific and engineering disciplines regarding the use of iterative methods for linear systems. The size and complexity of linear and nonlinear systems arisi......一起来看看 《Iterative Methods for Sparse Linear Systems, Second Edition》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具