关键词:

1. 定时任务

2. 分布式

3. 可动态配置触发时间

一般通过Quartz实现定时任务很简单。如果实现分布式定时任务需要结合分布式框架选择master节点触发也可以实现。但我们有个实际需求是,页面可动态配置定时任务触发周期(比如,假如下班前如果把先决条件完成了,正常可以18:00触发完成批量任务,假如完不成,需要将任务设置到很晚,等到先决条件完成再触发)。这个时候需要满足1,2,3都要满足。这样实现起来就有一定难度了。

下面来看看我是怎样实现的,如有更好的实现方案,欢迎在评论区提出,谢谢!

1. Quartz实现定时任务

我通过一个工具类来实现,如下: 

import java.text.SimpleDateFormat
import java.util.Date import org.quartz.{CronScheduleBuilder, CronTrigger, JobBuilder, JobKey, TriggerBuilder, TriggerKey, _}
import org.quartz.impl.StdSchedulerFactory import scala.collection.JavaConverters._ /**
* 定时任务管理类
*
* @author BarryWang create at 2018/5/11 14:22
* @version 0.0.1
*/
object QuartzManager {
private val stdSchedulerFactory = new StdSchedulerFactory
private val JOB_GROUP_NAME = "JOB_GROUP_NAME"
private val TRIGGER_GROUP_NAME = "TRIGGER_NAME" /**
* 根据指定格式(yyyy-MM-dd HH:mm:ss)时间字符串添加定时任务,使用默认的任务组名,触发器名,触发器组名
* @param jobName 任务名
* @param time 时间设置,参考quartz说明文档
* @param jobClass 任务类名
*/
def addJobByTime(jobName: String, time: String, jobClass: Class[_ <: Job]) : Unit = {
QuartzManager.addJobByTime(jobName, time, jobClass, Map("1"->"otherData"))
} /**
* 根据指定时间(java.util.Date)添加定时任务,使用默认的任务组名,触发器名,触发器组名
*
* @param jobName 任务名
* @param date 日期
* @param jobClass 任务类名
*/
def addJobByDate(jobName: String, date: Date, jobClass: Class[_ <: Job]): Unit = {
QuartzManager.addJobByDate(jobName, date, jobClass, Map("1"->"otherData"))
} /**
* 根据指定cron表达式添加定时任务,使用默认的任务组名,触发器名,触发器组名
*
* @param jobName 任务名
* @param jobClass 任务类名
* @param cron cron表达式
*/
def addJobByCron(jobName: String, cron : String, jobClass: Class[_ <: Job]): Unit = {
QuartzManager.addJobByCron(jobName, cron, jobClass, Map("1"->"otherData"))
} /**
* 函数描述: 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名
* @param jobName 任务名
* @param time 时间字符串, 格式为(yyyy-MM-dd HH:mm:ss)
* @param jobClass 任务类名
* @param paramsMap 定时器需要额外数据
*/
def addJobByTime(jobName: String, time: String, jobClass: Class[_ <: Job], paramsMap: Map[_ <: String, _ <: AnyRef]): Unit = {
addJobByTime(jobName, time, "yyyy-MM-dd HH:mm:ss", jobClass, paramsMap)
} /**
* 函数描述: 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名
* @param jobName 任务名
* @param time 时间设置,参考quartz说明文档
* @param jobClass 任务类名
* @param paramsMap 定时器需要额外数据
*/
def addJobByTime(jobName: String, time: String, timePattern: String, jobClass: Class[_ <: Job], paramsMap: Map[_ <: String, _ <: AnyRef]): Unit = {
val df = new SimpleDateFormat(timePattern)
val cron = getCron(df.parse(time))
addJobByCron(jobName, cron, jobClass, paramsMap)
} /**
* Description: 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名
*
* @param jobName 任务名
* @param date 日期
* @param cls 任务
* @param paramsMap 定时器需要额外数据
*/
def addJobByDate(jobName: String, date: Date, cls: Class[_ <: Job], paramsMap: Map[_ <: String, _ <: AnyRef]): Unit = {
val cron = getCron(date)
addJobByCron(jobName, cron, cls, paramsMap)
} /**
* 函数描述: 根据cron表达式添加定时任务(默认触发器组名及任务组名)
* @param jobId 任务ID
* @param cron 时间设置 表达式,参考quartz说明文档
* @param jobClass 任务的类
* @param paramsMap 可变参数需要进行传参的值
*/
def addJobByCron(jobId: String, cron: String, jobClass: Class[_ <: Job], paramsMap: Map[_ <: String, _ <: AnyRef]): Unit = {
addJob(jobId, cron, jobClass, paramsMap, JOB_GROUP_NAME, TRIGGER_GROUP_NAME)
} /**
* 函数描述: 根据cron表达式添加定时任务
* @param jobId 任务ID
* @param cron 时间设置 表达式,参考quartz说明文档
* @param jobClass 任务的类类型 eg:TimedMassJob.class
* @param paramsMap 可变参数需要进行传参的值
* @param jobGroupName 任务组名
* @param triggerGroupName 触发器组名
*/
def addJob(jobId: String, cron: String, jobClass: Class[_ <: Job], paramsMap: Map[_ <: String, _ <: AnyRef],
jobGroupName: String, triggerGroupName: String): Unit = {
val scheduler = stdSchedulerFactory.getScheduler
// 任务名,任务组,任务执行类
val jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobId, jobGroupName).build
//设置参数
jobDetail.getJobDataMap.putAll(paramsMap.asJava) val triggerBuilder = TriggerBuilder.newTrigger
// 触发器名,触发器组
//默认设置触发器名与任务ID相同
val triggerName = jobId
triggerBuilder.withIdentity(triggerName, triggerGroupName)
triggerBuilder.startNow
// 触发器时间设定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron))
// 创建Trigger对象
val trigger = triggerBuilder.build.asInstanceOf[CronTrigger]
// 调度容器设置JobDetail和Trigger
scheduler.scheduleJob(jobDetail, trigger)
// 启动
if (!scheduler.isShutdown) scheduler.start()
} /**
* 函数描述: 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名)
* @param cron 时间字符串
*/
def modifyJobTime(jobId: String, cron: String, jobClass: Class[_ <: Job]): Unit = {
modifyJobTime(jobId, cron, jobClass, Map("1"->"otherData"), JOB_GROUP_NAME, TRIGGER_GROUP_NAME)
} /**
* 函数描述: 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名)
* @param cron 时间字符串
*/
def modifyJobTime(jobId: String, cron: String, jobClass: Class[_ <: Job], paramsMap: Map[_ <: String, _ <: AnyRef]): Unit = {
modifyJobTime(jobId, cron, jobClass, paramsMap, JOB_GROUP_NAME, TRIGGER_GROUP_NAME)
} /**
* 函数描述: 修改一个任务的触发时间
* @param jobId 任务ID
* @param cron cron表达式
* @param jobClass 任务类名
* @param paramsMap 其他参数
* @param jobGroupName 任务组名
* @param triggerGroupName 触发器组
*/
def modifyJobTime(jobId: String, cron: String, jobClass: Class[_ <: Job], paramsMap: Map[_ <: String, _ <: AnyRef],
jobGroupName: String, triggerGroupName: String): Unit = {
val scheduler = stdSchedulerFactory.getScheduler()
//默认设置触发器名与任务ID相同
val triggerName = jobId
val triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName)
var trigger = scheduler.getTrigger(triggerKey).asInstanceOf[CronTrigger]
if (trigger != null) {
removeJob(jobId)
}
addJob(jobId, cron, jobClass, paramsMap, jobGroupName, triggerGroupName)
} /**
* 函数描述: 移除一个任务(使用默认的任务组名,触发器名,触发器组名)
* @param jobId 任务名称
*/
def removeJob(jobId: String): Unit = {
val scheduler = stdSchedulerFactory.getScheduler
//默认设置触发器名与任务ID相同
val triggerName = jobId
val triggerKey = TriggerKey.triggerKey(triggerName, TRIGGER_GROUP_NAME)
// 停止触发器
scheduler.pauseTrigger(triggerKey)
// 移除触发器
scheduler.unscheduleJob(triggerKey)
// 删除任务
scheduler.deleteJob(JobKey.jobKey(jobId , JOB_GROUP_NAME))
} /**
* 函数描述: 移除一个任务
* @param jobId 任务ID
* @param jobGroupName 任务组
* @param triggerName 触发器名称
* @param triggerGroupName 触发器组名
*/
def removeJob(jobId: String, jobGroupName: String, triggerName: String, triggerGroupName: String): Unit = {
val scheduler = stdSchedulerFactory.getScheduler
val triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName)
// 停止触发器
scheduler.pauseTrigger(triggerKey)
// 移除触发器
scheduler.unscheduleJob(triggerKey)
// 删除任务
scheduler.deleteJob(JobKey.jobKey(jobId , jobGroupName))
} /**
* 函数描述:启动所有定时任务
*/
def startJobs(): Unit = {
stdSchedulerFactory.getScheduler.start()
} /**
* 函数描述:关闭所有定时任务
*
*/
def shutdownJobs(): Unit = {
val sched = stdSchedulerFactory.getScheduler
if (!sched.isShutdown) sched.shutdown()
} /**
* 根据时间获取Cron表达式
* @param date 日期
* @return
*/
def getCron(date: Date): String = {
val dateFormat = "ss mm HH dd MM ? yyyy"
formatDateByPattern(date, dateFormat)
} /**
* 日期格式转换
* @param date 日期
* @param dateFormat 格式
* @return
*/
def formatDateByPattern(date : Date, dateFormat : String): String = {
val sdf = new SimpleDateFormat(dateFormat)
sdf.format(date)
}
}

  


2.分布式定时任务实现
  分布式定时任务:即在分布式服务的环境下,启动定时任务。分布式定时任务需要解决的问题是:同一定时任务,同一时间点,只能在一台服务器上启动。
为了解决分布式定时任务的问题,有两种方案可以解决:
1)执行定时任务是添加分布式锁
分布式锁实现请参考:

分布式锁的三种实现方式

2)执行定时任务选取一台服务作为master节点执行该定时任务
我们需要框架层面解决定时任务触发时,选举一台服务器作为master节点。
实现思路如下:
1. 服务启动注册服务时,为服务编号;
2.从注册服务中随机选中一台服务器作为master节点;
3. 服务挂掉或添加时重新选举。 代码如下: 
import java.util.HashMap;
import java.util.Map; /**
* Created by tangliu on 2016/7/13.
*/
public class MasterHelper { public static Map<String, Boolean> isMaster = new HashMap<>(); /**
* 根据serviceName, versionName,判断当前服务是否集群中的master
* todo 服务版本号是否作为master判断的依据??
* @param servieName
* @param versionName
* @return
*/
public static boolean isMaster(String servieName, String versionName) { String key = generateKey(servieName, versionName); if (!isMaster.containsKey(key))
return false;
else
return isMaster.get(key); } public static String generateKey(String serviceName, String versionName) {
return serviceName + ":" + versionName;
}
} 竞选master:
/**
* 监听服务节点下面的子节点(临时节点,实例信息)变化
*/
public void watchInstanceChange(RegisterContext context) {
String watchPath = context.getServicePath();
try {
List<String> children = zk.getChildren(watchPath, event -> {
LOGGER.warn("ServerZk::watchInstanceChange zkEvent:" + event);
//Children发生变化,则重新获取最新的services列表
if (event.getType() == Watcher.Event.EventType.NodeChildrenChanged) {
LOGGER.info("容器状态:{}, {}子节点发生变化,重新获取子节点...", ContainerFactory.getContainer().status(), event.getPath());
if (ContainerFactory.getContainer().status() == Container.STATUS_SHUTTING
|| ContainerFactory.getContainer().status() == Container.STATUS_DOWN) {
LOGGER.warn("Container is shutting down");
return;
}
watchInstanceChange(context);
}
});
boolean _isMaster = false;
if (children.size() > 0) {
_isMaster = checkIsMaster(children, MasterHelper.generateKey(context.getService(), context.getVersion()), context.getInstanceInfo());
}
//masterChange响应
LifecycleProcessorFactory.getLifecycleProcessor().onLifecycleEvent(
new LifeCycleEvent(LifeCycleEvent.LifeCycleEventEnum.MASTER_CHANGE,
context.getService(), _isMaster));
} catch (KeeperException | InterruptedException e) {
LOGGER.error(e.getMessage(), e);
create(context.getServicePath() + "/" + context.getInstanceInfo(), context, true);
}
} //-----竞选master---
private static Map<String, Boolean> isMaster = MasterHelper.isMaster; /**
* @param children 当前方法下的实例列表, eg 127.0.0.1:9081:1.0.0,192.168.1.12:9081:1.0.0
* @param serviceKey 当前服务信息 eg com.github.user.UserService:1.0.0
* @param instanceInfo 当前服务节点实例信息 eg 192.168.10.17:9081:1.0.0
*/
public boolean checkIsMaster(List<String> children, String serviceKey, String instanceInfo) {
if (children.size() <= 0) {
return false;
} boolean _isMaster = false; /**
* 排序规则
* a: 192.168.100.1:9081:1.0.0:0000000022
* b: 192.168.100.1:9081:1.0.0:0000000014
* 根据 lastIndexOf : 之后的数字进行排序,由小到大,每次取zk临时有序节点中的序列最小的节点作为master
*/
try {
Collections.sort(children, (o1, o2) -> {
Integer int1 = Integer.valueOf(o1.substring(o1.lastIndexOf(":") + 1));
Integer int2 = Integer.valueOf(o2.substring(o2.lastIndexOf(":") + 1));
return int1 - int2;
}); String firstNode = children.get(0);
LOGGER.info("serviceInfo firstNode {}", firstNode); String firstInfo = firstNode.replace(firstNode.substring(firstNode.lastIndexOf(":")), ""); if (firstInfo.equals(instanceInfo)) {
isMaster.put(serviceKey, true);
_isMaster = true;
LOGGER.info("({})竞选master成功, master({})", serviceKey, CURRENT_CONTAINER_ADDR);
} else {
isMaster.put(serviceKey, false);
_isMaster = false;
LOGGER.info("({})竞选master失败,当前节点为({})", serviceKey);
}
} catch (NumberFormatException e) {
LOGGER.error("临时节点格式不正确,请使用新版,正确格式为 etc. 192.168.100.1:9081:1.0.0:0000000022");
} return _isMaster;
}

  

分布式环境判断是否是master:
if (!MasterHelper.isMaster("com.today.api.financetask.service.FinanceScheduledService", "1.0.0")) {
//excute the task
}

  

3. 动态配置定时任务触发时间
实现定时任务可动态配置,需要通过数据库表保存最新一次修改的cron表达式来实现:
建表如下:
CREATE TABLE t_scheduled_task  (
job_name varchar(50) NOT NULL COMMENT 'job名称'
, job_id varchar(40) NOT NULL COMMENT 'job ID'
, job_cron varchar(50) NOT NULL COMMENT 'Job cron表达式'
, job_type int COMMENT 'Job类型'
, is_start tinyint(2) DEFAULT 1 NOT NULL COMMENT '是否已启动,0:否(no);1:是(yes)'
, remark varchar(256) COMMENT '备注'
, updated_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '更新时间'
, created_at timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间'
, has_deleted tinyint(2) DEFAULT 0 NOT NULL COMMENT '是否已删除,0:否(no);1:是(yes)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='定时任务配置表';
CREATE UNIQUE INDEX uk_t_scheduled_task ON t_scheduled_task ( job_id);
建表后需要页面调用接口实现先停掉上次的定时任务,再根据最新修改的触发时间新建一个新的定时任务:
如下:
//页面设置每天触发的时间,格式:HH:mm
val cron = convertHourMinuteToCron(processTime)
//修改定时任务时间, 保存入库
ScheduledTaskQuerySql.isExists(jobId) match {
case true => ScheduledTaskActionSql.updateTaskCron(jobId, cron)
case false => ScheduledTaskActionSql.insertTScheduledTask(
TScheduledTask( report.name,
jobId,
cron,
None,
TScheduledTaskIsStartEnum.YES.id,
None,
null,
null,
TScheduledTaskHasDeletedEnum.NO.id))
}
//关掉老的定时任务,添加新的定时任务
QuartzManager.modifyJobTime(jobId, cron, classOf[DailyGenIncomeDetailJob])

  

/**
* 每天定时触发-转换时分格式(hh:mm)为cron表达式
* @param hourMibuteStr
* @return
*/
def convertHourMinuteToCron(hourMibuteStr : String) : String = {
val splitHm = hourMibuteStr.split(":")
s"0 ${splitHm(1).trim.toInt} ${splitHm(0).trim.toInt} * * ?"
} /**
* 每天定时触发-转换时分格式(hh:hh:mm)为cron表达式
* @param dayStr
* @return
*/
def convertDayToCron(dayStr : String) : String = {
val splitHm = dayStr.split(":")
s"0 ${splitHm(1).trim.toInt} ${splitHm(0).trim.toInt} * * ?"
}

  

定义Job及父类:

Job定义


import java.util.Calendar

import com.today.api.checkaccount.scala.enums.FlatFormTypeEnum
import com.today.api.checkaccount.scala.request.ReconciliationRequest
import com.today.api.checkaccount.scala.CheckAccountServiceClient
import com.today.service.financetask.job.define.{AbstractJob, JobEnum}
import org.quartz.JobExecutionContext class CheckAccountJob extends AbstractJob{
/**
* get the api information
*
* @return (interface name, interface version, JobEnum)
*/
override def getJobAndApiInfo(context: JobExecutionContext): (String, String, JobEnum) = {
("com.today.api.financetask.service.CloseAccountScheduleService", "1.0.0", JobEnum.CHECK_ACCOUNT_PROCESS)
} /**
* start up the scheduled task
*
* @param context JobExecutionContext
*/
override def run(context: JobExecutionContext): Unit = {
val cal = Calendar.getInstance
cal.add(Calendar.DATE, -1)
new CheckAccountServiceClient().appReconciliation(new ReconciliationRequest(FlatFormTypeEnum.TODAY_APP,None)) }
}
 

公共父类

import java.io.{PrintWriter, StringWriter}

import com.github.dapeng.core.helper.MasterHelper
import com.today.api.financetask.scala.enums.TScheduledTaskLogEnum
import com.today.service.financetask.action.sql.ScheduledTaskLogSql
import com.today.service.financetask.util.{AppContextHolder, Debug}
import org.quartz.{Job, JobExecutionContext}
import org.slf4j.LoggerFactory
import org.springframework.transaction.TransactionStatus
import org.springframework.transaction.support.TransactionTemplate import scala.util.{Failure, Success, Try} /**
* the abstract class for job
*/
trait AbstractJob extends Job{
/** 日志 */
val logger = LoggerFactory.getLogger(getClass) /**
* execute the job
* @param context
*/
override def execute(context: JobExecutionContext): Unit = {
val jobAndApiInfo = getJobAndApiInfo(context)
if (!MasterHelper.isMaster(jobAndApiInfo._1, jobAndApiInfo._2)) {
logger.info(s"Can't select master to run the job ${jobAndApiInfo._3.jobId}: ${jobAndApiInfo._3.jobName}")
return
} val logId = ScheduledTaskLogSql.insertScheduledTaskLog(jobAndApiInfo._3)
context.put("logId", logId)
logger.info(s"Starting the job ${jobAndApiInfo._3.jobId}: ${jobAndApiInfo._3.jobName} ...") /**
* 事物处理
*/
val transactionTemplate: TransactionTemplate = AppContextHolder.getBean("transactionTemplate")
transactionTemplate.execute((status: TransactionStatus) =>{
Debug.reset()
Try(Debug.trace(s"${jobAndApiInfo._3.jobId}:${jobAndApiInfo._3.jobName}")(run(context))) match
{
case Success(x) => {
logger.info(s"Successfully execute the job ${jobAndApiInfo._3.jobId}: ${jobAndApiInfo._3.jobName}")
successLog(logId)
}
case Failure(e) => {
logger.error(s"Failure execute the job ${jobAndApiInfo._3.jobId}: ${jobAndApiInfo._3.jobName}", e)
failureLog(logId, status, e)
}
}
Debug.info()
})
} /**
* get the api information
* @return (interface name, interface version, JobEnum)
*/
def getJobAndApiInfo(context: JobExecutionContext) : (String, String, JobEnum) /**
* start up the scheduled task
* @param context JobExecutionContext
*/
def run(context: JobExecutionContext) /**
* 成功日志记录
* @param logId
*/
def successLog(logId: Long): Unit ={
ScheduledTaskLogSql.updateExportReportRecord(logId, TScheduledTaskLogEnum.SUCCESS, "Success")
} /**
* 失败日志记录
* @param logId
*/
def failureLog(logId: Long, status: TransactionStatus, e: Throwable): Unit ={
status.setRollbackOnly()
ScheduledTaskLogSql.updateExportReportRecord(logId, TScheduledTaskLogEnum.FAILURE, getExceptionStack(e))
} /**
*
* 功能说明:在日志文件中 ,打印异常堆栈
* @param e : Throwable
* @return : String
*/
def getExceptionStack(e: Throwable): String = {
val errorsWriter = new StringWriter
e.printStackTrace(new PrintWriter(errorsWriter))
errorsWriter.toString
}
}
4. 重启服务器,启动所有定时任务
重启定时任务,需要重启所有定时任务
这个过程需要将所有定时任务及触发周期保存到数据库,重启后,读取数据库启动恢复所有定时任务
代码如下(Spring框架下启动服务自动会启动ApplicationListener.onApplicationEvent(event: ContextRefreshedEvent)):

import com.today.api.financetask.scala.enums.{TReportTypeEnum, TScheduledTaskIsStartEnum}
import com.today.api.financetask.scala.request.QueryAutoConfigRequest
import com.today.service.financetask.job._
import com.today.service.financetask.job.define.JobEnum
import com.today.service.financetask.query.sql.{AutoConfigQuerySql, ScheduledTaskQuerySql}
import com.today.service.financetask.util.QuartzManager
import org.slf4j.LoggerFactory
import org.springframework.context.ApplicationListener
import org.springframework.context.event.ContextRefreshedEvent
import org.springframework.stereotype.Service /**
* 类功能描述: 定时器监听器, 服务启动时启动定时器
*
* @author BarryWang create at 2018/5/11 12:04
* @version 0.0.1
*/
@Service
class ScheduleStartListener extends ApplicationListener[ContextRefreshedEvent] {
/** 日志 */
val logger = LoggerFactory.getLogger(getClass)
/**
* 启动加载执行定时任务
*/
override def onApplicationEvent(event: ContextRefreshedEvent): Unit = {
logger.info("=======服务器重启定时任务启动start=======")
//1. 恢复日次处理定时任务
recoveryDayTimeProcessJob()
//2. 恢复每天营收明细报表生成定时任务
recoveryImcomeDetailGenJob()
logger.info("=======服务器重启定时任务启动end=======")
} /**
* 恢复日次处理定时任务
*/
private def recoveryDayTimeProcessJob(): Unit ={
try {
ScheduledTaskQuerySql.queryByJobId(JobEnum.DAY_TIME_PROCESS.jobId) match {
case Some(x) =>
if(x.isStart == TScheduledTaskIsStartEnum.YES.id)
QuartzManager.addJobByCron(JobEnum.DAY_TIME_PROCESS.jobId, x.jobCron, classOf[DayTimeProcessJob])
else
logger.info("定时任务:" + JobEnum.DAY_TIME_PROCESS.jobName + "is_start标志为0,不启动")
case None =>
QuartzManager.addJobByCron(JobEnum.DAY_TIME_PROCESS.jobId, "0 30 2 * * ?", classOf[DayTimeProcessJob])
}
} catch {
case e : Exception => logger.error(JobEnum.DAY_TIME_PROCESS.jobName + "启动失败, 失败原因:", e)
} } /**
* 恢复营收明细报表生成定时任务
*/
private def recoveryImcomeDetailGenJob(): Unit = {
val jobName = TReportTypeEnum.INCOMEDETAIL_REPORT.name
try {
val jobId = TReportTypeEnum.INCOMEDETAIL_REPORT.id.toString
ScheduledTaskQuerySql.queryByJobId(jobId) match {
case Some(x) =>
if (x.isStart == TScheduledTaskIsStartEnum.YES.id)
QuartzManager.addJobByCron(jobId, x.jobCron, classOf[DailyGenIncomeDetailJob])
else
logger.info("定时任务:" + jobName + "is_start标志为0,不启动")
case None =>
QuartzManager.addJobByCron(jobId, "0 10 0 * * ?", classOf[DailyGenIncomeDetailJob])
}
}catch {
case e : Exception => logger.error(jobName + "启动失败, 失败原因:", e)
}
}
}

  

大家也看到上面代码会使用使用JobEnum的枚举来定义不同定时任务信息。

如下就是使用Scala枚举定义定时信息如下:

case class JobEnum(val jobId: String, val jobName: String) extends Enumeration

/**
* 所有Job 枚举定义在此类, 不能重复
* jobId不能重复
* @author BarryWang create at 2018/5/12 10:45
* @version 0.0.1
*/
object JobEnum {
val DAY_TIME_PROCESS = JobEnum("DAY_TIME_PROCESS", "日次处理定时任务") val INVOICE_SYNC_PROCESS = JobEnum("INVOICE_SYNC_PROCESS", "采购系统同步单据数据定时任务") val RETIREMENT_SYNC_PROCESS = JobEnum("RETIREMENT_SYNC_PROCESS", "采购系统同步报废单据数据定时任务") val CLOSE_ACCOUNT_STATE_PROCESS = JobEnum("CLOSE_ACCOUNT_STATE_PROCESS","更新关账状态定时任务") val PURCHASE_ORDER_2_SYNC_PROCESS = JobEnum("PURCHASE_ORDER_2_SYNC_PROCESS","采购系统同步PO2数据定时任务") val SEND_EMAIL_PROCESS = JobEnum("SEND_EMAIL_PROCESS","计划付款通知和已付款通知定时任务") val CLOSE_ACCOUNT_BASE_DATA_PROCESS = JobEnum("CLOSE_ACCOUNT_BASE_DATA_PROCESS","关账基础数据同步定时任务")
}

这是可动态配置的分布式定时任务第一版已实现,但还是还是有优化的地方:

1.枚举里面只有jobId及jobName,为了启动服务时不需要每个定时任务都写一个恢复方法,可以将枚举里面定时任务信息多添加jobClass及cron表达式,这样以后添加新定时任务只需要维护枚举信息就可以;

2.每个Job子类里面都需要实现 override def getJobAndApiInfo(context: JobExecutionContext): (String, String, JobEnum) 方法,这个也可以省掉;

3.新添加定时任务完全可以制定一个Job子类,其他操作自动维护进去;

1,2请参考: Scala 定义复杂枚举 是实现,这种方式虽然做了简化,只需要定义Job子类及对应JobEnum。但理想状态所有定时任务信息只需要维护在Job子类就行了

3通过使用反射实现将所有定时任务信息只需要维护在Job子类,请参考: Java Scala获取注解的类信息

至此整个实现分布式环境下实现动态可配置的定时任务过程已完成

 

Quartz实现分布式可动态配置的定时任务的更多相关文章

  1. Quartz实现JAVA定时任务的动态配置

    什么是动态配置定时任务? 首先说下这次主题,动态配置.没接触过定时任务的同学可以先看下此篇:JAVA定时任务实现的几种方式 定时任务实现方式千人千种,不过基础的无外乎 1.JDK 的Timer类 2. ...

  2. Quartz Spring分布式集群搭建Demo

    注:关于单节点的Quartz使用在这里不做详细介绍,直接进阶为分布式集群版的 1.准备工作: 使用环境Spring4.3.5,Quartz2.2.3,持久化框架JDBCTemplate pom文件如下 ...

  3. springboot整合Quartz实现动态配置定时任务

    前言 在我们日常的开发中,很多时候,定时任务都不是写死的,而是写到数据库中,从而实现定时任务的动态配置,下面就通过一个简单的示例,来实现这个功能. 一.新建一个springboot工程,并添加依赖 & ...

  4. Quartz 在 Spring 中如何动态配置时间--转

    原文地址:http://www.iteye.com/topic/399980 在项目中有一个需求,需要灵活配置调度任务时间,并能自由启动或停止调度. 有关调度的实现我就第一就想到了Quartz这个开源 ...

  5. 基于spring+quartz的分布式定时任务框架

    问题背景 我公司是一个快速发展的创业公司,目前有200人,主要业务是旅游和酒店相关的,应用迭代更新周期比较快,因此,开发人员花费了更多的时间去更=跟上迭代的步伐,而缺乏了对整个系统的把控 没有集群之前 ...

  6. Quartz在Spring中动态设置cronExpression (spring设置动态定时任务)

    什么是动态定时任务:是由客户制定生成的,服务端只知道该去执行什么任务,但任务的定时是不确定的(是由客户制定).      这样总不能修改配置文件每定制个定时任务就增加一个trigger吧,即便允许客户 ...

  7. Quartz动态配置表达的方法

    在项目中有一个需求,需要灵活配置调度任务时间,并能自由启动或停止调度.有关调度的实现我就第一就想到了Quartz这个开源调度组件,因为很多项目使用过,Spring结合Quartz静态配置调度任务时间, ...

  8. Spring 与 Quartz 动态配置(数漫江湖)

    因为项目的需求,需要有动态配置计划任务的功能.本文在 Quartz JobBean 中获取配置的 Quartz cronExpression 时间表达式及 Spring Bean 的对象名.方法名并运 ...

  9. 3分钟掌握Quartz.net分布式定时任务的姿势

    引言 长话短说,今天聊一聊分布式定时任务,我的流水账笔记: ASP.NET Core+Quartz.Net实现web定时任务 AspNetCore结合Redis实践消息队列 细心朋友稍一分析,就知道还 ...

随机推荐

  1. 【手记】小心在where中使用NEWID()的大坑

    这个表达式: --把GUID弄成正整数,然后取模 是随机返回0.1.2这三个数,不可能返回其它东西,但是如果把它用在where里面,就会发生很神奇的事情,比如这个查询: --创建一个只有1列3行的表, ...

  2. Node.js使用supervisor遭遇‘supervisor’不是内部或外部命令,如果解决?

    如果你有 PHP 开发经验,会习惯在修改 PHP 脚本后直接刷新浏览器以观察结果,而你 在开发 Node.js 实现的 HTTP 应用时会发现,无论你修改了代码的哪一部份,都必须终止 Node.js ...

  3. How nginx "location if" works

    Nginx's if directive does have some weirdness in practice. And people may misuse it when they do not ...

  4. kafka 三个配置文件

    kafka的配置分为 broker.producter.consumer三个不同的配置 一 BROKER 的全局配置   最为核心的三个配置 broker.id.log.dir.zookeeper.c ...

  5. (二)Web应用体系结构

    容器 Servlet没有main()方法,它们受控于另一个Java应用,这个Java应用称为容器(Container).我们最常见的tomcat就是这样一个容器. Web服务器应用(如Apache)得 ...

  6. Python 模块详解及import本质

    同在当前目录下的模块和包导入 模块定义 本质就是.py结尾的python文件. 用来从逻辑上组织python代码(变量,函数,类,逻辑) 文件名: test.py;  对应的模块名 : test 模块 ...

  7. mysql服务设置远程连接 解决1251 client does not support ..问题

    在docker里面创建mysql容器后设置的密码在远程主机连接时候出现错误: 一.如果是在docker里面安装的mysql镜像则需要先进入mysql里面:参考上一篇:https://www.cnblo ...

  8. Android编译自己的程序到/system/bin

    背景 有时候我们想创建一个程序,放在系统中,供其他APP执行.我们知道,在生成system.img的时候,编译系统会将out/target/product/[product]/system/bin目录 ...

  9. 为什么面试你要25K,HR只给你20K?

    周末了,我们来聊个轻松的话题,关于涨薪,哈哈~ 前阵子,栈长给大家分享了<为什么公司宁愿 25K 重新招人,也不给你加到 20K?>,今天我们来聊一个差不多的话题: 为什么面试你要25K, ...

  10. 多租户实现之基于Mybatis,Mycat的共享数据库,共享数据架构

    前言 SaaS模式是什么? 传统的软件模式是在开发出软件产品后,需要去客户现场进行实施,通常部署在局域网,这样开发.部署及维护的成本都是比较高的. 现在随着云服务技术的蓬勃发展,就出现了SaaS模式. ...