使用XXL-JOB分布式任务调度平台

XXL-JOB分布式任务调度平台

定时任务的实现方式

  1. Timer

    java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少

    java 复制代码
    public void timer(){
        Timer timer = new Timer();
        TimerTask task =new TimerTask(){
            public void run(){
                system.out.print1n("开始运行");
            }
            timer.schedule(task, 2000);//2秒后开始启动任务
            timer.schedule(task,2000, 5000);//2秒后开始任务,然后每隔五秒执行一次
        }
  2. scheduledExecutorService

    jdk自带的一个类;是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。

    java 复制代码
    public static void main(String[] args) {
        ScheduledExecutorService task = Executors.newScheduledThreadPool(5);
        task.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
    
            }
        },5,30,TimeUnit.SECONDS); //5秒后开始执行线程,然后每隔30分钟执行一次
    }
  3. Spring Task

    Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。

    • 启动类添加注解 @EnableScheduling 开启任务调度

      java 复制代码
      @SpringBootApplication
      @EnableScheduling //开启任务调度
      public class SkyApplication {
          public static void main(String[] args) {
              SpringApplication.run(SkyApplication.class, args);
              log.info("server started");
          }
      }
    • 定义定时任务类

      java 复制代码
      /**
       * 自定义定时任务
       */
      @Component
      @Slf4j
      public class Task {
      
          @Scheduled(cron = "0 * * * * ?")
          public void processTimeoutOrder(){
            // 定时任务代码
          }
      }
  4. Quartz(用的比较少)

    一个功能比较强大的调度器,可以让你的程序在指定时间执行,也可以按照某个频率执行,配置稍微复杂,不支持分片,没有界面。

存在的问题

  • 执行一次

    如果想让它马上执行一次,这个时候可能就需要额外再写一个Rest接口或者再另外写一个单独的Job

  • 更改执行时间

    需要修改代码,提交测试,然后打包上线

  • 暂停任务

    比如一些定时报警的需求,当报警突然变得很多,这个时候需要暂停一下让其停止发送报警,我们可以用配置的开关去做,再逻辑中判断定时任务开关是否打开来做。这样做虽然也比较简单,但是我们这样需要新添加一些与任务无关的逻辑

  • 监控

    没有管理界面,不方便查看任务执行情况

  • 分片执行

    单台服务处理大批量数据时间太长、效率低下,需要其他机器协同执行。

解决方案

有如下需求:

  • 支持任务分片
  • 文档完善
  • 提供管理台
  • 接入简单
  • 弹性扩容
  • .....

XXL-JOB

调度中心通过获取DB锁来保证集群中执行任务的唯一性,如果短任务很多,随着调度中心集群数量增加,那么数据库的锁竞争会比较厉害,性能不好。

Elastic-Job

需要引入zookeeper

分片原理

项目源码

源码仓库地址:可以先看看github源码,上面有示例代码

源码仓库地址 Release Download
github.com/xuxueli/xxl... Download
gitee.com/xuxueli0323... Download

解压源码,按照maven格式将源码导入IDE, 使用maven进行编译即可,源码结构如下:

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

数据库导入sql文件。

启动xxl-job-admin调度中心,登录后台,默认账户是admin,密码:123456

自己的项目如何使用

  1. 引入依赖

    xml 复制代码
    <!-- xxl-job-core -->
    <dependency>
        <groupId>com.xuxueli</groupId>
        <artifactId>xxl-job-core</artifactId>
        <version>2.4.0</version>
    </dependency>
  2. 配置yml

    yaml 复制代码
    server:
      port: 8881
    xxl:
      job:
        #执行器通讯TOKEN,同xxl-job-admin配置相同
        accessToken: default_token
        admin:
          #调度中心部署跟地址:如调度中心集群部署存在多个地址则用逗号分隔。
          #执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调"
          addresses: http://127.0.0.1:8080/xxl-job-admin
        executor:
          #AppName执行器心跳注册分组依据,为空则关闭自动注册
          appname: xxl-job-demo
          #默认使用address注册,为空则使用ip : port
          address:
          ip:
          port: 9989
          #执行器Log文件定期清理功能,指定日志保存天数,日志文件过期自动删除。限制至少保持3天,否则功能不生效;
          logpath: ./data/applogs/xxl-job/jobhandler
          logretentiondays: 30
    logging:
      config: src/main/resources/logback.xml
  3. 配置文件

    java 复制代码
    /**
     * xxl-job config
     *
     */
    @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;
        }
    
        /**
         * 针对多网卡、容器内部署等情况,可借助 "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();
         */
    
    }
  4. 将自己的服务注册进来

如果项目是集群部署的话,集群每一个节点都会注册进去。

  1. 新增任务

    java 复制代码
    /**
     * XxlJob开发示例(Bean模式)
     *
     * 开发步骤:
     *      1、任务开发:在Spring Bean实例中,开发Job方法;
     *      2、注解配置:为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")",
     *                  注解value值对应的是调度中心新建任务的JobHandler属性的值。
     *      3、执行日志:需要通过 "XxlJobHelper.log" 打印执行日志;
     *      4、任务结果:默认任务结果为 "成功" 状态,不需要主动设置;
     *                 如有诉求,比如设置任务结果为失败,可以通过 "XxlJobHelper.handleFail/handleSuccess" 自主设置任务结果;
     */
    @Component
    public class SampleXxlJob {
        private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);
    
        /**
         * 2、分片广播任务
         */
        @XxlJob("sendMessage")
        public void shardingJobHandler() throws Exception {
    
            // 分片参数
            int shardIndex = XxlJobHelper.getShardIndex();//当前分片索引
            int shardTotal = XxlJobHelper.getShardTotal();//分片总数
    
           logger.info("分片参数:当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal);
        }
    }

    阻塞处理策略:调度过于密集执行器来不及处理时的处理策略;

    1. 单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行;
    2. 丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
    3. 覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;
  2. 执行

具体实现-代码分片

通过分片总数和当前分片索引对数据库主键id取余,这样我们就可以在每个分片节点获取不一样的数据。

项目中mapper文件sql语句:

mysql 复制代码
select user_id from `user_info` where MOD(id,#{shardTotal}) = #{shardIndex}

假设分片总数为3,当前节点获取到的分片索引为0,那么查询SQL如下:

sql 复制代码
select user_id from `user_info` where MOD(id,3) = 0

另外两个节点收到的分片索引分别是1、2

sql 复制代码
select user_id from `user_info` where MOD(id,3) = 1
select user_id from `user_info` where MOD(id,3) = 2

从而就实现了将大量数据分散到不同服务器上去执行的功能。

相关推荐
MadPrinter18 小时前
SpringBoot学习日记 Day11:博客系统核心功能深度开发
java·spring boot·后端·学习·spring·mybatis
dasseinzumtode18 小时前
nestJS 使用ExcelJS 实现数据的excel导出功能
前端·后端·node.js
淦出一番成就18 小时前
Java反序列化接收多种格式日期-JsonDeserialize
java·后端
Java中文社群18 小时前
Hutool被卖半年多了,现状是逆袭还是沉寂?
java·后端
程序员蜗牛18 小时前
9个Spring Boot参数验证高阶技巧,第8,9个代码量直接减半!
后端
yeyong18 小时前
咨询kimi关于设计日志告警功能,还是有启发的
后端
库森学长18 小时前
2025年,你不能错过Spring AI,那个汲取了LangChain灵感的家伙!
后端·openai·ai编程
Java水解19 小时前
Spring Boot 启动流程详解
spring boot·后端
学历真的很重要19 小时前
Claude Code Windows 原生版安装指南
人工智能·windows·后端·语言模型·面试·go
转转技术团队19 小时前
让AI成为你的编程助手:如何高效使用Cursor
后端·cursor