前沿
我们在开发大型项目或者是业务比较复杂的项目,例如金融项目、仓储系统,项目中会使用大量的定时任务,来处理业务,有时候定时任务会有几十上百个,每天都要去检查,这些定时任务是否都执行了,这也是一个很浪费时间工作,但是我们有时候不得不做,这时候是不是需要一个方案来解放这部分人力成本呢?
正文
针对上述的问题,设计一个设计文档:
一、需求背景
1、现状
1.1 定时任务总共好几十个,每天都要花一定时间检查其执行情况,重复做相同的事,基于上述原因,急切需要一个项目对这些定时任务进行统一的 自动检查。
2、需求
2.1 设计一个方案对定时任务自动检查
二、整体流程
自动检查定时任务执行流程
三、功能设计
1、定时任务接入流程
1)定义定时任务父类 EsJobExecuter,实现SimpleJob接口,增加一些protected属性,比如注入自动检查自动检查定时任务执行记录Bean
EsJobExecuter类核心代码
public class EsJobExecutor implements SimpleJob {
protected Logger logger = LoggerFactory.getLogger(getClass());
protected String warnEmail;
protected RedisService redisService;
protected EmailHelper emailHelper;
protected JsonUtils jsonUtil = JsonUtils.buildNormalBinder();
protected NormalJob normalEsJob;
//注入插入记录表bean
protected IAutoJobCheckRecord autoJobCheckRecord;
@Override
public void execute(ShardingContext shardingContext) {
long st = System.currentTimeMillis();
String redisLockedKey = null;
String redisLockId = null;
Long redisLockTime = null;
try {
Map jobParameterMap = null;
if (StringUtils.isNotBlank(shardingContext.getJobParameter())) {
try {
jobParameterMap = jsonUtil.fromJson(StringUtils.trim(shardingContext.getJobParameter()), Map.class);
} catch (Throwable e) {
logger.warn("can't trans shardingContext.getJobParameter():{} to map", shardingContext.getJobParameter(), e);
}
}
if (jobParameterMap == null) {
jobParameterMap = Maps.newHashMap();
}
redisLockedKey = normalEsJob.genLockJobKey(jobParameterMap);
redisLockId = normalEsJob.genLockIdUseDefIfNeed(jobParameterMap);
redisLockTime = normalEsJob.genLockTimeUseDefIfNeed(jobParameterMap);
if (StringUtils.isNotBlank(redisLockedKey)) {
try {
if (!redisService.tryGetDistributedLock(redisLockedKey, redisLockId, redisLockTime)) {
logger.info("{}:execute get redis lock failed, return.", normalEsJob.getJobName());
return;
}
} catch (Throwable e) {
logger.error("{}:execute redisService.tryGetDistributedLock for redisKey:{} error", normalEsJob.getJobName(), redisLockedKey, e);
return;
}
}
if (normalEsJob.canDoJob(jobParameterMap)) {
normalEsJob.doJob(jobParameterMap);
//todo 增加记录
autoJobCheckRecord.insert();
System.out.println("我是增加正确处理日志的记录");
} else {
logger.info("execute {} , but do not job", normalEsJob.getJobName());
}
} catch (Throwable ex) {
logger.error("execute {} error", normalEsJob.getJobName(), ex);
//todo 增加错误异常记录
autoJobCheckRecord.insert();
try {
//发送定时任务失败邮件
emailHelper.send();
logger.info("{} execute send error email end.", normalEsJob.getJobName());
} catch (Exception ex2) {
logger.error("{} execute send error email error.", normalEsJob.getJobName(), ex2);
}
} finally {
// 删除锁
if (StringUtils.isNotEmpty(redisLockedKey)) {
try {
redisService.releaseDistributedLock(redisLockedKey, redisLockId);
} catch (Throwable e) {
logger.warn("execute {} releaseDistributedLock redisKey:{} error", normalEsJob.getJobName(), redisLockedKey, e);
}
}
}
}
//省略get set方法
}
2)NormalJob只是一个接口,就不列举了
3)所有的定时任务都继承EsJobExecuter类,例如下面的例子
@Component
@ElasticSimpleJob(cron = "0 34 * * * ?", shardingTotalCount = 1, description = "TestJob")
public class TestJob extends EsJobExecutor {
@Override
@Resource
public void setEmailHelper(EmailHelper emailHelper) {
super.setEmailHelperv(emailHelper);
}
@Override
@Resource
public void setRedisService(RedisService redisService) {
super.setRedisService(redisService);
}
@Override
@Value("${common.warn-email}")
public void setWarnEmail(String warnEmail) {
super.setWarnEmail(warnEmail);
}
private class Job implements NormalJob {
@Override
public void doJob(Map jobParameterMap) {
logger.info("start AcAccountEventUseJob.......");
//定时任务业务处理逻辑
logger.info("end AcAccountEventUseJob.......");
}
@Override
public String genLockJobKey(Map jobParameterMap) {
return "TestJob";
}
@Override
public String getJobName() {
return "TestJob";
}
}
public AopTestJob() {
this.normalEsJob = new TestJob.Job();
}
2、增加自动检查定时任务bean
增加一个定时检查任务,查询自动检查定时任务配置表和记录表,核对数量是否一致,具体流程可查看二
这里有一个技术点,如何根据cron表达式确定昨天定时任务执行的次数,假如某个定时任务是每个月12号跑一次,今天是13号,那么今天我做自动检查的时候,要确定该定时任务执行的数量肯是一次,明天我再做自动检查的时候,计算该定时任务的次数就是0次。所以计算昨天定时任务执行次数很重要,下面计算方式作为参考:
try {
CronTriggerImpl cronTriggerImpl = new CronTriggerImpl();
cronTriggerImpl.setCronexpression(cron);
Date yesterday = DateUtil.getYesterday(new Date());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd 00:00:00");
Date dateStart = DateUtils.parseDate(sdf.format(yesterday), "yyyy-MM-dd HH:mm:ss");
SimpleDateFormat sdfEnd = new SimpleDateFormat("yyyy-MM-dd 23:59:59");
Date date2 = DateUtils.parseDate(sdfEnd.format(yesterday), "yyyy-MM-dd HH:mm:ss");
List dates = TriggerUtils.computeFireTimesBetween(cronTriggerImpl, null, dateStart,date2);
} catch (ParseException e) {
}
dates 集合就是昨天执行的时间点。
四、需求开发
1、涉及配置
数据库配置:
数据库配置:
新建自动检查定时任务配置表:auto_check_job_config
create table auto_check_job_config
(
id bigint auto_increment comment '自动job检测配置ID' primary key,
job_name varchar(64) default '' not null comment 'job名称即Bean的名称',
job_desc varchar(128) default '' not null comment 'job描述',
cron varchar(20) default '' not null comment 'cron表达式',
retry_type tinyint(1) default 0 not null comment '是否重试 0- 否 1-是',
retry_parameter_json varchar(128) default '' not null comment '重试参数',
created_at timestamp default CURRENT_TIMESTAMP not null comment '配置创建时间',
updated_at timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '配置更新时间'
)comment '自动检查定时任务配置表'
alter table auto_check_job_config add unique index uni_job_name(job_name) comment 'job名称唯一索引'
新建 自动检查定时任务执行记录表 auto_job_check_record
create table auto_check_job_record ( id bigint auto_increment comment '自动job检测配置ID' primary key, job_name varchar(64) default '' not null comment 'job名称即Bean的名称', handle_result tinyint(1) default 0 not null comment '执行结果 0-成功 1-失败', handle_error_des longtext null comment '处理失败描述', created_at timestamp default CURRENT_TIMESTAMP not null comment '插入时间', updated_at timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '插入更新时间', )comment '自动检查定时任务执行记录表' alter table auto_check_job_record add index idx_job_name_created_at(job_name,created_at) comment 'job名称、插入时间联合索引'
定时任务配置:
新建自动检查定时任务 类名称 AutoCheckJob , cron 可根据待检查定时任务执行情况来定,例如需要检查昨天的定时任务,那么这个时间可以定时今天凌晨
MQ配置:
无
其他配置:
2、涉及项目
| 序号 | 项目名称 | git地址 | 开发分支 | 部署地址 | 升级jar包 | 备注 |
| 1 | 无 |



