分布式系统中的定时任务全解(一)

时间:2021-03-28 06:08:38

概述

分布式系统中的定时任务全解(二)–常见工程实现
分布式系统中的定时任务全解(三)–ej实现
分布式系统中的定时任务全解(四)–补充

在网站系统里面定时任务是一个重要和不可缺的角色,很多地方需要使用定时执行一项任务。比如,订单系统的接单超时、支付超时,结算系统的定时结算、奖励计算,第三方的认证信息刷新(微信的token),dsp等推广平台数据定时对接,缓存数据的定时更新等。

定时任务实现方式

Timmer定时器

这时最简单和最基础的了,学习过计算机课程的都能知道也都能写。

虽然常见,这里也简单的说几个点:

触发时间点

Timmer有两种指定执行时间的方式,一种是给一个时间间隔(Interval),另一种就是给定一个具体的执行时间。

分布式系统中的定时任务全解(一)

这时,timer的所有方法,其中画红圈圈的。第一个是在指定的时间点触发,给的参数是一个Date类型;第二个是按照指定的时间间隔触发,给的参数是一个long类型。这两种在实际工程中都是会被用到的。

Timer和TimerTask之间的关系

先看一个最常见的Timer使用示例:

public class MyTimeTask {
public static void main(String[] args){
Timer timer = new Timer();
timer.schedule(new MyTask1(), 60 * 1000);
timer.schedule(new MyTask2(), 120 * 1000);
}

class MyTask1 extends TimerTask {
public void run(){
System.out.println("running1....");
}
}

class MyTask2 extends TimerTask {
public void run(){
System.out.println("running2....");
}
}
}

可以看到,一个Timer是可以调度多个Task的,这个也可以从Timer的源代码看出来:
分布式系统中的定时任务全解(一)

mainloop循环在不断的读取queue中的task,逐个执行。对于按间隔期不断执行的task,会被计算下次执行时间后被重新放入到队列中。

这里的queue是一个优先级队列,会按照task的执行时间排序,最近的task会被先取出来执行。
分布式系统中的定时任务全解(一)

这里想说的是什么呢,在工程中最好不要一个task就创建一个timer,这样有点过于浪费系统资源,因为一个timer就是一个线程,而且在不断的消耗系统的cpu资源。

quartz

quartz是java里面最流行的定时任务调度框架,python里面的定时任务框架APScheduler也是基于Quartz,实现了Quartz的所有功能。
这里把quartz的内容多介绍一些,因为后续集群部分涉及到的elastic-job,同样是基于quartz实现的。要了解更多关于quartz的只是,推荐两个网站:
官网教程目录页

http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/

以梦为马的个人博客(一个系列的3篇)

http://blog.csdn.net/ahmuamu/article/details/50364769

quartz的只要构成是schedule、trigger、job三元素的体系,体系中还提供了三者各自的Listener用来监听他们的事件和对事件作出响应。以下是一个最原始的、最基础的quartz使用示例代码(来自官网):

  SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();

Scheduler sched = schedFact.getScheduler();

sched.start();

// define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1")
.build();

// Trigger the job to run now, and then every 40 seconds
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();

// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);

贴在这里,是为了让大家理解三者之间的关系。job封装任务执行的代码,trigger封装任务执行的时间信息,schedule绑定job和trigger执行任务调度。

在java世界里,spring已经是无所不在,接下来简单的看一下spring集成quartz需要做的事情(理解的上述的代码,也就容易理解,为什么spring配置文件里要配置这些东西了)。

1.首先就是要把quartz的包引入进来,添加maven引用:

<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>${quartz.version}</version>
</dependency>

2.定义job,定义job有两种方式,一种是使用MethodInvokingJobDetailFactoryBean封装自己的bean,这样自己的bean可以是一个任意的pojo;第二种是使用JobDetailFactoryBean,同时让自己的job扩展QuartzJobBean,实现executeInternal的抽象方法。

具体可以参加(这里给的比较详细):http://websystique.com/spring/spring-4-quartz-scheduler-integration-example/

3.定义trigger
这里可以定义简单的trigger(SimpleTriggerFactoryBean),他使用的是jdk的timer做为调度;也可以定义一个cron表达式类型的trigger(CronTriggerFactoryBean)去定义一个更语义化的触发表达式。

关于cron表达式的知识,可以参见这里:http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/tutorial-lesson-06.html

4.定义scheduler,绑定trigger和job

spring-scheduled注解

spring自己也提供了一个轻量级的定时任务工具,而且是在core包里面。可以使用scheduled注解,也可以仅使用xml配置。虽然说它轻量级,但是他实现了quartz支持的两种时间触发机制,简单的和cron表达式的。说他轻量级,也是因为他不能支持quartz能够支持的集群功能。

这里先推荐一个spring的官方版本(如果对spring-scheduled没有概念,需要先查baidu了解一些之后再看这里,因为这个写的比较简洁):http://spring.io/guides/gs/scheduling-tasks/

接下来一块看下scheduled的注解使用,也是两种用法,一个是普通的timer类似调度,一种是cron表达式方式调度。

这里只看cron表达是方式的,普通方式的请见上述网址。以下示例来自于http://howtodoinjava.com/。关于spring-scheduled的使用推荐以下几个网址:

http://howtodoinjava.com/spring/spring-core/4-ways-to-schedule-tasks-in-spring-3-scheduled-example/
http://www.cnblogs.com/Gyoung/p/5339242.html
http://spring.io/guides/gs/scheduling-tasks/

1.引入task命名空间,开启注解自动识别

< ?xml  version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util/ http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"
>

<task:annotation-driven />

</beans>

2.在需要调度的方法上添加@scheduled注解

@Scheduled(fixedDelay =30000)
public void demoServiceMethod () {... }

@Scheduled(fixedRate=30000)
public void demoServiceMethod () {... }

@Scheduled(cron="0 0 * * * *")
public void demoServiceMethod () {... }

好了看今天先说到这里,下一篇整理一下quartz基于数据库的集群实现以及elastic-job的使用。最后在给出一篇elastic-job的解析,以及从解析看实际使用场景的实现。