###1.簡單介紹html
Quartz
的功能十分強大,我這裏只是簡單記錄我在項目中使用的過程,便於之後查缺補漏. Quartz的基本知識請看這裏Quartz教程.因爲我使用的版本爲最新版(Version:2.2.2),所以和此篇文章的方法實現仍是有小部分差別.java
###2.需求說明redis
因爲我須要實現一個定時任務,須要隨意的更改定時時間或者取消定時任務.而且在程序崩潰重啓後可以從新運行那些還未結束的定時任務. Akka自帶的Scheduler並不能徹底知足需求.同時Quartz
的jobName
式的key->value
異步實現方式配合PlayFrameWork來使用可謂事半功倍.api
Quartz
的持久化存儲,還在研究....異步
本次的key
值我暫時存儲在redis
中,每次新增一個定時任務將jobName
放在redis
中,完成定時任務移除redis
中的jobName
.系統重啓前從redis
中分別取出未完成的JobName
進行從新計算.ide
###3.功能實現工具
libraryDependencies += "org.quartz-scheduler" % "quartz" % "2.2.2"
2.QuartzManager
工具類測試
package utils /** * Created by jiang on 16/1/18. */ import models.basic._ import org.quartz.DateBuilder._ import org.quartz._ import org.quartz.impl.StdSchedulerFactory import org.quartz.JobBuilder._ import org.quartz.CronScheduleBuilder._ import org.quartz.TriggerBuilder._ import java.util.{HashMap, Date} import play.api.Logger import scala.collection.JavaConverters._ import org.quartz.impl.matchers.GroupMatcher /** * Created by jiang on 16/1/18. */ object QuartzManager { val scheduler = new StdSchedulerFactory().getScheduler() val jobGroupName = "jobs" val triggerGroupName = "triggers" // 在Global.onStart是提早初始化運行 def initialize() = { scheduler.clear() scheduler.start() } def getJobKey(jobName: String): JobKey = { val group = GroupMatcher.groupEquals[JobKey](jobGroupName) val keys = scheduler.getJobKeys(group).asScala.filter(_.getName == jobName) if (keys.nonEmpty) keys.head else null } def jobExists(jobName: String): Boolean = { getJobKey(jobName) != null } // 新增定時任務,只執行一次.同時將wid存入JobDataMap中. // stage 參數爲不一樣階段業務需求,存放在redis hash 不一樣的key關鍵字 def addJob(wid:Long,stage:String,time:Long,jobs:Job) = { var ret:Option[Int] = None val jobName,triggerName = wid+":"+stage val data = new JobDataMap() data.put("wid", wid) if(jobExists(jobName)) { Logger.info(s"$wid :後臺已經有一個定時任務,請先取消再從新設置") ret = Option(0) }else{ val job: JobDetail = newJob(jobs.getClass) .withIdentity(jobName,jobGroupName) .usingJobData(data) .build() val trigger = newTrigger .withIdentity(triggerName,triggerGroupName) .startAt(new Date(time)) .forJob(jobName, jobGroupName) .build() scheduler.scheduleJob(job, trigger) // 將key 值存入redis 中 val key = "WorkTask:"+stage RedisUtils.getJedis.hset(key,wid.toString,time.toString) ret = Option(1) } // O-> 已存在,1->設置成功,none->未知錯誤 ret } def deleteJob(wid:Long,stage:String) ={ val jobName = wid+":"+stage scheduler.deleteJob(getJobKey(jobName)) // redis 中一樣刪除數據 val key = "WorkTask:"+stage RedisUtils.getJedis.hdel(key,wid.toString) } // 系統忽然down 掉,從新恢復定時任務 def recoverJob() = { lazy val time = System.currentTimeMillis() // 當前時間,redis 中時間若小於當前時間則當即執行. val jedis = RedisUtils.getJedis //1.恢復準備中的定時任務 val readyWidKeyMap = jedis.hgetAll("WorkTask:ready").asScala if(readyWidKeyMap.nonEmpty){ readyWidKeyMap.foreach(p=>{ val wid:Long = p._1.toLong val recoverReadyTime:Long = if(p._2.toLong <= time ) time else p._2.toLong addJob(wid,"ready",recoverReadyTime,new readyJob()) }) } //2. 恢復進行中的定時任務 val ongoingWidKeyMap = jedis.hgetAll("WorkTask:ongoing").asScala if(ongoingWidKeyMap.nonEmpty){ ongoingWidKeyMap.foreach(p=>{ val wid:Long = p._1.toLong val recoverOnGoingTime:Long = if(p._2.toLong <= time ) time+5000 else p._2.toLong addJob(wid,"ongoing",recoverOnGoingTime,new ongoingJob()) }) } //3.恢復結束定時任務 val endWidKeyMap = jedis.hgetAll("WorkTask:end").asScala if(endWidKeyMap.nonEmpty){ endWidKeyMap.foreach(p=>{ val wid:Long = p._1.toLong val recoverEndTime:Long = if(p._2.toLong <= time ) time+10000 else p._2.toLong addJob(wid,"end",recoverEndTime,new endJob()) }) } } }
3.定時任務實現功能ui
class TaskJob extends Job{ val DateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); val d = new Date() val returnStr = DateFormat.format(d) override def execute(jobExecutionContext: JobExecutionContext): Unit ={ val wid = jobExecutionContext.getJobDetail.getJobDataMap.get("wid") println(returnStr +"★★★★★★★★★★★:"+wid) } }
4.測試scala
// 當前時間後的10執行 val startTime = System.currentTimeMillis() + 10000 QuartzManager.addJob(2,"ready",startTime,new TaskJob() ) //10s 後,在控制檯能夠看到 2016-01-19 11:01:53★★★★★★★★★★★:2
我都寫了啥啊?