在之前的文章《统一对象消息编程详解——通过分析定时任务模块来理解消息》中介绍了定时任务模块TLMsgTask。当时那个模块功能比较单一,只是简单周期任务的运行 ,现在对模块功能进行了改进,更加了定时任务的开启、停止等各项功能,并加进去了quartz 的cron表达式定时任务,完全实现了quartz的各种复杂定时。
为解析cron表达式,直接使用了quartz的类org.quartz.CronExpression,用来解析表达式及获取执行时间,这样保证了cron解析的准确及符合cron表达式的规范。除了使用quartz的cron解析以外,没有使用quartz的其他类或功能。
对于一个执行中的消息,如何能自由控制它的起、停或热加载,我这里采取了一个链条消息,在被执行的消息前增加了一个控制消息,每次任务执行时,先执行控制消息,然后在执行任务消息,当执行控制消息时,就可以自由的决定下一个任务消息的执行。设置控制消息非常简单:
private void startTask(TLMsg msg) {
String taskid = getTaskid(msg);
if (taskDatas.get(taskid) != null)
return;
TLMsg controlMsg = createMsg().setAction("taskControl");
TLMsg bmsg = createMsg();
bmsg.copyFrom(msg);
HashMap<String, Object> taskData = new HashMap<>();
taskData.put("times", 0);
taskDatas.put(taskid, taskData);
controlMsg.setParam("taskid", taskid);
controlMsg.setNextMsg(bmsg);
if (msg.getParam("cronExp") != null) {
CronExpression cron = null;
try {
cron = new CronExpression((String) msg.getParam("cronExp"));
} catch (ParseException e) {
e.printStackTrace();
}
Date now = new Date();
controlMsg.setParam("cron", cron).setParam("startTime", now).setParam("lastDisplayTime", 0);
}
executeTask(controlMsg);
putLog("task start ,taskid: " + taskid, LogLevel.INFO);
}
代码中,我们构建一个控制消息 controlMsg ,然后设置任务消息msg为它的下一个执行消息, controlMsg.setNextMsg(bmsg);
这样每次任务执行时先执行controlMsg。如果任务消息msg参数中含有cron表达式cronExp,则构建cron解析类加入控制消息中供控制方法taskControl使用。
在处理cron定时时,我们的消息模块是对每一个任务启动一个线程而周期运行,每次运行由控制消息来决定是否执行任务消息,如果达到下个定时则执行任务消息,否则继续循环。
配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<moduleConfig>
<params>
<status value="run"/>
<poolSize value="5"/>
</params>
<taskMsgTable>
<msg destination="pcboy" action="eat" taskid="pcboy_eat" status="stop" times="5" timeUnit="ms" begin="111" delay="30"></msg>
<msg destination="pcboy" action="play" taskid="pcboy_play" status="stop" cronExp="*/10 * * * * ?"></msg>
<msg destination="pcboy" action="sleep" taskid="pcboy_sleep" status="run" cronExp="0 25-35 15 * * ?"></msg>
</taskMsgTable>
</moduleConfig>
现在任务控制将非常灵活,参数:
params定义全局控制:
status :
run 定时模块可以启动;stop 停止所有任务不关闭线程;shutdown ,所有任务停止并关闭线程;
poolSize :线程池数量
<taskMsgTable>用来配置任务消息:
taskid:消息id,用来标识任务,唯一性
status:
stop 任务不可运行、停止任务。当管理模块运行后停止任务,线程不关闭
run 任务可运行,对于stop状态的任务再次执行 。管理模块启动时,只有状态为 run 的任务运行。
shutdown 停止任务并关闭线程。
restart 在管理模块启动后,启动或重启一个任务。
cronExp:cron表达式,对于设置cron表达式的消息任务,其他定时参数不在起作用。
以下参数用于非cron任务:
times:运行次数 ,例如运行5次后停止
begin:开始时间
delay:任务间隔时间
timeUnit: 时间单位 ms 毫秒 s秒 m分钟 h 小时 d 天
如 times="5" timeUnit="ms" begin="111" delay="30" 则只运行5次,时间单位是毫秒 ,管理模块启动后111毫秒开始任务运行 ,任务间隔为30毫秒。
通过配置文件监听模块可以动态更改配置文件,因此也就可以动态启停、增加任务。
程序入口函数:
package cn.tianlong.java.tlobjdemo.task;
import cn.tianlong.tlobjcet.base.TLMsg;
import cn.tianlong.tlobjcet.base.TLObjectFactory;
public class Main {
public static void main(String[] args)
{
String configdir = "/person/";
TLObjectFactory myfactory = TLObjectFactory.getInstance(configdir,"moduleFactory_config.xml");
myfactory.boot();
TLMsg taksMsg =new TLMsg().setAction("getModule").setParam("moduleName","myTaskManger");
myfactory.putMsg(myfactory,taksMsg);
}
}
myTaskManger即我们定时管理模块TLMsgTask 。
任务管理模块:
package cn.tianlong.tlobjcet.base;
import org.quartz.CronExpression;
import org.xmlpull.v1.XmlPullParser;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
/**
* 创建日期:2018/4/23 on 21:00
* 描述:
* 作者:tianlong
*/
public class TLMsgTask extends TLBaseModule {
protected ScheduledExecutorService executor;
protected ArrayList<TLMsg> taskMsgTable;
protected Map<String, HashMap<String, Object>> taskDatas = new ConcurrentHashMap<>();
protected int poolSize = 5;
public TLMsgTask() {
super();
}
public TLMsgTask(String name) {
super(name);
}
public TLMsgTask(String name, TLObjectFactory modulefactory) {
super(name, modulefactory);
}
@Override
protected Object setConfig() {
myConfig config = new myConfig();
mconfig = config;
super.setConfig();
taskMsgTable = config.getTaskMsgTable();
return config;
}
@Override
protected void initProperty() {
super.initProperty();
if (params != null) {
if (params.get("poolSize") != null) {
poolSize = Integer.parseInt(params.get("poolSize"));
} ;
}
}
@Override
protected void init() {
if (params.get("status") != null && !params.get("status").equals("run")) {
putLog("if you wang to run ,set status to run ", LogLevel.WARN);
return;
} ;
if (taskMsgTable == null || taskMsgTable.isEmpty())
return;
if (executor == null)
executor = Executors.newScheduledThreadPool(poolSize);
for (TLMsg msg : taskMsgTable)
{
if (msg.getParam("status").equals("run")) {
if (msg.getParam("cronExp") != null) {
msg.setParam("delay", "100").setParam("timeUnit","ms");
}
startTask(msg);
}
}
}
@Override
protected TLMsg checkMsgAction(Object fromWho, TLMsg msg) {
TLMsg returnMsg = null;
switch (msg.getAction()) {
case "registTask":
registTask(fromWho, msg);
break;
case "setTaskStatus":
setTaskStatus(fromWho, msg);
break;
case "unRegistTask":
unRegistTask(fromWho, msg);
break;
case "startTask":
init();
break;
case "taskControl":
returnMsg = taskControl(fromWho, msg);
break;
case "shutdown":
executor.shutdown();
break;
default:
}
return returnMsg;
}
private void setTaskStatus(Object fromWho, TLMsg msg) {
String nowTaskid = (String) msg.getParam("taskid");
TLMsg nowTaskMsg = null;
for (TLMsg tmsg : taskMsgTable) {
String taskid = getTaskid(tmsg);
if (nowTaskid.equals(taskid)) {
nowTaskMsg = tmsg;
break;
}
}
msg.removeParam("taskid");
nowTaskMsg.addArgs(msg.getArgs());
}
@Override
protected void reConfig() {
if (params != null) {
if (params.get("status") != null && params.get("status").equals("shutdown")) {
executor.shutdown();
putLog("all task shutdown", LogLevel.WARN);
return;
}
if (params.get("status") != null && params.get("status").equals("stop")) {
for (TLMsg tmsg : taskMsgTable) {
tmsg.setParam("status", "stop");
}
} ;
}
for (TLMsg tmsg : taskMsgTable)
{
String status = (String) tmsg.getParam("status");
if(status ==null)
continue;
HashMap<String, Object> nowTaskdata = taskDatas.get(tmsg.getParam("taskid"));
if( status.equals("restart"))
{
if(nowTaskdata !=null )
shutdownTask((String) tmsg.getParam("taskid"));
tmsg.setParam("status", "run");
if (tmsg.getParam("cronExp") != null) {
tmsg.setParam("delay", "100").setParam("timeUnit","ms");;
}
startTask(tmsg);
}
}
}
private TLMsg taskControl(Object fromWho, TLMsg msg) {
String nowTaskid = (String) msg.getParam("taskid");
TLMsg nowTaskMsg = null;
for (TLMsg tmsg : taskMsgTable) {
String taskid = getTaskid(tmsg);
if (nowTaskid.equals(taskid)) {
nowTaskMsg = tmsg;
break;
}
}
if (nowTaskMsg == null)
return null;
String status = (String) nowTaskMsg.getParam("status");
if (status != null && status.equals("shutdown")) {
shutdownTask(nowTaskid);
return new TLMsg().setParam(DONEXTMSG, "false");
}
if (status != null && status.equals("stop")) {
nowTaskMsg.setParam("status","stoped");
putLog("taskid:" + nowTaskid + " stop", LogLevel.WARN);
return new TLMsg().setParam(DONEXTMSG, "false");
}
if (status != null && status.equals("stoped")) {
return new TLMsg().setParam(DONEXTMSG, "false");
}
HashMap<String, Object> nowTaskdata = taskDatas.get(nowTaskid);
int nowTimes = (int) nowTaskdata.get("times");
String timesLimit = (String) nowTaskMsg.getParam("times");
if (timesLimit != null) {
int times = Integer.parseInt(timesLimit);
if (times > 0) {
if (times == nowTimes) {
nowTaskMsg.setParam("status","stoped");
putLog("taskid:" + nowTaskid + " stop,runing times:" + nowTimes, LogLevel.WARN);
return new TLMsg().setParam(DONEXTMSG, "false");
}
}
}
if (msg.getParam("cron") != null) {
Date statTime = (Date) msg.getParam("startTime");
CronExpression cron = (CronExpression) msg.getParam("cron");
Long now = System.currentTimeMillis();
Date execDate = cron.getTimeAfter(statTime);
if (execDate == null) {
ScheduledFuture<?> sf = (ScheduledFuture<?>) nowTaskdata.get("future");
putLog("taskid:" + nowTaskid + " is over,session shutdown", LogLevel.WARN);
taskDatas.remove(nowTaskid);
sf.cancel(true);
return new TLMsg().setParam(DONEXTMSG, "false");
}
long execTime = execDate.getTime();
long timeUntilExec = execTime - now;
if (timeUntilExec > 2) {
displayTimeUntil(nowTaskid, timeUntilExec / 1000, msg);
return new TLMsg().setParam(DONEXTMSG, "false");
} else
msg.setParam("startTime", new Date());
}
nowTaskdata.put("times", nowTimes + 1);
putLog("taskid:" + nowTaskid + " has runing times:" + (nowTimes + 1), LogLevel.INFO);
return null;
}
private void shutdownTask(String taskid)
{
HashMap<String, Object> nowTaskdata = taskDatas.get(taskid);
if(nowTaskdata ==null )
{
putLog("shutdown error, no taskid:" + taskid , LogLevel.ERROR);
return;
}
ScheduledFuture<?> sf = (ScheduledFuture<?>) nowTaskdata.get("future");
putLog("taskid:" + taskid + " shutdown", LogLevel.WARN);
taskDatas.remove(taskid);
sf.cancel(true);
}
private void displayTimeUntil(String taskid, Long time, TLMsg msg) {
int lastDisplayTime = (int) msg.getParam("lastDisplayTime");
String logContent =null;
if (time < 60 && lastDisplayTime > 100)
logContent= time + "s";
else if (60 <= time && lastDisplayTime > 600 )
logContent= time / 60 + "m";
if(logContent!=null)
{
putLog("taskid:" + taskid + " waiting:" + logContent, LogLevel.INFO);
msg.setParam("lastDisplayTime", 0);
}
else
msg.setParam("lastDisplayTime", lastDisplayTime + 1);
}
private void registTask(Object fromWho, TLMsg msg) {
TLMsg tmsg = (TLMsg) msg.getParam("msg");
if (taskMsgTable == null)
taskMsgTable = new ArrayList<>();
taskMsgTable.add(tmsg);
if (tmsg.getParam("status") != null && tmsg.getParam("status").equals("run"))
executeTask(tmsg);
}
private void startTask(TLMsg msg) {
String taskid = getTaskid(msg);
if (taskDatas.get(taskid) != null)
return;
TLMsg controlMsg = createMsg().setAction("taskControl");
TLMsg bmsg = createMsg();
bmsg.copyFrom(msg);
controlMsg.setParam("taskid", taskid);
controlMsg.setNextMsg(bmsg);
if (msg.getParam("cronExp") != null) {
CronExpression cron = null;
try {
cron = new CronExpression((String) msg.getParam("cronExp"));
} catch (ParseException e) {
putLog("cronExp error " , LogLevel.ERROR);
return;
}
Date now = new Date();
controlMsg.setParam("cron", cron).setParam("startTime", now).setParam("lastDisplayTime", 0);
}
HashMap<String, Object> taskData = new HashMap<>();
taskData.put("times", 0);
taskDatas.put(taskid, taskData);
executeTask(controlMsg);
putLog("taskid: " + taskid+" start ", LogLevel.INFO);
}
private void unRegistTask(Object fromWho, TLMsg msg) {
for (TLMsg tmsg : taskMsgTable) {
if (tmsg.getMsgId().equals((String) msg.getParam("msgId")))
taskMsgTable.remove(tmsg);
}
}
private String getTaskid(TLMsg msg) {
String taskid = (String) msg.getParam("taskid");
if (taskid == null || taskid.isEmpty())
taskid = msg.getDestination() + msg.getMsgId() + msg.getAction();
return taskid;
}
private void executeTask(TLMsg msg) {
Long begin = Long.valueOf(0);
TLMsg taskMsg = msg.getNextMsg();
String taskid = (String) msg.getParam("taskid");
String sbegin = (String) taskMsg.getParam("begin");
if (sbegin != null)
begin = Long.parseLong(sbegin);
String sdelay = (String) taskMsg.getParam("delay");
if (sdelay == null) {
putLog("no set delay,taskid" + taskid, LogLevel.ERROR);
return;
}
String timeUnitStr = (String) taskMsg.getParam("timeUnit");
if(timeUnitStr==null)
timeUnitStr="s";
TimeUnit timeUnit =getTimeUnit(timeUnitStr);
Long delay = Long.parseLong(sdelay);
taskMsg.removeParam("delay");
taskMsg.removeParam("begin");
taskMsg.removeParam("taskid");
taskMsg.removeParam("times");
taskMsg.removeParam("status");
taskMsg.removeParam("timeUnit");
taskMsg.removeParam("cronExp");
Runnable task = getMsgTask(this, msg);
HashMap<String, Object> nowTaskdata = taskDatas.get(taskid);
ScheduledFuture<?> sf = executor.scheduleAtFixedRate(task, begin, delay, timeUnit);
nowTaskdata.put("future", sf);
}
private TimeUnit getTimeUnit(String timeUnitStr) {
TimeUnit timeUnit;
switch (timeUnitStr) {
case "ms":
timeUnit =TimeUnit.MILLISECONDS;
break;
case "s":
timeUnit =TimeUnit.SECONDS;
break;
case "m":
timeUnit =TimeUnit.MINUTES;
break;
case "h":
timeUnit =TimeUnit.HOURS;
break;
default:
timeUnit =TimeUnit.SECONDS;
}
return timeUnit;
}
private Runnable getMsgTask(final TLMsgTask object, final TLMsg msg) {
Runnable task = new Runnable() {
@Override
public void run() {
object.getMsg(object, msg);
}
};
return task;
}
protected class myConfig extends TLModuleConfig {
protected ArrayList<TLMsg> taskMsgTable;
public myConfig() {
}
public ArrayList<TLMsg> getTaskMsgTable() {
return taskMsgTable;
}
protected void myConfig(XmlPullParser xpp) {
super.myConfig(xpp);
try {
if (xpp.getName().equals("taskMsgTable")) {
taskMsgTable = getMsgList(xpp, "taskMsgTable");
}
} catch (Throwable t) {
}
}
}
}
在模块工厂配置文件中设置启动配置文件监听模块:
<factoryBoot>
<msg action="getModule" moduleName="log" usePreReturnMsg="false" />
<msg action="getModule" moduleName="monitorConfig" usePreReturnMsg="false" />
</factoryBoot>
监听模块"monitorConfig自动监听配置文件改变。监听模块配置中增加监听任务定时管理模块:
<moduleConfig>
<params>
<delay value="5" />
<modules value="myTaskManger" />
</params>
</moduleConfig>
案例任务模块PcBoy 见上篇文章。