美文网首页spring开源框架-XXL-JOB系列
SpringBoot 2.2.5 整合Xxl-Job,实现各种任

SpringBoot 2.2.5 整合Xxl-Job,实现各种任

作者: 天不生我小金 | 来源:发表于2020-10-05 23:12 被阅读0次

    前言:该博客主要是记录自己学习的过程,方便以后查看,当然也希望能够帮到大家。

    说明

    1. Xxl-Job是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。
    2. Xxl-Job有两个核心,一个调度中心,一个执行器,需要同时部署才行。

    特性

    简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手
    动态:支持动态修改任务状态、启动/停止任务,以及终止运行中任务,即时生效
    调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA
    执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA
    注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址
    弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务
    路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等
    故障转移:任务路由策略选择"故障转移"情况下,如果执行器集群中某一台机器故障,将会自动Failover切换到一台正常的执行器发送调度请求。
    阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度
    任务超时控制:支持自定义任务超时时间,任务运行超时将会主动中断任务
    任务失败重试:支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;其中分片任务支持分片粒度的失败重试
    任务失败告警;默认提供邮件方式失败告警,同时预留扩展接口,可方便的扩展短信、钉钉等告警方式
    分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务
    动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度
    事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发
    任务进度监控:支持实时监控任务进度
    Rolling实时日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志
    GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯
    脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python、NodeJS、PHP、PowerShell等类型脚本
    命令行任务:原生提供通用命令行任务Handler(Bean任务,"CommandJobHandler");业务方只需要提供命令行即可
    任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔
    一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行
    自定义任务参数:支持在线配置调度任务入参,即时生效
    调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞
    数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性
    邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件
    推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用
    运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等
    全异步:任务调度流程全异步化设计实现,如异步调度、异步运行、异步回调等,有效对密集调度进行流量削峰,理论上支持任意时长任务的运行
    跨平台:原生提供通用HTTP任务Handler(Bean任务,"HttpJobHandler"),业务方只需要提供HTTP链接即可,不限制语言、平台
    国际化:调度中心支持国际化设置,提供中文、英文两种可选语言,默认为中文
    容器化:提供官方docker镜像,并实时更新推送dockerhub,进一步实现产品开箱即用
    线程池隔离:调度线程池进行隔离拆分,慢任务自动降级进入"Slow"线程池,避免耗尽调度线程,提高系统稳定性
    ...
    

    完整代码地址在结尾!!

    第一步,先部署调度中心,克隆地址:https://github.com/xuxueli/xxl-job,本文用的是2.2.0-SNAPSHOT,需要下载对应版本,步骤如下

    1. 克隆完代码后,进入db目录,将sql复制到MySQL执行一遍创建数据库。
    2. 进入xxl-job-admin目录,修改application.properties文件,将数据源的配置信息修改为自己刚刚创建的数据库。
    3. 启动xxl-job-admin项目,访问http://localhost:8080/xxl-job-admin,用户名:admin,密码:123456,到此调度中心部署完毕。

    第二步,部署执行器,克隆的代码里面有官方的示例项目,可以直接运行,也可以集成到自己的项目中,下面集成到自己的项目,在pom.xml加入依赖,如下

    <!-- 任务调度xxl-job -->
    <dependency>
        <groupId>com.xuxueli</groupId>
        <artifactId>xxl-job-core</artifactId>
        <version>2.2.0</version>
    </dependency>
    

    第三步,编写application.yml配置文件,如下

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

    第四步,创建XxlJobConfig配置文件,如下

    import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Slf4j
    @Configuration
    public class XxlJobConfig {
    
        @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() {
            log.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;
        }
    
        /**
         * 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP;
         *
         *      1、引入依赖:
         *          <dependency>
         *             <groupId>org.springframework.cloud</groupId>
         *             <artifactId>spring-cloud-commons</artifactId>
         *             <version>${version}</version>
         *         </dependency>
         *
         *      2、配置文件,或者容器启动变量
         *          spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
         *
         *      3、获取IP
         *          String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
         */
    
    }
    

    第五步,创建XxlJobService服务类,如下

    import com.xxl.job.core.biz.model.ReturnT;
    import com.xxl.job.core.handler.IJobHandler;
    import com.xxl.job.core.handler.annotation.XxlJob;
    import com.xxl.job.core.log.XxlJobLogger;
    import com.xxl.job.core.util.ShardingUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    
    import java.io.BufferedInputStream;
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.concurrent.TimeUnit;
    
    /**
     * XxlJob开发示例(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" 打印执行日志;
     *
     * @author xuxueli 2019-12-11 21:52:51
     */
    @Slf4j
    @Component
    public class XxlJobService {
    
        /**
         * 1、简单任务示例(Bean模式)
         */
        @XxlJob("demoJobHandler")
        public ReturnT<String> demoJobHandler(String param) throws Exception {
            XxlJobLogger.log("XXL-JOB, Hello World.");
    
            for (int i = 0; i < 5; i++) {
                XxlJobLogger.log("beat at:" + i);
                TimeUnit.SECONDS.sleep(2);
            }
            return ReturnT.SUCCESS;
        }
    
        /**
         * 2、分片广播任务
         */
        @XxlJob("shardingJobHandler")
        public ReturnT<String> shardingJobHandler(String param) throws Exception {
    
            // 分片参数
            ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
            XxlJobLogger.log("分片参数:当前分片序号 = {}, 总分片数 = {}", shardingVO.getIndex(), shardingVO.getTotal());
    
            // 业务逻辑
            for (int i = 0; i < shardingVO.getTotal(); i++) {
                if (i == shardingVO.getIndex()) {
                    XxlJobLogger.log("第 {} 片, 命中分片开始处理", i);
                } else {
                    XxlJobLogger.log("第 {} 片, 忽略", i);
                }
            }
    
            return ReturnT.SUCCESS;
        }
    
        /**
         * 3、命令行任务
         */
        @XxlJob("commandJobHandler")
        public ReturnT<String> commandJobHandler(String param) throws Exception {
            String command = param;
            int exitValue = -1;
    
            BufferedReader bufferedReader = null;
            try {
                // command process
                Process process = Runtime.getRuntime().exec(command);
                BufferedInputStream bufferedInputStream = new BufferedInputStream(process.getInputStream());
                bufferedReader = new BufferedReader(new InputStreamReader(bufferedInputStream));
    
                // command log
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    XxlJobLogger.log(line);
                }
    
                // command exit
                process.waitFor();
                exitValue = process.exitValue();
            } catch (Exception e) {
                XxlJobLogger.log(e);
            } finally {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
            }
    
            if (exitValue == 0) {
                return IJobHandler.SUCCESS;
            } else {
                return new ReturnT<String>(IJobHandler.FAIL.getCode(), "command exit value("+exitValue+") is failed");
            }
        }
    
        /**
         * 4、跨平台Http任务
         */
        @XxlJob("httpJobHandler")
        public ReturnT<String> httpJobHandler(String param) throws Exception {
    
            // request
            HttpURLConnection connection = null;
            BufferedReader bufferedReader = null;
            try {
                // connection
                URL realUrl = new URL(param);
                connection = (HttpURLConnection) realUrl.openConnection();
    
                // connection setting
                connection.setRequestMethod("GET");
                connection.setDoOutput(true);
                connection.setDoInput(true);
                connection.setUseCaches(false);
                connection.setReadTimeout(5 * 1000);
                connection.setConnectTimeout(3 * 1000);
                connection.setRequestProperty("connection", "Keep-Alive");
                connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
                connection.setRequestProperty("Accept-Charset", "application/json;charset=UTF-8");
    
                // do connection
                connection.connect();
    
                //Map<String, List<String>> map = connection.getHeaderFields();
    
                // valid StatusCode
                int statusCode = connection.getResponseCode();
                if (statusCode != 200) {
                    throw new RuntimeException("Http Request StatusCode(" + statusCode + ") Invalid.");
                }
    
                // result
                bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
                StringBuilder result = new StringBuilder();
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    result.append(line);
                }
                String responseMsg = result.toString();
    
                XxlJobLogger.log(responseMsg);
                return ReturnT.SUCCESS;
            } catch (Exception e) {
                XxlJobLogger.log(e);
                return ReturnT.FAIL;
            } finally {
                try {
                    if (bufferedReader != null) {
                        bufferedReader.close();
                    }
                    if (connection != null) {
                        connection.disconnect();
                    }
                } catch (Exception e2) {
                    XxlJobLogger.log(e2);
                }
            }
    
        }
    
        /**
         * 5、生命周期任务示例:任务初始化与销毁时,支持自定义相关逻辑;
         */
        @XxlJob(value = "demoJobHandler2", init = "init", destroy = "destroy")
        public ReturnT<String> demoJobHandler2(String param) throws Exception {
            XxlJobLogger.log("XXL-JOB, Hello World.");
            return ReturnT.SUCCESS;
        }
    
        public void init(){
            log.info("init");
        }
    
        public void destroy(){
            log.info("destory");
        }
    
    }
    
    注意
    1. 重点关注@XxlJob,后面要用到

    第六步,使用流程,如下

    1. 启动执行器项目,在调度中心->执行器管理,创建新执行器,AppName要与执行器配置文件里面配置的名称一致,选择自动注册即可
    2. 在调度中心->任务管理,创建新任务,选择我们刚刚创建的新执行器,重点关注JobHandler这个配置,名称要跟相应的任务方法上@XxlJob注解里面的名称一致,才能找到相应的任务去执行
    3. 创建完成后,在操作那里进行执行一次,查看对应的日志输出,可知配置成功
    4. 还有多种任务的玩法,这里就不一一举例了,需要的请自行百度查询
    完整代码地址:https://github.com/luoyusoft/springboot-demo
    注:此工程包含多个module,本文所用代码均在xxljob-demo模块下

    后记:本次分享到此结束,本人水平有限,难免有错误或遗漏之处,望大家指正和谅解,欢迎评论留言。

    相关文章

      网友评论

        本文标题:SpringBoot 2.2.5 整合Xxl-Job,实现各种任

        本文链接:https://www.haomeiwen.com/subject/zhezuktx.html