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

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

相关推荐
sin22012 分钟前
springboot测试类里注入不成功且运行报错
spring boot·后端·sqlserver
kirito学长-Java1 小时前
springboot/ssm网上宠物店系统Java代码编写web宠物用品商城项目
java·spring boot·后端
海绵波波1071 小时前
flask后端开发(9):ORM模型外键+迁移ORM模型
后端·python·flask
余生H1 小时前
前端Python应用指南(二)深入Flask:理解Flask的应用结构与模块化设计
前端·后端·python·flask·全栈
绝无仅有2 小时前
PHP语言laravel框架中基于Redis的异步队列使用实践与原理
后端·面试·架构
AI人H哥会Java2 小时前
【Spring】基于XML的Spring容器配置——<bean>标签与属性解析
java·开发语言·spring boot·后端·架构
计算机学长felix2 小时前
基于SpringBoot的“大学生社团活动平台”的设计与实现(源码+数据库+文档+PPT)
数据库·spring boot·后端
sin22012 小时前
springboot数据校验报错
spring boot·后端·python
去哪儿技术沙龙2 小时前
去哪儿机票智能预警系统-雷达系统落地实践
后端