手把手教你使用 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 將排程行為抽象形成“排程中心”公共平臺,而平臺自身並不承擔業務邏輯,“排程中心”負責發起排程請求;將任務抽象成分散的JobHandler,交由“執行器”統一管理,“執行器”負責接收排程請求並執行對應的JobHandler中業務邏輯。因此,“排程”和“任務”兩部分可以相互解耦,提高系統整體穩定性和擴充套件性。
XXL-JOB 整合與配置
- 初始化如下的
排程資料庫
:
#
# 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 做主從,排程中心叢集節點務必強制走主庫。
- 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-core
的 maven 依賴:
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.3.1</version>
</dependency>
- 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
- 在
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 小說資料同步任務示例
- 登入排程中心後臺,新增 novel 專案任務執行器:
注:AppName 的值需要和 novel 專案 application.yml 配置檔案中配置的值保持一致。
- 新增 Elasticsearch 資料同步任務:
- 在
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;
}
}
}
- 檢視任務執行器,可以發現已經有一臺機器自動註冊:
- 進入任務管理,我們可以啟動 Elasticsearch 資料同步任務,由配置的 Cron 表示式進行任務排程;也可以選擇手動觸發一次任務執行:
此時,我們可以在任意時刻手動同步資料庫的小說資料到 Elasticsearch 搜尋引擎中,極大的方便了我們的開發測試工作。