添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
public static void main(String[] args) { // run in a second final long timeInterval = 1000; Runnable runnable = new Runnable() { @Override public void run() { while (true) { System.out.println("Hello !!"); try { Thread.sleep(timeInterval); } catch (InterruptedException e) { e.printStackTrace(); Thread thread = new Thread(runnable); thread.start();

Timer 实现

JDK 自带的 Timer 是一个定时器工具,使用一个后台线程计划执行指定任务,可以安排任务“执行一次”或者定期“执行多次”

Timer 类的核心方法如下:

// 在指定延迟时间后执行指定的任务
schedule(TimerTask task,long delay);
// 在指定时间执行指定的任务。(只执行一次)
schedule(TimerTask task, Date time);
// 延迟指定时间(delay)之后,开始以指定的间隔(period)重复执行指定的任务
schedule(TimerTask task,long delay,long period);
// 在指定的时间开始按照指定的间隔(period)重复执行指定的任务
schedule(TimerTask task, Date firstTime , long period);
// 在指定的时间开始进行重复的固定速率执行任务
scheduleAtFixedRate(TimerTask task,Date firstTime,long period);
// 在指定的延迟后开始进行重复的固定速率执行任务
scheduleAtFixedRate(TimerTask task,long delay,long period);
// 终止此计时器,丢弃所有当前已安排的任务。
cancal();
// 从此计时器的任务队列中移除所有已取消的任务。
purge();

定义一个通用的 TimerTask 类,用于定义执行的任务

public class DoSomethingTimerTask extends TimerTask {
    private String taskName;
    public DoSomethingTimerTask(String taskName) {
        this.taskName = taskName;
    @Override
    public void run() {
        System.out.println(new Date() + " : 任务「" + taskName + "」被执行。");

在指定延迟时间后执行一次,这是比较常见的场景,比如:当系统初始化某个组件之后,延迟几秒,然后进行定时任务的执行

public class DelayOneDemo {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new DoSomethingTimerTask("DelayOneDemo"),1000L);

在指定的延迟时间开始执行定时任务,定时任务按照固定的间隔进行执行,比如:延迟 2 秒执行,固定执行间隔为 1 秒

public class PeriodDemo {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new DoSomethingTimerTask("PeriodDemo"),2000L,1000L);

在指定的延迟时间开始执行定时任务,定时任务按照固定的速率进行执行,比如:延迟 2 秒执行,固定速率为 1 秒

public class FixedRateDemo {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new DoSomethingTimerTask("FixedRateDemo"),2000L,1000L);

schedule 和 scheduleAtFixedRate 的区别在于:当某一次任务执行超时,恢复后两者都会立即执行下次任务,schedule 还是按照原来的间隔,scheduleAtFixedRate 则会加快节奏,努力追上进度

ScheduledExecutorService 实现

基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响

ScheduledExecutorService 主要有以下 4 个方法:

ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);
<V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit);
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);

ScheduledExecutorService 定义的这四个接口方法和 Timer 对应的方法几乎一致,只不过 Timer 的 scheduled 方法需要在外部传入一个 TimerTask 的抽象任务。ScheduledExecutorService 封装更加细致,传入 Runnable 或 Callable 内部都会封装类似 TimerTask 的抽象任务类(ScheduledFutureTask),然后传入线程池,启动线程去执行该任务

scheduleAtFixedRate 方法,按指定频率周期执行某个任务,定义及参数说明:

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
				long initialDelay,
				long period,
				TimeUnit unit);

参数对应含义:command 为被执行的线程,initialDelay 为初始化后延时执行时间,period 为两次开始执行最小间隔时间,unit 为计时单位

scheduleAtFixedRate 是以 period 为间隔来执行任务的,如果任务执行时间小于 period,则上次任务执行完成后会间隔 period 后再去执行下一次任务。但如果任务执行时间大于 period,则上次任务执行完毕后会不间隔的立即开始下次任务

public class ScheduleAtFixedRateDemo implements Runnable{
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleAtFixedRate(
                new ScheduleAtFixedRateDemo(),
                1000,
                TimeUnit.MILLISECONDS);
    @Override
    public void run() {
        System.out.println(new Date() + " : 任务「ScheduleAtFixedRateDemo」被执行。");
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();

scheduleWithFixedDelay 方法,按指定频率间隔执行某个任务。定义及参数说明:

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
				long initialDelay,
				long delay,
				TimeUnit unit);

参数对应含义:command 为被执行的线程;initialDelay 为初始化后延时执行时间,period 为前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间),unit 为计时单位

scheduleWithFixedDelay 不管任务执行多久,都会等上一次任务执行完毕后再延迟 delay 后去执行下次任务

public class ScheduleAtFixedRateDemo implements Runnable{
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleWithFixedDelay(
                new ScheduleAtFixedRateDemo(),
                1000,
                TimeUnit.MILLISECONDS);
    @Override
    public void run() {
        System.out.println(new Date() + " : 任务「ScheduleAtFixedRateDemo」被执行。");
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            e.printStackTrace();

Spring Task

Spring 自带一套定时任务工具 Spring-Task,支持注解和配置文件两种形式

基于注解形式的实现如下:

@Component("taskJob")
public class TaskJob {
    @Scheduled(cron = "0 0 3 * * ?")
    public void job1() {
        System.out.println("通过cron定义的定时任务");
    @Scheduled(fixedDelay = 1000L)
    public void job2() {
        System.out.println("通过fixedDelay定义的定时任务");
    @Scheduled(fixedRate = 1000L)
    public void job3() {
        System.out.println("通过fixedRate定义的定时任务");

分布式定时任务

在 Java 中,当一个定时任务布置在多机器上时,可通过以下几种方式保证只有一个定时任务执行:

1. 基于数据库实现的分布式锁

在数据库中创建一张锁表,包含锁名称、状态等字段。定时任务执行前,向锁表插入一条记录,若插入成功,表示获取锁成功,可执行任务,执行完后删除记录释放锁。若插入失败,说明锁已被占用,放弃执行

以 MySQL 为例,创建一张用于存储锁信息的表,表结构可以如下:

CREATE TABLE task_lock (
    lock_name VARCHAR(255) PRIMARY KEY,
    lock_status VARCHAR(50) NOT NULL,
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP

lock_name 字段用于标识不同的任务锁, lock_status 字段记录锁的状态,locked 表示已锁定,unlocked 表示未锁定, create_time 和 update_time 分别记录锁的创建时间和更新时间

在定时任务执行前,尝试获取数据库锁,示例代码如下:

@Component
public class DatabaseLock {
    private final JdbcTemplate jdbcTemplate;
    public DatabaseLock(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    public boolean tryLock(String lockName) {
        String sql = "INSERT INTO task_lock (lock_name, lock_status) VALUES (?, 'locked') ON DUPLICATE KEY UPDATE lock_status = 'locked'";
        int affectedRows = jdbcTemplate.update(sql, lockName);
        return affectedRows > 0;

tryLock 方法通过执行 SQL 语句尝试插入锁记录,如果插入成功( affectedRows > 0 ),则表示获取锁成功;如果因为 lock_name 唯一键冲突导致插入失败, ON DUPLICATE KEY UPDATE 语句会更新已存在的记录,但不会增加 affectedRows ,此时表示获取锁失败

在定时任务执行完毕后,需要释放数据库锁,通过 SQL 语句删除对应的锁记录,示例代码如下:

@Component
public class DatabaseLock {
    private final JdbcTemplate jdbcTemplate;
    public DatabaseLock(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    public void releaseLock(String lockName) {
        String sql = "DELETE FROM task_lock WHERE lock_name =?";
        jdbcTemplate.update(sql, lockName);

在定时任务中使用数据库锁

@Component
public class MyScheduledTask {
    @Autowired
    private DatabaseLock databaseLock;
    private static final String TASK_LOCK_NAME = "my_task_lock";
    @Scheduled(cron = "0 0 2 * *?") // 每天凌晨2点执行
    public void executeTask() {
        if (databaseLock.tryLock(TASK_LOCK_NAME)) {
            try {
                // 执行定时任务的具体逻辑
            } finally {
                databaseLock.releaseLock(TASK_LOCK_NAME);
        } else {
            System.out.println("获取锁失败,任务不执行");

2. 基于 Redis 实现的分布式锁

参考:https://www.cnblogs.com/Yee-Q/p/16632845.html

基于分布式锁实现的分布式任务需要解决两个问题:

  • 任务失败导致锁无法释放
  • 任务失败需要重试
  • 任务失败导致锁无法释放的解决方案如下:

  • 捕获异常:在定时任务执行的方法中,添加 try-catch 块捕获所有可能的异常,在 catch 块中记录详细的异常信息,在 finally 块执行释放锁的操作,保证即使任务失败也能释放锁
  • 开启事务:基于数据库实现的分布式锁,确保获取锁和后续操作在同一个事务中,若获取锁后任务执行过程中出现异常,事务回滚,自动释放锁,以 Spring 框架为例,可在方法上添加 @Transactional 注解,让 Spring 管理事务
  • 设置锁的超时时间:获取分布式锁时设置超时时间,过期则释放锁
  • 采用心跳机制:创建一张心跳表,记录每个获取锁的任务的心跳信息,定时任务执行过程中,按照一定的时间间隔向心跳表中更新当前任务的心跳时间,定期检查心跳表中的记录,若发现某个任务的最后心跳时间超过一定时长未更新,则释放锁
  • 任务失败重试的解决方案如下:创建任务执行记录表,用于记录定时任务的执行信息,包含任务 ID、任务名称、执行时间、执行结果、失败次数等字段。每次任务执行时,都在该表中插入一条记录,实时更新任务在表中的执行状态,如“执行中”、“成功”、“失败”等,当释放锁或因为其他线程竞争锁时,先检查任务执行状态,如果任务状态为“成功”,则释放锁不做处理,如果任务状态为“执行中”,则对锁续期,如果任务状态为“失败”,则释放锁并尝试重试

    Quartz 框架

    Quartz 是 Job scheduling(作业调度)领域的一个开源项目,既可以单独使用,也可以和 Spring 框架整合使用。使用Quartz 可以开发一个或者多个定时任务,每个定时任务可以单独指定执行的时间,例如每隔 1 小时执行一次,每个月第一天上午 10 点执行一次、每个月最后一天下午 5 点执行一次等等

    Quartz 的核心概念包括:

  • Scheduler:调度器,是 Quartz 的核心,负责管理和调度任务
  • Job:任务,是实际执行的工作单元。需要实现 Job 接口
  • JobDetail:定义任务的详细信息,包括任务的名称、组、以及任务的类
  • Trigger:触发器,定义任务何时执行。常用的触发器包括 SimpleTrigger 和 CronTrigger
  • JobStore:任务存储,定义任务的存储方式。常见的有 RAMJobStore(内存存储)和 JDBCJobStore(数据库存储)
  • 要使用 Quartz,首先需要在项目的 pom 文件中引入相应的依赖:

    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.3.2</version>
    </dependency>
    </dependency>
    

    定义执行任务的 Job,这里要实现 Quartz 提供的 Job 接口:

    public class HelloJob implements Job {
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            System.out.println("Hello, Quartz! Current time: " + System.currentTimeMillis());
    

    创建 Scheduler 和 Trigger,并执行定时任务:

    public class QuartzExample {
        public static void main(String[] args) throws SchedulerException {
            // 创建 Scheduler 实例
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            // 定义一个 JobDetail 实例
            JobDetail job = JobBuilder.newJob(HelloJob.class)
                    .withIdentity("helloJob", "group1")
                    .build();
            // 创建一个触发器,每隔5秒执行一次
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("helloTrigger", "group1")
                    .startNow()
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                            .withIntervalInSeconds(5)
                            .repeatForever())
                    .build();
            // 调度任务
            scheduler.start();
            scheduler.scheduleJob(job, trigger);
    

    CronTrigger 允许使用 Cron 表达式来定义复杂的调度规则

    public class CronTriggerExample {
        public static void main(String[] args) throws SchedulerException {
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            JobDetail job = JobBuilder.newJob(HelloJob.class)
                    .withIdentity("cronJob", "group1")
                    .build();
            // 使用 Cron 表达式创建触发器
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("cronTrigger", "group1")
                    .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
                    .build();
            scheduler.start();
            scheduler.scheduleJob(job, trigger);
    

    Cron 表达式用于定义任务调度的时间规则,它由 6 或 7 个字段组成,字段之间用空格分隔,以下是每个字段的含义:

    ┌───────────── 秒 (0 - 59)
    │ ┌───────────── 分 (0 - 59)
    │ │ ┌───────────── 小时 (0 - 23)
    │ │ │ ┌───────────── 日 (1 - 31)
    │ │ │ │ ┌───────────── 月 (1 - 12)
    │ │ │ │ │ ┌───────────── 星期几 (0 - 7) (0 和 7 都是星期日)
    │ │ │ │ │ │
    │ │ │ │ │ │
    * * * * * *
    

    如下特殊字符:

  • *:表示任意值
  • ?:仅在日和星期字段中使用,表示不指定值
  • -:表示范围,例如 10-12 表示从 10 到 12
  • `,:表示列表值,例如 1,2,3 表示 1、2、3
  • /:表示增量,例如 0/15 表示从 0 开始每 15 分钟
  • L:表示最后,例如 L 在日字段表示月的最后一天
  • W:表示最近的工作日,例如 15W 表示最接近 15 号的工作日
  • #:表示第几个星期几,例如 2#1 表示第一个星期一
  • 0 0 12 * * ?:每天中午 12 点执行
  • 0 15 10 ? * *:每天上午 10:15 执行
  • 0 15 10 * * ?:每天上午 10:15 执行
  • 0 15 10 * * ? 2024:2024 年每天上午 10:15 执行
  • 0 * 14 * * ?:每天下午 2 点到 2:59 每分钟执行一次
  • 0 0/5 14 * * ?:每天下午 2 点到 2:55 每 5 分钟执行一次
  • 0 0/5 14,18 * * ?:每天下午 2 点到 2:55 每 5 分钟执行一次,以及每天下午 6 点到 6:55 每 5 分钟执行一次
  • 0 0-5 14 * * ?:每天下午 2 点到 2:05 每分钟执行一次
  • 0 10,44 14 ? 3 WED:每年三月的每个星期三下午 2:10 和 2:44 执行
  • 使用 JobListener 可以在任务执行的不同阶段进行拦截和处理

    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.quartz.JobListener;
    public class MyJobListener implements JobListener {
        @Override
        public String getName() {
            return "MyJobListener";
        @Override
        public void jobToBeExecuted(JobExecutionContext context) {
          //在任务即将被执行时调用,可以在任务执行前进行一些准备工作或记录日志。
            System.out.println("Job is about to be executed: " + context.getJobDetail().getKey());
        @Override
        public void jobExecutionVetoed(JobExecutionContext context) {
          //在任务执行被否决时调用,当某些条件满足时,可以阻止任务的执行,并在此方法中执行相应的处理逻辑。
            System.out.println("Job execution was vetoed: " + context.getJobDetail().getKey());
        @Override
        public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
          //在任务执行完成后调用,可以在任务执行后进行一些清理工作或记录日志。如果任务执行过程中抛出异常,jobException 将包含该异常信息。
            System.out.println("Job was executed: " + context.getJobDetail().getKey());
            if (jobException != null) {
                System.out.println("Job encountered an exception: " + jobException.getMessage());
    

    在 Scheduler 中注册 JobListener

    import org.quartz.*;
    import org.quartz.impl.StdSchedulerFactory;
    public class JobListenerExample {
        public static void main(String[] args) throws SchedulerException {
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
          //创建任务
            JobDetail job = JobBuilder.newJob(HelloJob.class)
                    .withIdentity("listenerJob", "group1")
                    .build();
          创建触发器
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("listenerTrigger", "group1")
                    .startNow()
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                            .withIntervalInSeconds(5)
                            .repeatForever())
                    .build();
            // 创建并注册 JobListener
            MyJobListener listener = new MyJobListener();
            scheduler.getListenerManager().addJobListener(listener);
            scheduler.start();
            scheduler.scheduleJob(job, trigger);
    

    Quartz 默认任务是并发执行的,如果需要确保同一个任务实例不被并发执行,可以在 Job 添加 @DisallowConcurrentExecution 注解,此时新的任务实例只有在前一个实例完成后才会开始执行

    Spring Boot 集成 Quartz 的方式也很简单,引入封装好的 Quartz 依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    

    定义执行任务的 Job 和之前没有区别,定义 Scheduler 和 Trigger

    import com.example.demo.quartz.task.DongAoJob;
    import org.quartz.*;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
     * 定义任务描述和具体的执行时间
    @Configuration
    public class QuartzConfig {
        @Bean
        public JobDetail jobDetail() {
            //指定任务描述具体的实现类
            return JobBuilder.newJob(HelloJob.class)
                    // 指定任务的名称
                    .withIdentity("dongAoJob")
                    // 任务描述
                    .withDescription("任务描述:用于输出冬奥欢迎语")
                    // 每次任务执行后进行存储
                    .storeDurably()
                    .build();
        @Bean
        public Trigger trigger() {
            //创建触发器
            return TriggerBuilder.newTrigger()
                    // 绑定工作任务
                    .forJob(jobDetail())
                    // 每隔 5 秒执行一次 job
                    .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5))
                    .build();
    

    Quartz 默认采用 RAMJobStore,将任务相关信息保存在内存里,应用重启后,定时任务信息将会丢失,下面采用数据库方式存储任务信息

    在 application.properties 文件中加入 Quartz 相关配置

    # 将 Quartz 持久化方式修改为 jdbc
    spring.quartz.job-store-type=jdbc
    # 实例名称(默认为quartzScheduler)
    spring.quartz.properties.org.quartz.scheduler.instanceName=SC_Scheduler
    # 实例节点 ID 自动生成
    spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
    # 修改存储内容使用的类
    spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
    # 数据源信息
    spring.quartz.properties.org.quartz.jobStore.dataSource=quartz_jobs
    spring.quartz.properties.org.quartz.dataSource.quartz_jobs.driver=com.mysql.cj.jdbc.Driver
    spring.quartz.properties.org.quartz.dataSource.quartz_jobs.URL=jdbc:mysql://127.0.0.1:3306/quartz_jobs?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8
    spring.quartz.properties.org.quartz.dataSource.quartz_jobs.user=root
    spring.quartz.properties.org.quartz.dataSource.quartz_jobs.password=123456
    

    下载 Quartz 发布包,下载完成后,解压缩进入 /docs/dbTables 目录,找到匹配数据库的 SQL 文件,下载地址:https://www.quartz-scheduler.org/downloads/,创建 quartz_jobs 数据库,执行 SQL 文件即可

    在配置文件中开启分布式支持

    # 开启集群,多个 Quartz 实例使用同一组数据库表
    spring.quartz.properties.org.quartz.jobStore.isClustered=true
    

    Quartz 支持分布式定时任务的原理是在数据库中配置定时器信息, 以数据库锁的方式达到同一个任务始终只有一个节点在运行

    上图每个服务器节点有一个 Scheduler 实例(调度器线程),每个 Scheduler 实例会争抢访问数据库的 Trigger 并加行锁,比如一个 Scheduler 实例想要访问数据库看是否有 Trigger 将要触发,那么它就开一个事务,首先获取并占用 TRIGGER_ACCESS 锁,然后再处理业务,在此期间其他实例无法访问与该 Trigger 有关的数据表,也就无法执行任务。获得行锁的 Scheduler 实例处理完业务后 commit work,结束事务,TRIGGER_ACCESS 锁就被释放了

    如果一个 Scheduler 实例获取一个 TRIGGER_ACCESS 锁但是还没处理完就挂掉了,会导致与 Trigger 有关的表一直处于加锁状态无法被其他 Scheduler 实例访问,因此 Quartz 又提出了一个接管锁的机制:

  • 每个 Scheduler 实例都在 QRTZ_SCHEDULER_STATE 表里有自己的唯一 ID,例如以 hostname + time 标识
  • 每个 Scheduler 实例的 ClusterManager 线程定期往 QRTZ_SCHEDULER_STATE 表更新 LAST_CHECKIN_TIME 作为心跳
  • 当某个 Scheduler 实例超过一定时间没有心跳更新时,其它 Scheduler 实例得到这个信息,会接管对应的行锁,并恢复过时的任务
  • XXL-JOB

    XXL-JOB 是一个可以在 WEB 界面配置执行定时任务的中间件,支持分布式服务调用,XXL-JOB 自身也可以部署多个节点组成集群,本身是一个基于 SpringBoot 的 Java WEB 程序,可以通过下载 GitHub 源码进行部署

    XXL-JOB 中“调度模块”和“任务模块”完全解耦,调度模块进行任务调度时,将会解析不同的任务参数发起远程调用,调用各自的远程执行器服务。这种调用模型类似RPC调用,调度中心提供调用代理的功能,而执行器提供远程服务的功能

    基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务

    通过 git 下载源码

    git clone https://gitee.com/xuxueli0323/xxl-job.git
    

    打开并执行 doc/db/tables_xxl_job.sql 文件

    修改 xxl-job-admin/src/main/resources 的 application.properties 配置文件,修改数据库配置

    ### xxl-job, datasource
    spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
    spring.datasource.username=your_username
    spring.datasource.password=your_password
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    

    运行 xxl-job-admin/src/main/java 包下的 XxlJobAdminApplication的main() 方法,实际为一个 springboot 项目,启动后即运行一个 web 程序

    在浏览器中访问 http://localhost:8080/xxl-job-admin/toLogin ,出现如下页面,默认登录账户:admin,密码:123456,第一次登录成功后请修改密码

    创建一个 SpringBoot 项目,引入 maven 依赖

    <!-- xxl job -->
    <dependency>
        <groupId>com.xuxueli</groupId>
        <artifactId>xxl-job-core</artifactId>
        <version>2.4.0</version>
    </dependency>
    

    yaml 文件配置,将 addresses 修改为自己部署的 xxl-job-admin 地址

    accessToken: default_token # 默认值是 default_token executor: appname: ${spring.application.name} logpath: ${spring.application.name}/xxl-job logretentiondays: 30 admin: addresses: http://127.0.0.1:8080/xxl-job-admin

    xxl-job 没有使用 spring-boot-starter,需自行将配置类注入到 spring 容器

    @Configuration
    public class XxlJobConfig {
        private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
        @Value("${xxl.job.admin.addresses}")
        private String adminAddresses;
        @Value("${xxl.job.accessToken}")
        private String accessToken;
        @Value("${xxl.job.executor.appname}")
        private String appname;
        @Value("${server.port}")
        private int port;
        @Value("${xxl.job.executor.logpath}")
        private String logPath;
        @Value("${xxl.job.executor.logretentiondays}")
        private int logRetentionDays;
        @Bean
        public XxlJobSpringExecutor xxlJobExecutor() {
            logger.info(">>>>>>>>>>> xxl-job config init.");
            XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
            xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
            xxlJobSpringExecutor.setAppname(appname);
            xxlJobSpringExecutor.setPort(port + 10000);
            xxlJobSpringExecutor.setAccessToken(accessToken);
            xxlJobSpringExecutor.setLogPath(logPath);
            xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
            return xxlJobSpringExecutor;
    

    在 xxl-job-admin 创建执行器,将 appname 替换为你的服务名,选择自动注册

    启动客户端程序,将会自动注册到 xxl-job-admin 服务端

    创建一个任务调度,在客户端中编写如下代码,并重新启动客户端

    @Slf4j
    @Component
    public class XxlJobHandler {
         * 更新状态
        @XxlJob("UpdateStatus")
        public void updateStatus() {
            String jobParam = XxlJobHelper.getJobParam();
            log.info("任务执行" + jobParam);
    

    注意 @XxlJob("UpdateStatus") 注解的 UpdateStatus 表示任务的唯一名称,XxlJobHelper.getJobParam(); 可以用来获取执行任务时传递的参数

    主要在 Cron 中配置任务调度的时间周期,可选择 CRON 或固定速度,JobHandler 需配置 @XxlJob 注解的名称

    创建完成后,点击操作,点击执行一次,任务执行成功将会打印日志。点击启动任务,将会按照我们既定的调度周期执行任务