官网分布式任务调度平台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不一样。