分布式调度XXL-JOB

发布时间 2023-04-30 18:46:02作者:  不将就鸭

1. Spring提供的定时任务调度SpringTask

Spring3.0以后自主开发的定时任务工具,spring task,可以将它比作一个轻量级的Quartz,而且使用起来很简单,除spring相关的包外不需要额外的包,而且支持注解和配置文件两种形式。

视频教程:https://www.bilibili.com/video/BV1xJ411G7ff/?vd_source=61b6fb4e547748656e36b17ee95125fb


1.1 SpringTask使用

  1. 主启动类上添加注解:@EnableScheduling

  2. 在需要定时执行的方法上加注解:@Scheduled

// @Scheduled 注解介绍

@Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.ANNOTATION_TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
public @interface Scheduled  {  
  public abstract String cron();      // 指定cron表达式
  
  public abstract long fixedDelay();  // 上一个任务完成开始到下一个任务开始的间隔,单位是毫秒。
  
  public abstract long fixedRate();   // 上一个任务开始到下一个任务开始的间隔,单位是毫秒。
}
// 该代码实现了固定时间间隔完成定时任务的输出。
@EnableScheduling
@SpringBootApplication
public class SchedulerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SchedulerApplication.class, args);
    }

    @Scheduled(fixedRate = 5 * 1000) // 每5秒执行一次
    public void playSth() {
        System.out.println("好好学习  " + DateFormat.getDateTimeInstance().format(new Date()));
    }

    @Scheduled(fixedRate = 5 * 1000) // 每5秒执行一次
    public void playSth2() {
        System.out.println("天天向上  " + DateFormat.getDateTimeInstance().format(new Date()));
    }

}

// ===================  若想在特定时间执行定时任务可以使用 cron 表达式来解决。  ============================
// 在线Cron表达式生成器:https://cron.qqe2.com/
@EnableScheduling
@SpringBootApplication
public class SchedulerApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(SchedulerApplication.class, args);
    }
    
    @Scheduled(cron = "0 0/30 9-22 * * ?") // 设置9点到22点,每隔30分钟执行一次
    public void playSth() {
        System.out.println("好好学习  " + DateFormat.getDateTimeInstance().format(new Date()));
    }
    
    @Scheduled(fixedRate = "0 0 9-22/4 * * ?") // 设置9点到22点,每隔4小时执行一次
    public void playSth2() {
        System.out.println("天天向上  " + DateFormat.getDateTimeInstance().format(new Date()));
    }
    
}

1.2 SpringTask搭配多线程异步

虽然实现了Spring定时任务,但是,默认是单线程的定时任务,如果任务持续时间较长,或者定时间隔时间较短,可能会将后续定时任务拖延,从而导致丢失任务。

@EnableAsync   // 开启多线程异步注解支持
@EnableScheduling
@SpringBootApplication
public class SchedulerApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(SchedulerApplication.class, args);
    }
    
    @Async // 设置异步执行
    @Scheduled(cron = "0 0/30 9-22 * * ?") // 设置9点到22点,每隔30分钟执行一次
    public void playSth() {
        System.out.println("好好学习  " + DateFormat.getDateTimeInstance().format(new Date()));
    }
    
    @Async
    @Scheduled(fixedRate = "0 0 9-22/4 * * ?") // 设置9点到22点,每隔4小时执行一次
    public void playSth2() {
        System.out.println("天天向上  " + DateFormat.getDateTimeInstance().format(new Date()));
    }
    
}



2. XXL-JOB

2.1 什么是XXL-JOB

XXL-JOB:一个轻量级的分布式调度任务框架。为了自动完成特定任务,在约定的特定时刻去执行任务的过程。

官网:https://www.xuxueli.com/xxl-job/
请配合官网查看,官网中文,写的很详细。


2.2 为什么需要分布式任务调度

  1. 高可用:单机版只能在一台机器上运行,会出现单点故障。
  2. 瓶颈问题:单机版处理性能存在极限。
  3. 防止重复执行:当部署了多台服务器时,若多台服务器又有定时任务时,就可能导致任务重复执行。

2.3 快速入门 - 部署调度中心

注意:

    调度中心支持集群部署,集群情况下各节点务必连接同一个mysql实例。    如果mysql做主从,调度中心集群节点务必强制走主库;

源码仓库地址:

    https://github.com/xuxueli/xxl-job	
    http://gitee.com/xuxueli0323/xxl-job	

2.3.1 初始化调度数据库

请下载项目源码并解压,获取 “调度数据库初始化SQL脚本” 并执行即可。

“调度数据库初始化SQL脚本” 位置为: /xxl-job/doc/db/tables_xxl_job.sql


2.3.2 源码模块介绍

xxl-job-admin:调度中心
xxl-job-core:公共依赖
xxl-job-executor-samples:执行器Sample示例(选择合适的版本执行器,可直接使用,也可以参考其并将现有项目改造成执行器)
    :xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器,推荐这种方式;
    :xxl-job-executor-sample-frameless:无框架版本;

2.3.3 配置部署“调度中心”——配置文件内容说明

### 调度中心JDBC链接:链接地址请保持和 2.1章节 所创建的调度数据库的地址一致
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=root_pwd
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=

### 调度中心国际化配置 [必填]: 默认为 "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=30

2.3.4 配置部署“调度中心”——部署项目

注意:

    执行器支持集群部署,提升调度系统可用性,同时提升任务处理能力。
    执行器集群部署时,几点要求和建议:
        执行器回调地址(xxl.job.admin.addresses)需要保持一致;执行器根据该配置进行执行器自动注册等操作。
        同一个执行器集群内AppName(xxl.job.executor.appname)需要保持一致;调度中心根据该配置动态发现不同集群的在线执行器列表。

调度中心访问地址:http://localhost:8080/xxl-job-admin (该地址执行器将会使用到,作为回调地址)

默认登录账号 “admin/123456”, 登录后运行界面如下图所示。




2.4 快速入门 - 开发一个任务(执行器)

    <dependency>
        <groupId>com.xuxueli</groupId>
        <artifactId>xxl-job-core</artifactId>
        <version>${版本}</version>
    </dependency>
    ### 配置文件内容

    ### 调度中心部署根地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
    xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin

    ### 执行器通讯TOKEN [选填]:非空时启用;
    xxl.job.accessToken=

    ### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
    xxl.job.executor.appname=xxl-job-executor-sample

    ### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”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=30
    // XXL-JOB 配置类
    @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;
        }
    }
任务配置:
    - 运行模式:
        BEAN模式:任务以JobHandler方式维护在执行器端;需要结合 "JobHandler" 属性匹配执行器中任务;
        GLUE模式(Java):任务以源码方式维护在调度中心;该模式的任务实际上是一段继承自IJobHandler的Java类代码并 "groovy" 源码方式维护,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器里中的其他服务;
        GLUE模式(Shell):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "shell" 脚本;
        GLUE模式(Python):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "python" 脚本;
        GLUE模式(PHP):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "php" 脚本;
        GLUE模式(NodeJS):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "nodejs" 脚本;
        GLUE模式(PowerShell):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "PowerShell" 脚本;

    - JobHandler:运行模式为 "BEAN模式" 时生效,对应执行器中新开发的JobHandler类“@JobHandler”注解自定义的value值;
    - 执行参数:任务执行所需的参数;   

# GLUE 模式可以理解为:它能在项目已经上线的情况下,进行新建任务的调度。
# Bean 模式需要提前写好再上线项目。

GLUE模式讲解:https://www.bilibili.com/video/BV1824y1G7vT?p=6&vd_source=61b6fb4e547748656e36b17ee95125fb


Bean模式案例:
Bean模式可分为类形式,方法形式两种。

BEAN模式(类形式)

    优点:不限制项目环境,兼容性好。即使是无框架项目,如main方法直接启动的项目也可以提供支持,可以参考示例项目 “xxl-job-executor-sample-frameless”;
    缺点:
        每个任务需要占用一个Java类,造成类的浪费;
        不支持自动扫描任务并注入到执行器容器,需要手动注入。

    步骤一:执行器项目中,开发Job类:
        1、开发一个继承自"com.xxl.job.core.handler.IJobHandler"的JobHandler类,实现其中任务方法。
        2、手动通过如下方式注入到执行器容器。
        ```
        XxlJobExecutor.registJobHandler("demoJobHandler", new DemoJobHandler());
        ```

    步骤二:调度中心,新建调度任务
        后续步骤和 BEAN模式(方法形式) 一致,可以前往参考。

BEAN模式(方法形式)

    优点:
        每个任务只需要开发一个方法,并添加”@XxlJob”注解即可,更加方便、快速。
        支持自动扫描任务并注入到执行器容器。
    缺点:要求Spring容器环境;

    步骤一:执行器项目中,开发Job方法:
        1、任务开发:在Spring Bean实例中,开发Job方法;(创建一个类,该类加入IOC)

        2、注解配置:为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",注解value值对应的是调度中心新建任务的JobHandler属性的值。

        3、执行逻辑:在方法里面编写你想执行的任务逻辑

        4、任务结果:默认任务结果为 "成功" 状态,不需要主动设置;如有诉求,比如设置任务结果为失败,可以通过 "XxlJobHelper.handleFail/handleSuccess" 自主设置任务结果;

    @Component
    public class SampleXxlJob {
        private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);


        /**
         * 1、简单任务示例(Bean模式)
         */
        @XxlJob("demoJobHandler")
        public void demoJobHandler() throws Exception {
            System.out.println("哇哈哈");
        }
    }

启动任务:

更新任务:

关于JobHandler:
该属性舔写任务注解 “@XxlJob” 中定义的值。




2.5 快速入门 - 负载均衡测试

前言:

    如果使用 Spring Task可能会有重复执行,单体故障,单体瓶颈的问题。

    接下来我们看看 XXL-JOB 怎么避免重复执行的。

2.5.1 在IDEA中,开启多个SpringBoot项目。

视频:https://www.bilibili.com/video/BV1824y1G7vT?p=7&vd_source=61b6fb4e547748656e36b17ee95125fb


2.5.2 现象

出现的现象:该任务只会在某一个实例里面执行。


2.5.3 利用负载均衡改造

高级配置:
    - 路由策略:当执行器集群部署时,提供丰富的路由策略,包括;
        FIRST(第一个):固定选择第一个机器;
        LAST(最后一个):固定选择最后一个机器;
        ROUND(轮询):;
        RANDOM(随机):随机选择在线的机器;
        CONSISTENT_HASH(一致性HASH):每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。
        LEAST_FREQUENTLY_USED(最不经常使用):使用频率最低的机器优先被选举;
        LEAST_RECENTLY_USED(最近最久未使用):最久未使用的机器优先被选举;
        FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度;
        BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度;
        SHARDING_BROADCAST(分片广播):广播触发对应集群中所有机器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务;

    - 子任务:每个任务都拥有一个唯一的任务ID(任务ID可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务ID所对应的任务的一次主动调度。

    - 调度过期策略:
        - 忽略:调度过期后,忽略过期的任务,从当前时间开始重新计算下次触发时间;
        - 立即执行一次:调度过期后,立即执行一次,并从当前时间开始重新计算下次触发时间;

    - 阻塞处理策略:调度过于密集执行器来不及处理时的处理策略;
        单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行;
        丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
        覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;

    - 任务超时时间:支持自定义任务超时时间,任务运行超时将会主动中断任务;

    - 失败重试次数;支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;




3. 分片功能介绍