一 简单了解
目前,我们的定时任务都是基于SpringTask来实现的。但是SpringTask存在一些问题:
-
当微服务多实例部署时,定时任务会被执行多次。而事实上我们只需要这个任务被执行一次即可。
-
我们除了要定时创建表,还要定时持久化Redis数据到数据库,我们希望这多个定时任务能够按照顺序依次执行,SpringTask无法控制任务顺序
不仅仅是SpringTask,其它单机使用的定时任务工具,都无法实现像这种任务执行者的调度、任务执行顺序的编排、任务监控等功能。这些功能必须要用到分布式任务调度组件。
第一节 SpringTask
我们先来看看普通定时任务的实现原理,一般定时任务中会有两个组件:
-
任务:要执行的代码
-
任务触发器:基于定义好的规则触发任务
而多实例部署的时候,每个启动的服务实例都会有自己的任务触发器,这样就会导致各个实例各自运行,无法统一控制:
SpringTask存在一些问题:
-
当微服务多实例部署时,定时任务会被执行多次。而事实上我们只需要这个任务被执行一次即可。
-
我们除了要定时创建表,还要定时持久化Redis数据到数据库,我们希望这多个定时任务能够按照顺序依次执行,SpringTask无法控制任务顺序
不仅仅是SpringTask,其它单机使用的定时任务工具,都无法实现像这种任务执行者的调度、任务执行顺序的编排、任务监控等功能。这些功能必须要用到分布式任务调度组件。
第二节 分布式调度
把任务触发器提取到各个服务实例之外,去做统一的触发、统一的调度
第三节 计数选型
其中:
-
Quartz由于功能相对比较落后,现在已经很少被使用了。
-
SchedulerX是阿里巴巴的云产品,收费。
-
PowerJob是阿里员工自己开源的一个组件,功能非常强大,不过目前市值占比还不高,还需要等待市场检验。
-
XXL-JOB:开源免费,功能虽然不如PowerJob,不过目前市场占比最高,稳定性有保证。
第二章 简单入门
第一节 了解
XXL-JOB的运行原理和架构如图:(简化过后的)
进行多实例部署时,多个执行器会注册在注册器管理器上,这时候如果要执行任务,就可以像nacos一样利用某种算法选择一个执行器来完成,不会出现SpringTask多个实例中的任务各自都执行的情况
XXL-JOB分为两部分:
-
执行器:我们的服务引入一个XXL-JOB的依赖,就可以通过配置创建一个执行器。负责与XXL-JOB调度中心交互,执行本地任务。
-
调度中心:一个独立服务,负责管理执行器、管理任务、任务执行的调度、任务结果和日志收集。
第二节 docker部署
分为两步:
1. 运行初始化SQL,创建数据库表
-- --------------------------------------------------------
-- 主机: 192.168.150.101
-- 服务器版本: 8.0.29 - MySQL Community Server - GPL
-- 服务器操作系统: Linux
-- HeidiSQL 版本: 12.2.0.6576
-- --------------------------------------------------------
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
-- 导出 xxl_job 的数据库结构
CREATE DATABASE IF NOT EXISTS `xxl_job` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `xxl_job`;
-- 导出 表 xxl_job.xxl_job_group 结构
CREATE TABLE IF NOT EXISTS `xxl_job_group` (
`id` int NOT NULL AUTO_INCREMENT,
`app_name` varchar(64) NOT NULL COMMENT '执行器AppName',
`title` varchar(12) NOT NULL COMMENT '执行器名称',
`address_type` tinyint NOT NULL DEFAULT '0' COMMENT '执行器地址类型:0=自动注册、1=手动录入',
`address_list` text COMMENT '执行器地址列表,多地址逗号分隔',
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 正在导出表 xxl_job.xxl_job_group 的数据:~2 rows (大约)
DELETE FROM `xxl_job_group`;
INSERT INTO `xxl_job_group` (`id`, `app_name`, `title`, `address_type`, `address_list`, `update_time`) VALUES
(2, 'learning-service', '学习服务执行器', 0, NULL, '2023-02-16 20:50:48'),
(4, 'trade-service', '交易服务执行器', 0, NULL, '2023-02-16 20:50:48'),
(5, 'pay-service', '支付服务', 0, NULL, '2023-02-16 20:50:48'),
(6, 'promotion-service', '促销中心', 0, NULL, '2023-02-16 20:50:48');
-- 导出 表 xxl_job.xxl_job_info 结构
CREATE TABLE IF NOT EXISTS `xxl_job_info` (
`id` int NOT NULL AUTO_INCREMENT,
`job_group` int 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 NOT NULL DEFAULT '0' COMMENT '任务执行超时时间,单位秒',
`executor_fail_retry_count` int 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 NOT NULL DEFAULT '0' COMMENT '调度状态:0-停止,1-运行',
`trigger_last_time` bigint NOT NULL DEFAULT '0' COMMENT '上次调度时间',
`trigger_next_time` bigint NOT NULL DEFAULT '0' COMMENT '下次调度时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 正在导出表 xxl_job.xxl_job_info 的数据:~5 rows (大约)
DELETE FROM `xxl_job_info`;
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`, `trigger_status`, `trigger_last_time`, `trigger_next_time`) VALUES
(3, 4, '退款单处理', '2022-08-31 18:05:11', '2022-09-05 18:45:58', 'huyi.zhang', '', 'FIX_RATE', '300', 'DO_NOTHING', 'SHARDING_BROADCAST', 'refundRequestJobHandler', '5', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2022-08-31 18:05:11', '', 0, 0, 0),
(4, 2, '持久化积分排行榜', '2022-09-02 16:57:29', '2022-09-02 16:57:29', 'huyi.zhang', '', 'CRON', '1 0 0 1 * ? *', 'DO_NOTHING', 'FIRST', 'initSeasonJobHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2022-09-02 16:57:29', '', 0, 0, 0),
(5, 2, '持久化点赞数据', '2022-09-02 16:58:10', '2022-09-02 16:58:10', 'huyi.zhang', '', 'FIX_RATE', '120', 'DO_NOTHING', 'FIRST', 'replyLikesJobHandler', '500', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2022-09-02 16:58:10', '', 0, 0, 0),
(6, 5, '未支付订单检查', '2022-09-05 18:41:46', '2022-09-05 19:17:59', 'huyi.zhang', '', 'FIX_RATE', '120', 'DO_NOTHING', 'SHARDING_BROADCAST', 'payOrderCheckHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2022-09-05 18:41:46', '', 0, 0, 0),
(7, 5, '待退款订单检查', '2022-09-05 20:02:29', '2022-09-05 20:02:46', 'huyi.zhang', '', 'FIX_RATE', '123', 'DO_NOTHING', 'SHARDING_BROADCAST', 'refundOrderCheckHandler', '', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2022-09-05 20:02:29', '', 0, 0, 0),
(8, 6, '优惠券发放状态处理', '2022-09-16 12:23:07', '2022-09-16 12:23:07', 'huyi.zhang', '', 'FIX_RATE', '120', 'DO_NOTHING', 'SHARDING_BROADCAST', 'couponIssueJobHandler', '5', 'SERIAL_EXECUTION', 0, 0, 'BEAN', '', 'GLUE代码初始化', '2022-09-16 12:23:07', '', 0, 0, 0);
-- 导出 表 xxl_job.xxl_job_lock 结构
CREATE TABLE IF NOT EXISTS `xxl_job_lock` (
`lock_name` varchar(50) NOT NULL COMMENT '锁名称',
PRIMARY KEY (`lock_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 正在导出表 xxl_job.xxl_job_lock 的数据:~0 rows (大约)
DELETE FROM `xxl_job_lock`;
INSERT INTO `xxl_job_lock` (`lock_name`) VALUES
('schedule_lock');
-- 导出 表 xxl_job.xxl_job_log 结构
CREATE TABLE IF NOT EXISTS `xxl_job_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`job_group` int NOT NULL COMMENT '执行器主键ID',
`job_id` int 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 NOT NULL DEFAULT '0' COMMENT '失败重试次数',
`trigger_time` datetime DEFAULT NULL COMMENT '调度-时间',
`trigger_code` int NOT NULL COMMENT '调度-结果',
`trigger_msg` text COMMENT '调度-日志',
`handle_time` datetime DEFAULT NULL COMMENT '执行-时间',
`handle_code` int NOT NULL COMMENT '执行-状态',
`handle_msg` text COMMENT '执行-日志',
`alarm_status` tinyint 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 AUTO_INCREMENT=92 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 正在导出表 xxl_job.xxl_job_log 的数据:~0 rows (大约)
DELETE FROM `xxl_job_log`;
-- 导出 表 xxl_job.xxl_job_logglue 结构
CREATE TABLE IF NOT EXISTS `xxl_job_logglue` (
`id` int NOT NULL AUTO_INCREMENT,
`job_id` int 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 COLLATE=utf8mb4_0900_ai_ci;
-- 正在导出表 xxl_job.xxl_job_logglue 的数据:~0 rows (大约)
DELETE FROM `xxl_job_logglue`;
-- 导出 表 xxl_job.xxl_job_log_report 结构
CREATE TABLE IF NOT EXISTS `xxl_job_log_report` (
`id` int NOT NULL AUTO_INCREMENT,
`trigger_day` datetime DEFAULT NULL COMMENT '调度-时间',
`running_count` int NOT NULL DEFAULT '0' COMMENT '运行中-日志数量',
`suc_count` int NOT NULL DEFAULT '0' COMMENT '执行成功-日志数量',
`fail_count` int NOT NULL DEFAULT '0' COMMENT '执行失败-日志数量',
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `i_trigger_day` (`trigger_day`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=173 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 正在导出表 xxl_job.xxl_job_log_report 的数据:~136 rows (大约)
DELETE FROM `xxl_job_log_report`;
INSERT INTO `xxl_job_log_report` (`id`, `trigger_day`, `running_count`, `suc_count`, `fail_count`, `update_time`) VALUES
(1, '2022-06-14 00:00:00', 0, 0, 0, NULL),
(2, '2022-06-13 00:00:00', 0, 0, 0, NULL),
(3, '2022-06-12 00:00:00', 0, 0, 0, NULL),
(4, '2022-08-16 00:00:00', 0, 0, 0, NULL),
(5, '2022-08-15 00:00:00', 0, 0, 0, NULL),
(6, '2022-08-14 00:00:00', 0, 0, 0, NULL),
(7, '2022-08-17 00:00:00', 0, 0, 0, NULL),
(8, '2022-08-20 00:00:00', 0, 0, 0, NULL),
(9, '2022-08-19 00:00:00', 0, 0, 0, NULL),
(10, '2022-08-18 00:00:00', 0, 0, 0, NULL),
(11, '2022-08-21 00:00:00', 0, 0, 0, NULL),
(12, '2022-08-22 00:00:00', 0, 0, 0, NULL),
(13, '2022-08-23 00:00:00', 0, 0, 0, NULL),
(14, '2022-08-24 00:00:00', 0, 0, 0, NULL),
(15, '2022-08-25 00:00:00', 0, 0, 0, NULL),
(16, '2022-08-26 00:00:00', 0, 0, 0, NULL),
(17, '2022-08-27 00:00:00', 0, 0, 0, NULL),
(18, '2022-08-29 00:00:00', 0, 0, 0, NULL),
(19, '2022-08-28 00:00:00', 0, 0, 0, NULL),
(20, '2022-08-30 00:00:00', 0, 0, 0, NULL),
(21, '2022-08-31 00:00:00', 0, 6, 6, NULL),
(22, '2022-09-02 00:00:00', 0, 2, 1, NULL),
(23, '2022-09-01 00:00:00', 0, 0, 0, NULL),
(24, '2022-09-03 00:00:00', 0, 0, 0, NULL),
(25, '2022-09-05 00:00:00', 0, 5, 6, NULL),
(26, '2022-09-04 00:00:00', 0, 0, 0, NULL),
(27, '2022-09-06 00:00:00', 0, 0, 0, NULL),
(28, '2022-09-07 00:00:00', 0, 0, 0, NULL),
(29, '2022-09-08 00:00:00', 0, 0, 0, NULL),
(30, '2022-09-09 00:00:00', 0, 0, 0, NULL),
(31, '2022-09-13 00:00:00', 0, 0, 0, NULL),
(32, '2022-09-12 00:00:00', 0, 0, 0, NULL),
(33, '2022-09-11 00:00:00', 0, 0, 0, NULL),
(34, '2022-09-14 00:00:00', 0, 0, 0, NULL),
(35, '2022-09-15 00:00:00', 0, 0, 0, NULL),
(36, '2022-09-16 00:00:00', 0, 6, 1, NULL),
(37, '2022-09-17 00:00:00', 0, 0, 0, NULL),
(38, '2022-09-19 00:00:00', 0, 0, 0, NULL),
(39, '2022-09-18 00:00:00', 0, 0, 0, NULL),
(40, '2022-09-20 00:00:00', 0, 0, 0, NULL),
(41, '2022-09-21 00:00:00', 0, 0, 0, NULL),
(42, '2022-09-22 00:00:00', 0, 0, 0, NULL),
(43, '2022-09-23 00:00:00', 0, 0, 0, NULL),
(44, '2022-09-24 00:00:00', 0, 0, 0, NULL),
(45, '2022-09-26 00:00:00', 0, 0, 0, NULL),
(46, '2022-09-25 00:00:00', 0, 0, 0, NULL),
(47, '2022-09-27 00:00:00', 0, 0, 0, NULL),
(48, '2022-09-28 00:00:00', 0, 0, 0, NULL),
(49, '2022-09-29 00:00:00', 0, 0, 0, NULL),
(50, '2022-09-30 00:00:00', 0, 0, 0, NULL),
(51, '2022-10-02 00:00:00', 0, 0, 0, NULL),
(52, '2022-10-01 00:00:00', 0, 0, 0, NULL),
(53, '2022-10-06 00:00:00', 0, 7, 2, NULL),
(54, '2022-10-05 00:00:00', 0, 0, 0, NULL),
(55, '2022-10-04 00:00:00', 0, 0, 0, NULL),
(56, '2022-10-07 00:00:00', 0, 0, 0, NULL),
(57, '2022-10-08 00:00:00', 0, 0, 0, NULL),
(58, '2022-10-09 00:00:00', 0, 0, 0, NULL),
(59, '2022-10-10 00:00:00', 0, 0, 0, NULL),
(60, '2022-10-11 00:00:00', 0, 0, 0, NULL),
(61, '2022-10-12 00:00:00', 0, 0, 0, NULL),
(62, '2022-10-13 00:00:00', 0, 0, 0, NULL),
(63, '2022-10-14 00:00:00', 0, 0, 0, NULL),
(64, '2022-10-17 00:00:00', 0, 0, 0, NULL),
(65, '2022-10-16 00:00:00', 0, 0, 0, NULL),
(66, '2022-10-15 00:00:00', 0, 0, 0, NULL),
(67, '2022-10-18 00:00:00', 0, 0, 0, NULL),
(68, '2022-10-19 00:00:00', 0, 0, 0, NULL),
(69, '2022-10-20 00:00:00', 0, 0, 0, NULL),
(70, '2022-10-21 00:00:00', 0, 0, 0, NULL),
(71, '2022-10-22 00:00:00', 0, 0, 0, NULL),
(72, '2022-10-24 00:00:00', 0, 0, 0, NULL),
(73, '2022-10-23 00:00:00', 0, 0, 0, NULL),
(74, '2022-10-25 00:00:00', 0, 0, 0, NULL),
(75, '2022-10-26 00:00:00', 0, 2, 0, NULL),
(76, '2022-10-27 00:00:00', 0, 0, 0, NULL),
(77, '2022-10-28 00:00:00', 0, 0, 0, NULL),
(78, '2022-10-30 00:00:00', 0, 0, 0, NULL),
(79, '2022-10-29 00:00:00', 0, 0, 0, NULL),
(80, '2022-10-31 00:00:00', 0, 0, 0, NULL),
(81, '2022-11-02 00:00:00', 0, 0, 0, NULL),
(82, '2022-11-01 00:00:00', 0, 0, 0, NULL),
(83, '2022-11-03 00:00:00', 0, 3, 3, NULL),
(84, '2022-11-04 00:00:00', 0, 0, 0, NULL),
(85, '2022-11-05 00:00:00', 0, 0, 0, NULL),
(86, '2022-11-06 00:00:00', 0, 0, 0, NULL),
(87, '2022-11-07 00:00:00', 0, 0, 0, NULL),
(88, '2022-11-08 00:00:00', 0, 0, 0, NULL),
(89, '2022-11-09 00:00:00', 0, 0, 0, NULL),
(90, '2022-11-10 00:00:00', 0, 0, 0, NULL),
(91, '2022-11-11 00:00:00', 0, 0, 0, NULL),
(92, '2022-11-12 00:00:00', 0, 0, 0, NULL),
(93, '2022-11-14 00:00:00', 0, 0, 0, NULL),
(94, '2022-11-13 00:00:00', 0, 0, 0, NULL),
(95, '2022-11-15 00:00:00', 0, 0, 0, NULL),
(96, '2022-11-16 00:00:00', 0, 0, 0, NULL),
(97, '2022-11-18 00:00:00', 0, 0, 0, NULL),
(98, '2022-11-17 00:00:00', 0, 0, 0, NULL),
(99, '2022-11-19 00:00:00', 0, 0, 0, NULL),
(100, '2022-11-21 00:00:00', 0, 0, 0, NULL),
(101, '2022-11-20 00:00:00', 0, 0, 0, NULL),
(102, '2022-11-22 00:00:00', 0, 0, 0, NULL),
(103, '2022-11-23 00:00:00', 0, 0, 0, NULL),
(104, '2022-11-28 00:00:00', 0, 0, 0, NULL),
(105, '2022-11-27 00:00:00', 0, 0, 0, NULL),
(106, '2022-11-26 00:00:00', 0, 0, 0, NULL),
(107, '2022-11-29 00:00:00', 0, 0, 0, NULL),
(108, '2022-11-30 00:00:00', 0, 0, 0, NULL),
(109, '2022-12-01 00:00:00', 0, 0, 0, NULL),
(110, '2022-12-02 00:00:00', 0, 0, 0, NULL),
(111, '2022-12-03 00:00:00', 0, 0, 0, NULL),
(112, '2022-12-04 00:00:00', 0, 0, 0, NULL),
(113, '2022-12-05 00:00:00', 0, 0, 0, NULL),
(114, '2022-12-09 00:00:00', 0, 0, 0, NULL),
(115, '2022-12-08 00:00:00', 0, 0, 0, NULL),
(116, '2022-12-07 00:00:00', 0, 0, 0, NULL),
(117, '2022-12-10 00:00:00', 0, 0, 0, NULL),
(118, '2022-12-11 00:00:00', 0, 0, 0, NULL),
(119, '2022-12-21 00:00:00', 0, 0, 0, NULL),
(120, '2022-12-20 00:00:00', 0, 0, 0, NULL),
(121, '2022-12-19 00:00:00', 0, 0, 0, NULL),
(122, '2022-12-22 00:00:00', 0, 0, 0, NULL),
(123, '2022-12-26 00:00:00', 0, 0, 0, NULL),
(124, '2022-12-25 00:00:00', 0, 0, 0, NULL),
(125, '2022-12-24 00:00:00', 0, 0, 0, NULL),
(126, '2022-12-27 00:00:00', 0, 0, 0, NULL),
(127, '2022-12-28 00:00:00', 0, 0, 0, NULL),
(128, '2022-12-29 00:00:00', 0, 0, 0, NULL),
(129, '2022-12-31 00:00:00', 0, 0, 0, NULL),
(130, '2022-12-30 00:00:00', 0, 0, 0, NULL),
(131, '2023-01-01 00:00:00', 0, 0, 0, NULL),
(132, '2023-01-02 00:00:00', 0, 0, 0, NULL),
(133, '2023-01-05 00:00:00', 0, 0, 0, NULL),
(134, '2023-01-04 00:00:00', 0, 0, 0, NULL),
(135, '2023-01-03 00:00:00', 0, 0, 0, NULL),
(136, '2023-01-06 00:00:00', 0, 0, 0, NULL),
(137, '2023-01-07 00:00:00', 0, 0, 0, NULL),
(138, '2023-01-08 00:00:00', 0, 0, 0, NULL),
(139, '2023-01-09 00:00:00', 0, 0, 0, NULL),
(140, '2023-01-10 00:00:00', 0, 0, 0, NULL),
(141, '2023-01-11 00:00:00', 0, 0, 0, NULL),
(142, '2023-01-12 00:00:00', 0, 0, 0, NULL),
(143, '2023-01-13 00:00:00', 0, 0, 0, NULL),
(144, '2023-01-14 00:00:00', 0, 0, 0, NULL),
(145, '2023-01-15 00:00:00', 0, 0, 0, NULL),
(146, '2023-01-16 00:00:00', 0, 0, 0, NULL),
(147, '2023-01-17 00:00:00', 0, 0, 0, NULL),
(148, '2023-01-18 00:00:00', 0, 0, 0, NULL),
(149, '2023-01-26 00:00:00', 0, 0, 0, NULL),
(150, '2023-01-25 00:00:00', 0, 0, 0, NULL),
(151, '2023-01-24 00:00:00', 0, 0, 0, NULL),
(152, '2023-01-27 00:00:00', 0, 0, 0, NULL),
(153, '2023-01-28 00:00:00', 0, 0, 0, NULL),
(154, '2023-01-29 00:00:00', 0, 0, 0, NULL),
(155, '2023-01-30 00:00:00', 0, 0, 0, NULL),
(156, '2023-01-31 00:00:00', 0, 0, 0, NULL),
(157, '2023-02-01 00:00:00', 0, 0, 0, NULL),
(158, '2023-02-02 00:00:00', 0, 0, 0, NULL),
(159, '2023-02-03 00:00:00', 0, 0, 0, NULL),
(160, '2023-02-04 00:00:00', 0, 0, 0, NULL),
(161, '2023-02-05 00:00:00', 0, 0, 0, NULL),
(162, '2023-02-06 00:00:00', 0, 0, 0, NULL),
(163, '2023-02-07 00:00:00', 0, 0, 0, NULL),
(164, '2023-02-08 00:00:00', 0, 0, 0, NULL),
(165, '2023-02-09 00:00:00', 0, 0, 0, NULL),
(166, '2023-02-10 00:00:00', 0, 0, 0, NULL),
(167, '2023-02-11 00:00:00', 0, 0, 0, NULL),
(168, '2023-02-12 00:00:00', 0, 0, 0, NULL),
(169, '2023-02-13 00:00:00', 0, 0, 0, NULL),
(170, '2023-02-14 00:00:00', 0, 0, 0, NULL),
(171, '2023-02-15 00:00:00', 0, 0, 0, NULL),
(172, '2023-02-16 00:00:00', 0, 0, 0, NULL);
-- 导出 表 xxl_job.xxl_job_registry 结构
CREATE TABLE IF NOT EXISTS `xxl_job_registry` (
`id` int 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 AUTO_INCREMENT=287 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 正在导出表 xxl_job.xxl_job_registry 的数据:~0 rows (大约)
DELETE FROM `xxl_job_registry`;
-- 导出 表 xxl_job.xxl_job_user 结构
CREATE TABLE IF NOT EXISTS `xxl_job_user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '账号',
`password` varchar(50) NOT NULL COMMENT '密码',
`role` tinyint NOT NULL COMMENT '角色:0-普通用户、1-管理员',
`permission` varchar(255) DEFAULT NULL COMMENT '权限:执行器ID列表,多个逗号分割',
PRIMARY KEY (`id`),
UNIQUE KEY `i_username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-- 正在导出表 xxl_job.xxl_job_user 的数据:~2 rows (大约)
DELETE FROM `xxl_job_user`;
INSERT INTO `xxl_job_user` (`id`, `username`, `password`, `role`, `permission`) VALUES
(1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL),
(2, 'test', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;
-
xxl_job_lock:任务调度锁表;
-
xxl_job_group:执行器信息表,维护任务执行器信息;
-
xxl_job_info:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
-
xxl_job_log:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
-
xxl_job_log_report:调度日志报表:用户存储XXL-JOB任务调度日志的报表,调度中心报表功能页面会用到;
-
xxl_job_logglue:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
-
xxl_job_registry:执行器注册表,维护在线的执行器和调度中心机器地址信息;
-
xxl_job_user:系统用户表;
2. 利用Docker命令,创建并运行容器(记得改一下ip等信息)
docker run \
-e PARAMS="--spring.datasource.url=jdbc:mysql://192.168.150.101:3306/xxl_job?Unicode=true&characterEncoding=UTF-8 \
--spring.datasource.username=root \
--spring.datasource.password=123" \
--restart=always \
-p 28080:8080 \
-v xxl-job-admin-applogs:/data/applogs \
--name xxl-job-admin \
-d \
xuxueli/xxl-job-admin:2.3.0
第三节 微服务集成xxl-job
1. 依赖
<!--xxl-job-->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
2. 配置执行器
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appname);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
参数说明:
-
adminAddress:调度中心地址,天机学堂中就是填虚拟机地址
-
appname:微服务名称
-
ip和port:当前执行器的ip和端口,无需配置,自动获取
-
accessToken:访问令牌,在调度中心中配置令牌,所有执行器访问时都必须携带该令牌,否则无法访问。咱们项目的令牌已经配好,就是
tianji
。如果要修改,可以到虚拟机的/usr/local/src/xxl-job/application.properties
文件中,修改xxl.job.accessToken
属性,然后重启XXL-JOB即可。 -
logPath:任务运行日志的保存目录
-
logRetentionDays:日志最长保留时长
自动装配代码:
1. 配置属性类:
package com.tianji.common.autoconfigure.xxljob;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "tj.xxl-job")
public class XxlJobProperties {
private String accessToken;
private Admin admin;
private Executor executor;
@Data
public static class Admin {
private String address;
}
@Data
public static class Executor {
private String appName;
private String address;
private String ip;
private Integer port;
private String logPath;
private Integer logRetentionDays;
}
}
2. 自动装配类:
package com.tianji.common.autoconfigure.xxljob;
import com.tianji.common.utils.StringUtils;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
@ConditionalOnClass(XxlJobSpringExecutor.class)
@EnableConfigurationProperties(XxlJobProperties.class)
public class XxlJobConfig {
@Bean
public XxlJobSpringExecutor xxlJobExecutor(XxlJobProperties prop) {
log.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
XxlJobProperties.Admin admin = prop.getAdmin();
if (admin != null && StringUtils.isNotEmpty(admin.getAddress())) {
xxlJobSpringExecutor.setAdminAddresses(admin.getAddress());
}
XxlJobProperties.Executor executor = prop.getExecutor();
if (executor != null) {
if (executor.getAppName() != null)
xxlJobSpringExecutor.setAppname(executor.getAppName());
if (executor.getIp() != null)
xxlJobSpringExecutor.setIp(executor.getIp());
if (executor.getPort() != null)
xxlJobSpringExecutor.setPort(executor.getPort());
if (executor.getLogPath() != null)
xxlJobSpringExecutor.setLogPath(executor.getLogPath());
if (executor.getLogRetentionDays() != null)
xxlJobSpringExecutor.setLogRetentionDays(executor.getLogRetentionDays());
}
if (prop.getAccessToken() != null)
xxlJobSpringExecutor.setAccessToken(prop.getAccessToken());
log.info(">>>>>>>>>>> xxl-job config end.");
return xxlJobSpringExecutor;
}
}
3. 配置文件:——记得改一下ip,端口,服务名等
tj:
xxl-job:
access-token: tianji
admin:
address: http://192.168.150.101:8880/xxl-job-admin
executor:
appname: ${spring.application.name}
log-retention-days: 10
logPath: job/${spring.application.name}
4. 编写任务:——注解要改
第四节 注册执行器,配置任务
1. 注册执行器:
进入网址(记得改ip):http://192.168.150.101:8880/xxl-job-admin
输入用户名密码(用自己的,这里的是我上面的代码里写的账户密码):admin/123456
接下来,重启tj-learning
服务,登录XXL-JOB控制台,注册执行器。
AppName:就是你微服务的服务名
名称:无所谓
注册方式:选择自动注册就不用填下面的机器地址,否则就要输入宿主机的地址
2. 配置任务
其中比较关键的几个配置:
-
调度配置:也就是什么时候执行,一般选择cron表达式
-
任务配置:采用BEAN模式,指定JobHandler,这里指定的就是在项目中
@XxlJob
注解中的任务名称
-
路由策略:就是指如果有多个任务执行器,该由谁执行?这里支持的策略非常多:
路由策略说明:
-
FIRST(第一个):固定选择第一个执行器;
-
LAST(最后一个):固定选择最后一个执行器;
-
ROUND(轮询):在线的执行器按照轮询策略选择一个执行
-
RANDOM(随机):随机选择在线的执行器;
-
CONSISTENT_HASH(一致性HASH):每个任务按照Hash算法固定选择某一台执行器,且所有任务均匀散列在不同执行器上。
-
LEAST_FREQUENTLY_USED(最不经常使用):使用频率最低的执行器优先被选举;
-
LEAST_RECENTLY_USED(最近最久未使用):最久未使用的执行器优先被选举;
-
FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的执行器选定为目标执行器并发起调度;
-
BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的执行器选定为目标执行器并发起调度;
-
SHARDING_BROADCAST(分片广播):广播触发对应集群中所有执行器执行一次任务,同时系统自动传递分片参数;可根据分片参数开发分片任务
第五节 测试
如果需要测试,可以如下图执行一次:
查看日志:
第六节 任务链
在任务管理列表,我们会发现每个任务都有自己的任务id
然后我们选择第一个需要执行的任务,点击编辑
然后在子任务中,填写持久化榜单数据任务的id,本例中是12:
那么在执行完这个任务后,id为12的任务就是下一个执行对象,我们还可以为id为12的任务定义子任务,这样就形成了一条任务链
第三章 策略
第一节 数据分片
如果一次操作的数据量过大,我们一般会考虑采用分页,一批一批的操作。而数据量过于庞大的话,完成业务就过于耗时了。
如果说服务采用多实例部署,那我们其实可以采用数据分片。我们把数据分成多个小部分
-
起始页码:执行器编号是多少,起始页码就是多少
-
页跨度:执行器有几个,跨度就是多少。也就是说你要跳过别人读取过的页码
分页起始页码就是数据分片的编号(index)
起始页码和页跨度我们都可以从xxl-job里获得:
那么分片处理数据的代码参考如下:——注意定义起始页面和翻页的代码
// 3.2.查询数据
int index = XxlJobHelper.getShardIndex();
int total = XxlJobHelper.getShardTotal();
int pageNo = index + 1; // 起始页,就是分片序号+1 !!!!!
int pageSize = 10;
while (true) {
List<PointsBoard> boardList = pointsBoardService.queryCurrentBoardList(key, pageNo, pageSize);
if (CollUtils.isEmpty(boardList)) {
break;
}
// 4.持久化到数据库
// 4.1.把排名信息写入id
boardList.forEach(b -> {
b.setId(b.getRank().longValue());
b.setRank(null);
});
// 4.2.持久化
pointsBoardService.saveBatch(boardList);
// 5.翻页,跳过N个页,N就是分片数量 !!!!!
pageNo+=total;
}