前言
定时任务=调度+执行
调度有很多框架实现:elastic-job、quartz等
执行需要程序员实现任务队列。实现任务队列有很多方式:数据库、redis、kafka
实现任务队列虽然很简单,但稍有不慎,也很容易出问题。
代码实现
首先把问题简单化,假如需要在单库实现定时任务,我们逐步实现优化代码
第一次实现
public void execute1() {
List<JobEntity> jobEntities = jobEntityMapper.getAllToBeProcess();
for (JobEntity jobEntity : jobEntities) {
this.businessProcess(jobEntity);
}
}
以上代码会有什么问题呢?
内存可能顶不住(如果不走索引,定时任务执行慢)
如何优化?
采用分页、并且sql脚本需要走索引
sql实现分页,也有很多方式,效率不一样,同样需要认真考虑
比如:
select * from table limit m,n;
select * from table order by id limit m;
select * from table_name inner join ( select id from table_name where (user = xxx) limit 10000,10) b using (id)
这三个sql语句就各有优缺点
第二次实现
public void execute2() {
List<JobEntity> jobEntities = jobEntityMapper.getFirstPageToBeProcess();
while (null != jobEntities && !jobEntities.isEmpty()) {
for (JobEntity jobEntity : jobEntities) {
this.businessProcess(jobEntity);
}
jobEntities = jobEntityMapper.getNextPageToBeProcess();
}
}
这一次实现加入了分页功能
这次还存在什么问题呢?
如果第5行代码发生了异常,就会有问题(表象:执行慢、假死 )
第三次实现
public void execute3() {
List<JobEntity> jobEntities = jobEntityMapper.getFirstPageToBeProcess();
while (null != jobEntities && !jobEntities.isEmpty()) {
for (JobEntity jobEntity : jobEntities) {
try {
this.businessProcess(jobEntity);
} catch (Exception e) {
e.getStackTrace();
}
}
jobEntities = jobEntityMapper.getNextPageToBeProcess();
}
}
这一次加入try_catch捕获异常,这样就没有问题了吗?
答案是否定的
如果第6行代码发生异常,数据没有处理,下一次依旧会被查询出来处理,这样也会执行慢,并且可能假死状态。
第四次实现
public void execute4() {
List<JobEntity> jobEntities = jobEntityMapper.getFirstPageToBeProcess();
while (null != jobEntities && !jobEntities.isEmpty()) {
for (JobEntity jobEntity : jobEntities) {
try {
this.businessProcess(jobEntity);
} catch (Exception e) {
e.getStackTrace();
updateProcessCount(jobEntity);
}
}
jobEntities = jobEntityMapper.getNextPageToBeProcess();
}
}
这一次当出现异常的时候,会去更新这条异常数据的处理次数。
所以,定时任务的每一条任务一定要有一个明确的终止条件,包括正常处理的终止条件和异常的终止条件。
异常终止条件有几种实现方式,比如:
- 超过一定次数
- 超过一定时间
- 出现异常删除
- 用状态控制(待处理,处理失败)
总结
定时任务实现注意事项
- 一定要有分页、并且走索引;
- 一定要有异常捕获;
- 一定要有明确的正常/异常终止条件。