建设网站方面的知识,西宁最好网站建设公司哪家好,wordpress 自定义边栏,重庆市建设工程安全管理信息网Quartz的不足
Quartz 的不足#xff1a;Quartz 作为开源任务调度中的佼佼者#xff0c;是任务调度的首选。但是在集群环境中#xff0c;Quartz采用API的方式对任务进行管理#xff0c;这样存在以下问题#xff1a;
通过调用API的方式操作任务#xff0c;不人性化。需要…Quartz的不足
Quartz 的不足Quartz 作为开源任务调度中的佼佼者是任务调度的首选。但是在集群环境中Quartz采用API的方式对任务进行管理这样存在以下问题
通过调用API的方式操作任务不人性化。需要持久化业务的 QuartzJobBean 到底层数据表中系统侵入性相当严重。调度逻辑和QuartzJobBean耦合在同一个项目中这将导致一个问题在调度任务数量逐渐增多同时调度任务逻辑逐渐加重的情况下此时调度系统的性能将大大受限于业务。
Xxl-job介绍
官方说明XXL-JOB 是一个轻量级分布式任务调度平台其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线开箱即用。
通俗来讲XXL-JOB 是一个任务调度框架通过引入 XXL-JOB 相关的依赖按照相关格式撰写代码后可在其可视化界面进行任务的启动执行中止以及包含了日志记录与查询和任务状态监控。
更多详细介绍推荐阅读官方文档。
项目实践
Spring Boot集成XXL-JOB
Spring Boot 集成 XXL-JOB 主要分为以下两步
配置运行调度中心xxl-job-admin配置运行执行器项目
xxl-job-admin 可以从源码仓库中下载代码代码地址有两个
GitHubgithub.com/xuxueli/xxl…Giteegitee.com/xuxueli0323… 下载完之后在 doc/db 目录下有数据库脚本 tables_xxl_job.sql执行下脚本初始化调度数据库 xxl_job如下图所示 配置调度中心
将下载的源码解压用 IDEA 打开我们需要修改一下 xxl-job-admin 中的一些配置。我这里下载的是最新版 2.3.1
1、修改 application.properties主要是配置一下 datasource 以及 email其他不需要改变。
### xxl-job, datasource
spring.datasource.urljdbc:mysql://127.0.0.1:3306/xxl_job?useUnicodetruecharacterEncodingUTF-8autoReconnecttrueserverTimezoneAsia/Shanghai
spring.datasource.usernameroot
spring.datasource.passwordroot
spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver### xxl-job, email
spring.mail.hostsmtp.qq.com
spring.mail.port25
spring.mail.username1739468244qq.com
spring.mail.from1739468244qq.com
# 此处不是邮箱登录密码而是开启SMTP服务后的授权码
spring.mail.passwordxxxxx2、修改 logback.xml配置日志输出路径我是在解压的 xxl-job-2.3.1 项目包中新建了一个 logs 文件夹。
property namelog.path value/Users/xxx/xxl-job-2.3.1/logs/xxl-job-admin.log/然后启动项目正常启动后访问地址为http://localhost:8080/xxl-job-admin默认的账户为 admin密码为 123456访问后台管理系统后台。
这样就表示调度中心已经搞定了下一步就是创建执行器项目。
创建执行器项目
本项目与 Quartz 项目用的业务表和业务逻辑都一样所以引入的依赖会比较多。
环境配置
1、引入依赖
parentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.6.3/versionrelativePath/
/parentpropertiesjava.version1.8/java.versionfastjson.version1.2.73/fastjson.versionhutool.version5.5.1/hutool.versionmysql.version8.0.19/mysql.versionorg.mapstruct.version1.4.2.Final/org.mapstruct.versionorg.projectlombok.version1.18.20/org.projectlombok.versiondruid.version1.1.18/druid.versionspringdoc.version1.6.9/springdoc.version
/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependencydependencygroupIdcom.xuxueli/groupIdartifactIdxxl-job-core/artifactIdversion2.3.1/version/dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.1/version/dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus/artifactIdversion3.5.1/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion${mysql.version}/versionscoperuntime/scope/dependencydependencygroupIdcom.alibaba/groupIdartifactIddruid-spring-boot-starter/artifactIdversion${druid.version}/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.20/version/dependencydependencygroupIdcom.alibaba.fastjson2/groupIdartifactIdfastjson2/artifactIdversion2.0.12/version/dependencydependencygroupIdorg.mapstruct/groupIdartifactIdmapstruct/artifactIdversion${org.mapstruct.version}/version/dependencydependencygroupIdorg.mapstruct/groupIdartifactIdmapstruct-processor/artifactIdversion${org.mapstruct.version}/version/dependencydependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion${hutool.version}/version/dependencydependencygroupIdorg.springdoc/groupIdartifactIdspringdoc-openapi-ui/artifactIdversion${springdoc.version}/version/dependency
/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins
/build2、application.yml 配置文件
server:port: 9090# xxl-job
xxl:job:admin:addresses: http://127.0.0.1:8080/xxl-job-admin # 调度中心部署跟地址 [选填]如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行执行器心跳注册和任务结果回调为空则关闭自动注册executor:appname: hresh-job-executor # 执行器 AppName [选填]执行器心跳注册分组依据为空则关闭自动注册ip: # 执行器IP [选填]默认为空表示自动获取IP多网卡时可手动设置指定IP该IP不会绑定Host仅作为通讯实用地址信息用于 执行器注册 和 调度中心请求并触发任务port: 6666 # ### 执行器端口号 [选填]小于等于0则自动获取默认端口为9999单机部署多个执行器时注意要配置不同执行器端口logpath: /Users/xxx/xxl-job-2.3.1/logs/xxl-job # 执行器运行日志文件存储磁盘路径 [选填] 需要对该路径拥有读写权限为空则使用默认路径logretentiondays: 30 # 执行器日志文件保存天数 [选填] 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能accessToken: default_token # 执行器通讯TOKEN [选填]非空时启用spring:application:name: xxl-job-practicedatasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/xxl_job?serverTimezoneHongkongcharacterEncodingutf-8useSSLfalseusername: rootpassword: rootmybatis:mapper-locations: classpath:mapper/*Mapper.xmlconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpllazy-loading-enabled: true上述 xxl-job 的 logpath 配置与调度中心的输出日志用的是同一个目录accessToken 也与调度中心的 xxl.job.accessToken 一致。
核心类
1、xxl-job 配置类
Configuration
public class XxlJobConfig {Value(${xxl.job.admin.addresses})private String adminAddresses;Value(${xxl.job.executor.appname})private String appName;Value(${xxl.job.executor.ip})private String ip;Value(${xxl.job.executor.port})private int port;Value(${xxl.job.accessToken})private String accessToken;Value(${xxl.job.executor.logpath})private String logPath;Value(${xxl.job.executor.logretentiondays})private int logRetentionDays;Beanpublic XxlJobSpringExecutor xxlJobExecutor() {// 创建 XxlJobSpringExecutor 执行器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;}
}2、xxl-job 工具类
Component
RequiredArgsConstructor
public class XxlUtil {Value(${xxl.job.admin.addresses})private String xxlJobAdminAddress;private final RestTemplate restTemplate;// 请求Urlprivate static final String ADD_INFO_URL /jobinfo/addJob;private static final String REMOVE_INFO_URL /jobinfo/removeJob;private static final String GET_GROUP_ID /jobgroup/loadByAppName;/*** 添加任务** param xxlJobInfo* param appName* return*/public String addJob(XxlJobInfo xxlJobInfo, String appName) {MapString, Object params new HashMap();params.put(appName, appName);String json JSONUtil.toJsonStr(params);String result doPost(xxlJobAdminAddress GET_GROUP_ID, json);JSONObject jsonObject JSON.parseObject(result);MapString, Object map (MapString, Object) jsonObject.get(content);Integer groupId (Integer) map.get(id);xxlJobInfo.setJobGroup(groupId);String xxlJobInfoJson JSONUtil.toJsonStr(xxlJobInfo);return doPost(xxlJobAdminAddress ADD_INFO_URL, xxlJobInfoJson);}// 删除jobpublic String removeJob(long jobId) {MultiValueMapString, String map new LinkedMultiValueMapString, String();map.add(id, String.valueOf(jobId));return doPostWithFormData(xxlJobAdminAddress REMOVE_INFO_URL, map);}/*** 远程调用** param url* param json*/private String doPost(String url, String json) {HttpHeaders headers new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);HttpEntityString entity new HttpEntity(json, headers);ResponseEntityString responseEntity restTemplate.postForEntity(url, entity, String.class);return responseEntity.getBody();}private String doPostWithFormData(String url, MultiValueMapString, String map) {HttpHeaders headers new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);HttpEntityMultiValueMapString, String entity new HttpEntity(map, headers);ResponseEntityString responseEntity restTemplate.postForEntity(url, entity, String.class);return responseEntity.getBody();}
}此处我们利用 RestTemplate 来远程调用 xxl-job-admin 中的服务从而实现动态创建定时任务而不是局限于通过 UI 界面来创建任务。
这里我们用到三个接口都需要我们在 xxl-job-admin 中手动添加这样在调用接口时就不需要登录验证了这就要求在定义接口时加上一个 PermissionLimit并设置 limit 为 false那么这样就不用去登录就可以调用接口。
3、修改 JobGroupController新增 loadByAppName 方法
RequestMapping(/loadByAppName)
ResponseBody
PermissionLimit(limit false)
public ReturnTXxlJobGroup loadByAppName(RequestBody MapString, Object map) {XxlJobGroup jobGroup xxlJobGroupDao.loadByAppName(map);return jobGroup ! null ? new ReturnTXxlJobGroup(jobGroup): new ReturnTXxlJobGroup(ReturnT.FAIL_CODE, null);
}XxlJobGroupDao 文件以及对应的 xml 文件
XxlJobGroup loadByAppName(MapString, Object map);select idloadByAppName parameterTypejava.util.HashMap resultMapXxlJobGroupSELECTinclude refidBase_Column_List/FROM xxl_job_group AS tWHERE t.app_name #{appName}/select4、修改 JobInfoController增加 addJob 方法和 removeJob 方法
RequestMapping(/addJob)ResponseBodyPermissionLimit(limit false)public ReturnTString addJob(RequestBody XxlJobInfo jobInfo) {return xxlJobService.add(jobInfo);}RequestMapping(/removeJob)ResponseBodyPermissionLimit(limit false)public ReturnTString removeJob(String id) {return xxlJobService.remove(Integer.parseInt(id));}addJob 方法与 JobInfoController 文件中的 add 方法具体逻辑是一样的只是换个接口名。
RequestMapping(/add)ResponseBodypublic ReturnTString add(XxlJobInfo jobInfo) {return xxlJobService.add(jobInfo);}至此关于调度中心的修改就结束了。
5、XxlService 创建任务
Service
Slf4j
RequiredArgsConstructor
public class XxlService {private final XxlUtil xxlUtil;Value(${xxl.job.executor.appname})private String appName;public void addJob(XxlJobInfo xxlJobInfo) {xxlUtil.addJob(xxlJobInfo, appName);long triggerNextTime xxlJobInfo.getTriggerNextTime();log.info(任务已添加将在{}开始执行任务, DateUtils.formatDate(triggerNextTime));}}业务代码
1、UserService包括用户注册给用户发送欢迎消息以及发送天气温度通知。
Service
RequiredArgsConstructor
Slf4j
public class UserService {private final UserMapper userMapper;private final UserStruct userStruct;private final WeatherService weatherService;private final XxlService xxlService;/*** 假设有这样一个业务需求每当有新用户注册则1分钟后会给用户发送欢迎通知.** param userRequest 用户请求体*/Transactionalpublic void register(UserRequest userRequest) {if (Objects.isNull(userRequest) || isBlank(userRequest.getUsername()) ||isBlank(userRequest.getPassword())) {BusinessException.fail(账号或密码为空);}User user userStruct.toUser(userRequest);userMapper.insert(user);LocalDateTime scheduleTime LocalDateTime.now().plusMinutes(1L);XxlJobInfo xxlJobInfo XxlJobInfo.builder().jobDesc(定时给用户发送通知).author(hresh).scheduleType(CRON).scheduleConf(DateUtils.getCron(scheduleTime)).glueType(BEAN).glueType(BEAN).executorHandler(sayHelloHandler).executorParam(user.getUsername()).misfireStrategy(DO_NOTHING).executorRouteStrategy(FIRST).triggerNextTime(DateUtils.toEpochMilli(scheduleTime)).executorBlockStrategy(SERIAL_EXECUTION).triggerStatus(1).build();xxlService.addJob(xxlJobInfo);}public void sayHelloToUser(String username) {if (StrUtil.isBlank(username)) {log.error(用户名为空);}User user userMapper.selectByUserName(username);String message Welcome to Java,I am hresh.;log.info(user.getUsername() , hello, message);}public void pushWeatherNotification() {ListUser users userMapper.queryAll();log.info(执行发送天气通知给用户的任务。。。);WeatherInfo weatherInfo weatherService.getWeather(WeatherConstant.WU_HAN);for (User user : users) {log.info(user.getUsername() ---- weatherInfo.toString());}}
}2、WeatherService获取天气温度等信息这里就不贴代码了。
3、UserController只有一个用户注册方法
RestController
RequiredArgsConstructor
public class UserController {private final UserService userService;PostMapping(/register)public ResultObject register(RequestBody UserRequest userRequest) {userService.register(userRequest);return Result.ok();}}任务处理器
这里演示两种任务处理器一种是用于处理 UI 页面创建的任务另一种是处理代码创建的任务。
1、DemoHandler仅用作演示没什么实际含义。
RequiredArgsConstructor
Slf4j
public class DemoHandler extends IJobHandler {XxlJob(value demoHandler)Overridepublic void execute() throws Exception {log.info(自动任务 this.getClass().getSimpleName() 执行);}
}2、SayHelloHandler用户注册后再 xxl-job 上创建一个任务到时间后就调用该处理器。
Component
RequiredArgsConstructor
public class SayHelloHandler {private final UserService userService;XxlJob(value sayHelloHandler)public void execute() {String param XxlJobHelper.getJobParam();userService.sayHelloToUser(param);}
}在最新版本的 xxl-job 中任务核心类 “IJobHandler” 的 “execute” 方法取消出入参设计。改为通过 “XxlJobHelper.getJobParam” 获取任务参数并替代方法入参通过 “XxlJobHelper.handleSuccess/handleFail” 设置任务结果并替代方法出参示例代码如下
XxlJob(demoJobHandler)
public void execute() {String param XxlJobHelper.getJobParam(); // 获取参数XxlJobHelper.handleSuccess(); // 设置任务结果
}3、WeatherNotificationHandler每天定时发送天气通知
Component
RequiredArgsConstructor
public class WeatherNotificationHandler extends IJobHandler {private final UserService userService;XxlJob(value weatherNotificationHandler)Overridepublic void execute() throws Exception {userService.pushWeatherNotification();}
}测试
1、首先在执行器管理页面点击新增按钮弹出新增框。输入AppName 与application.yml中配置的appname保持一致名称注册方式默认自动注册点击保存。 2、新增任务 控制台输出
com.msdn.time.handler.DemoHandler : 自动任务DemoHandler执行2、利用 postman 来注册用户 去 UI 任务管理页面可以看到代码创建的任务。 1分钟后控制台输出如下 3、在 UI 任务管理页面手动新增任务用来发送天气通知。 点击执行一次控制台输出如下 实际应用中对于手动创建的任务直接点击启动就可以了。
这里还有一个问题如果每次有新用户注册都会创建一个定时任务而且只执行一次那么任务列表到时候就会有很多脏数据所以我们在执行完发送欢迎通知后就要删除。所以我们需要修改一下 SayHelloHandler
XxlJob(value sayHelloHandler)public void execute() {String param XxlJobHelper.getJobParam();userService.sayHelloToUser(param);long jobId XxlJobHelper.getJobId();xxlUtil.removeJob(jobId);}重启项目后比如说明再创建一个名为 hresh2 的用户然后任务列表就会新增一个任务。 等控制台输出 sayHello 后可以发现任务列表中任务 ID 为 20的记录被删除掉了。
问题
控制台输出邮件注册错误
11:01:48.740 logback [RMI TCP Connection(1)-127.0.0.1] WARN o.s.b.a.mail.MailHealthIndicator - Mail health check failed
javax.mail.AuthenticationFailedException: 535 Login Fail. Please enter your authorization code to login. More information in http://service.mail.qq.com/cgi-bin/help?subtype1id28no1001256原因xxl-job-admin 项目的 application.properties 文件中关于 spring.mail.password 的配置不对可能有人配置了自己邮箱的登录密码。
解决方案 总结
通过对比 Quartz 和 XXL-JOB 的使用可以发现后者更易上手代码侵入不严重且具备可视化界面。这就是推荐新手使用 XXL-JOB 的原因。
感兴趣的朋友可以去我的 Github 下载相关代码如果对你有所帮助不妨 Star 一下谢谢大家支持
参考文献
XXL-JOB动态创建任务详解篇2
Spring Boot 集成 XXL-JOB 任务调度平台