项目场景:
目前项目引入了 xxl-job 来跑定时任务,但是存在一个问题,项目执行结束的时间不固定,有峰值,在高峰期的时候会出现长阻塞一直排队等待,如图:

问题描述
需要做一种策略来解决长阻塞问题,精准控制下一次执行时间,尽量保证不空档
原因分析:
1.上游接口的接口限流或者峰值压力导致返回超时
2.高峰期数据量达到峰值处理不过来
3.设置的执行间隔太短,执行不过来
解决方案:
思路
编写一个cron表达式,时间一分钟一次,直接获取当前时间的下一次执行时间,并且更新到xxl-job的执行日志里面
注意
1.因为设置的1分钟59秒,所以在秒的位置设置了59,防止在59秒执行完更新超时,导致定时任务执行过时间
2.xxlJobInfo.setTriggerStatus(1); 设置这个,只需要点击立即执行就可以让它运行,但是后续可能需要加一个开关来控制需要执行的策略是一次或者多次
3.在xxl_job_info中schedule_conf是编写cron表达式,但是trigger_next_time才是下次执行时间,需要转为时间戳更新
实现方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| package com.tthk.inland.ticket.core.service.xxlJobInfo.impl; import com.tthk.inland.ticket.core.configurations.druidconfig.DataSource; import com.tthk.inland.ticket.core.entity.xxlJobInfo.XxlJobInfo; import com.tthk.inland.ticket.core.mapper.xxlJobInfo.XxlJobInfoMapper; import com.tthk.inland.ticket.core.service.xxlJobInfo.IXxlJobInfoService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.tthk.inland.ticket.core.utils.cron.CronUtils; import com.xxl.job.core.context.XxlJobHelper; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.text.SimpleDateFormat; import java.util.Date;
@Slf4j @Service public class XxlJobInfoServiceImpl extends ServiceImpl<XxlJobInfoMapper, XxlJobInfo> implements IXxlJobInfoService { @DataSource("xxlJobs") public void updateCronTime(){ try{ final var nextTime = CronUtils.getExecutionTimeByNum("59 0/1 * * * ?", 1).get(0); final var nextCron = CronUtils.getCronByTimeString(nextTime); final var xxlJobInfo = new XxlJobInfo(); xxlJobInfo.setId(74); xxlJobInfo.setScheduleConf(nextCron); final var simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); final var nextTimestamp = simpleDateFormat.parse(nextTime); xxlJobInfo.setTriggerNextTime(nextTimestamp.getTime()); xxlJobInfo.setTriggerStatus(1); this.updateById(xxlJobInfo); XxlJobHelper.log("更新下一次cron时间"+nextCron); }catch (Exception e){ log.error("cron解析异常",e); } } }
|
工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| package com.tthk.inland.ticket.core.utils.cron; import org.springframework.scheduling.support.CronSequenceGenerator; import java.text.SimpleDateFormat; import java.time.DateTimeException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*;
public class CronUtils {
public static List<String> getExecutionTimeByNum(String cronStr, Integer num) { CronSequenceGenerator cronSequenceGenerator = new CronSequenceGenerator(cronStr, TimeZone.getTimeZone("Asia/Shanghai")); List<String> result = new ArrayList<>(num); Date date = new Date(); for (Integer integer = 0; integer < num; integer++) { date = cronSequenceGenerator.next(date); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String format1 = format.format(date); result.add(format1); } return result; }
public static String getCronByTimeString(String times){ DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime dateTime = LocalDateTime.parse(times,dateTimeFormatter1); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy MM dd HH mm ss"); String format = dateTime.format(dateTimeFormatter); String[] dateTimeParts = format.split(" "); return String.format("%s %s %s %s %s ? *",dateTimeParts[5],dateTimeParts[4],dateTimeParts[3],dateTimeParts[2],dateTimeParts[1],dateTimeParts[0]); } }
|
效果

缺点
1.需要指定执行器id,需要加参数配置,比较麻烦[该配置最好是mysql或者redis,不要配置在param参数中,因为停止会多执行一次]
2.不适用集群模式下的任务
3.开启和关闭需要用户理解