XXL - Job 实战:从入门到应对高并发任务

XXL - Job 实战:从入门到应对高并发任务

引言

在微服务架构盛行的今天,定时任务的管理和调度变得尤为重要。XXL - Job 作为一个分布式任务调度平台,以其简单易用、功能强大的特点,受到了众多开发者的青睐。本文将详细介绍如何在 Java 8 环境的微服务项目中,利用 Docker 部署并使用 XXL - Job,同时探讨如何应对大量任务同时执行的场景。

一、XXL - Job 调度中心部署

1.1 准备数据库

XXL - Job 需要 MySQL 数据库来存储任务调度相关的数据。首先创建一个名为 xxl_job 的数据库,然后执行 xxl - job/doc/db/tables_xxl_job.sql 脚本。

1.2 拉取并运行 Docker 镜像

docker pull xuxueli/xxl-job-admin:2.3.1
docker run -e PARAMS="--spring.datasource.url=jdbc:mysql://<your_mysql_host>:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai --spring.datasource.username=<your_mysql_username> --spring.datasource.password=<your_mysql_password>" -p 8080:8080 -v /tmp:/data/applogs --name xxl-job-admin -d xuxueli/xxl-job-admin:2.3.1

<your_mysql_host><your_mysql_username><your_mysql_password> 替换为实际的数据库信息。

二、微服务项目引入 XXL - Job 执行器

2.1 添加依赖

在项目的 pom.xml 中添加以下依赖:

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

2.2 配置执行器

application.properties 中添加配置:

xxl.job.admin.addresses=http://<your_xxl_job_admin_host>:8080/xxl-job-admin
xxl.job.accessToken=
xxl.job.executor.appname=your_executor_appname
xxl.job.executor.address=
xxl.job.executor.ip=
xxl.job.executor.port=9999
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
xxl.job.executor.logretentiondays=30

<your_xxl_job_admin_host>your_executor_appname 替换为实际信息。

2.3 创建执行器配置类

import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class XxlJobConfig {

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses("http://<your_xxl_job_admin_host>:8080/xxl-job-admin");
        xxlJobSpringExecutor.setAppname("your_executor_appname");
        xxlJobSpringExecutor.setIp("");
        xxlJobSpringExecutor.setPort(9999);
        xxlJobSpringExecutor.setAccessToken("");
        xxlJobSpringExecutor.setLogPath("/data/applogs/xxl-job/jobhandler");
        xxlJobSpringExecutor.setLogRetentionDays(30);
        return xxlJobSpringExecutor;
    }
}

2.4 创建任务示例

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.springframework.stereotype.Component;

@Component
public class SampleXxlJob {

    @XxlJob("demoJobHandler")
    public void demoJobHandler() throws Exception {
        XxlJobHelper.log("XXL-Job sample job start.");
        // 业务逻辑
        XxlJobHelper.log("XXL-Job sample job end.");
    }
}

2.5 打包并部署到 Docker

创建 Dockerfile

FROM openjdk:8-jdk-alpine
WORKDIR /app
COPY target/your-service.jar app.jar
EXPOSE 9999
CMD ["java", "-jar", "app.jar"]

构建并运行 Docker 容器:

docker build -t your-service:1.0 .
docker run -p 9999:9999 --name your-service -d your-service:1.0

2.6 在调度中心配置任务

访问 http://<your_xxl_job_admin_host>:8080/xxl-job-admin,使用默认用户名 admin 和密码 123456 登录。在“执行器管理”中新增执行器,AppName 填写 your_executor_appname;在“任务管理”中新增任务,选择对应的执行器和任务处理器(如 demoJobHandler),配置调度策略和执行参数等。

三、常见问题及解决办法

3.1 访问令牌错误

在日志中可能会出现 The access token is wrong. 的错误,这是因为执行器和调度中心的访问令牌不一致。可以通过以下方式解决:

  • 确保调度中心和执行器的 accessToken 配置相同。
  • 若不想使用访问令牌,可将其置为空。

3.2 大量任务同时执行问题

当有大量任务(如 100 个或 500 个)都在同一时间(如早晨 11 点)执行时,可能会出现任务丢失未执行的情况,原因及解决办法如下:

3.2.1 调度中心负载过高

  • 原因:同一时刻触发大量任务,调度中心处理能力达到极限。
  • 解决办法:采用集群部署方式,通过负载均衡器将任务调度请求分发到多个调度中心实例上;为调度中心所在服务器分配足够的硬件资源,并调整 JVM 参数。

3.2.2 执行器资源不足

  • 原因:执行器的线程池、数据库连接池等资源被迅速耗尽。
  • 解决办法:调整执行器线程池大小和队列容量,例如:
    xxl.job.executor.threadpool.core-size=200
    xxl.job.executor.threadpool.max-size=500
    xxl.job.executor.threadpool.queue-capacity=1000
    同时,增加数据库连接池大小:
    spring.datasource.hikari.maximum-pool-size=200
    spring.datasource.hikari.minimum-idle=50

3.2.3 网络问题

  • 原因:调度中心和执行器之间的网络故障、丢包或延迟过高。
  • 解决办法:对网络进行实时监控,使用负载均衡器均衡网络流量。

3.2.4 任务阻塞策略

  • 原因:任务的阻塞策略设置不合理,部分任务可能被丢弃。
  • 解决办法:选择合适的阻塞策略,如串行执行策略:
    createParams.put("executorBlockStrategy", ExecutorBlockStrategyEnum.SERIAL_EXECUTION.name());

3.2.5 任务分散执行

为避免所有任务在同一时刻集中触发,可以将任务的执行时间分散在 11 点前后的一小段时间内:

import java.util.ArrayList;
import java.util.List;

public class CronDistributor {
    public static List<String> distributeCron(int taskCount, int startSecond, int endSecond) {
        List<String> cronExpressions = new ArrayList<>();
        int interval = (endSecond - startSecond) / taskCount;
        for (int i = 0; i < taskCount; i++) {
            int second = startSecond + i * interval;
            cronExpressions.add(second + " 0 11 * * ?");
        }
        return cronExpressions;
    }
}

四、代码创建和管理定时任务

可以通过 XXL - Job 提供的 API 以代码的方式创建和管理定时任务,示例代码如下:

4.1 创建任务处理类

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.springframework.stereotype.Component;

@Component
public class FiveSecondJob {

    @XxlJob("fiveSecondJobHandler")
    public void fiveSecondJobHandler() {
        try {
            XxlJobHelper.log("Five second job started.");
            System.out.println("This job is executed every 5 seconds.");
            XxlJobHelper.log("Five second job finished.");
        } catch (Exception e) {
            XxlJobHelper.log("Error occurred in five second job: {}", e.getMessage());
            XxlJobHelper.handleFail("Job execution failed due to an exception.");
        }
    }
}

4.2 使用 API 创建和启动任务

import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.enums.MisfireStrategyEnum;
import com.xxl.job.core.enums.RouteStrategyEnum;
import com.xxl.job.core.util.XxlJobRemotingUtil;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;

@Service
public class XxlJobManager {

    private static final String XXL_JOB_ADMIN_ADDRESS = "http://<your_xxl_job_admin_host>:8080/xxl-job-admin";
    private static final String ACCESS_TOKEN = "";

    public void createAndStartJob() {
        Map<String, String> createParams = new HashMap<>();
        // 配置任务参数
        ReturnT<String> createResult = XxlJobRemotingUtil.postBody(XXL_JOB_ADMIN_ADDRESS + "/jobinfo/add", ACCESS_TOKEN, 5000, createParams, String.class);
        if (createResult.getCode() == ReturnT.SUCCESS_CODE) {
            int jobId = Integer.parseInt(createResult.getContent());
            Map<String, Object> startParams = new HashMap<>();
            startParams.put("id", jobId);
            ReturnT<String> startResult = XxlJobRemotingUtil.postBody(XXL_JOB_ADMIN_ADDRESS + "/jobinfo/start", ACCESS_TOKEN, 5000, startParams, String.class);
            if (startResult.getCode() == ReturnT.SUCCESS_CODE) {
                System.out.println("Job started successfully.");
            } else {
                System.out.println("Failed to start job: " + startResult.getMsg());
            }
        } else {
            System.out.println("Failed to create job: " + createResult.getMsg());
        }
    }
}

4.3 调用创建和启动任务的方法

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class YourApplication implements CommandLineRunner {

    @Autowired
    private XxlJobManager xxlJobManager;

    public static void main(String[] args) {
        SpringApplication.run(YourApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        xxlJobManager.createAndStartJob();
    }
}

五、根据页面选择时间创建定时任务

假设需要根据页面选择的开始时间和结束时间,让任务每天早上 11 点执行,可以按照以下步骤实现:

5.1 接收前端传递的时间

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;

@RestController
public class JobController {

    @Autowired
    private XxlJobService xxlJobService;

    @PostMapping("/createJob")
    public String createJob(@RequestBody Map<String, String> request) {
        String startTime = request.get("startTime");
        String endTime = request.get("endTime");
        return xxlJobService.createJob(startTime, endTime);
    }
}

5.2 生成 Cron 表达式

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class CronGenerator {

    public static String generateCron(String startTime, String endTime) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date startDate = sdf.parse(startTime);
            Date endDate = sdf.parse(endTime);
            Calendar startCal = Calendar.getInstance();
            startCal.setTime(startDate);
            Calendar endCal = Calendar.getInstance();
            endCal.setTime(endDate);
            int startDay = startCal.get(Calendar.DAY_OF_MONTH);
            int endDay = endCal.get(Calendar.DAY_OF_MONTH);
            int startMonth = startCal.get(Calendar.MONTH) + 1;
            int endMonth = endCal.get(Calendar.MONTH) + 1;
            int startYear = startCal.get(Calendar.YEAR);
            int endYear = endCal.get(Calendar.YEAR);
            return "0 0 11 " + startDay + "-" + endDay + " " + startMonth + "-" + endMonth + " ? " + startYear + "-" + endYear;
        } catch (ParseException e) {
            e.printStackTrace();
            return null;
        }
    }
}

5.3 使用 XXL - Job API 创建和启动任务

import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.enums.MisfireStrategyEnum;
import com.xxl.job.core.enums.RouteStrategyEnum;
import com.xxl.job.core.util.XxlJobRemotingUtil;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;

@Service
public class XxlJobService {

    private static final String XXL_JOB_ADMIN_ADDRESS = "http://<your_xxl_job_admin_host>:8080/xxl-job-admin";
    private static final String ACCESS_TOKEN = "";

    public String createJob(String startTime, String endTime) {
        String cron = CronGenerator.generateCron(startTime, endTime);
        if (cron == null) {
            return "Failed to generate cron expression.";
        }
        Map<String, String> createParams = new HashMap<>();
        // 配置任务参数
        ReturnT<String> createResult = XxlJobRemotingUtil.postBody(XXL_JOB_ADMIN_ADDRESS + "/jobinfo/add", ACCESS_TOKEN, 5000, createParams, String.class);
        if (createResult.getCode() == ReturnT.SUCCESS_CODE) {
            int jobId = Integer.parseInt(createResult.getContent());
            Map<String, Object> startParams = new HashMap<>();
            startParams.put("id", jobId);
            ReturnT<String> startResult = XxlJobRemotingUtil.postBody(XXL_JOB_ADMIN_ADDRESS + "/jobinfo/start", ACCESS_TOKEN, 5000, startParams, String.class);
            if (startResult.getCode() == ReturnT.SUCCESS_CODE) {
                return "Job created and started successfully.";
            } else {
                return "Failed to start job: " + startResult.getMsg();
            }
        } else {
            return "Failed to create job: " + createResult.getMsg();
        }
    }
}

5.4 任务处理类

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.springframework.stereotype.Component;

@Component
public class YourJobHandler {

    @XxlJob("yourJobHandler")
    public void yourJobHandler() {
        try {
            XxlJobHelper.log("Job started.");
            System.out.println("This job is executed at 11:00 AM between selected dates.");
            XxlJobHelper.log("Job finished.");
        } catch (Exception e) {
            XxlJobHelper.log("Error occurred in job: {}", e.getMessage());
            XxlJobHelper.handleFail("Job execution failed due to an exception.");
        }
    }
}

六、总结

通过以上步骤,我们详细介绍了 XXL - Job 的部署、使用方法,以及如何应对大量任务同时执行的场景。在实际应用中,需要根据具体的业务需求和系统资源情况,对 XXL - Job 进行合理的配置和优化。

本作品采用《CC 协议》,转载必须注明作者和本文链接
MissYou-Coding
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
Coding Peasant @ 互联网
文章
193
粉丝
10
喜欢
60
收藏
63
排名:602
访问:1.3 万
私信
所有博文
博客标签
社区赞助商