微服务中XXL-JOB定时任务完整教程

微服务中XXL-JOB定时任务完整教程

前言:为什么需要分布式任务调度?

在开始学习XXL-JOB之前,让我们先理解一下为什么需要它。

传统定时任务的痛点

在没有分布式任务调度框架之前,我们通常使用以下方式实现定时任务:

Spring的@Scheduled注解:简单但功能有限,无法动态修改Quartz框架:功能强大但配置复杂,集群部署麻烦Linux的Crontab:脚本维护困难,没有可视化界面

这些方案在微服务架构下会遇到更多问题:

管理混乱:每个服务都有自己的定时任务,无法统一查看和管理重复执行:服务多实例部署时,同一个任务会被执行多次监控困难:任务执行情况分散在各个服务日志中,排查问题困难无法动态调整:修改任务配置需要重启服务,影响业务

一、XXL-JOB深入理解

1.1 什么是XXL-JOB?

XXL-JOB是一个分布式任务调度平台,其核心设计理念是将任务调度和任务执行分离。打个比方,如果把定时任务比作快递配送:

调度中心就像是快递分拣中心,负责决定什么时候派送、派给谁执行器就像是快递员,负责实际配送包裹任务就是要配送的包裹

1.2 核心概念详解

调度中心(Admin)

调度中心是整个XXL-JOB的核心,负责:

任务管理:创建、修改、删除定时任务任务调度:按照配置的时间规则触发任务执行执行器管理:管理所有注册的执行器,监控其健康状态日志管理:收集和展示任务执行日志监控统计:提供任务执行报表、成功率统计等

执行器(Executor)

执行器嵌入在业务服务中,负责:

接收调度请求:接收来自调度中心的任务调度请求执行任务:调用实际的业务代码执行任务回调结果:将执行结果反馈给调度中心心跳注册:定期向调度中心发送心跳,保持连接

任务(Job)

任务是实际要执行的业务逻辑,包含:

任务标识:唯一标识一个任务(JobHandler)执行逻辑:具体的业务代码执行参数:任务执行时需要的参数执行策略:路由策略、阻塞策略等

1.3 XXL-JOB的优势对比

特性 XXL-JOB Spring @Scheduled Quartz
动态配置 ✅ 支持热更新 ❌ 需要重启 ⚠️ 配置复杂
分布式执行 ✅ 原生支持 ❌ 不支持 ⚠️ 需要额外配置
可视化管理 ✅ 完善的Web界面 ❌ 无界面 ❌ 需要自己开发
监控告警 ✅ 内置监控 ❌ 需要自己实现 ❌ 需要自己实现
任务分片 ✅ 支持 ❌ 不支持 ⚠️ 支持但复杂
学习成本 很低

二、架构设计详解

2.1 整体架构图



┌─────────────────────────────────────────────────────────────┐
│                    XXL-JOB-Admin(调度中心)                  │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐          │
│  │任务管理  │ │调度器   │ │执行器管理│ │日志管理  │          │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘          │
│                          ▼                                   │
│                    ┌──────────┐                             │
│                    │ MySQL数据库│                            │
│                    └──────────┘                             │
└──────────────────────────┬──────────────────────────────────┘
                           │
        ┌──────────────────┼──────────────────┐
        │                  │                  │
        ▼                  ▼                  ▼
┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│   订单服务    │  │   用户服务    │  │   库存服务    │
│  ┌────────┐  │  │  ┌────────┐  │  │  ┌────────┐  │
│  │执行器   │  │  │  │执行器   │  │  │  │执行器   │  │
│  ├────────┤  │  │  ├────────┤  │  │  ├────────┤  │
│  │任务1    │  │  │  │任务3    │  │  │  │任务5    │  │
│  │任务2    │  │  │  │任务4    │  │  │  │任务6    │  │
│  └────────┘  │  │  └────────┘  │  │  └────────┘  │
└──────────────┘  └──────────────┘  └──────────────┘

2.2 工作流程详细说明

让我们通过一个具体的例子来理解整个工作流程:

场景:每天凌晨2点执行订单统计任务

任务配置阶段(手动操作)

运维人员登录调度中心Web界面创建一个新任务,设置CRON表达式为”0 0 2 * * ?”指定任务由订单服务的执行器执行

任务触发阶段(自动执行)

调度中心的调度线程每秒扫描一次发现当前时间是凌晨2点,触发订单统计任务根据路由策略选择一个订单服务实例

任务执行阶段(自动执行)

订单服务的执行器接收到调度请求执行器创建一个新线程执行任务任务代码查询数据库,统计订单数据

结果回调阶段(自动执行)

任务执行完成后,执行器将结果发送给调度中心调度中心记录执行日志和执行结果如果失败,根据配置决定是否重试

2.3 通信机制

XXL-JOB使用HTTP协议进行通信,这样设计的好处是:

简单易用:不需要额外的中间件跨语言:支持Java、Python、PHP等多种语言的执行器易于调试:可以使用Postman等工具测试接口

通信过程包括:

执行器注册:执行器启动时向调度中心注册自己心跳检测:执行器每30秒发送一次心跳任务调度:调度中心通过HTTP请求触发任务结果回调:执行器通过HTTP回调执行结果

三、环境搭建实战

3.1 准备工作

在开始搭建之前,请确保你的环境满足以下要求:

硬件要求

CPU:2核以上内存:4GB以上硬盘:10GB以上可用空间

软件要求

JDK:1.8或以上版本MySQL:5.7或以上版本Maven:3.0或以上版本

3.2 部署调度中心详细步骤

步骤1:创建数据库

首先,我们需要为XXL-JOB创建数据库和表结构:



-- 1. 登录MySQL
mysql -u root -p
 
-- 2. 创建数据库(注意字符集)
CREATE DATABASE IF NOT EXISTS xxl_job 
DEFAULT CHARACTER SET utf8mb4 
DEFAULT COLLATE utf8mb4_general_ci;
 
-- 3. 使用数据库
USE xxl_job;
 
-- 4. 导入表结构(从官方获取的SQL文件)
-- 主要的表说明:
-- xxl_job_info:任务信息表,存储所有的任务配置
-- xxl_job_log:任务日志表,记录任务执行历史
-- xxl_job_log_report:任务日志报表,用于统计分析
-- xxl_job_logglue:GLUE任务代码历史
-- xxl_job_registry:执行器注册表,记录在线的执行器
-- xxl_job_group:执行器分组表
-- xxl_job_user:用户表,管理登录用户
-- xxl_job_lock:任务锁表,防止重复执行
步骤2:下载和配置调度中心

有三种部署方式,我们逐一介绍:

方式一:源码编译部署(推荐学习时使用)



# 1. 克隆代码
git clone https://github.com/xuxueli/xxl-job.git
cd xxl-job
 
# 2. 修改配置文件
# 编辑 xxl-job-admin/src/main/resources/application.properties
vim xxl-job-admin/src/main/resources/application.properties

配置文件详细说明:



### 调度中心JDBC配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
 
### 连接池配置(根据实际情况调整)
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.auto-commit=true
 
### 调度中心端口配置
server.port=8080
server.servlet.context-path=/xxl-job-admin
 
### 调度中心国际化配置
spring.web.resources.static-locations=classpath:/static/
spring.freemarker.suffix=.ftl
spring.freemarker.request-context-attribute=request
 
### 调度线程池配置
xxl.job.triggerpool.fast.max=200
xxl.job.triggerpool.slow.max=100
 
### 日志保存天数(建议30-90天)
xxl.job.logretentiondays=30
 
### 调度中心通讯TOKEN(重要!生产环境必须修改)
xxl.job.accessToken=default_token_please_change


# 3. 编译打包
mvn clean package -Dmaven.test.skip=true
 
# 4. 启动服务
cd xxl-job-admin/target
java -jar xxl-job-admin-2.4.0.jar

方式二:Docker部署(推荐生产环境使用)



# 1. 拉取镜像
docker pull xuxueli/xxl-job-admin:2.4.0
 
# 2. 创建容器并启动
docker run -d 
  --name xxl-job-admin 
  --restart=always 
  -p 8080:8080 
  -v /data/xxl-job:/data/applogs 
  -e PARAMS="--spring.datasource.url=jdbc:mysql://192.168.1.100:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai 
  --spring.datasource.username=root 
  --spring.datasource.password=123456 
  --xxl.job.accessToken=my_custom_token" 
  xuxueli/xxl-job-admin:2.4.0
 
# 3. 查看日志
docker logs -f xxl-job-admin

方式三:Kubernetes部署(适合云原生环境)



# xxl-job-admin-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: xxl-job-admin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: xxl-job-admin
  template:
    metadata:
      labels:
        app: xxl-job-admin
    spec:
      containers:
      - name: xxl-job-admin
        image: xuxueli/xxl-job-admin:2.4.0
        ports:
        - containerPort: 8080
        env:
        - name: PARAMS
          value: "--spring.datasource.url=jdbc:mysql://mysql-service:3306/xxl_job"
---
apiVersion: v1
kind: Service
metadata:
  name: xxl-job-admin-service
spec:
  selector:
    app: xxl-job-admin
  ports:
  - port: 8080
    targetPort: 8080
  type: LoadBalancer
步骤3:验证部署

访问管理界面:http://localhost:8080/xxl-job-admin默认账号密码:admin/123456首次登录后建议立即修改密码

3.3 调度中心功能介绍

登录后,你会看到以下主要功能模块:

1. 运行报表

展示任务调度的整体情况包括任务数量、调度次数、执行器数量等可以快速了解系统运行状态

2. 任务管理

这是最核心的功能模块可以新增、编辑、删除任务支持启动、停止、手动执行任务查看任务的GLUE代码(在线编辑任务代码)

3. 调度日志

查看所有任务的执行历史支持按任务、执行器、时间等条件筛选可以查看详细的执行日志和错误信息

4. 执行器管理

管理所有注册的执行器查看执行器的在线状态支持手动录入和自动注册两种方式

四、微服务集成XXL-JOB详细指南

4.1 创建Spring Boot项目

如果你还没有微服务项目,可以按以下步骤创建:



<!-- 1. 创建父POM文件 -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>microservice-xxljob-demo</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    
    <modules>
        <module>order-service</module>
        <module>user-service</module>
    </modules>
</project>

4.2 添加XXL-JOB依赖

在你的微服务模块中添加以下依赖:



<dependencies>
    <!-- Spring Boot基础依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- XXL-JOB核心依赖 -->
    <dependency>
        <groupId>com.xuxueli</groupId>
        <artifactId>xxl-job-core</artifactId>
        <version>2.4.0</version>
    </dependency>
    
    <!-- 数据库相关(如果需要) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- 工具类 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
    
    <!-- Lombok(可选,简化代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

4.3 配置文件详解

创建application.yml配置文件,包含完整的配置说明:



# 服务基础配置
server:
  port: 8081  # 服务端口
  servlet:
    context-path: /order-service
 
spring:
  application:
    name: order-service  # 服务名称
  
  # 数据库配置(如果需要)
  datasource:
    url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      minimum-idle: 5
      maximum-pool-size: 20
 
# XXL-JOB配置
xxl:
  job:
    # 调度中心配置
    admin:
      # 调度中心地址,集群部署时用逗号分隔
      addresses: http://127.0.0.1:8080/xxl-job-admin
      # addresses: http://127.0.0.1:8080/xxl-job-admin,http://127.0.0.1:8081/xxl-job-admin
    
    # 执行器配置
    executor:
      # 执行器AppName,必须唯一,对应调度中心的执行器配置
      appname: ${spring.application.name}-executor
      
      # 执行器注册方式
      # 方式一:手动配置IP
      # ip: 192.168.1.100
      # 方式二:自动获取IP(推荐)
      # ip留空会自动获取
      ip:
      
      # 执行器端口,用于调度中心调用执行器
      # 默认9999,如果端口冲突可以修改
      port: 9999
      
      # 执行器运行日志文件存储磁盘路径
      logpath: /data/applogs/xxl-job/${spring.application.name}
      
      # 执行器日志文件保存天数,过期日志自动清理
      # 限制值大于等于3,否则功能不生效
      logretentiondays: 30
    
    # 执行器通讯TOKEN,必须与调度中心一致
    # 启用后调度中心与执行器之间的请求需要token验证
    accessToken: default_token_please_change
 
# 日志配置
logging:
  level:
    root: INFO
    com.xxl.job: DEBUG  # 开发时可以设置为DEBUG查看详细日志
  file:
    name: /data/logs/${spring.application.name}.log

4.4 配置类实现

创建XXL-JOB配置类,这是集成的核心:



package com.example.config;
 
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
/**
 * XXL-JOB配置类
 * 负责初始化执行器并注册到调度中心
 */
@Slf4j
@Configuration
public class XxlJobConfig {
    
    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;
    
    @Value("${xxl.job.executor.appname}")
    private String appname;
    
    @Value("${xxl.job.executor.ip}")
    private String ip;
    
    @Value("${xxl.job.executor.port}")
    private int port;
    
    @Value("${xxl.job.accessToken}")
    private String accessToken;
    
    @Value("${xxl.job.executor.logpath}")
    private String logPath;
    
    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;
    
    /**
     * 配置XXL-JOB执行器
     * 执行器负责接收调度请求并执行任务
     */
    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        log.info(">>>>>>>>>>> xxl-job config init.");
        log.info(">>>>>>>>>>> xxl-job adminAddresses: {}", adminAddresses);
        log.info(">>>>>>>>>>> xxl-job appname: {}", appname);
        
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        
        // 调度中心地址
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        
        // 执行器应用名称
        xxlJobSpringExecutor.setAppname(appname);
        
        // 执行器IP,为空则自动获取
        xxlJobSpringExecutor.setIp(ip);
        
        // 执行器端口
        xxlJobSpringExecutor.setPort(port);
        
        // 执行器通讯TOKEN
        xxlJobSpringExecutor.setAccessToken(accessToken);
        
        // 执行器日志路径
        xxlJobSpringExecutor.setLogPath(logPath);
        
        // 日志保留天数
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
        
        log.info(">>>>>>>>>>> xxl-job config init success.");
        return xxlJobSpringExecutor;
    }
}

五、编写定时任务实战

5.1 任务开发规范

在开始编写任务之前,我们先了解一下任务开发的规范和最佳实践:

命名规范

Handler名称:使用驼峰命名,如
orderDailyStatJobHandler
类名:以JobHandler结尾,如
OrderStatJobHandler
方法名:描述任务功能,如
executeDailyStat

代码规范

每个任务一个方法,保持单一职责使用
@XxlJob
注解标记任务方法合理使用日志,记录关键步骤异常处理要完善,避免任务异常终止

5.2 基础任务示例

让我们从最简单的任务开始:



package com.example.job;
 
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
 
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
 
/**
 * 简单任务示例
 * 演示XXL-JOB的基本使用方法
 */
@Slf4j
@Component
public class SimpleJobHandler {
    
    /**
     * 1. 最简单的任务:定时打印日志
     * 
     * @XxlJob注解说明:
     * - value: 任务名称,必须唯一,与调度中心配置的JobHandler对应
     * - init: 任务初始化方法
     * - destroy: 任务销毁方法
     */
    @XxlJob("demoJobHandler")
    public void demoJobHandler() {
        // 获取当前时间
        String currentTime = LocalDateTime.now()
            .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        
        // 使用XxlJobHelper记录日志(会显示在调度中心)
        XxlJobHelper.log("任务执行开始,当前时间:{}", currentTime);
        
        // 执行业务逻辑
        log.info("这是一个演示任务,执行时间:{}", currentTime);
        
        // 模拟业务处理
        try {
            Thread.sleep(2000);
            
            // 设置任务执行成功
            XxlJobHelper.handleSuccess("任务执行成功");
        } catch (InterruptedException e) {
            // 设置任务执行失败
            XxlJobHelper.handleFail("任务执行失败:" + e.getMessage());
        }
    }
    
    /**
     * 2. 带返回值的任务
     * 返回值会记录在调度日志中
     */
    @XxlJob("returnValueJobHandler")
    public String returnValueJobHandler() {
        XxlJobHelper.log("执行带返回值的任务");
        
        // 执行业务逻辑
        int processedCount = processSomeData();
        
        // 返回执行结果
        String result = String.format("处理完成,共处理%d条数据", processedCount);
        return result;
    }
    
    private int processSomeData() {
        // 模拟数据处理
        return 100;
    }
}

5.3 带参数的任务

实际业务中,我们经常需要给任务传递参数:



package com.example.job;
 
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
 
import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;
 
/**
 * 带参数的任务示例
 * 演示如何接收和解析任务参数
 */
@Slf4j
@Component
public class ParamJobHandler {
    
    private final ObjectMapper objectMapper = new ObjectMapper();
    
    /**
     * 1. 简单参数任务
     * 参数格式:普通字符串,如"2024-01-01"
     */
    @XxlJob("simpleParamJobHandler")
    public void simpleParamJobHandler() {
        // 获取任务参数
        String param = XxlJobHelper.getJobParam();
        XxlJobHelper.log("接收到参数:{}", param);
        
        if (StringUtils.isBlank(param)) {
            // 参数为空,使用默认值
            param = LocalDate.now().toString();
            XxlJobHelper.log("参数为空,使用默认日期:{}", param);
        }
        
        // 根据参数执行业务逻辑
        processDataByDate(param);
        
        XxlJobHelper.handleSuccess("处理完成");
    }
    
    /**
     * 2. JSON参数任务
     * 参数格式:JSON字符串,如{"startDate":"2024-01-01","endDate":"2024-01-31","type":"ORDER"}
     */
    @XxlJob("jsonParamJobHandler")
    public void jsonParamJobHandler() {
        String paramStr = XxlJobHelper.getJobParam();
        XxlJobHelper.log("接收到JSON参数:{}", paramStr);
        
        try {
            // 解析JSON参数
            TaskParam param = objectMapper.readValue(paramStr, TaskParam.class);
            
            XxlJobHelper.log("解析后参数 - 开始日期:{},结束日期:{},类型:{}", 
                param.getStartDate(), param.getEndDate(), param.getType());
            
            // 执行业务逻辑
            processDataByRange(param);
            
            XxlJobHelper.handleSuccess("处理成功");
        } catch (Exception e) {
            XxlJobHelper.log("参数解析失败:{}", e.getMessage());
            XxlJobHelper.handleFail("参数解析失败");
        }
    }
    
    /**
     * 3. 多参数任务(使用分隔符)
     * 参数格式:使用逗号分隔,如"param1,param2,param3"
     */
    @XxlJob("multiParamJobHandler")
    public void multiParamJobHandler() {
        String paramStr = XxlJobHelper.getJobParam();
        
        if (StringUtils.isNotBlank(paramStr)) {
            String[] params = paramStr.split(",");
            XxlJobHelper.log("解析到{}个参数", params.length);
            
            for (int i = 0; i < params.length; i++) {
                XxlJobHelper.log("参数{}:{}", i + 1, params[i]);
                // 处理每个参数
                processParam(params[i]);
            }
        }
        
        XxlJobHelper.handleSuccess();
    }
    
    /**
     * 4. 动态参数任务
     * 可以根据不同的参数执行不同的逻辑
     */
    @XxlJob("dynamicParamJobHandler")
    public void dynamicParamJobHandler() {
        String command = XxlJobHelper.getJobParam();
        XxlJobHelper.log("执行命令:{}", command);
        
        // 使用Map存储命令和对应的处理逻辑
        Map<String, Runnable> commandMap = new HashMap<>();
        commandMap.put("DAILY_STAT", this::executeDailyStat);
        commandMap.put("WEEKLY_REPORT", this::executeWeeklyReport);
        commandMap.put("CLEAN_CACHE", this::cleanCache);
        
        // 执行对应的命令
        Runnable task = commandMap.get(command);
        if (task != null) {
            task.run();
            XxlJobHelper.handleSuccess("命令执行成功");
        } else {
            XxlJobHelper.handleFail("未知命令:" + command);
        }
    }
    
    // 辅助方法
    private void processDataByDate(String date) {
        log.info("处理日期{}的数据", date);
    }
    
    private void processDataByRange(TaskParam param) {
        log.info("处理日期范围:{} 到 {}", param.getStartDate(), param.getEndDate());
    }
    
    private void processParam(String param) {
        log.info("处理参数:{}", param);
    }
    
    private void executeDailyStat() {
        log.info("执行每日统计");
    }
    
    private void executeWeeklyReport() {
        log.info("生成周报");
    }
    
    private void cleanCache() {
        log.info("清理缓存");
    }
    
    /**
     * 任务参数实体类
     */
    @Data
    public static class TaskParam {
        private String startDate;
        private String endDate;
        private String type;
    }
}

5.4 分片任务(重点)

分片任务是XXL-JOB的一大特色,适用于处理大量数据的场景:



package com.example.job;
 
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
 
/**
 * 分片任务示例
 * 适用于大数据量处理场景,可以将任务分配给多个执行器并行处理
 */
@Slf4j
@Component
public class ShardingJobHandler {
    
    @Autowired(required = false)
    private JdbcTemplate jdbcTemplate;
    
    /**
     * 1. 基础分片任务
     * 场景:有100万用户需要发送营销短信,使用分片任务可以多个实例并行处理
     */
    @XxlJob("basicShardingJobHandler")
    public void basicShardingJobHandler() {
        // 获取分片参数
        int shardIndex = XxlJobHelper.getShardIndex();  // 当前分片序号(0开始)
        int shardTotal = XxlJobHelper.getShardTotal();  // 总分片数
        
        XxlJobHelper.log("分片参数:当前分片={}/{}", shardIndex, shardTotal);
        
        // 模拟获取所有需要处理的用户ID
        List<Long> allUserIds = getAllUserIds();
        XxlJobHelper.log("总共需要处理{}个用户", allUserIds.size());
        
        // 计算当前分片需要处理的数据
        List<Long> myUserIds = new ArrayList<>();
        for (int i = 0; i < allUserIds.size(); i++) {
            // 使用取模方式分配数据
            if (i % shardTotal == shardIndex) {
                myUserIds.add(allUserIds.get(i));
            }
        }
        
        XxlJobHelper.log("分片{}需要处理{}个用户", shardIndex, myUserIds.size());
        
        // 处理分配给当前分片的数据
        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger failCount = new AtomicInteger(0);
        
        myUserIds.forEach(userId -> {
            try {
                // 发送短信
                sendSms(userId);
                successCount.incrementAndGet();
                
                // 每处理100条记录一次日志
                if (successCount.get() % 100 == 0) {
                    XxlJobHelper.log("分片{}已处理{}条", shardIndex, successCount.get());
                }
            } catch (Exception e) {
                failCount.incrementAndGet();
                log.error("处理用户{}失败", userId, e);
            }
        });
        
        // 记录执行结果
        String result = String.format("分片%d/%d执行完成,成功:%d,失败:%d", 
            shardIndex, shardTotal, successCount.get(), failCount.get());
        XxlJobHelper.log(result);
        XxlJobHelper.handleSuccess(result);
    }
    
    /**
     * 2. 数据库分片任务
     * 场景:清理数据库中的过期数据,按ID范围分片
     */
    @XxlJob("dbShardingJobHandler")
    public void dbShardingJobHandler() {
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();
        
        if (jdbcTemplate == null) {
            XxlJobHelper.log("数据库未配置,跳过执行");
            return;
        }
        
        // 获取数据ID范围
        Map<String, Object> range = getDataRange();
        long minId = (Long) range.get("minId");
        long maxId = (Long) range.get("maxId");
        long totalCount = maxId - minId + 1;
        
        // 计算每个分片处理的ID范围
        long perShardCount = totalCount / shardTotal;
        long startId = minId + shardIndex * perShardCount;
        long endId = (shardIndex == shardTotal - 1) ? maxId : startId + perShardCount - 1;
        
        XxlJobHelper.log("分片{}/{},处理ID范围:{} - {}", 
            shardIndex, shardTotal, startId, endId);
        
        // 分批处理数据
        long batchSize = 1000;
        long currentId = startId;
        int deletedCount = 0;
        
        while (currentId <= endId) {
            long batchEndId = Math.min(currentId + batchSize - 1, endId);
            
            // 删除过期数据
            String sql = "DELETE FROM order_table WHERE id >= ? AND id <= ? AND create_time < DATE_SUB(NOW(), INTERVAL 90 DAY)";
            int count = jdbcTemplate.update(sql, currentId, batchEndId);
            deletedCount += count;
            
            if (count > 0) {
                XxlJobHelper.log("删除ID范围{}-{}的过期数据{}条", currentId, batchEndId, count);
            }
            
            currentId = batchEndId + 1;
        }
        
        XxlJobHelper.handleSuccess(String.format("分片%d删除%d条过期数据", shardIndex, deletedCount));
    }
    
    /**
     * 3. 高级分片任务:动态分片
     * 根据实际情况动态调整每个分片的处理量
     */
    @XxlJob("advancedShardingJobHandler")
    public void advancedShardingJobHandler() {
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();
        
        // 获取任务参数,支持动态配置
        String param = XxlJobHelper.getJobParam();
        int priority = parsePriority(param);
        
        XxlJobHelper.log("分片{}/{},优先级:{}", shardIndex, shardTotal, priority);
        
        // 根据优先级分配不同的处理量
        List<Task> tasks = getTasksByPriority(priority);
        List<Task> myTasks = new ArrayList<>();
        
        // 使用一致性哈希或其他算法分配任务
        for (Task task : tasks) {
            int hash = task.getId().hashCode();
            if (Math.abs(hash) % shardTotal == shardIndex) {
                myTasks.add(task);
            }
        }
        
        XxlJobHelper.log("分片{}分配到{}个任务", shardIndex, myTasks.size());
        
        // 并行处理任务(可以使用线程池)
        myTasks.parallelStream().forEach(this::processTask);
        
        XxlJobHelper.handleSuccess();
    }
    
    // 辅助方法
    private List<Long> getAllUserIds() {
        // 模拟返回用户ID列表
        List<Long> userIds = new ArrayList<>();
        for (long i = 1; i <= 10000; i++) {
            userIds.add(i);
        }
        return userIds;
    }
    
    private void sendSms(Long userId) {
        // 模拟发送短信
        log.debug("向用户{}发送短信", userId);
    }
    
    private Map<String, Object> getDataRange() {
        // 模拟获取数据范围
        return Map.of("minId", 1L, "maxId", 1000000L);
    }
    
    private int parsePriority(String param) {
        // 解析优先级参数
        return param != null ? Integer.parseInt(param) : 1;
    }
    
    private List<Task> getTasksByPriority(int priority) {
        // 模拟根据优先级获取任务
        List<Task> tasks = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            tasks.add(new Task(i, "Task-" + i, priority));
        }
        return tasks;
    }
    
    private void processTask(Task task) {
        // 处理单个任务
        log.debug("处理任务:{}", task.getName());
    }
    
    // 内部类
    static class Task {
        private Integer id;
        private String name;
        private int priority;
        
        public Task(Integer id, String name, int priority) {
            this.id = id;
            this.name = name;
            this.priority = priority;
        }
        
        public Integer getId() { return id; }
        public String getName() { return name; }
        public int getPriority() { return priority; }
    }
}

5.5 复杂业务任务示例

让我们看一个更贴近实际业务的例子:



package com.example.job;
 
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
 
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
 
/**
 * 订单业务相关的定时任务
 * 包含实际业务场景的任务示例
 */
@Slf4j
@Component
public class OrderBusinessJobHandler {
    
    @Autowired(required = false)
    private OrderService orderService;
    
    @Autowired(required = false)
    private EmailService emailService;
    
    private final ExecutorService executorService = Executors.newFixedThreadPool(10);
    
    /**
     * 1. 订单自动确认收货
     * 场景:发货后15天未确认收货的订单自动确认
     */
    @XxlJob("autoConfirmOrderJobHandler")
    @Transactional
    public void autoConfirmOrder() {
        XxlJobHelper.log("开始执行订单自动确认任务");
        
        try {
            // 获取需要自动确认的订单
            LocalDateTime confirmTime = LocalDateTime.now().minusDays(15);
            List<Order> orders = orderService.getUnconfirmedOrders(confirmTime);
            
            XxlJobHelper.log("找到{}个需要自动确认的订单", orders.size());
            
            int successCount = 0;
            int failCount = 0;
            
            for (Order order : orders) {
                try {
                    // 确认收货
                    orderService.confirmReceipt(order.getId());
                    
                    // 发送通知
                    sendConfirmNotification(order);
                    
                    successCount++;
                    
                    // 记录关键订单信息
                    if (order.getAmount().compareTo(new BigDecimal("1000")) > 0) {
                        XxlJobHelper.log("大额订单自动确认:订单号={},金额={}", 
                            order.getOrderNo(), order.getAmount());
                    }
                } catch (Exception e) {
                    failCount++;
                    log.error("订单{}自动确认失败", order.getOrderNo(), e);
                    XxlJobHelper.log("订单{}自动确认失败:{}", order.getOrderNo(), e.getMessage());
                }
            }
            
            String result = String.format("自动确认完成,成功:%d,失败:%d", successCount, failCount);
            XxlJobHelper.log(result);
            XxlJobHelper.handleSuccess(result);
            
        } catch (Exception e) {
            XxlJobHelper.handleFail("任务执行异常:" + e.getMessage());
            throw e;
        }
    }
    
    /**
     * 2. 订单超时自动取消
     * 场景:未支付订单30分钟后自动取消
     */
    @XxlJob("autoCancelOrderJobHandler")
    public void autoCancelOrder() {
        XxlJobHelper.log("开始执行订单超时取消任务");
        
        // 获取分片参数,支持分布式处理
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();
        
        try {
            // 获取超时未支付的订单
            LocalDateTime timeoutTime = LocalDateTime.now().minusMinutes(30);
            List<Order> timeoutOrders = orderService.getUnpaidOrders(timeoutTime);
            
            // 分片处理
            List<Order> myOrders = filterOrdersBySharding(timeoutOrders, shardIndex, shardTotal);
            XxlJobHelper.log("分片{}/{},处理{}个超时订单", shardIndex, shardTotal, myOrders.size());
            
            // 使用CompletableFuture并行处理
            List<CompletableFuture<Void>> futures = myOrders.stream()
                .map(order -> CompletableFuture.runAsync(() -> {
                    try {
                        // 取消订单
                        orderService.cancelOrder(order.getId(), "超时未支付");
                        
                        // 恢复库存
                        orderService.restoreStock(order);
                        
                        // 释放优惠券
                        if (order.getCouponId() != null) {
                            orderService.releaseCoupon(order.getCouponId());
                        }
                        
                        log.info("订单{}已取消", order.getOrderNo());
                    } catch (Exception e) {
                        log.error("取消订单{}失败", order.getOrderNo(), e);
                    }
                }, executorService))
                .toList();
            
            // 等待所有任务完成
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
                .get(5, TimeUnit.MINUTES);
            
            XxlJobHelper.handleSuccess("处理完成");
            
        } catch (Exception e) {
            XxlJobHelper.handleFail("任务执行失败:" + e.getMessage());
        }
    }
    
    /**
     * 3. 每日营收统计报表
     * 场景:每天凌晨统计前一天的营收数据并发送报表
     */
    @XxlJob("dailyRevenueReportJobHandler")
    public void dailyRevenueReport() {
        XxlJobHelper.log("开始生成每日营收报表");
        
        try {
            // 获取统计日期(默认昨天)
            String dateParam = XxlJobHelper.getJobParam();
            LocalDate statDate = StringUtils.isBlank(dateParam) 
                ? LocalDate.now().minusDays(1) 
                : LocalDate.parse(dateParam);
            
            XxlJobHelper.log("统计日期:{}", statDate);
            
            // 统计各项数据
            RevenueReport report = new RevenueReport();
            report.setStatDate(statDate);
            
            // 订单统计
            report.setOrderCount(orderService.countOrdersByDate(statDate));
            report.setOrderAmount(orderService.sumOrderAmountByDate(statDate));
            
            // 支付统计
            report.setPaymentCount(orderService.countPaymentsByDate(statDate));
            report.setPaymentAmount(orderService.sumPaymentAmountByDate(statDate));
            
            // 退款统计
            report.setRefundCount(orderService.countRefundsByDate(statDate));
            report.setRefundAmount(orderService.sumRefundAmountByDate(statDate));
            
            // 计算关键指标
            report.setPaymentRate(calculateRate(report.getPaymentCount(), report.getOrderCount()));
            report.setAvgOrderAmount(calculateAvg(report.getOrderAmount(), report.getOrderCount()));
            
            // 保存报表
            orderService.saveRevenueReport(report);
            
            // 生成Excel报表
            String filePath = generateExcelReport(report);
            
            // 发送邮件
            sendReportEmail(report, filePath);
            
            XxlJobHelper.log("报表生成成功:订单数={},营收={}", 
                report.getOrderCount(), report.getOrderAmount());
            XxlJobHelper.handleSuccess("报表已生成并发送");
            
        } catch (Exception e) {
            XxlJobHelper.handleFail("生成报表失败:" + e.getMessage());
            
            // 发送告警
            sendAlertEmail("每日营收报表生成失败", e.getMessage());
        }
    }
    
    /**
     * 4. 订单数据归档
     * 场景:将3个月前的订单数据归档到历史表
     */
    @XxlJob("orderArchiveJobHandler")
    public void orderArchive() {
        XxlJobHelper.log("开始执行订单归档任务");
        
        try {
            // 计算归档日期
            LocalDate archiveDate = LocalDate.now().minusMonths(3);
            XxlJobHelper.log("归档{}之前的订单", archiveDate);
            
            // 分批归档,避免大事务
            int batchSize = 1000;
            int totalArchived = 0;
            
            while (true) {
                // 获取待归档订单
                List<Order> orders = orderService.getOrdersBeforeDate(archiveDate, batchSize);
                if (orders.isEmpty()) {
                    break;
                }
                
                // 归档订单
                int archived = orderService.archiveOrders(orders);
                totalArchived += archived;
                
                XxlJobHelper.log("已归档{}条订单", totalArchived);
                
                // 避免长时间占用资源
                Thread.sleep(100);
            }
            
            XxlJobHelper.handleSuccess(String.format("归档完成,共归档%d条订单", totalArchived));
            
        } catch (Exception e) {
            XxlJobHelper.handleFail("归档失败:" + e.getMessage());
        }
    }
    
    // 辅助方法
    private List<Order> filterOrdersBySharding(List<Order> orders, int shardIndex, int shardTotal) {
        return orders.stream()
            .filter(order -> Math.abs(order.getId().hashCode()) % shardTotal == shardIndex)
            .toList();
    }
    
    private void sendConfirmNotification(Order order) {
        // 发送确认收货通知
        log.info("发送确认收货通知:{}", order.getOrderNo());
    }
    
    private BigDecimal calculateRate(int count1, int count2) {
        if (count2 == 0) return BigDecimal.ZERO;
        return new BigDecimal(count1).divide(new BigDecimal(count2), 4, BigDecimal.ROUND_HALF_UP);
    }
    
    private BigDecimal calculateAvg(BigDecimal amount, int count) {
        if (count == 0) return BigDecimal.ZERO;
        return amount.divide(new BigDecimal(count), 2, BigDecimal.ROUND_HALF_UP);
    }
    
    private String generateExcelReport(RevenueReport report) {
        // 生成Excel报表
        return "/tmp/revenue_report_" + report.getStatDate() + ".xlsx";
    }
    
    private void sendReportEmail(RevenueReport report, String filePath) {
        // 发送报表邮件
        log.info("发送报表邮件");
    }
    
    private void sendAlertEmail(String subject, String content) {
        // 发送告警邮件
        log.error("发送告警邮件:{} - {}", subject, content);
    }
}

六、调度中心配置详解

6.1 创建执行器

登录调度中心

访问:http://localhost:8080/xxl-job-admin账号:admin / 123456

配置执行器

点击左侧菜单【执行器管理】点击【新增执行器】填写配置信息:



AppName:order-service-executor (必须与配置文件中的appname一致)
名称:订单服务执行器 (显示名称,便于识别)
注册方式:自动注册 (推荐,执行器自动注册到调度中心)
机器地址:不填 (自动注册时不需要填写)

6.2 创建任务

进入任务管理

点击左侧菜单【任务管理】选择对应的执行器点击【新增】

填写任务信息

基础配置:



执行器:选择刚创建的执行器
任务描述:订单自动确认任务
负责人:张三
报警邮件:zhangsan@example.com,lisi@example.com(多个用逗号分隔)

调度配置:



调度类型:
  - CRON:使用CRON表达式(最常用)
  - 固定速度:按固定间隔执行
  - 固定延迟:上次执行完成后延迟固定时间再执行
  - API:通过API触发
  - 手动:只能手动触发
 
CRON表达式:0 0 2 * * ? (每天凌晨2点执行)
 
CRON生成器使用:
  - 点击CRON输入框旁的图标
  - 选择执行规则
  - 自动生成表达式

任务配置:



运行模式:
  - BEAN模式:执行Spring Bean中的方法(推荐)
  - GLUE(Java):在线编辑Java代码
  - GLUE(Shell):执行Shell脚本
  - GLUE(Python):执行Python脚本
  - GLUE(PHP):执行PHP脚本
  - GLUE(NodeJS):执行NodeJS脚本
  - GLUE(PowerShell):执行PowerShell脚本
 
JobHandler:autoConfirmOrderJobHandler (对应@XxlJob注解的value)
执行参数:可选,传递给任务的参数

高级配置:



路由策略:
  - 第一个:固定选择第一个机器
  - 最后一个:固定选择最后一个机器
  - 轮询:依次选择执行
  - 随机:随机选择机器
  - 一致性HASH:根据任务参数Hash选择
  - 最不经常使用:选择使用频率最低的机器
  - 最近最久未使用:选择最久未使用的机器
  - 故障转移:顺序选择第一个健康的机器
  - 忙碌转移:选择不忙碌的机器
  - 分片广播:广播触发所有机器并分片
 
阻塞处理策略:
  - 单机串行(默认):新任务进入队列等待
  - 丢弃后续调度:丢弃新的调度请求
  - 覆盖之前调度:停止当前运行的任务并执行新任务
 
任务超时时间:0(不限制),单位秒
失败重试次数:3(失败后重试次数)

6.3 任务操作

创建任务后,可以进行以下操作:

启动/停止:控制任务的调度状态执行一次:立即触发一次任务执行(用于测试)查看日志:查看任务的执行历史和日志编辑:修改任务配置复制:复制任务配置创建新任务删除:删除任务

6.4 CRON表达式详解

CRON表达式由6或7个空格分隔的时间字段组成:


秒 分 时 日 月 周 [年]

字段说明:

字段 允许值 允许的特殊字符
0-59 , – * /
0-59 , – * /
0-23 , – * /
1-31 , – * / ? L W
1-12 , – * /
1-7 , – * / ? L #
1970-2099 , – * /

特殊字符说明:


*
:匹配任意值
?
:不指定值(日和周互斥时使用)
-
:范围,如10-12
,
:列举,如1,3,5
/
:增量,如0/15表示从0开始每15个
L
:最后,如月份中L表示最后一天
W
:工作日
#
:第几个,如2#3表示第3个星期二

常用CRON表达式示例:



每5秒执行:*/5 * * * * ?
每分钟执行:0 * * * * ?
每小时执行:0 0 * * * ?
每天0点执行:0 0 0 * * ?
每天8点30分执行:0 30 8 * * ?
每月1号0点执行:0 0 0 1 * ?
每周一0点执行:0 0 0 ? * MON
每月最后一天23点执行:0 0 23 L * ?
每天9-17点整点执行:0 0 9-17 * * ?
朝九晚五工作时间内每半小时:0 0/30 9-17 * * ?
每个季度第一天:0 0 0 1 1,4,7,10 ?

七、生产环境最佳实践

7.1 高可用部署

调度中心集群部署:



# docker-compose.yml
version: '3'
services:
  xxl-job-admin-1:
    image: xuxueli/xxl-job-admin:2.4.0
    container_name: xxl-job-admin-1
    ports:
      - "8080:8080"
    environment:
      PARAMS: "--spring.datasource.url=jdbc:mysql://mysql:3306/xxl_job"
    networks:
      - xxl-job-network
      
  xxl-job-admin-2:
    image: xuxueli/xxl-job-admin:2.4.0
    container_name: xxl-job-admin-2
    ports:
      - "8081:8080"
    environment:
      PARAMS: "--spring.datasource.url=jdbc:mysql://mysql:3306/xxl_job"
    networks:
      - xxl-job-network
      
  nginx:
    image: nginx:alpine
    container_name: xxl-job-nginx
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    networks:
      - xxl-job-network
      
networks:
  xxl-job-network:
    driver: bridge

Nginx配置:



upstream xxl-job-admin {
    server xxl-job-admin-1:8080 weight=1;
    server xxl-job-admin-2:8080 weight=1;
}
 
server {
    listen 80;
    location / {
        proxy_pass http://xxl-job-admin;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

7.2 任务编写规范



/**
 * 生产环境任务模板
 */
@Slf4j
@Component
public class ProductionJobTemplate {
    
    @XxlJob("productionJobHandler")
    public void execute() {
        // 1. 记录任务开始
        long startTime = System.currentTimeMillis();
        XxlJobHelper.log("任务开始执行,参数:{}", XxlJobHelper.getJobParam());
        
        // 2. 参数校验
        if (!validateParams()) {
            XxlJobHelper.handleFail("参数校验失败");
            return;
        }
        
        // 3. 幂等性检查
        if (isAlreadyExecuted()) {
            XxlJobHelper.log("任务已执行,跳过");
            XxlJobHelper.handleSuccess("任务已执行");
            return;
        }
        
        try {
            // 4. 执行业务逻辑
            BusinessResult result = doBusinessLogic();
            
            // 5. 记录执行结果
            recordExecutionResult(result);
            
            // 6. 性能监控
            long costTime = System.currentTimeMillis() - startTime;
            XxlJobHelper.log("任务执行成功,耗时:{}ms", costTime);
            
            // 7. 告警检查
            if (costTime > 60000) {
                sendAlert("任务执行时间过长:" + costTime + "ms");
            }
            
            XxlJobHelper.handleSuccess(result.toString());
            
        } catch (Exception e) {
            // 8. 异常处理
            log.error("任务执行失败", e);
            XxlJobHelper.log("任务执行失败:{}", e.getMessage());
            
            // 9. 告警通知
            sendAlert("任务执行失败:" + e.getMessage());
            
            // 10. 设置失败状态,触发重试
            XxlJobHelper.handleFail(e.getMessage());
        }
    }
    
    private boolean validateParams() {
        // 参数校验逻辑
        return true;
    }
    
    private boolean isAlreadyExecuted() {
        // 幂等性检查
        return false;
    }
    
    private BusinessResult doBusinessLogic() {
        // 业务逻辑
        return new BusinessResult();
    }
    
    private void recordExecutionResult(BusinessResult result) {
        // 记录执行结果
    }
    
    private void sendAlert(String message) {
        // 发送告警
    }
}

7.3 监控和告警

1. 配置邮件告警:



# application.properties
### 邮件告警配置
xxl.job.mail.host=smtp.qq.com
xxl.job.mail.port=25
xxl.job.mail.username=xxx@qq.com
xxl.job.mail.password=xxx
xxl.job.mail.sendFrom=xxx@qq.com
xxl.job.mail.sendNick=XXL-JOB

2. 自定义监控指标:



@Component
public class JobMetricsCollector {
    
    private final MeterRegistry meterRegistry;
    
    @XxlJob("metricsJobHandler")
    public void collectMetrics() {
        Timer.Sample sample = Timer.start(meterRegistry);
        
        try {
            // 执行任务
            doTask();
            
            // 记录成功次数
            meterRegistry.counter("xxl.job.success", "job", "metricsJob").increment();
            
        } catch (Exception e) {
            // 记录失败次数
            meterRegistry.counter("xxl.job.fail", "job", "metricsJob").increment();
            throw e;
        } finally {
            // 记录执行时间
            sample.stop(meterRegistry.timer("xxl.job.duration", "job", "metricsJob"));
        }
    }
}

7.4 性能优化

1. 数据库优化:



-- 添加索引优化查询
ALTER TABLE xxl_job_log ADD INDEX idx_trigger_time (trigger_time);
ALTER TABLE xxl_job_log ADD INDEX idx_handle_time (handle_time);
 
-- 定期清理历史数据
DELETE FROM xxl_job_log WHERE trigger_time < DATE_SUB(NOW(), INTERVAL 30 DAY);
 
-- 分区表(大数据量场景)
ALTER TABLE xxl_job_log PARTITION BY RANGE (TO_DAYS(trigger_time)) (
    PARTITION p202401 VALUES LESS THAN (TO_DAYS('2024-02-01')),
    PARTITION p202402 VALUES LESS THAN (TO_DAYS('2024-03-01'))
);

2. 执行器优化:



xxl:
  job:
    executor:
      # 增加执行线程池大小
      core-pool-size: 10
      max-pool-size: 20
      # 优化日志配置
      logpath: /data/applogs/xxl-job
      logretentiondays: 7  # 减少日志保留天数

7.5 故障处理

常见问题及解决方案:

问题 原因 解决方案
执行器注册失败 网络不通或TOKEN不一致 检查网络连接和TOKEN配置
任务不执行 任务未启动或CRON错误 检查任务状态和CRON表达式
任务重复执行 多个执行器同名或策略配置错误 检查执行器配置和路由策略
执行超时 任务执行时间过长 优化业务逻辑或调整超时时间
内存溢出 处理数据量过大 使用分片或分批处理

八、进阶特性

8.1 动态任务(GLUE模式)

GLUE模式允许在线编辑任务代码,无需重启服务:



// 在调度中心直接编写代码
package com.xxl.job.service.handler;
 
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.IJobHandler;
 
public class DemoGlueJobHandler extends IJobHandler {
    
    @Override
    public void execute() throws Exception {
        XxlJobHelper.log("GLUE模式任务执行");
        
        // 可以直接在这里修改代码,立即生效
        String param = XxlJobHelper.getJobParam();
        XxlJobHelper.log("参数:" + param);
        
        // 执行业务逻辑
        System.out.println("Hello XXL-JOB!");
        
        XxlJobHelper.handleSuccess();
    }
}

8.2 任务依赖

配置任务依赖关系,实现任务编排:



@Component
public class JobChainHandler {
    
    @XxlJob("parentJobHandler")
    public void parentJob() {
        XxlJobHelper.log("父任务执行");
        
        // 执行业务逻辑
        processParentLogic();
        
        // 触发子任务
        triggerChildJob();
        
        XxlJobHelper.handleSuccess();
    }
    
    @XxlJob("childJobHandler")
    public void childJob() {
        // 获取父任务传递的参数
        String parentData = XxlJobHelper.getJobParam();
        XxlJobHelper.log("子任务执行,接收参数:{}", parentData);
        
        processChildLogic(parentData);
        
        XxlJobHelper.handleSuccess();
    }
    
    private void triggerChildJob() {
        // 通过API触发子任务
        XxlJobHelper.log("触发子任务");
    }
}

8.3 跨语言支持

XXL-JOB支持多种语言的执行器:

Python执行器示例:



#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
import time
from xxl_job_executor import XxlJobExecutor
 
def test_job():
    """Python任务示例"""
    print("Python job executing...")
    time.sleep(2)
    return "success"
 
if __name__ == "__main__":
    # 初始化执行器
    executor = XxlJobExecutor(
        admin_address="http://127.0.0.1:8080/xxl-job-admin",
        app_name="python-executor",
        port=9998
    )
    
    # 注册任务
    executor.register_job("pythonTestJob", test_job)
    
    # 启动执行器
    executor.start()

九、总结

9.1 XXL-JOB的优势总结

简单易用:开箱即用,5分钟即可接入功能完善:支持任务管理、监控、告警等完整功能高可用:支持集群部署,故障自动转移高性能:支持任务分片,可处理海量数据动态调度:支持动态修改任务配置,无需重启跨语言:支持Java、Python、Shell等多种语言

9.2 适用场景

定时统计:每日报表、数据汇总定时清理:日志清理、缓存清理、过期数据清理定时同步:数据同步、状态同步批量处理:批量发送邮件、短信定时检查:健康检查、数据校验工作流编排:复杂的任务依赖和编排

9.3 技术选型建议

场景 推荐方案
简单定时任务(单机) Spring @Scheduled
分布式定时任务 XXL-JOB
复杂工作流编排 XXL-JOB + 工作流引擎
实时流处理 Flink/Storm
消息驱动任务 MQ + 消费者

9.4 踩坑总结

执行器端口冲突:确保执行器端口未被占用网络配置:容器部署时注意网络互通时区问题:注意服务器时区设置日志清理:定期清理日志避免磁盘满分片任务数据分配:注意数据均匀分配事务问题:长时间任务注意事务超时

十、快速开始清单

为了帮助你快速上手,这里提供一个检查清单:

[ ] 安装MySQL并创建数据库[ ] 部署调度中心(Admin)[ ] 在微服务中添加XXL-JOB依赖[ ] 配置执行器参数[ ] 编写第一个任务(使用@XxlJob注解)[ ] 在调度中心创建执行器[ ] 在调度中心创建任务[ ] 启动任务并测试[ ] 查看执行日志[ ] 配置告警邮箱

恭喜你!完成以上步骤,你就已经成功搭建了XXL-JOB分布式任务调度系统!

附录:相关资源

官方文档:https://www.xuxueli.com/xxl-job/GitHub地址:https://github.com/xuxueli/xxl-job社区论坛:https://www.xuxueli.com/xxl-job/#/?id=官方群视频教程:在B站搜索”XXL-JOB教程”


希望这份详细的教程能帮助你快速掌握XXL-JOB!如果有任何问题,欢迎查阅官方文档或在社区提问。祝你使用愉快!

© 版权声明

相关文章

暂无评论

none
暂无评论...