1.缘起
基于hotfox的程序,到v3.2止只支持2种后台任务调度方式:(1)每间隔:适用于指定一段时间重复执行的任务,如每小时执行一次
(2)每天执行一次:需要指定执行的时间,精确到分钟,如02:30.
近期的2个项目分别提出了超出上述能力的需求,这些需求的含义描述如下:
- 要求每N周执行一次,可指定一周的哪天的什么时间,如每2周的周三19:00执行一次
- 要求每月执行一次,可指定哪天和时间点,如每月1号的20:00执行一次.
增强后实现了对以下调度策略的支持:
ID | 名称 | 参数 | 参数说明 | 上次活动标记 | 示例及说明 |
1 | 每间隔 | 间隔时间
| 间隔时间单位:秒 |
| 1,0,60 表示间隔60秒 |
2 | 每天执行 | 执行时间(HHMM),到期检查间隔 |
| YYYYMMDD | 2,0,200,60 每天2:00执行,到期检测间隔60秒 |
3 | 按周调度 | 周数,星期几,执行时间(HHMM),到期检查间隔 | 星期几,执行时间可选 | YY YYMMDD | 3,0,2,1,200,60 每2周的周一的2:00执行,到期检查间隔60秒 |
4 | 按月调度 | 每月N号,执行时间(HHMM),到期检查间隔 | 执行时间可选 | YYYYMM | 4,0,(1),(2200),60 每月1号22:00执行,到期检查间隔60秒 |
策略定义格式:策略ID,属性,参数
策略属性:保留,如可用于指定一次性任务。
记录上次活动的时间标记可以在服务重起启动时能正确地接续,避免任务重做或漏做。
标记的时间是任务开始执行的时间(而非任务结束的时间,这对每天执行运行时间跨天的情况存在语义上的差异).
2.配置说明
以下以esb插件为例说明如何配置后台任务的调度策略: <ScheduleStrategy>
<strategy>4,0,(1),(2200),60</strategy>
<last_time_tag>201308</last_time_tag>
</ScheduleStrategy>
<Schedule Type="0">
<Interval>6</Interval>
</Schedule>
<ScheduleStrategy>是新的策略定义节点,<Schedule>是原有的策略定义.
优先采用<ScheduleStrategy>定义,只有在没有<ScheduleStrategy>时才检查<Schedule>.
上述<ScheduleStrategy>配置中,<strategy>为策略定义,<last_time_tag>为任务上次执行的时间标记.
<strategy>4,0,(1),(2200),60</strategy>
表示:
该任务采用每月1号22:00执行一次.到期检查间隔为60秒.
其中,"(1)"和"(2200)"参数中使用括号是考虑对每月某几天或某天的几个时间点执行.
3.实现说明
实现方法考虑以下特性:(1)必须兼容现有部署运行的系统
(2)既然无法一劳永逸适应所有的可能,模块应该方便应需求驱动进行扩展.
3.1hotfox
增强依赖对hotfox关于后台任务(DeamonTask,DeamonTaskManager)接口的改变,新的IDeamonTask接口增加了以下方法:
typedef int (*OnTaskDoneFunc)(void *arg,const char *tag); ///< 任务结束回调
struct IDeamonTask {
public:
///< 取任务的调度策略
virtual IScheduleStrategy* get_strategy() = 0;
///< 指定任务的调度策略
virtual void set_strategy(IScheduleStrategy *strategy) = 0;
///< 设置按周调度策略
virtual int set_week_strategy(unsigned short week_num,unsigned short week_day,unsigned short start_time,unsigned short interval=60,unsigned long last_day=0) = 0;
///< 获取任务ID
virtual unsigned long get_id() const = 0;
///< 设置回调函数
virtual void set_done_cb(OnTaskDoneFunc func,void *arg=0) = 0;
///< 获取回调函数
virtual OnTaskDoneFunc get_done_cb() = 0;
///< 获取回调参数
virtual void* get_done_cb_arg() = 0;
};
OnTaskDoneFunc回调函数的用途是在任务执行成功后修改上次任务时间标记.
该标记可能保存在本地配置文件中,也可以集中保存在数据库中.这由具体的应用体系来决定。
set_week_strategy提供内置的按周调度的策略设置方法.
get_id返回任务ID,该ID没有全局意义.仅用于执行回调修改上次任务时间标记时能正确匹配。
按周调度作为内置功能提供,按月调度采用框架外实现.目的一是作为以后扩展的示例,二是避免每增加新的调度策略需要框架修改.
主要实现修改内容:
3.2 esb_task.h
class c_esb_scan_rule_week : public i_esb_scan_rule {
public:
short week_num_;
short wday_;
long l_time_;
short n_interval_;
unsigned long last_day_;
public:
c_esb_scan_rule_week():n_interval_(60),last_day_(0) {
}
virtual int ReadConfig(INode* parentNode) { return 0;}
virtual void settaskinfo(IDeamonTask* task);
bool tg_flag() const { return true;}
};
class c_esb_scan_rule_month : public i_esb_scan_rule {
public:
short n_day_;
long l_time_;
short n_interval_;
unsigned long last_month_;
public:
c_esb_scan_rule_month():n_interval_(60),last_month_(0) {
}
virtual int ReadConfig(INode* parentNode) { return 0;}
virtual void settaskinfo(IDeamonTask* task);
bool tg_flag() const { return true;}
};
3.3 esb_task.cpp
i_esb_scan_rule* c_esb_scan_rule_factory::new_rule(INode* parentNode) {
INode* subnode = parentNode->GetChildNodes()->GetChildNode("strategy"); ///< 策略描述串
if (subnode==NULL)
return NULL;
string ss = (string)*subnode;
i_esb_scan_rule* rule = 0;
CStrategyStringParser ssp;
ssp.set_ss(ss.c_str());
int type = ssp.get_type();
subnode = parentNode->GetChildNodes()->GetChildNode("last_time_tag"); ///< 最近一次任务时间标记
if (type!=1&&subnode==NULL) ///< 每间隔调度策略目前不记录上次执行时间标记(影响性能,并且不必要)
return NULL;
string tg = (string)*subnode;
ssp.get_prop();
char *buffer = 0;
switch(type) {
case 1: {
c_esb_scan_rule_time *c_rule = new c_esb_scan_rule_time;
ssp.get_next_item(&buffer);
c_rule->l_interval_ = atol(buffer);
delete []buffer;
rule = c_rule;
break;
}
case 2: {
c_esb_scan_rule_date *c_rule = new c_esb_scan_rule_date;
ssp.get_next_item(&buffer);
c_rule->l_time_ = atoi(buffer);
delete []buffer;
if (ssp.get_next_item(&buffer)) {
c_rule->n_interval_ = atoi(buffer);
delete []buffer;
}
c_rule->last_day_ = atol(tg.c_str());
rule = c_rule;
break;
}
case 3: {
c_esb_scan_rule_week *c_rule = new c_esb_scan_rule_week;
c_rule->week_num_ = atoi(buffer);
delete []buffer;
if (ssp.get_next_item(&buffer)) {
c_rule->wday_ = atoi(buffer);
delete []buffer;
}
if (ssp.get_next_item(&buffer)) {
c_rule->l_time_ = atol(buffer);
delete []buffer;
}
if (ssp.get_next_item(&buffer)) {
c_rule->n_interval_ = atoi(buffer);
delete []buffer;
}
c_rule->last_day_ = atol(tg.c_str());
rule = c_rule;
break;
}
case 4: {
c_esb_scan_rule_month *c_rule = new c_esb_scan_rule_month;
char *buffer = 0;
ssp.get_next_item(&buffer);
c_rule->n_day_ = atol(buffer);
delete []buffer;
if (ssp.get_next_item(&buffer)!=-1) {
c_rule->l_time_ = atoi(buffer);
delete []buffer;
}
if (ssp.get_next_item(&buffer)) {
c_rule->n_interval_ = atoi(buffer);
delete []buffer;
}
c_rule->last_month_ = atol(tg.c_str());
rule = c_rule;
break;
}
default:
return 0;
}
return rule;
}
void c_esb_scan_rule_week::settaskinfo(IDeamonTask* task) {
task->set_week_strategy(week_num_,wday_,l_time_,n_interval_,last_day_);
}
void c_esb_scan_rule_month::settaskinfo(IDeamonTask* task) {
CMonthStrategy *stg = new CMonthStrategy;
stg->last_month_ = last_month_;
stg->active_day_ = n_day_;
stg->start_time_ = l_time_;
stg->interval_ = n_interval_;
task->set_strategy(stg);
}
3.4 esb.cpp
int esbPlugin::ReadPrivateConfig()
nodelist_sheet = parentNode->GetChildNodes()->GetChildNodes("ScanTask");
count = nodelist_sheet->Count();
for(i=0;i<count;i++){
INode *subnode = nodelist_sheet->GetChildNode(i);
c_esb_task* task = new c_esb_task();
task->set_id(i+1); ///< 设置任务ID
vec_tasklist.push_back(task);
ret = task->ReadConfig(subnode);
if(ret){
config_->Close();
ACE_ERROR_RETURN((LM_ERROR,"esb:open config::Tasks::SheetFileScanTask[%s] 返回值=[%d].\n",cf_.c_str(),ret),-1);
}
}
int esbPlugin::RegisterTask() {
vector<c_esb_task*>::iterator iter = vec_tasklist.begin();
for(; iter != vec_tasklist.end(); iter++)
{
c_esb_task* esbtask = *iter;
IDeamonTask *task = deamon_mgr_->add(OnTaskTranslate1,"esbPlugin::OnTaskTranslate1",esbtask);
esbtask->settaskinfo(task);
if (esbtask->c_rule_->tg_flag()) {
task->set_done_cb(::OnTaskDoneProc,(void*)esbtask->id_); ///< 设置任务结束回调函数
}
}
return 0;
}
int OnTaskDoneProc(void *arg,const char *tag) {
return esbPlugin_Singleton::instance()->OnTaskDoneProc(arg,tag);
}
int esbPlugin::OnTaskDoneProc(void *arg,const char *tag) {
AUTO_CLOSE_CONFIG(config_);
int t_id = (int)arg;
if (config_->Open(cf_.c_str())) {
return -1;
}
INode *root = config_->GetChildNodes()->GetChildNode("config");
INode *node,*attr;
INode *task_list_node = root->GetChildNodes()->GetChildNode("Tasks");
if (task_list_node==0)
return -1;
INodeList *nl = task_list_node->GetChildNodes()->GetChildNodes("ScanTask");
int task_num = nl->Count();
for (int i=0;i<task_num;i++) {
INode *subnode = nl->GetChildNode(i);
if (t_id==(i+1)) {
INode *stg_node = subnode->GetChildNodes()->GetChildNode("ScheduleStrategy");
if (stg_node==0)
return 0;
INode *last_time_tag_node = stg_node->GetChildNodes()->GetChildNode("last_time_tag");
last_time_tag_node->operator=((char*)tag);
config_->Save();
break;
}
}
nl->Release();
config_->Close();
return 0;
}
3.5 工程
.增加:quartz_month_strategy,quartz_strategy_parser(.cpp,.h文件)4.HOWTOD
4.1如何使用已实现的调度策略
4.2如何增加新的调度策略
5.TODO