手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(八) - XXL-JOB 整合與配置

xxyopen發表於2022-06-06

手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(一) - 介紹
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(二) - 資料庫設計
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(三) - 專案初始化
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(四) - 日誌 & 跨域配置
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(五) - MyBatis-Plus & 程式碼生成器整合與配置
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(六) - 本地快取 Caffeine 和 分散式快取 Redis 整合與配置
手把手教你使用 Spring Boot 3 開發上線一個前後端分離的生產級系統(七) - Elasticsearch 8.2 整合與配置

XXL-JOB 介紹

XXL-JOB 是一個開箱即用的開源分散式任務排程平臺,其核心設計目標是開發迅速、學習簡單、輕量級、易擴充套件。由排程模組和執行模組構成:

  • 排程模組(排程中心):

負責管理排程資訊,按照排程配置發出排程請求,自身不承擔業務程式碼。排程系統與任務解耦,提高了系統可用性和穩定性,同時排程系統效能不再受限於任務模組;支援視覺化、簡單且動態的管理排程資訊,包括任務新建,更新,刪除,GLUE開發和任務報警等,所有上述操作都會實時生效,同時支援監控排程結果以及執行日誌,支援執行器Failover。

  • 執行模組(執行器):

負責接收排程請求並執行任務邏輯。任務模組專注於任務的執行等操作,開發和維護更加簡單和高效;接收“排程中心”的執行請求、終止請求和日誌請求等。

XXL-JOB 架構圖

XXL-JOB 將排程行為抽象形成“排程中心”公共平臺,而平臺自身並不承擔業務邏輯,“排程中心”負責發起排程請求;將任務抽象成分散的JobHandler,交由“執行器”統一管理,“執行器”負責接收排程請求並執行對應的JobHandler中業務邏輯。因此,“排程”和“任務”兩部分可以相互解耦,提高系統整體穩定性和擴充套件性。

XXL-JOB 整合與配置

  1. 初始化如下的排程資料庫
#
# XXL-JOB v2.4.0-SNAPSHOT
# Copyright (c) 2015-present, xuxueli.

CREATE database if NOT EXISTS `xxl_job` default character set utf8mb4 collate utf8mb4_unicode_ci;
use `xxl_job`;

SET NAMES utf8mb4;

CREATE TABLE `xxl_job_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `job_group` int(11) NOT NULL COMMENT '執行器主鍵ID',
  `job_desc` varchar(255) NOT NULL,
  `add_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `author` varchar(64) DEFAULT NULL COMMENT '作者',
  `alarm_email` varchar(255) DEFAULT NULL COMMENT '報警郵件',
  `schedule_type` varchar(50) NOT NULL DEFAULT 'NONE' COMMENT '排程型別',
  `schedule_conf` varchar(128) DEFAULT NULL COMMENT '排程配置,值含義取決於排程型別',
  `misfire_strategy` varchar(50) NOT NULL DEFAULT 'DO_NOTHING' COMMENT '排程過期策略',
  `executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '執行器路由策略',
  `executor_handler` varchar(255) DEFAULT NULL COMMENT '執行器任務handler',
  `executor_param` varchar(512) DEFAULT NULL COMMENT '執行器任務引數',
  `executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞處理策略',
  `executor_timeout` int(11) NOT NULL DEFAULT '0' COMMENT '任務執行超時時間,單位秒',
  `executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失敗重試次數',
  `glue_type` varchar(50) NOT NULL COMMENT 'GLUE型別',
  `glue_source` mediumtext COMMENT 'GLUE原始碼',
  `glue_remark` varchar(128) DEFAULT NULL COMMENT 'GLUE備註',
  `glue_updatetime` datetime DEFAULT NULL COMMENT 'GLUE更新時間',
  `child_jobid` varchar(255) DEFAULT NULL COMMENT '子任務ID,多個逗號分隔',
  `trigger_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '排程狀態:0-停止,1-執行',
  `trigger_last_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '上次排程時間',
  `trigger_next_time` bigint(13) NOT NULL DEFAULT '0' COMMENT '下次排程時間',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `job_group` int(11) NOT NULL COMMENT '執行器主鍵ID',
  `job_id` int(11) NOT NULL COMMENT '任務,主鍵ID',
  `executor_address` varchar(255) DEFAULT NULL COMMENT '執行器地址,本次執行的地址',
  `executor_handler` varchar(255) DEFAULT NULL COMMENT '執行器任務handler',
  `executor_param` varchar(512) DEFAULT NULL COMMENT '執行器任務引數',
  `executor_sharding_param` varchar(20) DEFAULT NULL COMMENT '執行器任務分片引數,格式如 1/2',
  `executor_fail_retry_count` int(11) NOT NULL DEFAULT '0' COMMENT '失敗重試次數',
  `trigger_time` datetime DEFAULT NULL COMMENT '排程-時間',
  `trigger_code` int(11) NOT NULL COMMENT '排程-結果',
  `trigger_msg` text COMMENT '排程-日誌',
  `handle_time` datetime DEFAULT NULL COMMENT '執行-時間',
  `handle_code` int(11) NOT NULL COMMENT '執行-狀態',
  `handle_msg` text COMMENT '執行-日誌',
  `alarm_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '告警狀態:0-預設、1-無需告警、2-告警成功、3-告警失敗',
  PRIMARY KEY (`id`),
  KEY `I_trigger_time` (`trigger_time`),
  KEY `I_handle_code` (`handle_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_log_report` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `trigger_day` datetime DEFAULT NULL COMMENT '排程-時間',
  `running_count` int(11) NOT NULL DEFAULT '0' COMMENT '執行中-日誌數量',
  `suc_count` int(11) NOT NULL DEFAULT '0' COMMENT '執行成功-日誌數量',
  `fail_count` int(11) NOT NULL DEFAULT '0' COMMENT '執行失敗-日誌數量',
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `i_trigger_day` (`trigger_day`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_logglue` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `job_id` int(11) NOT NULL COMMENT '任務,主鍵ID',
  `glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE型別',
  `glue_source` mediumtext COMMENT 'GLUE原始碼',
  `glue_remark` varchar(128) NOT NULL COMMENT 'GLUE備註',
  `add_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_registry` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `registry_group` varchar(50) NOT NULL,
  `registry_key` varchar(255) NOT NULL,
  `registry_value` varchar(255) NOT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `i_g_k_v` (`registry_group`,`registry_key`,`registry_value`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_group` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `app_name` varchar(64) NOT NULL COMMENT '執行器AppName',
  `title` varchar(12) NOT NULL COMMENT '執行器名稱',
  `address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '執行器地址型別:0=自動註冊、1=手動錄入',
  `address_list` text COMMENT '執行器地址列表,多地址逗號分隔',
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '賬號',
  `password` varchar(50) NOT NULL COMMENT '密碼',
  `role` tinyint(4) NOT NULL COMMENT '角色:0-普通使用者、1-管理員',
  `permission` varchar(255) DEFAULT NULL COMMENT '許可權:執行器ID列表,多個逗號分割',
  PRIMARY KEY (`id`),
  UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `xxl_job_lock` (
  `lock_name` varchar(50) NOT NULL COMMENT '鎖名稱',
  PRIMARY KEY (`lock_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO `xxl_job_group`(`id`, `app_name`, `title`, `address_type`, `address_list`, `update_time`) VALUES (1, 'xxl-job-executor-sample', '示例執行器', 0, NULL, '2018-11-03 22:21:31' );
INSERT INTO `xxl_job_info`(`id`, `job_group`, `job_desc`, `add_time`, `update_time`, `author`, `alarm_email`, `schedule_type`, `schedule_conf`, `misfire_strategy`, `executor_route_strategy`, `executor_handler`, `executor_param`, `executor_block_strategy`, `executor_timeout`, `executor_fail_retry_count`, `glue_type`, `glue_source`, `glue_remark`, `glue_updatetime`, `child_jobid`) VALUES (1, 1, '測試任務1', '2018-11-03 22:21:31', '2018-11-03 22:21:31', 'XXL', '', 'CRON', '0 0 0 * * ? *', 'DO_NOTHING', 'FIRST', 'demoJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE程式碼初始化', '2018-11-03 22:21:31', '');
INSERT INTO `xxl_job_user`(`id`, `username`, `password`, `role`, `permission`) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
INSERT INTO `xxl_job_lock` ( `lock_name`) VALUES ( 'schedule_lock');

commit;

注:排程中心支援叢集部署,叢集情況下各節點務必連線同一個 mysql 例項,如果 mysql 做主從,排程中心叢集節點務必強制走主庫。

  1. Docker 映象方式搭建排程中心:
/**
* 如需自定義 mysql 等配置,可通過 "-e PARAMS" 指定,引數格式 PARAMS="--key=value  --key2=value2" ;
* 如需自定義 JVM 記憶體引數 等配置,可通過 "-e JAVA_OPTS" 指定,引數格式 JAVA_OPTS="-Xmx512m" ;
*/
docker run \
 -e PARAMS=' \
 --spring.datasource.url=jdbc:mysql://47.106.243.172:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai \
 --spring.datasource.username=test \
 --spring.datasource.password=test!1234 \
 --xxl.job.accessToken=123' \
 -p 8080:8080 \
 -v /tmp:/data/applogs \
 --name xxl-job-admin \
 -d xuxueli/xxl-job-admin:{指定版本} 

注:如上所示,資料庫密碼中如果包含特殊字元(例如,& 或 !),需要對特殊字元進行轉義,PARAMS 引數值一定要使用使用單引號而不能使用雙引號。

常用轉義字元 作用
反斜槓(\) 使反斜槓後面的一個變數變為單純的字串,如果放在引號裡面,是不起作用的
單引號(’’) 轉義其中所有的變數為單純的字串
雙引號("") 保留其中的變數屬性,不進行轉義處理

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

預設登入賬號 “admin/123456”,登入後如下圖所示:

XXL-JOB 首頁

  1. 專案中引入xxl-job-core的 maven 依賴:
<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>2.3.1</version>
</dependency>
  1. application.yml 中加入執行器配置:
# XXL-JOB 配置
xxl:
  job:
    admin:
      ### 排程中心部署根地址 [選填]:如排程中心叢集部署存在多個地址則用逗號分隔。執行器將會使用該地址進行"執行器心跳註冊"和"任務結果回撥";為空則關閉自動註冊;
      addresses: http://127.0.0.1:8080/xxl-job-admin
    executor:
      ### 執行器AppName [選填]:執行器心跳註冊分組依據;為空則關閉自動註冊
      appname: xxl-job-executor-novel
      ### 執行器執行日誌檔案儲存磁碟路徑 [選填] :需要對該路徑擁有讀寫許可權;為空則使用預設路徑;
      logpath: logs/xxl-job/jobhandler
    ### xxl-job, access token
    accessToken: 123
  1. io.github.xxyopen.novel.core.config包下建立 XXL-JOB 配置類配置執行器元件:
/**
 * XXL-JOB 配置類
 *
 * @author xiongxiaoyang
 * @date 2022/5/31
 */
@Configuration
@Slf4j
public class XxlJobConfig {

    @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.logpath}")
    private String logPath;

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        log.info(">>>>>>>>>>> xxl-job config init.");
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setAppname(appname);
        xxlJobSpringExecutor.setLogPath(logPath);
        return xxlJobSpringExecutor;
    }

}

Elasticsearch 小說資料同步任務示例

  1. 登入排程中心後臺,新增 novel 專案任務執行器:

建立任務執行器

注:AppName 的值需要和 novel 專案 application.yml 配置檔案中配置的值保持一致。

  1. 新增 Elasticsearch 資料同步任務:

建立任務

  1. io.github.xxyopen.novel.core.task包下增加 Elasticsearch 資料同步任務:
/**
 * 小說資料同步到 Elasticsearch 任務
 *
 * @author xiongxiaoyang
 * @date 2022/5/23
 */
@ConditionalOnProperty(prefix = "spring.elasticsearch", name = "enable", havingValue = "true")
@Component
@RequiredArgsConstructor
@Slf4j
public class BookToEsTask {

    private final BookInfoMapper bookInfoMapper;

    private final ElasticsearchClient elasticsearchClient;

    @SneakyThrows
    @XxlJob("saveToEsJobHandler") // 此處需要和排程中心建立任務時填寫的 JobHandler 值保持一致
    public ReturnT<String> saveToEs() {
         try {
            QueryWrapper<BookInfo> queryWrapper = new QueryWrapper<>();
            List<BookInfo> bookInfos;
            long maxId = 0;
            for (; ; ) {
                queryWrapper.clear();
                queryWrapper
                        .orderByAsc(DatabaseConsts.CommonColumnEnum.ID.getName())
                        .gt(DatabaseConsts.CommonColumnEnum.ID.getName(), maxId)
                        .last(DatabaseConsts.SqlEnum.LIMIT_30.getSql());
                bookInfos = bookInfoMapper.selectList(queryWrapper);
                if (bookInfos.isEmpty()) {
                    break;
                }
                BulkRequest.Builder br = new BulkRequest.Builder();

                for (BookInfo book : bookInfos) {
                    br.operations(op -> op
                            .index(idx -> idx
                                    .index(EsConsts.BookIndex.INDEX_NAME)
                                    .id(book.getId().toString())
                                    .document(EsBookDto.build(book))
                            )
                    ).timeout(Time.of(t -> t.time("10s")));
                    maxId = book.getId();
                }

                BulkResponse result = elasticsearchClient.bulk(br.build());

                // Log errors, if any
                if (result.errors()) {
                    log.error("Bulk had errors");
                    for (BulkResponseItem item : result.items()) {
                        if (item.error() != null) {
                            log.error(item.error().reason());
                        }
                    }
                }
            }
            return ReturnT.SUCCESS;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return ReturnT.FAIL;
        }
    }

}

  1. 檢視任務執行器,可以發現已經有一臺機器自動註冊:

線上序號產生器器

  1. 進入任務管理,我們可以啟動 Elasticsearch 資料同步任務,由配置的 Cron 表示式進行任務排程;也可以選擇手動觸發一次任務執行:

任務啟動

此時,我們可以在任意時刻手動同步資料庫的小說資料到 Elasticsearch 搜尋引擎中,極大的方便了我們的開發測試工作。

相關文章