项目场景:
目前项目引入了 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才是下次执行时间,需要转为时间戳更新
实现方法
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;
/**
* <p>
* 服务实现类
* </p>
*
* @author Foam
* @since 2023-05-31
*/
@Slf4j
@Service
public class XxlJobInfoServiceImpl extends ServiceImpl<XxlJobInfoMapper, XxlJobInfo> implements IXxlJobInfoService {
@DataSource("xxlJobs")
public void updateCronTime(){
try{
// 更新下一次cron时间
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);
}
}
}
工具类
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.*;
/**
* @Description:cron表达式工具类
* @date:2022/10/8_15:09
* @author:Foam
*/
public class CronUtils {
/**
* 解析cron对应次数
* @Author Foam
* @Date 2023/1/6
* @param cronStr cron 表达式
* @param num 解析最近几次执行时间
* @return List<String> 返回执行时间列表
**/
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;
}
/**
* @Description CN: 字符串解析cron表达式
* @Description EN:
* @Description KR:
* @Author Foam
* @Date 2023/5/29
**/
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 = DateTimeFormatter.ofPattern("yyyy MM dd HH mm ss");
String format = dateTime.format(dateTimeFormatter);
// 拆分字符串
String[] dateTimeParts = format.split(" ");
// 构建cron表达式
return String.format("%s %s %s %s %s ? *",dateTimeParts[5],dateTimeParts[4],dateTimeParts[3],dateTimeParts[2],dateTimeParts[1],dateTimeParts[0]);
}
}
效果
缺点
1.需要指定执行器id,需要加参数配置,比较麻烦
2.不适用集群模式下的任务
3.开启和关闭需要用户理解