做为业务开发人员,会常常须要写一个定时任务。目前,写定时任务应用最普遍最成熟的方案是OpenSymphony开源组织在任务调度领域的一个开源项目quartz,好比要写一个定时数据同步任务,在完成quartz的相关配置后,只须要写一个jobBean_A就能基于quartz完成调度;若是要再开发一个任务,那么再写一个jobBean_B。html
这样作的方式有一个地方不是很好,quartz调度与job业务耦合在一块儿,当任务数量愈来愈多,甚至达到成千上万个,对于项目自己的维护也是一个挺大的挑战。这时,会考虑将quartz任务项目拆分出来,但这样每个任务工程都须要依赖quartz,不方便统一调度管理。为了能统一管理调度任务,又能将调度和job业务分离,咱们提出云调度方案。git
云调度中心ferrari的设计目标:github
基于上述初衷,咱们提出的云调度中心设计方案,如图: web
总共分为三层:调度控制层,调度接入层,业务层。spring
这样,要开发一个任务,基本不用关心调度控制层和接入层的逻辑,只需在业务层实现任务逻辑便可。任务开发完后,在调度控制中心新增一个调度任务信息,即可接收调度中心的调度。数据库
云调度中心的实现是基于quartz,因此对quartz必须有个清楚的理解。apache
Quartz任务调度的核心元素是scheduler(调度器),trigger(触发器,用于定义调度时间规则) 和 job(任务),其中 trigger 和 job 是任务调度的元数据,scheduler 是实际执行调度的控制器。quartz内部的调度原理能够查看后面列举的参考文档,这里具体讲讲使用quartz的几个注意点。 ####3.1 线程池配置 quartz.properties里的线程池配置:架构
#Configure ThreadPool org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 15 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
Quartz 中自带了一个线程池的实现SimpleThreadPool,并经过threadCount来设置最大并发数,通常设置在10~50比较合适。 ####3.2 misfire策略 misfire,即错过的,指原本应该被执行但实际没有被执行的任务调度,通常来讲引发misfire的状况有如下4种:并发
quartz对misfire的产生有个时间条件,超过这个设定的时间则认为是misfire,配置以下:app
#Misfire org.quartz.jobStore.misfireThreshold: 120000 #120秒 org.quartz.jobStore.maxMisfiresToHandleAtATime: 1
为了处理 misfired job,Quartz 中为 trigger 定义了处理策略,主要有下面两种:
ferrari使用的是第2种策略:
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();
####3.3 调度集群设置 Quartz 中的 trigger 和 job 须要存储下来才能被使用,有两种存储方式:内存和数据库。若是将调度信息存储在内存,那么只要quartz应用重启,这些信息就会丢失,因此在生产环境中,通常都用数据库存储的方式,配置以下:
#Configure JobStore for RAM #org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore #for cluster org.quartz.jobStore.tablePrefix = XXX_ org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.isClustered: true org.quartz.jobStore.clusterCheckinInterval: 20000
jobStore设置为jdbcjobstore.JobStoreTX即表明数据库存储方式,因为quartz集群是经过数据表来实现并发锁控制,因此须要设置集群的节点检查轮训时间clusterCheckinInterval,这里设置为20s。 ####3.4 调度器配置 spring对quartz进行了整合,这里采用基于spring的quartz配置,以下:
<!-- 默认 lazy-init="false"spring-context-support version: 3.2.14.RELEASE quartz version:2.2.2--> <bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 设置自动启动 --> <property name="autoStartup" value="true" /> <!--必须的,QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动 --> <property name="startupDelay" value="20" /> <!--须要overwrite已经存在的job,若是须要动态的修改已经存在的job,就须要设置为true,不然会以数据库中已经存在的为准--> <property name="overwriteExistingJobs" value="true" /> <property name="applicationContextSchedulerContextKey" value="applicationContextKey" /> <property name="configLocation" value="classpath:quartz.properties"/> </bean>
####3.5 quartz任务开发 基于云调度中心ferrari的架构,在调度控制中心层,咱们只要开发一个quartzJobBean。在这个jobBean中,基于ferrari协议将须要执行的类名、方法名、入参、任务机器地址等信息封装成一个request,而后发送请求(ferrari用的是http请求)到任务目标机器。
在业务层,任务机器接收到调度控制中心的指令,解析出任务信息,便交给任务执行线程池,任务执行线程经过反射调用目标任务类进行执行。做为业务层,无需基于quartz作任何开发,只需开发一个普通的类(称为任务类),而后将任务信息配置到调度控制中心,即可实现调度。
####4.1 maven依赖
<dependency> <groupId>com.dianping</groupId> <artifactId>ferrari-core</artifactId> <version>1.2.4</version> </dependency>
####4.2 web.xml配置servlet入口
<servlet> <servlet-name>FerrariServlet</servlet-name> <servlet-class>com.cip.ferrari.core.FerrariDirectServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>FerrariServlet</servlet-name> <url-pattern>/xxx/*</url-pattern> </servlet-mapping>
####4.3 开始写你的任务类及方法,类名、方法、入参在新增任务时配置 ferrari新增任务界面: ####4.4 云调度中心日志接入 因为job任务不在调度中心执行,而是有另外的job服务机器执行,因此要看业务日志代码,必须登陆对应的业务机器。若是job不少,又散落在各个机器,那么要查看job运行日志,效率就会比较低。为了方便日志查看,ferrari提供了日志接入方案,在log4j.xml中增长一个append配置:
<appender name="FERRARI" class="com.cip.ferrari.core.log.FerrariFileAppender"> <param name="filePath" value="/data/applogs/xxx/"/> <param name="append" value="true"/> <param name="encoding" value="UTF-8"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss} [%c]-[%t]-[%M]-[%L]-[%p] %m%n"/> </layout> </appender>
其中,filePath 是日志文件夹路径。只要将日志输出在这个appender上,就能在调度控制中心远程查看业务执行的日志,如图所示:
云调度中心ferrari的实现源码请移步: https://github.com/tkyuan/ferrari 记得给star^_^ ####参考文档: