使用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

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

相关推荐
王码码20356 小时前
Go语言的测试:从单元测试到集成测试
后端·golang·go·接口
王码码20356 小时前
Go语言中的测试:从单元测试到集成测试
后端·golang·go·接口
嵌入式×边缘AI:打怪升级日志7 小时前
使用JsonRPC实现前后台
前端·后端
小码哥_常8 小时前
从0到1:Spring Boot 中WebSocket实战揭秘,开启实时通信新时代
后端
lolo大魔王8 小时前
Go语言的异常处理
开发语言·后端·golang
IT_陈寒10 小时前
Python多进程共享变量那个坑,我差点没爬出来
前端·人工智能·后端
码事漫谈10 小时前
2026软考高级·系统架构设计师备考指南
后端
AI茶水间管理员11 小时前
如何让LLM稳定输出 JSON 格式结果?
前端·人工智能·后端
其实是白羊12 小时前
我用 Vibe Coding 搓了一个 IDEA 插件,复制URI 再也不用手动拼了
后端·intellij idea
用户83562907805112 小时前
Python 操作 Word 文档节与页面设置
后端·python