• 分布式定时调度:xxl-job 最佳实践详解


    一、定时任务概述
    1.1. 什么是定时任务

    定时任务是按照指定时间周期运行任务。使用场景为在某个固定时间点执行,或者周期性的去执行某个任务,比如:每天晚上24点做数据汇总,定时发送短信等。

    1.2. 常见定时任务方案
    • While + Sleep : 通过循环加休眠的方式定时执行
    • Timer和TimerTask实现 :JDK自带的定时任务,可以实现简单的间隔执行任务(在指定时间点执行某一任务,也能定时的周期性执行),无法实现按日历去调度执行任务。
    • ScheduledExecutorService : Java并发包下,JDK1.5出现,是比较理想的定时任务实现方案。Eureka就使用的是它
    • QuartZ : 使用Quartz,它是一个异步任务调度框架,功能丰富,可以实现按日历调度,支持持久化。
    • 使用Spring Task,Spring 3.0后提供Spring Task实现任务调度,支持按日历调度,相比Quartz功能稍简单,但是在开发基本够用,支持注解编程方式。
    • SpringBoot中的Schedule : 通过@EnableScheduling+@Scheduled最实现定时任务,底层使用的是Spring Task
    1.3. 分布式定时任务面临的问题

    遇到什么问题
    上述的定时任务都是集中式(单体项目使用)的定时任务,在分布式中将会面临一些问题或不足

    • 业务量大,单机性能瓶颈需要扩展
    • 多台机器部署如何保证定时任务不重复执行
    • 定时任务时间需要可调整,可以暂停
    • 机器发生故障down机,定时任务依然可用,如何实现故障转移
    • 定时任务,执行日志是否可监控
    1.4. 分布式定时任务xxl-job

    XXL-JOB是一个分布式任务调度平台,于2015问世,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。其具备且不止如下能力

    • 简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
    • 动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效;
    • 调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA;执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
    • 弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
    • 路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致- 性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等;
    • 故障转移:任务路由策略选择"故障转移"情况下,如果执行器集群中某一台机器故障,将会自动Failover切换到一台正常的执行器发送调度请求。
    • 任务失败告警:默认提供邮件方式失败告警,同时预留扩展接口,可方面的扩展短信、钉钉等告警方式;
      具体见:https://github.com/xuxueli/xxl-job/tree/v2.0.0
    二、xxl-job架构设计
    2.1. 设计思想

    将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求。

    将任务抽象成分散的JobHandler,交由“执行器”统一管理,“执行器”负责接收调度请求并执行对应的JobHandler中业务逻辑。因此,“调度”和“任务”两部分可以相互解耦,提高系统整体稳定性和扩展性;

    2.2. 架构设计图

    xxl-job分为 调度中心和执行器两大模块

    调度模块(调度中心)
    负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。调度系统与任务解耦,提高了系统可用性和稳定性,同时调度系统性能不再受限于任务模块;

    支持可视化、简单且动态的管理调度信息,包括任务新建,更新,删除,GLUE开发和任务报警等,所有上述操作都会实时生效,同时支持监控调度结果以及执行日志,支持执行器Failover(故障转移)。

    执行模块(执行器
    负责接收调度请求并执行任务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效;

    接收“调度中心”的执行请求、终止请求和日志请求等。
    在这里插入图片描述
    调度中心高可用
    基于数据库的集群方案,数据库选用Mysql;集群分布式并发环境中进行定时任务调度时,会在各个节点会上报任务,存到数据库中,执行时会从数据库中取出触发器来执行,如果触发器的名称和执行时间相同,则只有一个节点去执行此任务。

    并行调度
    调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。XXL-JOB调度模块默认采用并行机制,在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。

    XXL-JOB的不同任务之间并行调度、并行执行。XXL-JOB的单个任务,针对多个执行器是并行运行的,针对单个执行器是串行执行的。同时支持任务终止。

    执行器(任务)高可用
    执行器如若集群部署,调度中心将会感知到在线的所有执行器,如“127.0.0.1:9997, 127.0.0.1:9998, 127.0.0.1:9999”。多个执行器可以选择“路由策略”来采用轮询,随机等方式进行多机器调度。

    当任务”路由策略”选择”故障转移(FAILOVER)”时,当调度中心每次发起调度请求时,会按照顺序对执行器发出心跳检测请求,第一个检测为存活状态的执行器将会被选定并发送调度请求。调度成功后,可在日志监控界面查看“调度备注”

    三、xxl-job安装
    3.1.下载源码

    请下载项目源码并解压,使用IDEA工具导入项目

    源码仓库地址

    • https://github.com/xuxueli/xxl-job
      在这里插入图片描述

    • https://gitee.com/xuxueli0323/xxl-job
      在这里插入图片描述

    • 项目代码结构如下:
      在这里插入图片描述

    • doc :文档,即SQL脚本所在目录

    • db : “调度数据库”建表脚本

    • xxl-job-admin : 调度中心项目源码

    • xxl-job-core : 核心模块,公共Jar依赖

    • xxl-job-executor-samples : 执行器,Sample示例项目(大家可以在该项目上进行开发,也可以将现有项目改造生成执行器项目)

    3.2. 导入数据库

    打开项目代码,获取 “调度数据库初始化SQL脚本” 并执行即可。“调度数据库初始化SQL脚本” 位置为: xxl-job/doc/db/tables_xxl_job.sql ,数据库名:xxl_job
    在这里插入图片描述
    数据库如下:
    在这里插入图片描述

    • xxl_job_lock:任务调度锁表;
    • xxl_job_group:执行器信息表,维护任务执行器信息;
    • xxl_job_info:调度扩展信息表:用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
    • xxl_job_log:调度日志表:用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
    • xxl_job_log_report:调度日志报表:用户存储XXL-JOB任务调度日志的报表,调度中心报表功能页面会用到;
    • xxl_job_logglue:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
    • xxl_job_registry:执行器注册表,维护在线的执行器和调度中心机器地址信息;
    • xxl_job_user:系统用户表;
    3.3. 启动调度中心

    打开 xxl-job-admin 的配置文件,
    xxl-job/xxl-job-admin/src/main/resources/application.properties
    对调度中心进行配置,重要配置如下

    • server.port : 根据情况修改端口
    • spring.datasource.url :指向刚才准备的数据库
    • spring.datasource.password : 记得修改成自己的数据库密码
    • spring.mail.username :配置自己的邮件账号
    • spring.mail.password :邮件的授权码,我下面是以qq邮箱为例
      下面根据自己的情况进行修改,不要直接复制
    ### web
    server.port=8080
    server.servlet.context-path=/pis-job-admin
    #server.servlet.context-path=/xxl-job-admin
    
    ### actuator
    management.server.base-path=/actuator
    management.health.mail.enabled=false
    
    ### resources
    spring.mvc.servlet.load-on-startup=0
    spring.mvc.static-path-pattern=/static/**
    spring.web.resources.static-locations=classpath:/static/
    
    ### freemarker
    spring.freemarker.templateLoaderPath=classpath:/templates/
    spring.freemarker.suffix=.ftl
    spring.freemarker.charset=UTF-8
    spring.freemarker.request-context-attribute=request
    spring.freemarker.settings.number_format=0.##########
    
    ### mybatis
    mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml
    #mybatis.type-aliases-package=com.xxl.job.admin.core.model
    
    ### 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=root
    spring.datasource.password=123456
    # mysql 8.x
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    # mysql 5.7x
    #spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    
    ### datasource-pool
    spring.datasource.type=com.zaxxer.hikari.HikariDataSource
    spring.datasource.hikari.minimum-idle=10
    spring.datasource.hikari.maximum-pool-size=30
    spring.datasource.hikari.auto-commit=true
    spring.datasource.hikari.idle-timeout=30000
    spring.datasource.hikari.pool-name=HikariCP
    spring.datasource.hikari.max-lifetime=900000
    spring.datasource.hikari.connection-timeout=10000
    spring.datasource.hikari.connection-test-query=SELECT 1
    spring.datasource.hikari.validation-timeout=1000
    
    ### xxl-job, email
    spring.mail.host=smtp.qq.com
    spring.mail.port=25
    spring.mail.username=xxx@qq.com
    spring.mail.from=xxx@qq.com
    spring.mail.password=邮箱授权码,不是登录密码
    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
    
    ### xxl-job, access 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=30
    
    ### 配置路径输出
    log.path=J:/zx/data/applogs/xxl-job/xxl-job-admin.log
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    然后启动调度中心 ,执行 XxlJobAdminApplication#main 方法 ,
    在这里插入图片描述
    启动之后,浏览器访问 http://localhost:8080/xxl-job-admin;注意URL中有个上下文路径。默认登录账号 admin/123456, 登录后运行界面如下图所示。
    在这里插入图片描述

    3.4. 配置部署“执行器项目

    “执行器”项目:xxl-job-executor-sample-springboot (提供多种版本执行器供选择,现以 springboot 版本为例,可直接使用,也可以参考其并将现有项目改造成执行器)

    作用:负责接收“调度中心”的调度并执行;可直接部署执行器,也可以将执行器集成到现有业务项目中。

    修改配置:
    /xxl-job/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties

    xxl.job.admin.addresses : 调度中心的地址,如果调度中心修改过端口,这里也要对应修改

    # web port
    server.port=8082
    # no web
    #spring.main.web-environment=false
    
    # log config
    logging.config=classpath:logback.xml
    
    
    ### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
    xxl.job.admin.addresses=http://127.0.0.1:8080/pis-job-admin
    
    ### xxl-job, access token
    xxl.job.accessToken=default_token
    
    ### xxl-job executor appname
    xxl.job.executor.appname=xxl-job-executor-sample
    ### xxl-job executor registry-address: default use address to registry , otherwise use ip:port if address is null
    xxl.job.executor.address=
    ### xxl-job executor server-info
    xxl.job.executor.ip=
    xxl.job.executor.port=9999
    ### xxl-job executor log-path
    xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
    ### xxl-job executor log-retention-days
    xxl.job.executor.logretentiondays=30
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    上面配置是为了在Spring容器中创建一个 XxlJobSpringExecutor 执行器Bean,见:com.xxl.job.executor.core.config.XxlJobConfig#xxlJobExecutor

     @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;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    3.5. 案例参考

    在com.xxl.job.executor.service.jobhandler.SampleXxlJob中提供了简单的定时任务实例

    为方便用户参考与快速实用,示例执行器内原生提供多个Bean模式任务Handler,可以直接配置实用,如下:

    • demoJobHandler:简单示例任务,任务内部模拟耗时任务逻辑,用户可在线体验Rolling Log等功能;
    • shardingJobHandler:分片示例任务,任务内部模拟处理分片参数,可参考熟悉分片任务;
    • httpJobHandler:通用HTTP任务Handler;业务方只需要提供HTTP链接等信息即可,不限制语言、平台。示例任务入参如下:
        /**
         * 1、简单任务示例(Bean模式)
         */
        @XxlJob("demoJobHandler")
        public void demoJobHandler() throws Exception {
            XxlJobHelper.log(" demoJobHandler start");
    
            XxlJobHelper.log("XXL-JOB, Hello World.");
            logger.info("demoJobHandler execute......");
            // 注意xxl-job统一只接受一个String类型的参数,如果有多个参数,请自定义规则,获取到参数后自行切割
            String param = XxlJobHelper.getJobParam();
            for (int i = 0; i < 5; i++) {
                XxlJobHelper.log("beat at:" + i);
                TimeUnit.SECONDS.sleep(2);
            }
            XxlJobHelper.handleSuccess(" demoJobHandler complete");
        }
    
    
        /**
         * 2、分片广播任务
         */
        @XxlJob("shardingJobHandler")
        public void shardingJobHandler() throws Exception {
            XxlJobHelper.log(" shardingJobHandler start");
    
            logger.info("shardingJobHandler execute......");
    
            // 分片参数
            int shardIndex = XxlJobHelper.getShardIndex();
            int shardTotal = XxlJobHelper.getShardTotal();
    
            XxlJobHelper.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal);
    
            // 业务逻辑
            for (int i = 0; i < shardTotal; i++) {
                if (i == shardIndex) {
                    XxlJobHelper.log("第 {} 片, 命中分片开始处理", i);
                } else {
                    XxlJobHelper.log("第 {} 片, 忽略", i);
                }
            }
            XxlJobHelper.handleSuccess(" shardingJobHandler complete");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    【重要】 如果我们要写自己的定时任务,参照上面方法,在方法上注解一个@XxlJob(“任务名字”) ,方法可以接受一个字符串参数,方法需要返回ReturnT格式。

  • 相关阅读:
    第5章 uin-app本地主机数据跨域(Cors)数据交互实现
    bit byte 和各进制关系(位、字节、字符、进制)、常见编码格式
    Java夏招必知必会八股文198题,看完offer拿到手软
    IP风险查询:抵御DDoS攻击和CC攻击的关键一步
    mysql使用--带搜索条件的查询
    Docker 存储驱动解析:选择最适合你的存储方案
    思科路由器地址转换配置
    安卓rec教程
    Android开发基础——Kotlin:标准函数和静态方法
    驱动加载module license ‘unspecified‘ taints kernel问题
  • 原文地址:https://blog.csdn.net/weixin_40816738/article/details/128209155