Spring Boot 集成 XXL-Job 完整实现方案
一、概述
在微服务架构中,分布式任务调度是一个常见的需求。本文将介绍基于 Spring Boot 集成 XXL-Job 的完整实现方案,包括执行器配置、任务管理、Feign 调用等核心功能。
主要包含以下模块:
| 模块 | 说明 |
|---|---|
config |
XXL-Job 配置类、Feign 配置、Cookie 管理 |
feignApi |
远程调用 XXL-Job Admin 的 Feign 接口 |
handler |
任务处理器,定义具体的任务逻辑 |
properties |
配置属性类,支持 Nacos 动态配置 |
model/vo/from |
数据模型和请求/响应对象 |
二、核心配置类
2.1 XxlJobConfig - 执行器配置
这是整个 XXL-Job 集成的核心配置类,负责初始化和管理执行器实例。
java
@Data
@Configuration
@RefreshScope
public class XxlJobConfig {
@Value("${xxl.job.admin.accessToken:default_token}")
private String accessToken;
@Value("${xxl.job.executor.appname:unstructured-task-executor}")
private String appName;
@Value("${xxl.job.admin.addresses:http://127.0.0.1:8080/xxl-job-admin}")
private String adminAddresses;
@Value("${XXL_JOB_REGISTER_SERVER_IP:127.0.0.1}")
private String serverIp;
@Value("${XXL_JOB_REGISTER_SERVER_PORT:${random.int(9999,65535)}}")
private Integer serverPort;
private volatile XxlJobSpringExecutor currentExecutor;
private final AtomicBoolean isRegistered = new AtomicBoolean(false);
@Bean
@ConditionalOnMissingBean(name = "xxlJobExecutor")
@Profile("!dev") // 开发环境不注册
public XxlJobSpringExecutor xxlJobExecutor() {
// 执行器初始化逻辑
XxlJobSpringExecutor executor = createExecutor();
if (executor != null) {
currentExecutor = executor;
isRegistered.set(true);
}
return executor;
}
}
核心特性:
- @RefreshScope 支持:配置可动态刷新,配合 Nacos 使用
- 开发环境隔离 :通过
@Profile("!dev")注解,开发环境不注册到调度中心 - 自动端口分配 :使用
${random.int(9999,65535)}自动生成端口,避免端口冲突 - 线程安全设计 :使用
AtomicBoolean保证注册状态的线程安全
2.2 执行器重新注册机制
配置变更时支持热更新,无需重启服务:
java
public synchronized void reRegister() {
// 1. 停止旧执行器
if (currentExecutor != null) {
currentExecutor.destroy();
currentExecutor = null;
}
// 2. 等待端口释放
Thread.sleep(3000);
// 3. 创建新执行器
XxlJobSpringExecutor newExecutor = createExecutor();
if (newExecutor != null) {
newExecutor.start();
currentExecutor = newExecutor;
isRegistered.set(true);
}
}
2.3 XxlJobCnf - 配置属性类
支持从 Nacos 读取配置,结构清晰:
java
@Data
@Component
@RefreshScope
@ConfigurationProperties(prefix = "xxl.job")
public class XxlJobCnf {
private Admin admin; // 调度中心配置
private Executor executor; // 执行器配置
private String userName; // 登录用户名
private String password; // 登录密码
@Data
public static class Admin {
private String addresses; // 调度中心地址
private String accessToken; // 访问令牌
}
@Data
public static class Executor {
private String appname; // 执行器名称
private String ip; // 执行器IP
private Integer port; // 执行器端口
private String logpath; // 日志路径
private Integer logretentiondays; // 日志保留天数
}
}
2.4 yml配置
yaml
xxl:
job:
executor:
appName: unstructured-task-executor
userName: admin
password: 123456
admin:
addresses: http://10.19.68.7:9016/xxl-job-admin
accessToken: default_token
executor:
logpath: /data/applogs/xxl-job/jobhandler
logretentiondays: 30
corePoolSize: 0
maximumPoolSize: 200
keepAliveTime: 60
XXL_JOB_REGISTER_SERVER_IP: 10.19.68.7
XXL_JOB_REGISTER_SERVER_PORT: 19046
三、Feign 远程调用
3.1 RemoteXxlJobApiFeign - 任务管理接口
封装了 XXL-Job Admin 的 REST API:
java
@FeignClient(contextId = "xxlJobService",
url = "#{@xxlJobConfig.adminAddresses}",
name = "xxlJobAdminClient",
path = "/jobinfo",
fallbackFactory = RemoteXxlJobFallbackFactory.class)
public interface RemoteXxlJobApiFeign {
// 分页查询任务列表
@RequestMapping(value = "/pageList", consumes = "application/x-www-form-urlencoded")
XxlJobInfoListVoReturnT pageList(@RequestParam("start") Integer start,
@RequestParam("length") Integer length,
@RequestParam(value = "jobGroup", required = false) Integer jobGroup,
@RequestParam(value = "triggerStatus", defaultValue = "-1") Integer triggerStatus);
// 新增任务
@RequestMapping(value = "/add", consumes = "application/x-www-form-urlencoded")
ReturnT<String> add(@ModelAttribute XxlJobInfoFrom form);
// 更新任务
@RequestMapping(value = "/update", consumes = "application/x-www-form-urlencoded")
ReturnT<String> update(@ModelAttribute XxlJobInfoFrom form);
// 删除任务
@RequestMapping(value = "/remove", consumes = "application/x-www-form-urlencoded")
ReturnT<String> remove(@RequestParam("id") Long id);
// 启动/停止任务
@RequestMapping(value = "/start", consumes = "application/x-www-form-urlencoded")
ReturnT<String> start(@RequestParam("id") Long id);
@RequestMapping(value = "/stop", consumes = "application/x-www-form-urlencoded")
ReturnT<String> stop(@RequestParam("id") Long id);
// 手动触发任务
@RequestMapping(value = "/trigger", consumes = "application/x-www-form-urlencoded")
ReturnT<String> trigger(@RequestParam("id") Integer id,
@RequestParam("executorParam") String executorParam,
@RequestParam("addressList") String addressList);
}
3.2 RemoteXxlJobGroupFeign - 执行器组管理
java
@FeignClient(contextId = "xxlJobGroupService",
url = "#{@xxlJobConfig.adminAddresses}",
name = "xxlJobAdminJobGroupClient",
path = "/",
fallbackFactory = RemoteXxlJobFallbackFactory.class)
public interface RemoteXxlJobGroupFeign {
// 登录获取Cookie
@PostMapping("/login")
ResponseEntity<ReturnT<String>> login(@RequestParam("userName") String userName,
@RequestParam("password") String password);
// 查询执行器列表
@GetMapping("/jobgroup/pageList")
XXlJobGroupReturnT pageList(@RequestParam("start") Integer start,
@RequestParam("length") Integer length,
@RequestParam(value = "appname", required = false) String appname);
// 根据ID查询执行器
@PostMapping("/jobgroup/loadById")
ReturnT<XxlJobGroup> loadById(@RequestParam("id") Integer id);
}
3.3 Cookie 管理机制
XXL-Job Admin 需要登录验证,通过定时刷新 Cookie 保证请求合法性:
java
@Component
public class XxlJobCookieComponent {
public static String COOKIE = null;
@Resource
RemoteXxlJobGroupFeign remoteXxlJobGroupFeign;
@Scheduled(fixedRate = 20 * 60 * 1000) // 每20分钟刷新一次
public void refreshCookie() {
ResponseEntity<ReturnT<String>> response =
remoteXxlJobGroupFeign.login(xxlJobConfig.getUserName(),
xxlJobConfig.getPassword(), null);
List<String> setCookies = response.getHeaders().get("Set-Cookie");
if (setCookies != null && !setCookies.isEmpty()) {
COOKIE = String.join(";", setCookies)
.replace("Path=/; HttpOnly", "")
.replace("Secure", "")
.trim();
}
}
}
3.4 Feign 请求拦截器
自动为请求添加 Cookie:
java
@Component
public class XxlJobFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
if (XxlJobCookieComponent.COOKIE != null) {
template.header("Cookie", XxlJobCookieComponent.COOKIE);
}
}
}
3.5 服务降级处理
java
@Component
public class RemoteXxlJobFallbackFactory implements FallbackFactory<RemoteXxlJobApiFeign> {
@Override
public RemoteXxlJobApiFeign create(Throwable throwable) {
log.error("XXL-Job服务调用失败:{}", throwable.getMessage());
return new RemoteXxlJobApiFeign() {
@Override
public ReturnT<String> add(XxlJobInfoFrom form) {
return new ReturnT(ReturnT.FAIL_CODE, "XxlJob新增任务信息失败");
}
// ... 其他方法降级处理
};
}
}
四、任务处理器
4.1 JobHandlerContext - 数据处理任务
java
@Component
@AllArgsConstructor
@Slf4j
public class JobHandlerContext {
private final UnstructuredDataProcessingService unstructuredDataProcessingService;
private final UnstructuredDataProcessingTaskMapper taskMapper;
@XxlJob(value = "dataProcessTaskHandler")
public void dataProcessTaskHandler() {
String jobParam = XxlJobHelper.getJobParam();
log.info("执行参数:{}", jobParam);
JSONObject jsonObject = JSONUtil.parseObj(jobParam);
Long instanceId = jsonObject.getLong("instanceId", 0L);
// 异步执行,不阻塞调度线程
CompletableFuture.runAsync(() -> {
try {
execTask(instanceId);
} catch (Exception e) {
log.error("异步处理失败", e);
}
});
}
private void execTask(Long instanceId) {
// 1. 查询任务配置
UnstructuredDataProcessingTaskPo taskPo = taskMapper.selectById(instanceId);
if (taskPo == null) {
log.warn("任务配置不存在");
return;
}
// 2. 校验发布状态
if (!Objects.equals(taskPo.getReleaseState(), UnstructuredDatasetReleaseEnum.ONLINE.getType())) {
log.info("任务未上线,跳过执行");
return;
}
// 3. 校验调度时间范围
if (!isWithinScheduleTime(startTime, endTime, instanceId)) {
log.info("当前时间不在调度范围内");
return;
}
// 4. 执行任务
unstructuredDataProcessingService.run(instanceId, "xxlJob");
}
}
任务执行流程:
- 参数解析 :从
XxlJobHelper.getJobParam()获取任务参数 - 异步提交 :使用
CompletableFuture.runAsync()异步执行,避免阻塞调度线程 - 状态校验:检查任务是否已发布、调度开关是否开启
- 时间校验:判断当前时间是否在调度时间范围内
- 任务执行:调用业务服务执行具体逻辑
4.2 时间范围校验
java
private boolean isWithinScheduleTime(String startTimeStr, String endTimeStr, Long instanceId) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
// 校验开始时间
if (StringUtils.isNotBlank(startTimeStr)) {
LocalDateTime startTime = LocalDateTime.parse(startTimeStr, formatter);
if (startTime.isAfter(now)) {
return false;
}
}
// 校验结束时间
if (StringUtils.isNotBlank(endTimeStr)) {
LocalDateTime endTime = LocalDateTime.parse(endTimeStr, formatter);
if (endTime.isBefore(now)) {
return false;
}
}
return true;
}
五、Controller 示例
XXLTestController 提供了完整的任务管理 REST API:
java
@RestController
@RequestMapping("/xxl/test")
public class XXLTestController {
@Resource
private RemoteXxlJobApiFeign remoteXxlJobApiFeign;
@Resource
private RemoteXxlJobGroupFeign remoteXxlJobGroupFeign;
/**
* 创建定时任务
*/
@PostMapping("/create")
public R create(@RequestParam Long dataId, @RequestParam String corn) {
XxlJobGroup xxlJobGroup = getGroupId();
XxlJobInfoFrom xxlJobInfoFrom = new XxlJobInfoFrom();
xxlJobInfoFrom.setScheduleConf(corn);
xxlJobInfoFrom.setJobGroup(xxlJobGroup.getId());
xxlJobInfoFrom.setExecutorHandler("dataProcessTaskHandler");
xxlJobInfoFrom.setGlueType(GlueTypeEnum.BEAN.getDesc());
xxlJobInfoFrom.setTriggerStatus(0); // 初始状态为停止
ReturnT<String> addResult = remoteXxlJobApiFeign.add(xxlJobInfoFrom);
if (addResult.getCode() != ReturnT.SUCCESS_CODE) {
return R.fail("创建任务失败");
}
return R.ok(addResult);
}
/**
* 启动任务
*/
@PostMapping("/start")
public R start(@RequestParam Long jobId) {
ReturnT<String> result = remoteXxlJobApiFeign.start(jobId);
if (result.getCode() != ReturnT.SUCCESS_CODE) {
return R.fail("启动任务失败");
}
return R.ok(result);
}
// 停止、更新、删除方法类似...
}
六、配置说明
6.1 application.yml 配置示例
java
xxl:
job:
admin:
addresses: http://127.0.0.1:8080/xxl-job-admin
accessToken: default_token
executor:
appname: unstructured-task-executor
logpath: /data/applogs/xxl-job/jobhandler
logretentiondays: 30
userName: admin
password: 123456
# 执行器注册IP(容器环境使用环境变量)
XXL_JOB_REGISTER_SERVER_IP: ${POD_IP:127.0.0.1}
XXL_JOB_REGISTER_SERVER_PORT: ${random.int(9999,65535)}
6.2 关键配置说明
| 配置项 | 说明 | 默认值 |
|---|---|---|
xxl.job.admin.addresses |
调度中心地址 | localhost |
xxl.job.executor.appname |
执行器名称 | unstructured-task-executor |
xxl.job.executor.logpath |
日志路径 | /data/applogs/xxl-job/jobhandler |
xxl.job.userName |
登录用户名 | admin |
xxl.job.password |
登录密码 | 123456 |
七、设计亮点
7.1 高可用设计
- 自动端口分配:使用随机端口避免冲突
- 配置热更新:支持 Nacos 配置动态刷新
- 优雅降级:Feign 调用失败时有降级处理
7.2 安全机制
- Cookie 自动刷新:定时刷新登录状态
- 请求拦截器:自动添加认证信息
- AccessToken 校验:支持接口访问令牌
7.3 性能优化
- 异步执行:任务处理异步化,不阻塞调度线程
- 线程安全:使用原子变量保证并发安全
- 日志分级:不同环境日志级别可控
八、总结
该 XXL-Job 集成方案具有以下特点:
- 完整的配置管理:支持动态配置和热更新
- 灵活的任务管理:提供完整的 CRUD 操作
- 安全的远程调用:Cookie 自动管理和请求拦截
- 健壮的异常处理:完善的降级和错误处理机制
- 生产级可用:经过实际项目验证,稳定可靠
通过以上设计,实现了一个完整、健壮的分布式任务调度解决方案,适用于企业级微服务架构。