xxl-job,任務排程中心快速上手

PromiseForYou發表於2024-12-07

前言

XXL-JOB是一個可以在WEB介面配置執行定時任務中介軟體,支援分散式服務呼叫,XXL-JOB自身也可以部署多個節點組成叢集,本身是一個基於SpringBoot的Java WEB程式,我們可以透過下載GitHub原始碼進行部署。

一、XXL-JOB 安裝教程

進入 xxl-job官網,裡面有更詳細的教程 XXL-JOB官網

1、下載原始碼
git clone http://gitee.com/xuxueli0323/xxl-job

透過IDEA開啟後目錄如下:

2、初始化"排程資料庫"

“排程資料庫初始化SQL指令碼” 位置為:

`/xxl-job/doc/db/tables_xxl_job.sql`

沒錯,你得執行這個sql檔案到你的資料庫中,執行完後 會有如下庫和表出現在資料庫中

3、專案構造
  1. xxl-job-admin:排程中心
  2. xxl-job-core:公共依賴
  3. xxl-job-executor-samples:執行器Sample示例(選擇合適的版本執行器,可直接使用,也可以參考其並將現有專案改造成執行器)
    1. :xxl-job-executor-sample-springboot:Springboot版本,透過Springboot管理執行器,本文章以這種方式介紹,官方推薦該方式;
    2. :xxl-job-executor-sample-frameless:無框架版本;
好了~,到這一步你的XXL—JOB算是裝好了,下一步讓我們來啟動它

二、啟動排程中心前的準備

  1. 排程中心專案:xxl-job-admin 《超關鍵,起的就是這玩意兒》
  2. 作用:統一管理任務排程平臺上排程任務,負責觸發排程執行,並且提供任務管理平臺。

先修改一下排程中心xxl-job-admin的配置檔案。

`/xxl-job/xxl-job-admin/src/main/resources/application.properties`

主要修改資料庫為自己的剛才執行sql的庫

### xxl-job, datasource
spring.datasource.url=jdbc:mysql://123.45.678.90:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=xxx使用者
spring.datasource.password=xxx密碼
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

然後啟動專案

`xxl-job-admin/src/main/java/com/xxl/job/admin/XxlJobAdminApplication.java`

排程中心訪問地址:http://localhost:8080/xxl-job-admin (該地址執行器將會使用到,作為回撥地址)

預設登入賬號 “admin/123456”, 登入後執行介面如下圖所示。

三、啟動執行器專案

1、maven依賴

確認執行器專案的pom檔案中引入了 xxl-job-core 的maven依賴;

<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>${project.parent.version}</version> <!-- 跟隨排程中心的版本 -->
</dependency>
2、執行器配置

執行器配置,配置檔案地址:

/xxl-job/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties

執行器配置,配置內容說明:

# web port
server.port=8081
# no web
#spring.main.web-environment=false

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


### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
### 指向排程中心的地址
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin

### xxl-job, access token
xxl.job.accessToken=default_token

### xxl-job executor appname 執行器AppName [選填]:執行器心跳註冊分組依據;為空則關閉自動註冊
### 執行器分組名稱,關係到會註冊到哪個組裡
xxl.job.executor.appname=learn-xxl-job
### xxl-job executor registry-address: default use address to registry , otherwise use ip:port if address is null
xxl.job.executor.address=
### xxl-job executor server-info
xxl.job.executor.ip=
xxl.job.executor.port=9999
### xxl-job executor log-path
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job executor log-retention-days
xxl.job.executor.logretentiondays=30

主要就是xxl.job.admin.addressesxxl.job.executor.appname 這倆,第一個必須指向正確的排程中心地址

3、建立啟動配置檔案

因xxl-job沒有使用spring-boot-starter,需自行將配置類注入到spring容器中。

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;

/**
 * xxl-job config
 *
 * @author xuxueli 2017-04-28
 */
@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;
    }

    /**
     * 針對多網路卡、容器內部署等情況,可藉助 "spring-cloud-commons" 提供的 "InetUtils" 元件靈活定製註冊IP;
     *
     *      1、引入依賴:
     *          <dependency>
     *             <groupId>org.springframework.cloud</groupId>
     *             <artifactId>spring-cloud-commons</artifactId>
     *             <version>${version}</version>
     *         </dependency>
     *
     *      2、配置檔案,或者容器啟動變數
     *          spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
     *
     *      3、獲取IP
     *          String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
     */

}

然後啟動執行器,成功註冊後,在排程中心可以看到

四、編寫一個定時任務

1、建立一個任務排程

注意@XxlJob(“sendSMS”)註解中的sendSMS,即任務的唯一名稱,之後執行任務排程將會使用。

// 顯示在排程中心日誌的內容
XxlJobHelper.log("無引數執行一個定時/指定任務");
// 獲取任務引數
XxlJobHelper.getJobParam();
// 用於在任務執行失敗時向排程中心報告失敗資訊。排程中心會記錄任務的失敗狀態,並可以根據配置進行相應的處理(如重試、告警等)。
XxlJobHelper.handleFail("引數傳遞異常");
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @author Cyf
 * @ Date: 2024/12/6 下午5:23
 */
@Component
public class MyJob {
    public static final Logger log = LoggerFactory.getLogger(MyJob.class);
    
//	  可使用@Resource/@Autowire注入執行器裡中的其他服務
//    @Autowired
//    private UserService userService;

    /**
     * 無需引數傳遞
     * @throws Exception
     */
    @XxlJob("sendSMS")
    public void sendSMS() throws Exception {
        
        // 記錄任務開始的日誌
        //只會顯示再專案日誌中
        log.info("slf4j的框架");
        //只會顯示在xxl-job的日誌中
        XxlJobHelper.log("無引數執行一個定時/指定任務");
        
    }

    /**
     * 單個引數傳遞
     * @throws Exception
     */
    @XxlJob("oneParameter")
    public void sendMessage() throws Exception {
        XxlJobHelper.log("單個任務引數為:" + XxlJobHelper.getJobParam());
    }

    /**
     * 多個引數,應用 , 分割
     * @throws Exception
     */
    @XxlJob("moreParameters")
    public void sendMessage2() throws Exception {
        try {
            // 獲取引數
            String param = XxlJobHelper.getJobParam();
            String[] methodParams = param.split(",");

            XxlJobHelper.log("引數1為:" + methodParams[0] + ",引數2為" + methodParams[1]);
        }catch (Exception e){
            XxlJobHelper.handleFail("引數傳遞異常");
        }
    }
}

2、 xxl-job-admin中新增任務

在Cron中配置任務排程的時間週期,可選擇CRON或固定速度。JobHandler中需配置@XxlJob註解中的名稱。

3、執行任務

4、檢視執行日誌


專案日誌

18:23:05.259 logback [xxl-job, JobThread-9-1733566985234] INFO  c.x.j.e.service.jobhandler.MyJob - slf4j的框架
4、定製化攔截器

可以透過Spring Aop攔截@XxlJob註解,去處理一些通用業務邏輯。
例如追加TraceId 進行日誌定位

import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.LocalDateTime;
import java.util.UUID;

/**
 * @author 為每個執行器增加traceId
 */
@Slf4j
@Order(1)
@Aspect
@Component
public class XxlJobAspect {

    @Pointcut("@annotation(com.xxl.job.core.handler.annotation.XxlJob)")
    public void pointCut() {
    }

    @Around("pointCut() && @annotation(xxlJob)")
    public Object doAround(ProceedingJoinPoint point, XxlJob xxlJob) {
        String traceId = UUID.randomUUID().toString();
        MDC.put("traceId", traceId);

        String jobName = xxlJob.value();
        StopWatch sw = new StopWatch();
        sw.start();
        log.info("定時任務[{}]開始,開始時間:{},輸入引數:{}", jobName, LocalDateTime.now(), XxlJobHelper.getJobParam());
        Object proceed;
        try {
            proceed = point.proceed();
        } catch (Throwable e) {
            log.warn("定時任務[{}]執行失敗", jobName, e);
            failure(e, traceId);
            return null;
        }
        sw.stop();
        log.info("定時任務[{}]結束!執行時間:{} ms", jobName, sw.getTotalTimeMillis());
        success(traceId);
        return proceed;
    }

    private void failure(Throwable e, String traceId) {
        //將異常資訊輸出到xxl-job日誌中
        XxlJobHelper.handleFail("traceId=" + traceId + ",<br>exception=" + getStackTrace(e));
        MDC.remove("traceId");
    }

    private void success(String traceId) {
        XxlJobHelper.handleSuccess("traceId=" + traceId);
        MDC.remove("traceId");
    }

    /**
     * 該方法來捕獲異常的堆疊跟蹤資訊,並將其轉換為字串
     * @param e 異常資訊
     * @return 堆疊跟蹤字串
     */
    private String getStackTrace(Throwable e) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        e.printStackTrace(printWriter);
        return stringWriter.toString();
    }

}

在日誌中新增TraceId,快速定位任務鏈路

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="1 seconds">

    <contextName>logback</contextName>
    <property name="log.path" value="/data/applogs/xxl-job/xxl-job-executor-sample-springboot.log"/>
<!--    定義輸出格式 -->
    <!-- %X{traceId} 新增traceId到日誌中 -->
    <property name="PATTERN" value="%d{HH:mm:ss.SSS} %contextName [%thread] [%X{traceId}] %-5level %logger{36} - %msg%n" />
<!--   ConsoleAppender:表示日誌將輸出到控制檯。 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
<!--            引入格式 -->
            <pattern>${PATTERN}</pattern>
        </encoder>
    </appender>

    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}.%d{yyyy-MM-dd}.zip</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
            </pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="file"/>
        <!--   引用了前面定義的 STDOUT appender,表示所有符合 info 級別及以上的日誌訊息都將透過 STDOUT 輸出到控制檯。-->
        <appender-ref ref="STDOUT"/>
    </root>

</configuration>

然後重啟專案,再次執行任務將列印如下日誌

18:23:05.254 logback [xxl-job, JobThread-9-1733566985234] [531a6504-13bc-4bc6-91de-ff64b235110d] INFO  c.x.j.e.core.config.XxlJobAspect - 定時任務[sendSMS]開始,開始時間:2024-12-07T18:23:05.254,輸入引數:
18:23:05.259 logback [xxl-job, JobThread-9-1733566985234] [531a6504-13bc-4bc6-91de-ff64b235110d] INFO  c.x.j.e.service.jobhandler.MyJob - slf4j的框架
18:23:05.260 logback [xxl-job, JobThread-9-1733566985234] [531a6504-13bc-4bc6-91de-ff64b235110d] INFO  c.x.j.e.core.config.XxlJobAspect - 定時任務[sendSMS]結束!執行時間:10 ms

[531a6504-13bc-4bc6-91de-ff64b235110d] 為該次請求中的traceId,在排程日誌中也會有:

結束語

個人感覺這是個很強大的一款任務排程中心,從0到1幫助小白快速上手,如果此文章對你有幫助,希望留個贊再走 v

相關文章