XXL-JOB任务调度


前言

前段时间工作中用到了XXL-JOB,所以想着写一篇博客记录一下,比较懒,拖了很久。。。

一、简介

官网地址:https://www.xuxueli.com/xxl-job/

设计思想

将"调度"和"任务"进行解耦。

系统组成

调度模块(调度中心):负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。调度系统与任务解耦,提高了系统可用性和稳定性,同时调度系统性能不再受限于任务模块;支持可视化、简单且动态的管理调度信息,包括任务新建,更新,删除,GLUE开发和任务报警等,所有上述操作都会实时生效,同时支持监控调度结果以及执行日志,支持执行器Failover。

执行模块(执行器):负责接收调度请求并执行任务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效;接收“调度中心”的执行请求、终止请求和日志请求等。

架构图

在这里插入图片描述

二、如何使用

下载地址

源码地址:https://gitee.com/xuxueli0323/xxl-job/releases
目前最新版本是2.3.0
源码下载下来之后,执行其中的sql文件(xxl-job-master\doc\db\tables_xxl_job.sql),所使用的的数据库是mysql,版本5.7或以上。

简单说说

启动xxl-job-admin模块,在浏览器访问localhost:8080//xxl-job-admin,输入用户密码admin/123456,即可进入页面。
在这里插入图片描述
这里一些可视化的功能就不去介绍了。
对照界面中的任务管理和执行器管理以及数据库的表数据,可以看出对应关系。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以,可以猜到源码中包含可以查询执行器和调度任务的接口,是的,就在这里。
在这里插入图片描述

一个栗子

在XXL-JOB上新建一个执行器xxl-job-executor-test,这里的注册方式选择自动注册就好,业务服务在启动时会将自己机器的ip和端口号注册到这个执行器上。
在这里插入图片描述
新建一个属于该执行器的执行任务,这里选择“测试执行器”,也就刚才新建的执行器,调度类型选择固定速度,5秒执行一次,运行模式选择bean,执行时会根据注册的执行地址以及JobHandler找到对应的执行方法。
调度任务这里有Cron和固定速度两种,假如使用cron,它会以固定的时间点执行任务,而不是在定时器启动后开始计算时间执行;使用固定速度,那么就会从定时器启动后开始计算下一次执行时间。
在这里插入图片描述
新建一个springboot服务,当做是业务服务。
依赖:

        <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
            <version>2.3.0</version>
        </dependency>

配置文件

server:
  port: 8090
  tomcat:
    uri-encoding: UTF-8
    max-connections: 1024
    max-threads: 100

xxl:
  job:
    isConfigure: true  #选择性注册,true说明将自己机器的ip和端口号注册进执行器
    scheduleConf: 60  #定时任务的执行间隔
    admin:
      addresses: http://127.0.0.1:8080/xxl-job-admin  #xxl-job的地址
      userName: admin
      password: 123456
    executor:
      appname: xxl-job-executor-sample   #执行器的名字
      logpath: /data/applogs/xxl-job/jobhandler/
      logretentiondays: -1  #过期日志自动清理;限制大于等于7时生效,否则, 如-1,关闭自动清理功能;
      port: 9999  #任务执行地址的端口

向执行器注册执行地址的配置类

@Configuration
@Slf4j
@ConditionalOnProperty(value = "xxl.job.isConfigure", havingValue = "true", matchIfMissing = false)
public class XxlJobConfig {

    @Autowired
    private XxlJobProperties xxlJobProperties;

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        log.info(">>>>>>>>>>> xxl-job config init.   xxlJobProperties:{}", JSONObject.toJSONString(xxlJobProperties));
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(xxlJobProperties.getAdmin().getAddresses());
        xxlJobSpringExecutor.setAppname(xxlJobProperties.getExecutor().getAppname());
        xxlJobSpringExecutor.setPort(xxlJobProperties.getExecutor().getPort());
        xxlJobSpringExecutor.setLogPath(xxlJobProperties.getExecutor().getLogpath());
        xxlJobSpringExecutor.setLogRetentionDays(xxlJobProperties.getExecutor().getLogretentiondays());
        return xxlJobSpringExecutor;
    }
}

yaml文件属性注入的配置类

@Data
@Component
@ConfigurationProperties(prefix = "xxl.job")
public class XxlJobProperties {
    private Admin admin;
    private Executor executor;
    private String scheduleConf;
    private String orderPoolName;

    @Data
    public static class Admin {
        private String addresses;
        private String userName;
        private String password;
    }

    @Data
    public static class Executor {
        private String appname;
        private String logpath;
        private int logretentiondays;
        private int port;
    }
}

任务执行逻辑,xxl-job会根据注解@XxlJob(“JobHandler”)找到对应执行方法

@Component
@Slf4j
public class XxlJobHandler {

    @XxlJob("JobHandler")
    public ReturnT<String> jobHandler() {
    	String param = XxlJobHelper.getJobParam();
        String strDateFormat = "yyyy-MM-dd HH:mm:ss";
        SimpleDateFormat sdf = new SimpleDateFormat(strDateFormat);
        log.info("time : {} param: {}", sdf.format(new Date()), param);
        return ReturnT.SUCCESS;
    }
}

启动两个业务服务,要注意的是,除了两个服务的启动端口不同之外,还有注册到XXL-JOB的端口也要不同。
如果上述操作正确,那么在XXL-JOB界面上的执行器中会看到两个注册成功的地址,如下:
在这里插入图片描述
自此,这个执行器上的定时任务将会根据这两个注册地址去查找对应的JobHandler并执行任务。
然后启动定时任务,查看业务服务的表现
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
仔细看时间戳会发现,这个执行策略其实是轮训,因为在创建执行器时,将路由策略选择为轮训
在这里插入图片描述

三、动态添加定时任务

有时候我们需要在业务服务中心动态的添加定时任务,之前有说过,XXL-JOB的源码中提供了对外的api,支持对执行器和定时任务的查询和新增。
对XXL-JOB的api的封装:

@Component
@Slf4j
public class XxlJobCommon {
    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;
    @Value("${xxl.job.admin.userName}")
    private String userName;
    @Value("${xxl.job.admin.password}")
    private String password;
    @Value("${xxl.job.executor.appname}")
    private String appname;

    /**
     * 动态添加订单取消的定时器
     *
     * @param xxlJobInfo
     * @return
     */
    public int addJob(XxlJobInfo xxlJobInfo) {
        log.info("添加定时器入参xxlJobInfo:{}", JSONObject.toJSONString(xxlJobInfo));

        int jobId = 0;
        getCookie();//获取cookie
        try {
            String path = adminAddresses + "/jobinfo/add";
            String jobgroupPath = "/jobgroup/pageList";
            Map<String, Object> paramMap = new HashMap<>();
            paramMap.put("jobGroup", getJobGroupId(jobgroupPath));
            paramMap.put("jobDesc", xxlJobInfo.getJobDesc());
            paramMap.put("executorRouteStrategy", xxlJobInfo.getExecutorRouteStrategy());// 路由策略
            paramMap.put("glueType", xxlJobInfo.getGlueType());
            paramMap.put("executorHandler", xxlJobInfo.getExecutorHandler()); // 此处hander需提前在项目中定义
            paramMap.put("executorBlockStrategy", xxlJobInfo.getExecutorBlockStrategy());
            paramMap.put("executorTimeout", xxlJobInfo.getExecutorTimeout());
            paramMap.put("executorFailRetryCount", xxlJobInfo.getExecutorFailRetryCount());//执行失败重试
            paramMap.put("author", xxlJobInfo.getAuthor());
            paramMap.put("scheduleType", xxlJobInfo.getScheduleType());
            paramMap.put("scheduleConf", xxlJobInfo.getScheduleConf());
            paramMap.put("glueRemark", xxlJobInfo.getGlueRemark());
            paramMap.put("triggerStatus", xxlJobInfo.getTriggerStatus());  //调度状态:0-停止,1-运行
            paramMap.put("misfireStrategy", xxlJobInfo.getMisfireStrategy());
            paramMap.put("executorParam", xxlJobInfo.getExecutorParam());//入参

            HttpResponse response = HttpRequest.post(path).form(paramMap).execute();

            if (HttpStatus.HTTP_OK != response.getStatus()) {
                log.error("订单:{} 定时器创建失败", xxlJobInfo.getExecutorParam());
            }
            JSONObject jsonObject = JSON.parseObject(response.body());
            log.info("--->jsonObject:{}", jsonObject.toJSONString());
            jobId = jsonObject.getIntValue("content");
        } catch (Exception e) {
            log.error("订单:{} 定时器创建失败", xxlJobInfo.getExecutorParam(), e);
        }
        return jobId;
    }

    /**
     * 获取jogGroup的id
     *
     * @param jobgroupPath
     * @return
     * @throws Exception
     */
    public int getJobGroupId(String jobgroupPath) throws Exception {
        Map<String, Object> jobgroupParamMap = new HashMap<>();
        //获取jobGroup的Id
        jobgroupParamMap.put("appname", appname);
        HttpResponse jobgroupResponse = HttpRequest.post(adminAddresses + jobgroupPath).form(jobgroupParamMap).execute();
        log.info("jobgroupResponse : {}", JSON.toJSONString(jobgroupResponse.body()));
        Map<String, Object> stringObjectMap = JSON.parseObject(jobgroupResponse.body(), new TypeReference<Map<String, Object>>() {
        });
        List<XxlJobGroup> data = JSON.parseObject(JSON.toJSONString(stringObjectMap.get("data")), new TypeReference<List<XxlJobGroup>>() {
        });
        return data.get(0).getId();
    }

    /**
     * 获取XxlJobInfo
     *
     * @param jobDesc
     * @return
     */
    public XxlJobInfo getXxlJobInfo(String jobDesc) throws Exception {
        String path = adminAddresses + "/jobinfo/pageList";
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("jobDesc", jobDesc);
        String jobgroupPath = "/jobgroup/pageList";
        paramMap.put("jobGroup", getJobGroupId(jobgroupPath));
        paramMap.put("triggerStatus", 1);
        HttpResponse response = HttpRequest.post(path).form(paramMap).execute();
        log.info("response : {}", JSON.toJSONString(response.body()));
        Map<String, Object> stringObjectMap = JSON.parseObject(response.body(), new TypeReference<Map<String, Object>>() {
        });

        List<XxlJobInfo> data = JSON.parseObject(JSON.toJSONString(stringObjectMap.get("data")), new TypeReference<List<XxlJobInfo>>() {
        });
        return data.get(0);
    }

    /**
     * 停止定时器
     *
     * @param jobId
     */
    public void stop(int jobId) {
        String pathStop = adminAddresses + "/jobinfo/stop";
        Map<String, Object> paramMapStop = new HashMap<>();
        paramMapStop.put("id", jobId);
        HttpRequest.post(pathStop).form(paramMapStop).execute();
    }

    /**
     * 删除定时器
     *
     * @param jobDesc
     */
    public void remove(String jobDesc) throws Exception {
        XxlJobInfo xxlJobInfo = getXxlJobInfo(jobDesc);
        String pathRemove = adminAddresses + "/jobinfo/remove";
        Map<String, Object> paramMapRemove = new HashMap<>();
        paramMapRemove.put("id", xxlJobInfo.getId());
        HttpRequest.post(pathRemove).form(paramMapRemove).execute();
    }

    /**
     * 获取cookie
     *
     * @return
     */
    public String getCookie() {
        String path = adminAddresses + "/login";
        Map<String, Object> hashMap = new HashMap();
        hashMap.put("userName", userName);
        hashMap.put("password", password);
        HttpResponse response = HttpRequest.post(path).form(hashMap).execute();
        List<HttpCookie> cookies = response.getCookie();
        StringBuilder sb = new StringBuilder();
        for (HttpCookie cookie : cookies) {
            sb.append(cookie.toString());
        }
        String cookie = sb.toString();
        log.info("获取cookie:{}", cookie);
        return cookie;
    }
}

模拟动态添加定时任务

@RestController
@RequestMapping("/demo")
@Slf4j
public class AddJobController {

    @Autowired
    private XxlJobCommon xxlJobCommon;
    @Autowired
    private XxlJobProperties xxlJobProperties;

    @GetMapping("/addJob/{jobParam}")
    @ResponseBody
    public String addJob(@PathVariable("jobParam") String jobParam) {
        try{
            XxlJobInfo xxlJobInfo = new XxlJobInfo();
            xxlJobInfo.setJobDesc(jobParam);
            xxlJobInfo.setExecutorRouteStrategy("FAILOVER");// 路由策略
            xxlJobInfo.setGlueType("BEAN");
            xxlJobInfo.setExecutorHandler("JobHandler");// 此处hander需提前在项目中定义
            xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
            xxlJobInfo.setExecutorTimeout(0);
            xxlJobInfo.setExecutorFailRetryCount(3);//执行失败重试
            xxlJobInfo.setAuthor("admin");
            xxlJobInfo.setScheduleType("FIX_RATE");
            xxlJobInfo.setScheduleConf(xxlJobProperties.getScheduleConf());// 时间间隔
            xxlJobInfo.setGlueRemark("GLUE代码初始化");
            xxlJobInfo.setTriggerStatus(1); //调度状态:0-停止,1-运行
            xxlJobInfo.setMisfireStrategy("DO_NOTHING");
            xxlJobInfo.setExecutorParam(jobParam); //执行时的入参
            xxlJobCommon.addJob(xxlJobInfo);
        }catch (Exception e){
            log.error("异常:",e);
        }
        return "OK";
    }

}

请求接口http://localhost:8091/demo/addJob/addJobParam之后,会发现XXL-JOB中多了一个定时任务,这个定时器的各种属性也符合在业务中编写的属性。
在这里插入图片描述
在这里插入图片描述
也可以看到这个执行任务的执行
在这里插入图片描述

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值