目录
一、摘要
二、集成方案
三、集成步骤
3.1 springboot集成xxl-job
3.2 适配高斯数据库(postgresql)
3.3 页面集成
3.4 登录集成
3.5 接口集成
四、部署
一、摘要
公司现在打算重构产品,将原来的quartz替换成xxl-job,主要因为quartz不能动态发布任务,而xxl-job开箱即用,还支持一系列新特性,有 动态编写任务、调度中心和执行器高可用(可集群部署)、任务路由策略多样、 弹性扩容、失败重试 等。
二、集成方案
xxl-job架构分为管理端(调度中心)和客户端(各应用执行器),由调度中心调用各执行器执行任务,而且它有自己的用户和角色以及页面,目前只有mysql初始化脚本。因此公司产品集成xxl-job,主要有以下几点需要集成:
1、数据库集成:公司的产品是需要适配mysql和opengauss,因此xxl-job也需要适配opengauss,改造方案为:初始化脚本需要有opengauss版本的SQL、接口与后台任务运行的不兼容的SQL用改成兼容通用语法或修改逻辑用mybatis plus兼容,实在不行的则配置DatabaseIdProvider并用databaseId区分两种数据库的SQL,数据源配置统一改成公司的数据源配置。
2、页面集成:由于xxl-job页面风格不一致、且页面不支持配置任务执行时间段,这里任务管理页面还是采用原公司任务管理页面,然后再开发个任务源码编辑查看页。任务管理页面的菜单和按钮权限还是和以前权限管理设置一样,不同的角色和用户设置不同的任务管理菜单和按钮。
3、登录集成:由于公司有自己的单点登录,因此需要将xxl-job的鉴权适配,改造方案为:登录拦截器改造,接口request里若没有符合单点登录的token则跳转公司登录页,若有token则给用户赋管理员权限。
4、接口集成:调用quartz的接口全部改成调用xxl-job,任务运行的注解也改成xxl-job的注解。
三、集成步骤
3.1 springboot集成xxl-job
这里先确定xxl-job的版本,看xxl-job在gitee和github的代码,人们对2.3.1的版本提的问题较少,且2.3.1也能满足需求,新特性都支持,这里选择2.3.1版本,然后下载下来。
下载后,得到源码如下:
可以看到,这里有三个模块:xxl-job-admin(调度中心)、xxl-job-core(执行器需添加的依赖)、xxl-job-executor-samples(执行器示例)。
初始化数据库的SQL也有,具体如下:
找个mysql数据库运行下初始化脚本,然后改改xxl-job-admin里的配置(数据源、端口)再启动,访问管理页面,默认情况为 ip:8080/xxl-job-admin,首次登录会跳转登录页,用amdin/123456登录后可以看到类似如下首页:
执行器可以参考 xxl-job-executor-sample-springboot的示例来配置。主要是加入xxl-job-core依赖,声明好 ip、port、appname以及admin.addresses。注意的是,执行器的appname一定要在admin的执行器管理里进行添加,执行器才能连上admin(这个类似于先在调度中心申请一个凭证appname,后续执行器用凭证appname连上admin)。具体如下:
测试可行后,原定时任务在系统管理模块,因此,系统管理模块需加入xxl-job-core依赖并进行配置作为执行器、项目里需新建定时任务管理模块(存放xxl-job-admin代码)作为调度中心。
具体目录结构如下:
3.2 适配高斯数据库(postgresql)
由于高斯数据库完全支持postgresql语法,所以根据xxl-job的mysql表结构编写pg的SQL,具体如下:
CREATE TABLE xxl_job_info (
id serial constraint xxl_job_info_pkey primary key,
job_group integer NOT NULL,
job_desc varchar(255) NOT NULL,
add_time timestamp DEFAULT NULL,
update_time timestamp DEFAULT NULL,
author varchar(64) DEFAULT NULL ,
alarm_email varchar(255) DEFAULT NULL ,
schedule_type varchar(50) NOT NULL DEFAULT 'NONE' ,
schedule_conf varchar(128) DEFAULT NULL,
misfire_strategy varchar(50) NOT NULL DEFAULT 'DO_NOTHING' ,
executor_route_strategy varchar(50) DEFAULT NULL ,
executor_handler varchar(255) DEFAULT NULL ,
executor_param varchar(512) DEFAULT NULL ,
executor_block_strategy varchar(50) DEFAULT NULL ,
executor_timeout integer NOT NULL DEFAULT '0',
executor_fail_retry_count integer NOT NULL DEFAULT '0' ,
glue_type varchar(50) NOT NULL ,
glue_source text ,
glue_remark varchar(128) DEFAULT NULL ,
glue_updatetime timestamp DEFAULT NULL ,
child_jobid varchar(255) DEFAULT NULL ,
trigger_status smallint NOT NULL DEFAULT '0' ,
trigger_last_time bigint NOT NULL DEFAULT '0' ,
trigger_next_time bigint NOT NULL DEFAULT '0'
);
comment on table xxl_job_info is '任务信息表';
comment on column xxl_job_info.id is '主键';
comment on column xxl_job_info.job_group is '执行器主键ID';
comment on column xxl_job_info.job_desc is '任务描述';
comment on column xxl_job_info.add_time is '任务创建时间';
comment on column xxl_job_info.update_time is '任务更新时间';
comment on column xxl_job_info.author is '作者';
comment on column xxl_job_info.alarm_email is '报警邮件';
comment on column xxl_job_info.schedule_type is '调度类型';
comment on column xxl_job_info.schedule_conf is '调度配置,值含义取决于调度类型';
comment on column xxl_job_info.misfire_strategy is '调度过期策略';
comment on column xxl_job_info.executor_route_strategy is '执行器路由策略';
comment on column xxl_job_info.executor_handler is '执行器任务handler';
comment on column xxl_job_info.executor_param is '执行器任务参数';
comment on column xxl_job_info.executor_block_strategy is '阻塞处理策略';
comment on column xxl_job_info.executor_timeout is '任务执行超时时间,单位秒';
comment on column xxl_job_info.executor_fail_retry_count is '失败重试次数';
comment on column xxl_job_info.glue_type is 'GLUE类型';
comment on column xxl_job_info.glue_source is 'GLUE源代码';
comment on column xxl_job_info.glue_remark is 'GLUE备注';
comment on column xxl_job_info.glue_updatetime is 'GLUE更新时间';
comment on column xxl_job_info.child_jobid is '子任务ID,多个逗号分隔';
comment on column xxl_job_info.trigger_status is '调度状态:0-停止,1-运行';
comment on column xxl_job_info.trigger_last_time is '上次调度时间';
comment on column xxl_job_info.trigger_next_time is '下次调度时间';
CREATE TABLE xxl_job_log (
id serial constraint xxl_job_log_pkey primary key,
job_group integer NOT NULL,
job_id integer NOT NULL,
executor_address varchar(255) DEFAULT NULL,
executor_handler varchar(255) DEFAULT NULL,
executor_param varchar(512) DEFAULT NULL,
executor_sharding_param varchar(20) DEFAULT NULL ,
executor_fail_retry_count integer NOT NULL DEFAULT '0',
trigger_time timestamp DEFAULT NULL,
trigger_code integer NOT NULL,
trigger_msg text,
handle_time timestamp DEFAULT NULL,
handle_code integer NOT NULL,
handle_msg text ,
alarm_status smallint NOT NULL DEFAULT '0'
) ;
CREATE INDEX I_trigger_time ON xxl_job_log (trigger_time);
CREATE INDEX I_handle_code ON xxl_job_log (handle_code);
comment on table xxl_job_log is '任务日志表';
comment on column xxl_job_log.id is '主键';
comment on column xxl_job_log.job_group is '执行器主键ID';
comment on column xxl_job_log.job_id is '任务,主键ID';
comment on column xxl_job_log.executor_address is '执行器地址,本次执行的地址';
comment on column xxl_job_log.executor_handler is '执行器任务handler';
comment on column xxl_job_log.executor_param is '执行器任务参数';
comment on column xxl_job_log.executor_sharding_param is '执行器任务分片参数,格式如 1/2';
comment on column xxl_job_log.executor_fail_retry_count is '失败重试次数';
comment on column xxl_job_log.trigger_time is '调度-时间';
comment on column xxl_job_log.trigger_code is '调度-结果';
comment on column xxl_job_log.trigger_msg is '调度-日志';
comment on column xxl_job_log.handle_time is '执行-时间';
comment on column xxl_job_log.handle_code is '执行-状态';
comment on column xxl_job_log.handle_msg is '执行-日志';
comment on column xxl_job_log.alarm_status is '告警状态:0-默认、1-无需告警、2-告警成功、3-告警失败';
create or replace function upd_timestamp() returns trigger as
$$
begin
new.update_time = current_timestamp;
return new;
end
$$
language plpgsql;
CREATE TABLE xxl_job_log_report (
id serial constraint xxl_job_log_report_pkey primary key,
trigger_day timestamp DEFAULT NULL,
running_count integer NOT NULL DEFAULT '0',
suc_count integer NOT NULL DEFAULT '0',
fail_count integer NOT NULL DEFAULT '0',
update_time timestamp DEFAULT NULL
);
CREATE UNIQUE INDEX i_trigger_day ON xxl_job_log_report (trigger_day);
comment on column xxl_job_log_report.id is '主键';
comment on column xxl_job_log_report.trigger_day is '调度-时间';
comment on column xxl_job_log_report.running_count is '运行中-日志数量';
comment on column xxl_job_log_report.suc_count is '执行成功-日志数量';
comment on column xxl_job_log_report.fail_count is '执行失败-日志数量';
comment on column xxl_job_log_report.update_time is '更新时间';
CREATE TABLE xxl_job_logglue (
id serial constraint xxl_job_logglue_pkey primary key,
job_id integer NOT NULL,
glue_type varchar(50) DEFAULT NULL,
glue_source text,
glue_remark varchar(128) NOT NULL,
add_time timestamp DEFAULT NULL,
update_time timestamp DEFAULT NULL
);
create trigger t_xxl_job_logglue_update_time before update on xxl_job_logglue for each row execute procedure upd_timestamp();
comment on table xxl_job_logglue is '任务GLUE日志表';
comment on column xxl_job_logglue.id is '主键';
comment on column xxl_job_logglue.job_id is '任务,主键ID';
comment on column xxl_job_logglue.glue_type is 'GLUE类型';
comment on column xxl_job_logglue.glue_source is 'GLUE源代码';
comment on column xxl_job_logglue.glue_remark is 'GLUE备注';
comment on column xxl_job_logglue.add_time is '创建时间';
comment on column xxl_job_logglue.update_time is '修改时间';
CREATE TABLE xxl_job_registry (
id serial constraint xxl_job_registry_pkey primary key,
registry_group varchar(50) NOT NULL,
registry_key varchar(255) NOT NULL,
registry_value varchar(255) NOT NULL,
update_time timestamp DEFAULT NULL
);
CREATE INDEX i_g_k_v ON xxl_job_registry (registry_group,registry_key,registry_value);
comment on table xxl_job_registry is '任务注册表';
comment on column xxl_job_registry.id is '主键';
comment on column xxl_job_registry.registry_group is '注册分组';
comment on column xxl_job_registry.registry_key is '注册键';
comment on column xxl_job_registry.registry_value is '注册值';
comment on column xxl_job_registry.update_time is '更新时间';
CREATE TABLE xxl_job_group (
id serial constraint xxl_job_group_pkey primary key,
app_name varchar(64) NOT NULL,
title varchar(12) NOT NULL,
address_type smallint NOT NULL DEFAULT '0',
address_list text ,
update_time timestamp DEFAULT NULL
);
comment on table xxl_job_group is '任务分组表';
comment on column xxl_job_group.id is '主键';
comment on column xxl_job_group.app_name is '执行器AppName';
comment on column xxl_job_group.title is '执行器名称';
comment on column xxl_job_group.address_type is '执行器地址类型:0=自动注册、1=手动录入';
comment on column xxl_job_group.address_list is '执行器地址列表,多地址逗号分隔';
CREATE TABLE xxl_job_user (
id serial constraint xxl_job_user_pkey primary key,
username varchar(50) NOT NULL,
password varchar(50) NOT NULL,
role smallint NOT NULL,
permission varchar(255) DEFAULT NULL
) ;
CREATE UNIQUE INDEX i_username ON xxl_job_user (username);
comment on table xxl_job_user is '任务用户表';
comment on column xxl_job_user.id is '主键';
comment on column xxl_job_user.username is '账号';
comment on column xxl_job_user.password is '密码';
comment on column xxl_job_user.role is '角色:0-普通用户、1-管理员';
comment on column xxl_job_user.permission is '权限:执行器ID列表,多个逗号分割';
CREATE TABLE xxl_job_lock (
lock_name varchar(50) NOT NULL,
PRIMARY KEY (lock_name)
);
comment on table xxl_job_lock is '任务锁表';
comment on column xxl_job_lock.lock_name is '锁名称';
INSERT INTO xxl_job_group( app_name, title, address_type, address_list, update_time) VALUES ('xxl-job-executor-sample', '示例执行器', 0, NULL, '2018-11-03 22:21:31' );
INSERT INTO xxl_job_info( 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', '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( username, password, role, permission) VALUES ( 'admin', 'e10adc3949ba59abbe56e057f20f883e', 1, NULL);
INSERT INTO xxl_job_lock ( lock_name) VALUES ( 'schedule_lock');
接着xml文件里将“`”去除,具体如下:
调整findDead逻辑,原SQL(<![CDATA[ < ]]> DATE_ADD(#{nowTime},INTERVAL -#{timeout} SECOND))不兼容pg语法, 如下:
这句SQL是找出更新时间小于(当前时间-配置的超时时间)的注册组,查了下网上写法,都不怎么好,这里改用集成mybatis plus,用mybatis plus来进行查询。具体如下:
另外,使用了mybatis plus,有些表会发生主键插入异常,具体解决办法如下:
调整不等于判断,具体如下:
将驼峰改成小写适配高斯数据库
基本就以上这些了,连接pg数据库也能正常运行admin并新增执行器和任务运行,并能看到日志以及统计。
3.3 页面集成
页面集成意味着客户无法看到admin管理页,只能看到原来的任务页,原页面如下:
目前页面保持不变,页面的对应的菜单和按钮权限都不变,接口的url以及参数和返回值都不变,xxl-job-admin的管理页对用户不可见,我这里修改路径和端口,并不暴露给前端和nginx。由于管理页不可见,需要把新增执行器appname的sql写在初始化SQL里。
3.4 登录集成
这里调整拦截器PermissionInterceptor和toLogin处理即可。具体如下:
这里PermissionInterceptor拦截器去除needAdminUser判断,直接从request里获取token,获取不到或根据token查不到登录用户信息,则跳转toLogin,若能获取到用户登录信息,则给用户赋值xxl-job的管理员权限并保存到request的属性中即可。
这里toLogin处理直接重定向公司的单点登录地址即可。
3.5 接口集成
这里系统管理模块里原来任务controller保持不变,服务层原来调用quartz的逻辑全部换成xxl-job。具体如下:
系统管理模块(执行器)保存时调用feign接口,进而调用xxl-job-admin的新增方法。这里通过任务参数关联系统管理的任务,后续执行器执行任务时,可以通过任务参数找到添加的任务配置,进而进行相应处理。任务注解就简单了,换用@XxlJob注解即可。剩下的任务业务逻辑各自按业务进行调整。
四、部署
这里考虑高可用,所以系统管理模块(执行器)和任务管理模块(调度中心xxl-job-admin)都是集群部署。这里部署需要注意,所有机器的时间必须保持一致。调度中心连接同一个数据库多实例启动即可,执行器配在admin.address配置各调度中心的地址后多实例启动即可。