定时任务调度框架XXL-Job

官网分布式任务调度平台XXL-JOB (xuxueli.com)

 启动调度中心

* 通过路径访问:http://localhost:8080/xxl-job-admin

一  需求分析

在我们呼叫滴滴相关的软件里面,有一个功能叫搜索司机。一般我们搜索司机都是会有时间限度的,在一段时间内不断搜索附近可以接单的司机。这就需要一个定时的工具帮我们实现这一个功能

二  介绍

xxl-job: 是大众点评员工徐雪里于2015年发布的分布式任务调度平台,是一个轻量级分布式任务调度框架,其核心设计目标是开发迅速、学习简单、轻量级、易扩展,其在唯品会内部已经发部署350+个节点,每天任务调度4000多万次。同时,管理和统计也是它的亮点。使用案例 大众点评、易信(IM)、京东(电商系统)、360金融(金融系统)、易企秀、随行付(支付系统)、优信二手车。

三  快速入门

1  下载官方源码

在官网里面下载

2  创建XXL-JOB使用数据库和相关表

运行sql文件

3  部署调度中心

3.1  修改xxl-job-admin项目里面配置文件

修改admin下面的配置文件,主要修改端口号和数据库信息

3.2  启动调度中心

* 通过路径访问:http://localhost:8080/xxl-job-admin

* 默认用户名和密码: admin/123456

4  部署执行器项目

用有springboot的那个项目

4.1  修改配置文件,把执行器项目在调度中心进行注册

注意!!!!!!!!!!!

地址要填调度中心所在机器的ip地址!!!

不要127.0.0.1!!!!!

要自己修改

相关配置的意思

# web port
server.port=8281

# log config
logging.config=classpath:logback.xml


### 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin

### 执行器通讯TOKEN [选填]:非空时启用;
xxl.job.accessToken=

### 执行器AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
xxl.job.executor.appname=xxl-job-executor-sample
### 执行器注册 [选填]:优先使用该配置作为注册地址,为空时使用内嵌服务 ”IP:PORT“ 作为注册地址。从而更灵活的支持容器类型执行器动态IP和动态映射端口问题。
xxl.job.executor.address=
### 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
xxl.job.executor.ip=
### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
xxl.job.executor.port=9999
### 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
xxl.job.executor.logpath=C:\\tingshu
### 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
xxl.job.executor.logretentiondays=30

4.2   创建配置类,获取任务调用过程中需要使用相关参数

我这里使用了默认的,本来就创建好的

4.3  启动执行器项目

5  注册到调度中心里面

appname在配置文件里面有这一栏,可以手动配置

6  开发执行器项目job方法

7  创建任务并启动任务

通过图形化界面方式进行操作

7.1  新增任务

注意点1:

cron的值

注意点2:

对应着注解里面的内容

7.2  启动任务

启动!!!

四  项目集成XXL-JOB

用尚硅谷项目作为案例测试

1  引入依赖

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

2  创建配置类

这个配置类可以在源码那里赋值

package com.atguigu.daijia.dispatch.xxl.config;

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@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;
    }

}

3  编写测试类调试

@Component
public class DispatchJobHandler {
    
    @XxlJob("firstJobHandler")
    public void testJobHandler() {
        System.out.println("xxl-job项目集成测试");
    }
}

4  调试

第一步

 启动调度中心

启动执行器项目服务

配置中心调好参数

第二步 在调度中心创建任务

5  封装客户端

在我们之前使用中,我们都是使用操作面板来实现的,但是实际开发中我们要基于java代码来实现上面添加任务,启动任务等功能。

5.1  封装思路

我们需要在调度中心去创建相关方法,然后在客户端,就是我们的执行器端远程调用这些方法来实现具体的功能

5.2  在调度中心编辑方法

添加下面的方法

其中    @PermissionLimit(limit = false)  代表禁用登陆校验。url自己定义。方便执行器调用

//自定义任务操作的方法
	//添加任务
	@RequestMapping("/addJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> addJobInfo(@RequestBody XxlJobInfo jobInfo) {
		return xxlJobService.add(jobInfo);
	}

	//删除任务
	@RequestMapping("/removeJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> removeJob(@RequestBody XxlJobInfo jobInfo) {
		return xxlJobService.remove(jobInfo.getId());
	}

	//修改任务
	@RequestMapping("/updateJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> updateJob(@RequestBody XxlJobInfo jobInfo) {
		return xxlJobService.update(jobInfo);
	}

	//停止任务
	@RequestMapping("/stopJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> pauseJob(@RequestBody XxlJobInfo jobInfo) {
		return xxlJobService.stop(jobInfo.getId());
	}

	//启动任务
	@RequestMapping("/startJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> startJob(@RequestBody XxlJobInfo jobInfo) {
		return xxlJobService.start(jobInfo.getId());
	}

	//添加并启动任务
	@RequestMapping("/addAndStartJob")
	@ResponseBody
	@PermissionLimit(limit = false)
	public ReturnT<String> addAndStartJob(@RequestBody XxlJobInfo jobInfo) {
		ReturnT<String> result = xxlJobService.add(jobInfo);

		String content = result.getContent();
		int id = Integer.parseInt(content);
		xxlJobService.start(id);

		//立即执行一次
		JobTriggerPoolHelper.trigger(id, TriggerTypeEnum.MANUAL, -1, null, jobInfo.getExecutorParam(), "");
		return result;
	}

5.3  配置文件添加任务方法

添加请求任务信息的url,就是上面接口的url。这里是方便调用,在客户端读取这个配置信息就可以直接调用

这里的地址是调度中心的ip地址,我的调度中心服务在本机执行,所以是localhost

xxl:
  job:
    admin:
      # 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地            
      # 址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册
      addresses: http://localhost:8080/xxl-job-admin
    client:
      jobGroupId: 1
      addUrl: ${xxl.job.admin.addresses}/jobinfo/addJob
      removeUrl: ${xxl.job.admin.addresses}/jobinfo/removeJob
      startJobUrl: ${xxl.job.admin.addresses}/jobinfo/startJob
      stopJobUrl: ${xxl.job.admin.addresses}/jobinfo/stopJob
      addAndStartUrl: ${xxl.job.admin.addresses}/jobinfo/addAndStartJob

5.4  添加相关配置类

创建配置类,读取配置文件里面调用的调度中心任务操作的方法的地址,用于后续远程调用时发送请求

@Data
@Component
@ConfigurationProperties(prefix = "xxl.job.client")
public class XxlJobClientConfig {

    private Integer jobGroupId;
    private String addUrl;
    private String removeUrl;
    private String startJobUrl;
    private String stopJobUrl;
    private String addAndStartUrl;
}

5.5  创建客户端类,编写调用调度中心里面的方法

这是固定的代码,就是创建一个操作对象并设置参数,通过restTemplate来向调度中心发送请求,实现具体逻辑。

@Slf4j
@Component
public class XxlJobClient {

    @Autowired
    private XxlJobClientConfig xxlJobClientConfig;

    //客户端调用服务端里面的方法
    @Autowired
    private RestTemplate restTemplate;

    @SneakyThrows
    public Long addJob(String executorHandler, String param, String corn, String desc){
        XxlJobInfo xxlJobInfo = new XxlJobInfo();
        xxlJobInfo.setJobGroup(xxlJobClientConfig.getJobGroupId());
        xxlJobInfo.setJobDesc(desc);
        xxlJobInfo.setAuthor("qy");
        xxlJobInfo.setScheduleType("CRON");
        xxlJobInfo.setScheduleConf(corn);
        xxlJobInfo.setGlueType("BEAN");
        xxlJobInfo.setExecutorHandler(executorHandler);
        xxlJobInfo.setExecutorParam(param);
        xxlJobInfo.setExecutorRouteStrategy("FIRST");
        xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
        xxlJobInfo.setMisfireStrategy("FIRE_ONCE_NOW");
        xxlJobInfo.setExecutorTimeout(0);
        xxlJobInfo.setExecutorFailRetryCount(0);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

        String url = xxlJobClientConfig.getAddUrl();
        ResponseEntity<JSONObject> response =
                restTemplate.postForEntity(url, request, JSONObject.class);

        if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
            log.info("增加xxl执行任务成功,返回信息:{}", response.getBody().toJSONString());
            //content为任务id
            return response.getBody().getLong("content");
        }
        log.info("调用xxl增加执行任务失败:{}", response.getBody().toJSONString());
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }

    public Boolean startJob(Long jobId) {
        XxlJobInfo xxlJobInfo = new XxlJobInfo();
        xxlJobInfo.setId(jobId.intValue());

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

        String url = xxlJobClientConfig.getStartJobUrl();
        ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
        if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
            log.info("启动xxl执行任务成功:{},返回信息:{}", jobId, response.getBody().toJSONString());
            return true;
        }
        log.info("启动xxl执行任务失败:{},返回信息:{}", jobId, response.getBody().toJSONString());
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }

    public Boolean stopJob(Long jobId) {
        XxlJobInfo xxlJobInfo = new XxlJobInfo();
        xxlJobInfo.setId(jobId.intValue());

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

        String url = xxlJobClientConfig.getStopJobUrl();
        ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
        if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
            log.info("停止xxl执行任务成功:{},返回信息:{}", jobId, response.getBody().toJSONString());
            return true;
        }
        log.info("停止xxl执行任务失败:{},返回信息:{}", jobId, response.getBody().toJSONString());
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }

    public Boolean removeJob(Long jobId) {
        XxlJobInfo xxlJobInfo = new XxlJobInfo();
        xxlJobInfo.setId(jobId.intValue());

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

        String url = xxlJobClientConfig.getRemoveUrl();
        ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
        if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
            log.info("删除xxl执行任务成功:{},返回信息:{}", jobId, response.getBody().toJSONString());
            return true;
        }
        log.info("删除xxl执行任务失败:{},返回信息:{}", jobId, response.getBody().toJSONString());
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }

    //添加并启动任务
    public Long addAndStart(String executorHandler, String param, String corn, String desc) {
        XxlJobInfo xxlJobInfo = new XxlJobInfo();
        xxlJobInfo.setJobGroup(xxlJobClientConfig.getJobGroupId());
        xxlJobInfo.setJobDesc(desc);
        xxlJobInfo.setAuthor("qy");
        xxlJobInfo.setScheduleType("CRON");
        xxlJobInfo.setScheduleConf(corn);
        xxlJobInfo.setGlueType("BEAN");
        xxlJobInfo.setExecutorHandler(executorHandler);
        xxlJobInfo.setExecutorParam(param);
        xxlJobInfo.setExecutorRouteStrategy("FIRST");
        xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
        xxlJobInfo.setMisfireStrategy("FIRE_ONCE_NOW");
        xxlJobInfo.setExecutorTimeout(0);
        xxlJobInfo.setExecutorFailRetryCount(0);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<XxlJobInfo> request = new HttpEntity<>(xxlJobInfo, headers);

        //获取调度中心请求路径
        String url = xxlJobClientConfig.getAddAndStartUrl();

        //restTemplate
        ResponseEntity<JSONObject> response = restTemplate.postForEntity(url, request, JSONObject.class);
        if(response.getStatusCode().value() == 200 && response.getBody().getIntValue("code") == 200) {
            log.info("增加并开始执行xxl任务成功,返回信息:{}", response.getBody().toJSONString());
            //content为任务id
            return response.getBody().getLong("content");
        }
        log.info("增加并开始执行xxl任务失败:{}", response.getBody().toJSONString());
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }
}

上面的处理异常注解解析:

【Java用法】Lombok中@SneakyThrows注解的使用方法和作用_lombok.sneakythrows-CSDN博客

之后的业务,就可以调用方法来实现任务的新增与启动了

五  个人理解

admin是任务调度中心,就是那个操作面板控制的东西。

我们的执行器,就是下面这个东西

就是用来执行方法的。他有他的名字。并且,他要指定调度中心的地址,因为他要在调度中心才能使用。并且,一个执行器可以有多个方法,就是多个任务

有了这两个东西,我们有两种方法来启动执行器执行任务。

第一个就是在操作面板,选择执行器,并添加要执行哪个方法。每个任务只能执行一个指定执行器内的方法

第二种方法就是通过代码的方式来创建任务,从而让执行器执行任务

在尚硅谷代驾项目中我们就采用这个方法来实现。

首先在admin下的infocontroller里面定义关于新增任务,启动任务,删除任务等的api,然后在需要使用xxl的微服务来远程调用这些api从而实现添加任务的效果

报错

1  未选择执行器错误

我们在创建任务的时候,调用添加任务add的方法,发现他load方法加载不出来东西。

然后我们去看源码,这个load方法load了什么。我们发现,他是查询一个叫xxl-job-group的表的id来加载。

我们看到,这个表是用来记录执行器的。

回到刚刚的load方法,他要load的值是一,也就是ID是1,然而这里并没有

什么原因呢?

答案就是:我们手动创建了多个执行器,然后会分配id。但是代码不知道我们手动创建过,创建执行器仍然从id为0开始。如果我们配置的执行器是我们手动创建的,那它的id肯定和代码认为的ID不一样。

  • 8
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值