手撸XXL-JOB(一)——定时任务的执行

SpringBoot执行定时任务

对于定时任务的执行,SpringBoot提供了三种创建方式:
1)基于注解(@Scheduled)
2)基于接口(SchedulingConfigurer)
3)基于注解设定多线程定时任务

基于Scheduled注解

首先我们创建一个SpringBoot项目,然后引入spring-boot-starter-web依赖,在启动类上添加EnableScheduling注解开启定时任务管理。

package com.yang.demo1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class ScheduledDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(ScheduledDemoApplication.class, args);
    }
}

然后我们创建一个定时任务,并使用Scheduled注解

package com.yang.demo1;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class DemoTask {
    @Scheduled(cron = "0/5 * * * * ?") // 每5秒执行一次
    public void execute() {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("hello world: " + df.format(new Date()));
    }
}

启动项目,执行结果如下所示,每隔5秒,便会执行一次定时任务。
image.png
Scheduled注解类的源码如下:

package org.springframework.scheduling.annotation;
 
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
    String CRON_DISABLED = "-";
 
    String cron() default "";
 
    String zone() default "";
 
    long fixedDelay() default -1L;
 
    String fixedDelayString() default "";
 
    long fixedRate() default -1L;
 
    String fixedRateString() default "";
 
    long initialDelay() default -1L;
 
    String initialDelayString() default "";
}

除了刚才代码中使用到的cron参数,我们还可以使用fixedDelay和fixedRate等参数。fixedDelay和fixedRate很相似,但又略有不同,其中fixedDelay表示在某次任务执行完毕后,间隔fixedDelay的时间再执行,而fixedRate表示在某次任务执行开始后,间隔fixedRate的时间再执行。
我们对这两个参数,添加对应的测试方法:

  @Scheduled(fixedDelay = 5000)
    public void executeFixedDelay() {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("executeFixedDelay开始执行了==========" + df.format(new Date()));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Scheduled(fixedRate = 5000)
    public void executeFixedRate() {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("executeFixedRate开始执行了==========" + df.format(new Date()));
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

启动项目,执行结果如下。我们可以看到,executeFixedDelay在任务执行完毕后,间隔5秒才执行下一个任务,也就是说,两个任务之间间隔7秒,而executeFixedRate在任务执行开始后,间隔5秒执行下一个任务,也就是两个任务之间间隔5秒。综上所示,fixedDelay和fixedRate之间的区别,其实就在于计算两个任务执行间隔时,需不需要考虑任务的执行时长。
image.png

基于SchedulingConfigurer接口

首先我们在数据库中创建t_cron表,并添加数据:

DROP TABLE IF EXISTS t_cron;
CREATE TABLE t_cron  (
  cron_id VARCHAR(30) NOT NULL PRIMARY KEY,
  cron VARCHAR(30) NOT NULL  
);
 
INSERT INTO t_cron VALUES ('1', '0/5 * * * * ?');

然后修改pom.xml,加上数据库的相关依赖:

  <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>

修改配置文件:

spring:
  datasource:
    username: root
    password: 3fa4d180
    url: jdbc:mysql://localhost:3306/test01?useSSL=false&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

创建对应的mapper:

package com.yang.demo1.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface CronMapper {
    @Select("select cron from t_cron limit 1")
    String getCron();
}

然后,我们编写定时任务,通过读取我们在数据库设置好的执行周期,来执行定时任务,代码如下:

package com.yang.demo1;

import com.yang.demo1.mapper.CronMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;

import java.time.LocalDateTime;

@Configuration
@EnableScheduling
public class DynamicScheduleConfigurer implements SchedulingConfigurer {
    @Autowired
    private CronMapper cronMapper;

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.addTriggerTask(
                () -> execute(),
                triggerContext -> {
                    String cron = cronMapper.getCron();
                    if (StringUtils.isEmpty(cron)) {
                        throw new RuntimeException("cron 格式有误");
                    }
                    return new CronTrigger(cron).nextExecutionTime(triggerContext);
                }
        );
    }

    private void execute() {
        System.out.println("hello world:" + new Date());
    }
}

执行结果如下所示,一开始的cron是每5秒执行一次,后来我们修改cron,改为10秒执行一次,此时任务的执行频率,也随之我们数据库的修改动态地进行了调整。
image.png

EnableAsync注解

对于Scheduled,默认为单线程,当开启多个任务时,任务地执行实际会受到上一个任务执行时间地影响,所以需要使用Async注解,通过该注解开启多线程使任务之间不会相互影响。

ScheduledExecutorService执行定时任务

SpringBoot执行定时任务很简单,但是如果我们不使用SpringBoot,又该如何启动定时任务?这个时候,我们就可以使用ScheduledExecutorService接口,这个接口用于在一些预定义的延迟之后运行任务或定期运行任务。我们可以通过Executors类的工厂方法实例化ScheduledExecutorService,如下:

        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();

在ScheduledExecutorService接口中,有三个主要方法:
1)schedule:允许在指定的延迟后执行一次任务。
2)scheduleAtFixedRate:允许在指定的初始延迟后执行任务,然后以一定的周期重复执行,其中period参数用于指定两个任务的开始时间之间的间隔时间,因此任务执行的频率是固定的。
3)scheduleWithFixedDelay:类似于scheduleAtFixedRate,它也重复执行给定的任务,单period参数用于指定前一个任务的结束和下一个任务的开始之间的间隔时间,也就是指定下一个任务延时多久后才执行,执行频率可能会有所不同,具体取决于执行任务给定任务所需的时间。

schedule方法
package com.yang.demo2;

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledExecutorServiceMain {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.schedule(() -> {
            System.out.println("Hello World:" + new Date());
        }, 1, TimeUnit.SECONDS);

        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        scheduledExecutorService.shutdown();
        try {
            if (!scheduledExecutorService.awaitTermination(10, TimeUnit.SECONDS)) {
                scheduledExecutorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果如下:在延迟一秒后,开始执行任务,且只执行一次。
image.png

scheduleAtFixRate

当我们需要在固定延迟后,定期执行任务时,可以使用scheduleAtFixedRate方法,如下所示,每隔2秒执行相同的任务

public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("Hello World:" + new Date());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 0, 2, TimeUnit.SECONDS);

        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        scheduledExecutorService.shutdown();
        try {
            if (!scheduledExecutorService.awaitTermination(10, TimeUnit.SECONDS)) {
                scheduledExecutorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

结果如下:
image.png
如果任务执行时间比间隔时间长,那么scheduledExecutorService将等待当前任务执行完毕后再开始执行下一个任务,我们修改代码如下:

 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("Hello World:" + new Date());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 0, 2, TimeUnit.SECONDS);

        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        scheduledExecutorService.shutdown();
        try {
            if (!scheduledExecutorService.awaitTermination(10, TimeUnit.SECONDS)) {
                scheduledExecutorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

结果如下,每隔3秒执行一次任务
image.png

scheduleWithFixedDelay方法

如果任务之间必须具有固定长度的延迟,那么可以使用scheduleWithFixedDelay方法。

  public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            System.out.println("Hello World:" + new Date());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 0, 2, TimeUnit.SECONDS);

        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        scheduledExecutorService.shutdown();
        try {
            if (!scheduledExecutorService.awaitTermination(10, TimeUnit.SECONDS)) {
                scheduledExecutorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

在上述代码中,任务执行时长需要1秒,然后设置的间隔时间为2秒,因此,我们可以看到,每隔任务之间,间隔3秒执行一次
image.png

### 使用XXL-JOB任务调度中心配置和管理定时任务 #### 配置环境 为了能够顺利使用XXL-JOB进行任务调度,首先需要完成基本的安装与配置工作。这通常涉及到下载并部署XXL-JOB的服务端程序以及客户端SDK,并确保两者之间的网络通信畅通无阻。 #### 创建新任务 进入XXL-JOB管理员界面后,在左侧菜单栏找到“任务列表”,点击新增按钮即可创建个新的调度任务。此时需填写任务名称、描述信息等内容[^1]。 #### 设置执行策略 对于每个新建的任务而言,都需要指定具体的执行逻辑——即当该任务被触发时应该做什么操作;同时也要定义好其运行频率(Cron表达式)、超时时间等参数设置。这些都可以在编辑页面中的相应选项卡内完成配置[^2]。 #### 测试与调试 保存所作更改之后就可以尝试动触发次这个刚建立起来的新任务了。如果切正常的话,则会看到状态变为成功;反之则可根据日志记录排查错误原因所在之处。 #### 安全注意事项 值得注意的是,在实际生产环境中应当特别关注系统的安全性问题。由于可能存在某些潜在的安全隐患(如命令注入),因此建议定期审查官方发布的安全公告并及时更新版本以修复已知漏洞[^3]。 ```bash # 示例:简单的Shell脚本作为任务执行体 #!/bin/bash echo "This is a test job" date >> /tmp/testjob.log ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值