前言
实现逻辑:定时器分业务定时器和补录定时器,业务定时器如果执行失败需要补录,调相关方法写入待补录信息,有补录定时器定时扫描补录。
支持设置补录次数。
一、Quartz是什么?
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:
持久性作业 - 就是保持调度定时的状态;
作业管理 - 对调度作业进行有效的管理;
二、使用步骤
1.补录采集器
ps:AbstractCollector为自封装调度器。
代码如下(示例):
/***
* <b>function:补录基础采集器(没有继承风险统计通用类)</b>
*/
public abstract class BaseSupplementCollectorWithoutStatiscs extends AbstractCollector {
private static final Logger LOG = LoggerFactory.getLogger(BaseSupplementCollectorWithoutStatiscs.class);
protected final static String DATE_FORMAT = "yyyyMMddHHmm";
/**
* waitForRun:保存信息至缓存,等待补录定时器重新执行. <br/>
* @author chenpb
* @param date
* @param collectorFlag
*/
public void waitForRun(String dateStr, String collectorFlag) {
NoCollectorData data = NoCollectorDataCacheManager.find(dateStr, collectorFlag);
if(data == null) {
data = new NoCollectorData(dateStr, collectorFlag);
LOG.info("添加进缓存等待补录,{}", data.toString());
NoCollectorDataCacheManager.getNoCollectorData().add(data);
}
}
/**
* waitForRun:保存信息至缓存,等待补录定时器重新执行. <br/>
* @author chenpb
* @param date
* @param collectorFlag
*/
public void waitForRun(Date date, String collectorFlag) {
String dateStr = DateUtil.dateToString(date, DATE_FORMAT);
waitForRun(dateStr, collectorFlag);
}
/**
* checkIsSupplement:检查是否是补录,是则日期取补录数据的时次. <br/>
* @author chenpb
* @param configuration
* @param date
* @return
*/
public Date checkIsSupplement(BaseNoCollectorDataConfiguration configuration, Date date) {
LOG.info("是否补录:" + configuration.getFlag());
if(configuration.getFlag()) {
date = DateUtil.str2Date(configuration.getNoCollectorDate(), DATE_FORMAT);
}
return date;
}
/**
* checkRemove:当是补录时,删除对应缓存信息. <br/>
* @author chenpb
* @param configuration
* @param date
* @param collectorFlag
*/
public void checkRemove(BaseNoCollectorDataConfiguration configuration, Date date, String collectorFlag) {
String dateStr = DateUtil.date2Str(date, DATE_FORMAT);
if(configuration.getFlag()) {
NoCollectorDataCacheManager.remove(new NoCollectorData(dateStr, collectorFlag));
LOG.info("{}的时次 {} 补录成功~~~~", collectorFlag, dateStr);
}
}
public void lock(BaseNoCollectorDataConfiguration configuration) {
configuration.setLocked(true);
}
public void unlock(BaseNoCollectorDataConfiguration configuration) {
configuration.setLocked(false);
}
}
2.业务定时器
继承补录采集器,重写collector定时任务主方法,关键代码:
(1)检查是否补录并加锁,加锁是为了防止该定时器在上一次调度没完成时重复执行
//检查是否是补录,是日期取补录数据的时次
date = checkIsSupplement(getRACSwanQPEConfiguration, date);
lock(getRACSwanQPEConfiguration); //先加锁,防止在多次补录过程中同时进行,出现多个任务对同一份文件进行操作
(2)接口数据异常捕捉,记录待补录信息到缓存,等待补录定时器重写补录
catch (CollectInterfaceException | NoCollectDataException e) {
// 接口异常、接口无数据异常
LOG.error(e.getMessage());
unlock(getRACSwanQPEConfiguration); //解锁
//保存信息至缓存,等待另一个定时任务重新执行
waitForRun(date, COLLECTOR_FLAG);
//一定要返回,不进行下面的删除缓存操作
return null;
}
(3)调度完成后统一执行代码块,如果是补录,删除对应缓存信息,并解锁
//当是补录时,没有出现接口异常、接口无数据异常,则删除对应缓存信息,使得不会再继续补录该时次了
checkRemove(getRACSwanQPEConfiguration, date, COLLECTOR_FLAG);
unlock(getRACSwanQPEConfiguration); //解锁
代码如下(示例):
/**
* 采集QPE数据<br />
* 采集过去一小时、过去三小时、过去三小时的强降水
*/
public class GetRACSwanQPECollector extends BaseSupplementCollectorWithoutStatiscs {
private static final Logger LOG = LoggerFactory.getLogger(GetRACSwanQPECollector.class);
private final static String COLLECTOR_FLAG = "getRACSwanQPEConfiguration";//本采集器标识,对应collector-context.xml配置
@Override
public List<DataStore> collector(Configuration config) {
LOG.info("{} collector execute.", config.getJobName());
GetRACSwanQPEConfiguration getRACSwanQPEConfiguration = null;
long start = System.currentTimeMillis();
Date date = null;
try {
// 获取配置文件
getRACSwanQPEConfiguration = (GetRACSwanQPEConfiguration) config;
// 采集时间是否移动(可以加减分钟数)
long moveMin = getRACSwanQPEConfiguration.getMoveMin();
date = new Date(System.currentTimeMillis() + moveMin * 60 * 1000);
//检查日期分钟是否为6的倍数,不是,向前取最近的
date = checkDateTime(date);
//检查是否是补录,是日期取补录数据的时次
date = checkIsSupplement(getRACSwanQPEConfiguration, date);
lock(getRACSwanQPEConfiguration); //先加锁,防止在多次补录过程中同时进行,出现多个任务对同一份文件进行操作
// 业务逻辑
collector(getRACSwanQPEConfiguration, date);
} catch (ClassCastException e) {
// 传入配置类型错误
LOG.error("collector'configuration is error,{}采集器的配置类型应为{}",
config.getJobName(), config.getClass().getName());
} catch (CollectInterfaceException | NoCollectDataException e) {
// 接口异常、接口无数据异常
LOG.error(e.getMessage());
unlock(getRACSwanQPEConfiguration); //解锁
//保存信息至缓存,等待另一个定时任务重新执行
waitForRun(date, COLLECTOR_FLAG);
//一定要返回,不进行下面的删除缓存操作
return null;
} catch (Exception e) {
LOG.info("系统出现未知异常,异常消息如下:{}", e);
}
//当是补录时,没有出现接口异常、接口无数据异常,则删除对应缓存信息,使得不会再继续补录该时次了
checkRemove(getRACSwanQPEConfiguration, date, COLLECTOR_FLAG);
LOG.info("【耗时】:{} 共耗时 :【{}】秒", this.getClass().getSimpleName(),
(System.currentTimeMillis() - start) / 1000.0);
unlock(getRACSwanQPEConfiguration); //解锁
return null;
}
@Override
public DataStore dataMakeup(Configuration config, DataStore dataStore) {
return null;
}
}
xml配置
<!-- QPE采集器 -->
<bean id="getRACSwanQPECollector" class="com.linkcm.wmp.gd.collector.GetRACSwanQPECollector" />
<!-- QPE采集配置 每6分钟执行 -->
<bean id="getRACSwanQPEConfiguration" class="com.linkcm.wmp.gd.configure.GetRACSwanQPEConfiguration">
<property name="cronExpression" value="0 0/6 * * * ?" />
<property name="collector" ref="getRACSwanQPECollector" />
<property name="url"
value="采集接口" />
<property name="dataFormat" value="yyyyMMddHHmm00" />
<!-- 可选,采集时间偏移,用于数据源数据时间跟实际有偏差的情况,比如这里是每次采集当前时间-12分钟的时间数据-->
<property name="moveMin" value="-12" />
<!-- 可选,补录次数,不配置取补录采集器默认配置-->
<!-- <property name="maxTimes" value="5"/> -->
</bean>
3.补录定时器
业务定时器执行失败,需要补录时,写入缓存相关信息,由补录定时器定时扫描进行补录。
/***
* <b>function: 补录程序</b>
*/
public class SupplementCollector extends AbstractCollector {
private static final Logger LOG = LoggerFactory.getLogger(SupplementCollector.class);
/**
* 补录数据主方法.
* @see com.linkcm.collector.core.collector.ICollector#collector(com.linkcm.collector.core.configure.Configuration)
*/
@Override
public List<DataStore> collector(Configuration configuration) {
Set<NoCollectorData> set = NoCollectorDataCacheManager.getNoCollectorData();
LOG.info("需要补录数据:{}", set.toString());
synchronized(set) {
Iterator<NoCollectorData> it = set.iterator();
while(it.hasNext()) {
final NoCollectorData data = it.next();
if(data.getLocked()) {
LOG.info("为锁状态,需等待解锁才能进行补录!");
continue;
}
final CollectorFactory collectorFactory = (CollectorFactory) SpringContextUtil.getBean("collectorFactory");
final Map<String, Configuration> configs = collectorFactory.getConfigurations(); //获取collector-context.xml配置的configurations属性
LOG.info("开始补录数据,{}", data.toString());
new Thread(new Runnable() {
@Override
public void run() {
BaseNoCollectorDataConfiguration config = (BaseNoCollectorDataConfiguration) configs.get(data.getConfiguration()); //获取对应Collector的配置信息
//需要拷贝副本,不然会导致正常采集和补录采集的Configuration配置冲突
BaseNoCollectorDataConfiguration configCopy = config.clone();
SupplementConfiguration config2 = (SupplementConfiguration) configs.get("supplementConfiguration"); //获取补录采集器的配置信息
Integer maxTimes = configCopy.getMaxTimes() != null ? configCopy.getMaxTimes() : config2.getMaxTimes();
if(data.getNumber() < maxTimes) { //控制补录次数
LOG.info("开始进行{}的第{}次补录", data.toString(), data.getNumber() + 1);
//更新该时次已执行次数+1
data.setNumber(data.getNumber() + 1);
NoCollectorDataCacheManager.addNoCollectorData(data);
configCopy.setFlag(true); //设置为补录状态
configCopy.setNoCollectorDate(data.getDataTime()); //设置补录数据的时次
Map<String, Object> collectorTypeMap = config2.getCollectorType();
if(collectorTypeMap != null && collectorTypeMap.size() > 0) {
Object o = collectorTypeMap.get(data.getConfiguration());
if(o != null) {
if(o instanceof BaseSupplementCollector) {
BaseSupplementCollector collector = (BaseSupplementCollector) o; //采集器类型-继承风险统计通用类
collector.collector(configCopy);
} else if(o instanceof BaseSupplementCollectorWithoutStatiscs) {
BaseSupplementCollectorWithoutStatiscs collector = (BaseSupplementCollectorWithoutStatiscs) o; //采集器类型-不继承风险统计通用类
collector.collector(configCopy);
} else {
LOG.error("不支持的采集器类型!");
}
} else {
LOG.warn("补录采集器collectorType属性没有配置{}!", data.getConfiguration());
data.setNumber(maxTimes);
}
}
LOG.info("进行{}的第{}次补录结束", data.toString(), data.getNumber());
} else {
LOG.info("已超过最大补录次数:{},该时次({})不再采集", maxTimes, data.toString());
}
}
}).start();
it.remove();
}
}
return null;
}
@Override
public DataStore dataMakeup(Configuration arg0, DataStore arg1) {
// TODO Auto-generated method stub
return null;
}
@Override
public void executeStore(Configuration arg0, DataStore arg1) {
// TODO Auto-generated method stub
}
@Override
public void afterPropertiesSet() throws Exception {
// TODO Auto-generated method stub
}
}
xml配置
collectorType配置需要补录的业务定时器
<!-- 补录采集配置 -->
<bean id="supplementConfiguration" class="com.linkcm.wmp.gd.configure.SupplementConfiguration">
<property name="cronExpression" value="0 0/2 * * * ? 2099"/>
<property name="collector" ref="supplementCollector"/>
<!-- 默认补录次数 -->
<property name="maxTimes" value="5"/>
<!-- key对应业务定时器的COLLECTOR_FLAG标识 -->
<property name="collectorType">
<map>
<entry key="getRACSwanQPEConfiguration" value-ref="getRACSwanQPECollector" />
</map>
</property>
</bean>