Java中实现定时任务,有多少种解决方案?

前言

好久没更新博客了,最近上班做了点小东西,总结复盘一下

参考资料:

SpringBoot 设置动态定时任务,千万别再写死了~ (qq.com)

3千字带你搞懂XXL-JOB任务调度平台-阿里云开发者社区 (aliyun.com)

一、定时任务

1. 引入依赖

创建Springboot应用,引入相应依赖:

<dependencies>  
    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-web</artifactId>  
        <exclusions>  
            <exclusion>  
                <artifactId>spring-boot-starter-logging</artifactId>  
                <groupId>org.springframework.boot</groupId>  
            </exclusion>  
        </exclusions>  
    </dependency>  

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

    <dependency>  
        <groupId>org.projectlombok</groupId>  
        <artifactId>lombok</artifactId>  
        <optional>true</optional>  
    </dependency>  
</dependencies>

在spring-boot-starter-web中排除spring-boot-starter-logging是为了不使用springboot默认的日志实现logback,而是引入log4j2的日志实现

引入lombok是为了使用@Data、@RequiredArgsConstructor等注解

2. 代码实现

在启动类上添加注解@EnableScheduling

package com.example.demo;  
  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.scheduling.annotation.EnableScheduling;  
  
@SpringBootApplication  
@EnableScheduling   
public class DttNoticeApplication {  
    public static void main(String[] args) {  
        SpringApplication.run(DttNoticeApplication.class, args);  
    }  
}

配置文件指定运行的端口:

server:
    port: 8080

编写实现定时任务的类,用@Scheduled修饰执行定时任务的方法,并用@Component将该类注册为Bean

package com.example.demo;
 
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
 
@Component
public class Task{
    // cron表达式常用于定时任务,此处表示每10秒执行一次
    @Scheduled(cron="0/10 * * * * ?")  
    public void scheduledTask(){
        // ...
    }
}

二、动态定时任务

定时任务执行时间的配置文件,位于resources/task-config.ini:

printTime.cron=0/10 * * * * ?

编写实现定时任务的类,利用@PropertySource指定获取的配置文件并用@Value注入到相应成员中,并用@Component将该类注册为Bean

实现SchedulingConfigurer接口,重载configureTasks函数,

其中,configureTasks函数接收一个ScheduledTaskRegistrar类型的参数,调用该对象的addTriggerTask,接收一个Runnable对象用于执行任务,以及一个Trigger对象用于计算下一次执行任务的时间

package com.example.demo.task;  
  
import lombok.Data;  
import lombok.RequiredArgsConstructor;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.context.annotation.PropertySource;  
import org.springframework.scheduling.Trigger;  
import org.springframework.scheduling.TriggerContext;  
import org.springframework.scheduling.annotation.SchedulingConfigurer;  
import org.springframework.scheduling.config.ScheduledTaskRegistrar;  
import org.springframework.scheduling.support.CronTrigger;  
import org.springframework.scheduling.support.PeriodicTrigger;  
import org.springframework.stereotype.Component;  

  
@Data  
@Slf4j  
@Component  
@RequiredArgsConstructor  
@PropertySource("classpath:task-config.ini")  
public class ScheduleTask implements SchedulingConfigurer {  
  
    // private Long timer = 100 * 1000L;  

    @Value("${printTime.cron}")  
    private String cron;  

    @Override  
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {  
        // 间隔触发的任务  
        taskRegistrar.addTriggerTask(new Runnable() {  
        @Override  
        public void run(){  
           // ...
        }
        }, new Trigger() {  
        @Override  
        public Date nextExecutionTime(TriggerContext triggerContext) {  
            // 使用CronTrigger触发器,可动态修改cron表达式来操作循环规则  
            CronTrigger cronTrigger = new CronTrigger(cron);  
            Date nextExecutionTime = cronTrigger.nextExecutionTime(triggerContext);  
            return nextExecutionTime;  
            
            // 使用PerodicTrigger触发器,修改timer变量指定操作间隔,单位为毫秒
            // PeriodicTrigger periodicTrigger = new PeriodicTrigger(timer);  
            // Date nextExecutionTime = periodicTrigger.nextExecutionTime(triggerContext);  
            // return nextExecutionTime;  
        }  
        });  
    }  
}

注意到这里使用了@Data注解,是为了能够直接调用成员变量的setter更改cron表达式(或timer)的值

编写Controller提供修改定时任务执行时间的接口:

package com.example.demo.controller;  
  
import lombok.RequiredArgsConstructor;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.web.bind.annotation.GetMapping;  
import com.szhg.dttnotice.task.ScheduleTask;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  
  
@Slf4j  
@RestController  
@RequiredArgsConstructor  
@RequestMapping("/notice")  
public class NoticeController {  

    private final ScheduleTask scheduleTask;  

    @GetMapping("/updateCron")  
    public String updateCron(String cron) {  
        log.info("new cron :{}", cron);  
        scheduleTask.setCron(cron);  
        return "执行任务的表达式修改为: " + cron ;  
    }  

    // @GetMapping("/updateTimer")  
    // public String updateTimer(Long timer) {  
    //     log.info("new timer :{}", timer);  
    //     scheduleTask.setTimer(timer * 1000);  
    //     return "执行任务的时间间隔修改为" + timer + "s";  
    // }  
}

三、分布式定时任务

在分布式的架构中,我们需要一个支持集群、支持监控、支持告警等等功能的解决方案,那么上述方法就比较麻烦了。

主流的分布式任务调度平台包括elastic-job、xxl-job、quartz等

本文重点介绍xxl-job

首先从源码仓库地址将代码拉到本地:xuxueli/xxl-job: A distributed task scheduling framework.(分布式任务调度平台XXL-JOB) (github.com)

1. 运行调度中心

从根路径下找到doc/db/tables_xxl_job.sql,在数据库中新建Schema,执行该sql脚本

DataGrip的示例如下:
在这里插入图片描述

回看项目的根路径下有哪些模块:

  1. xxl-job-admin:任务调度的管理平台,跑起来后可在浏览器中访问
  2. xxl-job-core:项目的公共依赖
  3. xxl-job-executor-samples:执行器(也就是需要执行的任务)的示例

找到admin项目下的application.properties文件:

### 调度中心JDBC链接
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=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
### 报警邮箱
spring.mail.host=smtp.qq.com
spring.mail.port=25
spring.mail.username=xxx@qq.com
spring.mail.password=xxx
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
### 调度中心通讯TOKEN [选填]:非空时启用;
xxl.job.accessToken=default_token
### 调度中心国际化配置 [必填]: 默认为 "zh_CN"/中文简体, 可选范围为 "zh_CN"/中文简体, "zh_TC"/中文繁体 and "en"/英文;
xxl.job.i18n=zh_CN
## 调度线程池最大线程配置【必填】
xxl.job.triggerpool.fast.max=200
xxl.job.triggerpool.slow.max=100
### 调度中心日志表数据保存天数 [必填]:过期日志自动清理;限制大于等于7时生效,否则, 如-1,关闭自动清理功能;
xxl.job.logretentiondays=10

datasource配置连接到我们刚才创建的数据库
mail配置报警邮箱
accessToken(重要)配置后,执行的任务也需要配置相同的accessToken

运行启动类(或者打成jar包运行)后,可在浏览器中访问到管理平台

2. 注册定时任务

新建一个Springboot项目,并添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency><!-- 官网的demo是2.2.1,中央maven仓库还没有,所以就用2.2.0 -->
        <groupId>com.xuxueli</groupId>
        <artifactId>xxl-job-core</artifactId>
        <version>2.2.0</version>
    </dependency>
</dependencies>

配置application.properties

# web port
server.port=8081
# log config
logging.config=classpath:logback.xml
spring.application.name=xxljob-demo
### 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### 执行器通讯TOKEN [选填]:非空时启用;
xxl.job.accessToken=default_token
### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
xxl.job.executor.appname=xxl-job-demo
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
xxl.job.executor.address=
### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
xxl.job.executor.ip=127.0.0.1
### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
xxl.job.executor.port=9999
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
xxl.job.executor.logretentiondays=10

注意这里的accessToken要与前面admin配置的accessToken保持一致

在resources目录下,配置日志输出logback.xml:

<?xml version="1.0" encoding="UTF-8"?>  
<configuration debug="false" scan="true" scanPeriod="1 seconds">  

    <contextName>logback</contextName>  
    <property name="log.path" value="/data/applogs/xxl-job/xxl-job-executor-sample-springboot.log"/>  

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">  
        <encoder>  
            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>  
        </encoder>  
    </appender>  

    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">  
        <file>${log.path}</file>  
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
            <fileNamePattern>${log.path}.%d{yyyy-MM-dd}.zip</fileNamePattern>  
        </rollingPolicy>  
        <encoder>  
            <pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n  
            </pattern>  
        </encoder>  
    </appender>  

    <root level="info">  
        <appender-ref ref="console"/>  
        <appender-ref ref="file"/>  
    </root>  

</configuration>

编写一个配置类,实例化一个XxlJobSpringExecutor类的Bean:

package com.example.xxljobdemo.config;  
  
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
  
@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("${xxl.job.executor.address}")  
    private String address;  
    @Value("${xxl.job.executor.ip}")  
    private String ip;  
    @Value("${xxl.job.executor.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.setAddress(address);  
        xxlJobSpringExecutor.setIp(ip);  
        xxlJobSpringExecutor.setPort(port);  
        xxlJobSpringExecutor.setAccessToken(accessToken);  
        xxlJobSpringExecutor.setLogPath(logPath);  
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);  
        return xxlJobSpringExecutor;  
    }  
}

在管理平台注册执行器:

在这里插入图片描述

AppName为配置文件中的xxl.job.executor.appname
手动录入才需要填写机器地址一栏
IP和端口号分别为配置文件中的:xxl.job.executor.ip 和 xxl.job.executor.port

编写一个任务类,使用Bean模式,也就是在任务对应的方法上添加@XxlJob注解,自定义JobHandler的名称

package com.example.xxljobdemo.jobhandler;  
  
import com.xxl.job.core.biz.model.ReturnT;  
import com.xxl.job.core.context.XxlJobHelper;  
import com.xxl.job.core.handler.annotation.XxlJob;  
import org.springframework.stereotype.Component;  
  
@Component  
public class XxlJobDemoHandler {  
    /**  
    * Bean模式,一个方法为一个任务  
    * 1、在Spring Bean实例中,开发Job方法,方式格式要求为 "public ReturnT<String> execute(String param)"  
    * 2、为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",注解value值对应的是调度中心新建任务的JobHandler属性的值。  
    * 3、执行日志:需要通过 "XxlJobLogger.log" 打印执行日志;  
    */  
    @XxlJob("demoJobHandler")  
    public ReturnT<String> demoJobHandler(String param) throws Exception {  
        XxlJobHelper.log("java, Hello World~~~");  
        XxlJobHelper.log("param:" + param);  
        return ReturnT.SUCCESS;  
    }  
}

在管理平台新建任务:

在这里插入图片描述

JobHandler为上述代码@XxlJob注解中的值

随后运行该Springboot应用,可在管理平台执行一次或启动任务

在这里插入图片描述

  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值