定时任务模板代码

261 阅读3分钟

定时任务模板代码

  • 适用多微服务同时执行相同定时任务
  • 适用于失败重试, 如每日8点,9点,10点执行定时任务,后两次用于检查是否需要失败重试

代码

  1. TaskTemplateCode.java
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import varyuan.po.Task;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@EnableScheduling
public class TaskTemplateCode {

    // 3个task模拟多个相同的微服务
    @Scheduled(fixedRate = 10 * 1000)// 每10s执行一次
    public void task1() {
        templateCode();
    }

    @Scheduled(fixedRate = 10 * 1000)
    public void task2() {
        templateCode();
    }

    @Scheduled(fixedRate = 10 * 1000)
    public void task3() {
        templateCode();
    }


    @Resource
    private TaskService taskService;

    // 定时任务模板代码
    private void templateCode() {
        log.info("定时任务--开始");
        // taskName+taskSer 为唯一键, taskSer的生成规则应该根据任务的执行周期自定义
        String taskName = "dev_test";
        String taskSer = LocalDateTime.now().format(DateTimeFormatter.BASIC_ISO_DATE);// yyyyMMdd
        try {
            Task task = taskService.select(taskName, taskSer);
            if (task == null) {
                // 首次执行
                if (taskService.insert(taskName, taskSer)) {
                    log.info("首次执行在本台机器");
                    exec();
                    taskService.updateSucc(taskName, taskSer);
                } else {
                    log.info("首次执行在其他机器");
                }
            } else {
                // 非首次执行判断是否需要失败重试
                int status = task.getStatus();
//                if (status == TaskService.SUCC) {
//                    log.info("定时任务已执行成功,不再重试");
//                } else if (status == TaskService.RUNNING) {
//                    log.info("定时任务正在运行,不再重试");
//                } else
                if (status == TaskService.FAIL) {
                    // 乐观锁重试
                    if (taskService.updateRetryTimes(taskName, taskSer, task.getRetryTimes())) {
                        log.info("失败重试执行在本台机器");
                        exec();
                        taskService.updateSucc(taskName, taskSer);
                    } else {
                        log.info("失败重试执行在其他机器");
                    }
                }

            }

        } catch (Exception e) {
            taskService.updateFail(taskName, taskSer);
            log.error("定时任务执行异常: ", e);
        }
        log.info("定时任务--结束");
    }

    // 定时任务执行内容
    private void exec() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        throw new RuntimeException();// 模拟执行失败
    }
}
  1. 定时任务表sql
CREATE TABLE `task` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `task_name` varchar(255) NOT NULL,
  `task_ser` varchar(255) NOT NULL,
  `status` int NOT NULL DEFAULT 0 COMMENT '任务执行状态,-1为失败,0为进行中,1为成功',
  `retry_times` int NOT NULL DEFAULT '0' COMMENT '重试次数',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_key` (`task_name`,`task_ser`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT '定时任务表';
  1. Task.java
import lombok.Data;

import java.time.LocalDateTime;

@Data
public class Task {
    private Long id;
    private String taskName;
    private String taskSer;
    private Integer status;
    private Integer retryTimes;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}
  1. TaskDao.java
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import varyuan.po.Task;

public interface TaskDao {

    @Select("select * from task where task_name=#{taskName} and task_ser=#{taskSer}")
    Task select(@Param("taskName") String taskName, @Param("taskSer") String taskSer);

    @Insert("insert into task(task_name,task_ser) values(#{taskName},#{taskSer})")
    int insert(@Param("taskName") String taskName, @Param("taskSer") String taskSer);

    @Update("update task set status=#{status} where task_name=#{taskName} and task_ser=#{taskSer}")
    int updateStatus(@Param("taskName") String taskName, @Param("taskSer") String taskSer, @Param("status") int status);

    @Update("update task set status=0,retry_times=retry_times+1 where task_name=#{taskName} and task_ser=#{taskSer} and retry_times=#{oldRetryTimes}")
    int updateRetryTimes(@Param("taskName") String taskName, @Param("taskSer") String taskSer, @Param("oldRetryTimes") int oldRetryTimes);
}
  1. TaskService.java
import org.springframework.stereotype.Service;
import varyuan.dao.TaskDao;
import varyuan.po.Task;

import javax.annotation.Resource;

@Service
public class TaskService {

    // 定时任务的3种状态
    public static final int FAIL = -1;
    public static final int RUNNING = 0;
    public static final int SUCC = 1;

    @Resource
    private TaskDao taskDao;

    public Task select(String taskName, String taskSer) {
        return taskDao.select(taskName, taskSer);
    }

    public boolean insert(String taskName, String taskSer) {
        try {
            return taskDao.insert(taskName, taskSer) > 0;
        } catch (Exception e) {
            return false;
        }
    }

    public boolean updateSucc(String taskName, String taskSer) {
        return taskDao.updateStatus(taskName, taskSer, SUCC) > 0;
    }

    public boolean updateFail(String taskName, String taskSer) {
        return taskDao.updateStatus(taskName, taskSer, FAIL) > 0;
    }

    // 更新失败重试次数 乐观锁重试
    public boolean updateRetryTimes(String taskName, String taskSer, int oldRetryTimes) {
        return taskDao.updateRetryTimes(taskName, taskSer, oldRetryTimes) > 0;
    }
}

cook

  1. spring执行定时任务@Scheduled和异步任务@Async的线程池大小固定为1, 自定义线程池大小代码如下,更多信息参见@EnableScheduling注解的说明
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

import java.util.concurrent.Executors;

@Configuration
public class SimpleSchedulingConfigurer implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5));
    }
}