加入jfinal club也有1个月之多了,学习到很多,总是向人家索取,自己并未付出的这种状态不是我想要的。本人实力有限也想尽可能的提供自己的绵薄之力,这次就抛砖引入分享一下Quartz定时任务Redis集群方案,参考社区之前有人已经提供了的QuartzPlugin和波总在cron4j中解答的如何去现实cron4j的集群方案
为什么要做redis集群方案
有人会说Quartz已经有一套成熟的集群方案了,但是那个是数据库版本,而且配置我也觉得挺麻烦,我不喜欢直连数据库,感觉很费资源
QuartzPlugin我就不贴出来了,之前就有人分享过
主要实现的接口,里面用到的一些redis操作是自己写的RedisCache,模仿jfinal ehcache的写法
主要流程
package com.jfinalshop.task;
import com.jfinal.ext2.kit.JsonExtKit;
import com.jfinal.kit.JsonKit;
import com.jfinal.kit.StrKit;
import com.jfinalshop.util.IQuartzJobLoader;
import com.jfinalshop.util.RedisCacheKit;
import org.apache.commons.collections.map.HashedMap;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by little fish on 2017/8/5.
*/
public abstract class RedisJob implements Job {
private final String JOB_CACHE_PREFIX = "quartz_job_";
private final String JOB_LOCK_KEY = "isLock";
private final String JOB_RUN_COUNT_KEY = "runCount";
private final String JOB_FAIL_COUNT_KEY = "failCount";
private final String JOB_FAIL_MESSAGE_KEY = "failMessage";
private final String JOB_LAST_FIRE_TIME_KEY = "lastFireTime";
private final Integer JOB_LOCK_TRUE = 1;
private final Integer JOB_LOCK_FALSE = 0;
private static ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<String, ReentrantLock>();
private ReentrantLock getLock(String key) {
ReentrantLock lock = lockMap.get(key);
if (lock != null)
return lock;
lock = new ReentrantLock();
ReentrantLock previousLock = lockMap.putIfAbsent(key, lock);
return previousLock == null ? lock : previousLock;
}
private String genJobCacheName(JobExecutionContext jobExecutionContext){
return String.format("%s%s", JOB_CACHE_PREFIX, jobExecutionContext.getJobInstance().getClass().getSimpleName());
}
private Map<Object, Object> genResetJobCacheMap(int runCount, Long fireTime,int failCount, String failMessage){
Map<Object, Object> loadData = new HashedMap() ;
loadData.put(JOB_LOCK_KEY, JOB_LOCK_FALSE);//去锁
loadData.put(JOB_LAST_FIRE_TIME_KEY, fireTime);//同步最后一次触发时间
loadData.put(JOB_RUN_COUNT_KEY, runCount++);//运行次数加1
loadData.put(JOB_FAIL_COUNT_KEY, failCount);//错误次数
loadData.put(JOB_FAIL_MESSAGE_KEY, failMessage);//错误信息
return loadData;
}
public boolean checkJobValid(JobExecutionContext jobExecutionContext, IQuartzJobLoader jobLoader){
String cacheName = genJobCacheName(jobExecutionContext);
Integer isLock = RedisCacheKit.hget(cacheName, JOB_LOCK_KEY);
if(isLock == JOB_LOCK_TRUE){
return false;
}
Lock lock = getLock(cacheName);
lock.lock();
//初始化一些默认值
Integer runCount = 0;
Integer failCount = 0;
Long lastFireTime = 0L;
String failMessageJsonStr = "";
//实际插入redis的数据
Map<Object, Object> loadData = null;
try {
//再次拿出数据做double check检查并发
Map<Object, Object> redisCacheData = RedisCacheKit.hgetAll(cacheName);
isLock = (Integer)redisCacheData.get(JOB_LOCK_KEY);
lastFireTime = (Long)redisCacheData.get(JOB_LAST_FIRE_TIME_KEY);
lastFireTime = lastFireTime != null?lastFireTime:0;
runCount = (Integer)redisCacheData.get(JOB_RUN_COUNT_KEY);
runCount = runCount != null?runCount:0;
failCount = (Integer)redisCacheData.get(JOB_FAIL_COUNT_KEY);
failCount = failCount != null?failCount:0;
failMessageJsonStr = (String) redisCacheData.get(JOB_FAIL_MESSAGE_KEY);
failMessageJsonStr = failMessageJsonStr != null?failMessageJsonStr:"";
//检查是否满足执行条件
if ((isLock == null || isLock == JOB_LOCK_FALSE) && lastFireTime < jobExecutionContext.getFireTime().getTime()) {
//Redis加锁
RedisCacheKit.hset(cacheName, JOB_LOCK_KEY, JOB_LOCK_TRUE);
//处理Task主要业务逻辑
jobLoader.load();
//处理成功后把最新的状态同步到Redis中
loadData = genResetJobCacheMap(JOB_LOCK_FALSE, jobExecutionContext.getFireTime().getTime(), failCount, failMessageJsonStr);
}
return true;
}catch (Exception e){
e.printStackTrace();
failCount++;//失败次数+1
//添加失败信息以便之后检查
List<RedisJobFailMessage> faileMessageArray = null;
if(StrKit.isBlank(failMessageJsonStr)){
faileMessageArray = new ArrayList<RedisJobFailMessage>();
}else{
try {
faileMessageArray = JsonExtKit.jsonToJSONArray(failMessageJsonStr).toJavaList(RedisJobFailMessage.class);
}catch (Exception ex){
ex.printStackTrace();
}
if(faileMessageArray == null){
faileMessageArray = new ArrayList<RedisJobFailMessage>();
}
}
faileMessageArray.add(new RedisJobFailMessage(lastFireTime, e.getMessage()));
failMessageJsonStr = JsonKit.toJson(faileMessageArray);
loadData = genResetJobCacheMap(JOB_LOCK_FALSE, jobExecutionContext.getFireTime().getTime(), failCount, failMessageJsonStr);
}finally {
RedisCacheKit.hmsetForever(cacheName, loadData);
lock.unlock();
}
return false;
}
}
一个Loader接口其实就是和IDataLoader一模一样
- package com.jfinalshop.util;
- /**
- * Created by little fish on 2017/7/16.
- */
- public interface IQuartzJobLoader {
- public void load(Object... args);
- }
最后调用
- package com.jfinalshop.task;
- import com.jfinal.kit.LogKit;
- import com.jfinalshop.util.IQuartzJobLoader;
- import org.quartz.JobExecutionContext;
- import org.quartz.JobExecutionException;
- /**
- * Created by little fish on 2017/8/5.
- */
- public class TaskTest extends RedisJob {
- @Override
- public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
- this.checkJobValid(jobExecutionContext, new IQuartzJobLoader() {
- @Override
- public void load(Object... args) {
- //验证通过执行任务
- LogKit.info(String.format("getClass = %s fireTime = %s runtime = %s nextFireTime = %s result = %s",jobExecutionContext.getJobInstance().getClass().getSimpleName(),jobExecutionContext.getFireTime(),jobExecutionContext.getJobRunTime(),jobExecutionContext.getNextFireTime(),jobExecutionContext.getResult()));
- }
- });
- }
- }
刚写完的,代码写的很粗糙,只是做过冒烟测试,可以正常执行,没有做过优化