diff --git a/.gitignore b/.gitignore index 29204700..52c1b008 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,7 @@ -# for eclipse -/.settings/ -/.project - -# for idea +.idea +.classpath +.project *.iml -/.idea -*/target +target/ .DS_Store .gitattributes diff --git a/.travis.yml b/.travis.yml index e3782d9b..304990e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: java jdk: - - oraclejdk7 + - oraclejdk8 install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true #script: mvn test script: mvn -DskipTests=true clean package \ No newline at end of file diff --git a/README.md b/README.md index 1c9c2678..802d8ddb 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,10 @@ - + + + +

@@ -44,28 +47,30 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ## Features - 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手; - 2、动态:支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效; -- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现,可保证调度中心HA; +- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA; - 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA; -- 5、任务Failover:执行器集群部署时,任务路由策略选择"故障转移"情况下调度失败时将会平滑切换执行器进行Failover; -- 6、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行; -- 7、自定义任务参数:支持在线配置调度任务入参,即时生效; -- 8、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞; -- 9、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务; -- 10、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件; -- 11、状态监控:支持实时监控任务进度; -- 12、Rolling执行日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志; -- 13、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。 -- 14、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性; -- 15、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔; -- 16、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用; -- 17、任务注册: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址; -- 18、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等; -- 19、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等; -- 20、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python等类型脚本; -- 21、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度; -- 22、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试; -- 23、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发对应集群中所有执行器执行一次任务,同时传递分片参数;可根据分片参数开发分片任务; -- 24、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。 +- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址; +- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务; +- 7、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等; +- 8、故障转移:任务路由策略选择"故障转移"情况下,如果执行器集群中某一台机器故障,将会自动Failover切换到一台正常的执行器发送调度请求。 +- 9、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试; +- 10、失败重试:调度中心调度失败且启用"失败重试"策略时,将会自动重试一次;执行器执行失败且回调失败重试状态时,也将会自动重试一次; +- 11、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度; +- 12、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务; +- 13、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。 +- 14、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。 +- 15、任务进度监控:支持实时监控任务进度; +- 16、Rolling实时日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志; +- 17、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。 +- 18、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python、NodeJS等类型脚本; +- 19、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔; +- 20、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行; +- 21、自定义任务参数:支持在线配置调度任务入参,即时生效; +- 22、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞; +- 23、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性; +- 24、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件; +- 25、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用; +- 26、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等; ## Development @@ -79,8 +84,10 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 于2017-05-13,在上海举办的 "[第62期开源中国源创会](https://www.oschina.net/event/2236961)" 的 "放码过来" 环节,我登台对XXL-JOB做了演讲,台下五百位在场观众反响热烈([图文回顾](https://www.oschina.net/question/2686220_2242120) )。 +于2017-12-11,XXL-JOB有幸参会《[InfoQ ArchSummit全球架构师峰会](http://bj2017.archsummit.com/)》,并被拍拍贷架构总监"杨波老师"在专题 "[微服务原理、基础架构和开源实践](http://bj2017.archsummit.com/training/2)" 中现场介绍。 + > 我司大众点评目前已接入XXL-JOB,内部别名《Ferrari》(Ferrari基于XXL-JOB的V1.1版本定制而成,新接入应用推荐升级最新版本)。** -据最新统计, 自2016-01-21接入至2017-07-07期间,该系统已调度约60万余次,表现优异。新接入应用推荐使用最新版本,因为经过数个大版本的更新,系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升,核心功能更加稳定高效。 +据最新统计, 自2016-01-21接入至2017-12-01期间,该系统已调度约100万次,表现优异。新接入应用推荐使用最新版本,因为经过数个大版本的更新,系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升,核心功能更加稳定高效。 至今,XXL-JOB已接入多家公司的线上产品线,接入场景如电商业务,O2O业务和大数据作业等,截止2016-07-19为止,XXL-JOB已接入的公司包括不限于: @@ -127,6 +134,22 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 - 41、广州瀚农网络科技有限公司 - 42、享点科技有限公司 - 43、杭州比智科技有限公司 + - 44、圳临界线网络科技有限公司 + - 45、广州知识圈网络科技有限公司 + - 46、国誉商业上海有限公司 + - 47、海尔消费金融有限公司,嗨付、够花 (海尔) + - 48、广州巴图鲁信息科技有限公司 + - 49、深圳市鹏海运电子数据交换有限公司 + - 50、深圳市亚飞电子商务有限公司 + - 51、上海趣医网络有限公司 + - 52、聚金资本 + - 53、北京父母邦网络科技有限公司 + - 54、中山元赫软件科技有限公司 + - 55、中商惠民(北京)电子商务有限公司 + - 56、凯京集团 + - 57、华夏票联(北京)科技有限公司 + - 58、拍拍贷 + - 59、北京尚德机构在线教育有限公司 - …… > 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。 @@ -136,12 +159,7 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ## Communication -- 腾讯QQ群(6):399758605 -- 腾讯QQ群(5):138274130 -- 腾讯QQ群(4):464762661 -- 腾讯QQ群(3):242151780 -- 腾讯QQ群(2):438249535 -- 腾讯QQ群(1):367260654 +- [社区交流](http://www.xuxueli.com/page/community.html) - [Gitter](https://gitter.im/xuxueli/xxl-job) @@ -161,9 +179,6 @@ This product is open source and free, and will continue to provide free communit ## Donate -No matter how much the amount is enough to express your thought, thank you very much :) - -无论金额多少都足够表达您这份心意,非常感谢 :) +No matter how much the amount is enough to express your thought, thank you very much :) [To donate](http://www.xuxueli.com/page/donate.html ) -微信: -支付宝: +无论金额多少都足够表达您这份心意,非常感谢 :) [前往捐赠](http://www.xuxueli.com/page/donate.html ) diff --git a/doc/XXL-JOB-English-Documentation.md b/doc/XXL-JOB-English-Documentation.md index d949a5d0..0835599a 100644 --- a/doc/XXL-JOB-English-Documentation.md +++ b/doc/XXL-JOB-English-Documentation.md @@ -5,6 +5,8 @@ [![GitHub release](https://img.shields.io/github/release/xuxueli/xxl-job.svg)](https://github.com/xuxueli/xxl-job/releases) [![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0.html) [![Gitter](https://badges.gitter.im/xuxueli/xxl-job.svg)](https://gitter.im/xuxueli/xxl-job?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![donate](https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square)](http://www.xuxueli.com/page/donate.html) + ## 1. Brief introduction @@ -35,6 +37,9 @@ XXL-JOB is a lightweight distributed task scheduling framework, the core design - 22.Failure handling strategy:Handling strategy when scheduling fails, the strategy includes: failure alarm (default), failure retry; - 23.Sharding broadcast task: When an executor cluster is deployed, task routing strategy select "sharding broadcast", a task schedule will broadcast all the actuators in the cluster to perform it once, you can develop sharding tasks based on sharding parameters; - 24.Dynamic sharding: The sharding broadcast task is sharded by the executors to support the dynamic expansion of the executor cluster to dynamically increase the number of shardings and cooperate with the business handle; In the large amount of data operations can significantly improve the task processing capacity and speed. +- 25、Event trigger:In addition to "Cron" and "Task Dependency" to trigger tasks, support event-based triggering tasks. The dispatch center provides API service that triggers a single execution of the task, it can be triggered flexibly according to business events. + + ### 1.3 Development In 2015, I created the XXL-JOB project repository on github and submitted the first commit, followed by the system structure design, UI selection, interactive design ... In 2015 - November, XXL-JOB finally RELEASE the first big version of V1.0, then I will be released to OSCHINA, XXL-JOB OSCHINA won the popular recommendation of @红薯, the same period reached OSCHINA's " Popular move "ranked first and git.oschina open source software monthly heat ranked first, especially thanks for @红薯, thank you for the attention and support. @@ -89,6 +94,17 @@ So far, XXL-JOB has access to a number of companies online product line, access - 41、广州瀚农网络科技有限公司 - 42、享点科技有限公司 - 43、杭州比智科技有限公司 + - 44、圳临界线网络科技有限公司 + - 45、广州知识圈网络科技有限公司 + - 46、国誉商业上海有限公司 + - 47、海尔消费金融有限公司,嗨付、够花 (海尔) + - 48、广州巴图鲁信息科技有限公司 + - 49、深圳市鹏海运电子数据交换有限公司 + - 50、深圳市亚飞电子商务有限公司 + - 51、上海趣医网络有限公司 + - 52、聚金资本 + - 53、北京父母邦网络科技有限公司 + - 54、中山元赫软件科技有限公司 - …… > The company that access and use this product is welcome to register at the [address](https://github.com/xuxueli/xxl-job/issues/1 ), only for product promotion. @@ -114,18 +130,12 @@ Source repository address | Release Download com.xuxueli xxl-job-core - 1.8.1 + 1.8.2 ``` -#### Technical exchange group (technical exchange only) - -- Tecent QQ Group 6:399758605 -- Tecent QQ Group 5:138274130 -- Tecent QQ Group 4:464762661 -- Tecent QQ Group 3:242151780 -- Tecent QQ Group 2:438249535 -- Tecent QQ Group 1:367260654 +#### Technical exchange group +- [社区交流](http://www.xuxueli.com/page/community.html) - [Gitter](https://gitter.im/xuxueli/xxl-job) ### 1.5 Environment @@ -339,7 +349,7 @@ On the log console,you can view task execution log on the executor immediately a GLUE模式(Java):task source code is maintened in the schedule center,it must implement IJobHandler and explain by "groovy" in the executor instance,inject other bean instace by annotation @Resource/@Autowire. GLUE模式(Shell):it’s source code is a shell script and maintained in the schedule center. GLUE模式(Python):it’s source code is a python script and maintained in the schedule center. - - JobHandler:it’s used in "BEAN模式",it’s instance is defined by annotation @JobHander on the JobHandler class name. + - JobHandler:it’s used in "BEAN模式",it’s instance is defined by annotation @JobHandler on the JobHandler class name. - 子任务Key:every task has a unique key (task Key can acquire from task list),when main task is done successfully it’s child task stand for by this key will be scheduled. - 阻塞处理策略:the stategy handle the task when this task is scheduled too frequently and the task is block to wait for cpu time. 单机串行(默认):task schedule request go into the FIFO queue and execute serially. @@ -358,7 +368,7 @@ The task logic exist in the executor project as JobHandler,the develop steps as #### Step 1:develp obHandler in the executor project - 1, create new java class implent com.xxl.job.core.handler.IJobHandler; - 2, if you add @Component annotation on the top of the class name it’s will be managed as a bean instance by spring container; - - 3, add “@JobHander(value=" customize jobhandler name")” annotation,the value stand for JobHandler name,it will be used as JobHandler property when create a new task in the schedule center. + - 3, add “@JobHandler(value=" customize jobhandler name")” annotation,the value stand for JobHandler name,it will be used as JobHandler property when create a new task in the schedule center. (go and see DemoJobHandler in the xxl-job-executor-example project, as shown below) ![输入图片说明](https://static.oschina.net/uploads/img/201607/23232347_oLlM.png "在这里输入图片标题") @@ -674,7 +684,7 @@ On the task log page ,you can see matched child task and triggered child task’ ### 5.5 Task "run mode" analysis #### 5.5.1 "Bean模式" task Development steps:go and see "chapter 3" . -principle: every Bean mode task is a Spring Bean instance and it is maintained in executor project’s Spring container. task class nedd to add “@JobHander(value="name")” annotation, because executor identify task bean instance in spring container through annotation. Task class nedd to implements interface IJobHandler, task logic code in method execute(), the task logic in execute() method will be executed when executor received a schedule request from schedule center. +principle: every Bean mode task is a Spring Bean instance and it is maintained in executor project’s Spring container. task class nedd to add “@JobHandler(value="name")” annotation, because executor identify task bean instance in spring container through annotation. Task class nedd to implements interface IJobHandler, task logic code in method execute(), the task logic in execute() method will be executed when executor received a schedule request from schedule center. #### 5.5.2 "GLUE模式(Java)" task Development steps:go and see "chapter 3" . @@ -695,7 +705,7 @@ Executor is actually an embedded Jetty server with default port 9999, as shown b ![输入图片说明](https://static.oschina.net/uploads/img/201703/10174923_TgNO.png "在这里输入图片标题") -Executor will identify Bean mode task in spring container through @JobHander When project start, it will be managed use the value of annotation as key. +Executor will identify Bean mode task in spring container through @JobHandler When project start, it will be managed use the value of annotation as key. When executor received schedule request from schedule center, if task type is “Bean模式” it will match bean mode task in Spring container and call it’s execute() method and execute task logic. if task type is “GLUE模式”, it will load Glue code, instantiate a Java object and inject other spring service(notice: the spring service injected in Glue code must exist in the same executor project), then call execute() method and execute task logic. @@ -758,6 +768,18 @@ There are only two settings when communication between scheduler center and exec - one:do not configure AccessToken on both, close security check. - two:configure the same AccessToken on both; +### 5.11 Dispatching center API services +The scheduling center provides API services for executors and business parties to choose to use, and the currently available API services are available. + + 1. Job result callback service; + 2. Executor registration service; + 3. Executor registration remove services; + 4. Triggers a single execution service, and support the task to be triggered according to the business event; + +The scheduling center API service location: com.xxl.job.core.biz.AdminBiz.java + +The scheduling center API service requests reference code:com.xxl.job.dao.impl.AdminBizTest.java + ## 6 Version update log ### 6.1 version V1.1.x,New features [2015-12-05] @@ -790,13 +812,13 @@ There are only two settings when communication between scheduler center and exec - stability; ### 6.3 version V1.3.0,New features [2016-05-19] -- 1、discard local task module, remote task was recommended, easy to decouple system, the JobHander of task was called executor. +- 1、discard local task module, remote task was recommended, easy to decouple system, the JobHandler of task was called executor. - 2、dicard underlying communication type servlet, JETTY was recommended, schedule and callback bidirectional communication, rebuild the communication logic; - 3、UI interactive optimization:optimize left menu expansion and menu item selected status , task list opens the table with compression optimization; - 4、【important】executor is subdivided into two develop mode:BEAN、GLUE: Introduction to the executor mode: - - BEAN mode executor:every executor is a Spring Bean instance,it was recognized and scheduled by XXL-JOB through @JobHander annotation; + - BEAN mode executor:every executor is a Spring Bean instance,it was recognized and scheduled by XXL-JOB through @JobHandler annotation; -GLUE mode executor:every executor corresponds to a piece of code,edited and maintained online by Web, Dynamic compile and takes effect in real time, executor is responsible for loading GLUE code and executing; ### 6.4 version V1.3.1,New features [2016-05-23] @@ -995,7 +1017,4 @@ This product is open source and free, and will continue to provide free communit --- ### Donate -No matter how much the amount is enough to express your thought, thank you very much :) - -Webchat: -Alipay: +No matter how much the amount is enough to express your thought, thank you very much :) [To donate](http://www.xuxueli.com/page/donate.html ) diff --git a/doc/XXL-JOB官方文档.md b/doc/XXL-JOB官方文档.md index 143b1305..b9fa7b7d 100644 --- a/doc/XXL-JOB官方文档.md +++ b/doc/XXL-JOB官方文档.md @@ -5,6 +5,8 @@ [![GitHub release](https://img.shields.io/github/release/xuxueli/xxl-job.svg)](https://github.com/xuxueli/xxl-job/releases) [![License](https://img.shields.io/badge/license-GPLv3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0.html) [![Gitter](https://badges.gitter.im/xuxueli/xxl-job.svg)](https://gitter.im/xuxueli/xxl-job?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![donate](https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square)](http://www.xuxueli.com/page/donate.html) + ## 一、简介 @@ -14,28 +16,30 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ### 1.2 特性 - 1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手; - 2、动态:支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效; -- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现,可保证调度中心HA; +- 3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现并支持集群部署,可保证调度中心HA; - 4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA; -- 5、任务Failover:执行器集群部署时,任务路由策略选择"故障转移"情况下调度失败时将会平滑切换执行器进行Failover; -- 6、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行; -- 7、自定义任务参数:支持在线配置调度任务入参,即时生效; -- 8、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞; -- 9、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务; -- 10、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件; -- 11、状态监控:支持实时监控任务进度; -- 12、Rolling执行日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志; -- 13、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。 -- 14、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性; -- 15、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔; -- 16、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用; -- 17、任务注册: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址; -- 18、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等; -- 19、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等; -- 20、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python等类型脚本; -- 21、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度; -- 22、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试; -- 23、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务; -- 24、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。 +- 5、注册中心: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址; +- 6、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务; +- 7、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等; +- 8、故障转移:任务路由策略选择"故障转移"情况下,如果执行器集群中某一台机器故障,将会自动Failover切换到一台正常的执行器发送调度请求。 +- 9、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试; +- 10、失败重试:调度中心调度失败且启用"失败重试"策略时,将会自动重试一次;执行器执行失败且回调失败重试状态时,也将会自动重试一次; +- 11、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度; +- 12、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发集群中所有执行器执行一次任务,可根据分片参数开发分片任务; +- 13、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。 +- 14、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。 +- 15、任务进度监控:支持实时监控任务进度; +- 16、Rolling实时日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志; +- 17、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。 +- 18、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python、NodeJS等类型脚本; +- 19、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔; +- 20、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行; +- 21、自定义任务参数:支持在线配置调度任务入参,即时生效; +- 22、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞; +- 23、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性; +- 24、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件; +- 25、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用; +- 26、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等; ### 1.3 发展 于2015年中,我在github上创建XXL-JOB项目仓库并提交第一个commit,随之进行系统结构设计,UI选型,交互设计…… @@ -48,8 +52,10 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 于2017-05-13,在上海举办的 "[第62期开源中国源创会](https://www.oschina.net/event/2236961)" 的 "放码过来" 环节,我登台对XXL-JOB做了演讲,台下五百位在场观众反响热烈([图文回顾](https://www.oschina.net/question/2686220_2242120) )。 +于2017-12-11,XXL-JOB有幸参会《[InfoQ ArchSummit全球架构师峰会](http://bj2017.archsummit.com/)》,并被拍拍贷架构总监"杨波老师"在专题 "[微服务原理、基础架构和开源实践](http://bj2017.archsummit.com/training/2)" 中现场介绍。 + > 我司大众点评目前已接入XXL-JOB,内部别名《Ferrari》(Ferrari基于XXL-JOB的V1.1版本定制而成,新接入应用推荐升级最新版本)。 -据最新统计, 自2016-01-21接入至2017-07-07期间,该系统已调度约60万余次,表现优异。新接入应用推荐使用最新版本,因为经过数个大版本的更新,系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升,核心功能更加稳定高效。 +据最新统计, 自2016-01-21接入至2017-12-01期间,该系统已调度约100万次,表现优异。新接入应用推荐使用最新版本,因为经过数个大版本的更新,系统的任务模型、UI交互模型以及底层调度通讯模型都有了较大的优化和提升,核心功能更加稳定高效。 至今,XXL-JOB已接入多家公司的线上产品线,接入场景如电商业务,O2O业务和大数据作业等,截止2016-07-19为止,XXL-JOB已接入的公司包括不限于: @@ -96,6 +102,22 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 - 41、广州瀚农网络科技有限公司 - 42、享点科技有限公司 - 43、杭州比智科技有限公司 + - 44、圳临界线网络科技有限公司 + - 45、广州知识圈网络科技有限公司 + - 46、国誉商业上海有限公司 + - 47、海尔消费金融有限公司,嗨付、够花 (海尔) + - 48、广州巴图鲁信息科技有限公司 + - 49、深圳市鹏海运电子数据交换有限公司 + - 50、深圳市亚飞电子商务有限公司 + - 51、上海趣医网络有限公司 + - 52、聚金资本 + - 53、北京父母邦网络科技有限公司 + - 54、中山元赫软件科技有限公司 + - 55、中商惠民(北京)电子商务有限公司 + - 56、凯京集团 + - 57、华夏票联(北京)科技有限公司 + - 58、拍拍贷 + - 59、北京尚德机构在线教育有限公司 - …… > 更多接入的公司,欢迎在 [登记地址](https://github.com/xuxueli/xxl-job/issues/1 ) 登记,登记仅仅为了产品推广。 @@ -125,18 +147,13 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 com.xuxueli xxl-job-core - 1.8.1 + 1.8.2 ``` #### 技术交流 - -- 腾讯QQ群(6):399758605 -- 腾讯QQ群(5):138274130 -- 腾讯QQ群(4):464762661 -- 腾讯QQ群(3):242151780 -- 腾讯QQ群(2):438249535 -- 腾讯QQ群(1):367260654 +- [社区交流](http://www.xuxueli.com/page/community.html) +- [Gitter](https://gitter.im/xuxueli/xxl-job) ### 1.5 环境 - JDK:1.7+ @@ -174,7 +191,7 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ### 2.3 配置部署“调度中心” 调度中心项目:xxl-job-admin - 作用:统一管理任务调度平台上调度任务,负责触发调度执行。 + 作用:统一管理任务调度平台上调度任务,负责触发调度执行,并且提供任务管理平台。 #### 步骤一:调度中心配置: 调度中心配置文件地址: @@ -223,7 +240,7 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ### 2.4 配置部署“执行器项目” “执行器”项目:xxl-job-executor-sample-spring (如新建执行器项目,可参考该Sample示例执行器项目的配置步骤;) - 作用:负责接收“调度中心”的调度并执行; + 作用:负责接收“调度中心”的调度并执行;可直接部署执行器,也可以将执行器集成到现有业务项目中。 #### 步骤一:maven依赖 确认pom文件中引入了 "xxl-job-core" 的maven依赖; @@ -334,16 +351,16 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 - 执行器:任务的绑定的执行器,任务触发调度时将会自动发现注册成功的执行器, 实现任务自动发现功能; 另一方面也可以方便的进行任务分组。每个任务必须绑定一个执行器, 可在 "执行器管理" 进行设置; - 描述:任务的描述信息,便于任务管理; - 路由策略:当执行器集群部署时,提供丰富的路由策略,包括; - FIRST(第一个):固定选择第一个执行器; - LAST(最后一个):固定选择最后一个执行器; + FIRST(第一个):固定选择第一个机器; + LAST(最后一个):固定选择最后一个机器; ROUND(轮询):; - RANDOM(随机):随机选择在线的执行器; - CONSISTENT_HASH(一致性HASH):分组下机器地址相同,不同JOB均匀散列在不同机器上,保证分组下机器分配JOB平均;且每个JOB固定调度其中一台机器; - LEAST_FREQUENTLY_USED(最不经常使用):单个JOB对应的每个执行器,使用频率最低的优先被选举; - LEAST_RECENTLY_USED(最近最久未使用):单个JOB对应的每个执行器,最久为使用的优先被选举; + RANDOM(随机):随机选择在线的机器; + CONSISTENT_HASH(一致性HASH):每个任务按照Hash算法固定选择某一台机器,且所有任务均匀散列在不同机器上。 + LEAST_FREQUENTLY_USED(最不经常使用):使用频率最低的机器优先被选举; + LEAST_RECENTLY_USED(最近最久未使用):最久为使用的机器优先被选举; FAILOVER(故障转移):按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度; BUSYOVER(忙碌转移):按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度; - SHARDING_BROADCAST(分片广播):广播触发对应集群中所有执行器执行一次任务,同时传递分片参数;可根据分片参数开发分片任务; + SHARDING_BROADCAST(分片广播):广播触发对应集群中所有机器执行一次任务,同时传递分片参数;可根据分片参数开发分片任务; - Cron:触发任务执行的Cron表达式; - 运行模式: @@ -351,15 +368,16 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 GLUE模式(Java):任务以源码方式维护在调度中心;该模式的任务实际上是一段继承自IJobHandler的Java类代码并 "groovy" 源码方式维护,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器里中的其他服务; GLUE模式(Shell):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "shell" 脚本; GLUE模式(Python):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "python" 脚本; - - JobHandler:运行模式为 "BEAN模式" 时生效,对应执行器中新开发的JobHandler类“@JobHander”注解自定义的value值; + GLUE模式(NodeJS):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "nodejs" 脚本; + - JobHandler:运行模式为 "BEAN模式" 时生效,对应执行器中新开发的JobHandler类“@JobHandler”注解自定义的value值; - 子任务Key:每个任务都拥有一个唯一的任务Key(任务Key可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务Key所对应的任务的一次主动调度。 - 阻塞处理策略:调度过于密集执行器来不及处理时的处理策略; 单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行; 丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败; 覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务; - 失败处理策略;调度失败时的处理策略; - 失败告警(默认):调度失败时,将会触发失败报警,如发送报警邮件; - 失败重试:调度失败时,将会主动进行一次失败重试调度,重试调度后仍然失败将会触发一失败告警。注意当任务以failover方式路由时,每次失败重试将会触发新一轮路由。 + 失败告警(默认):调度失败和执行失败时,都将会触发失败报警,默认会发送报警邮件; + 失败重试:调度失败时,除了进行失败告警之外,将会自动重试一次;注意在执行失败时不会重试,而是根据回调返回值判断是否重试; - 执行参数:任务执行所需的参数,多个参数时用逗号分隔,任务执行时将会把多个参数转换成数组传入; - 报警邮件:任务调度失败时邮件通知的邮箱地址,支持配置多邮箱地址,配置多个邮箱地址时用逗号分隔; - 负责人:任务的负责人; @@ -370,13 +388,13 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 #### 步骤一:执行器项目中,开发JobHandler: - 1、 新建一个继承com.xxl.job.core.handler.IJobHandler的Java类; - 2、 该类被Spring容器扫描为Bean实例,如加“@Component”注解; - - 3、 添加 “@JobHander(value="自定义jobhandler名称")”注解,注解的value值为自定义的JobHandler名称,该名称对应的是调度中心新建任务的JobHandler属性的值。 + - 3、 添加 “@JobHandler(value="自定义jobhandler名称")”注解,注解的value值为自定义的JobHandler名称,该名称对应的是调度中心新建任务的JobHandler属性的值。 (可参考Sample示例执行器中的DemoJobHandler,见下图) ![输入图片说明](https://static.oschina.net/uploads/img/201607/23232347_oLlM.png "在这里输入图片标题") #### 步骤二:调度中心,新建调度任务 -参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "BEAN模式",JobHandler属性填写任务注解@JobHander中定义的值; +参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "BEAN模式",JobHandler属性填写任务注解“@JobHandler”中定义的值; ![输入图片说明](https://static.oschina.net/uploads/img/201704/27225124_yrcO.png "在这里输入图片标题") @@ -419,6 +437,16 @@ XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是 ![输入图片说明](https://static.oschina.net/uploads/img/201704/27232305_BPLG.png "在这里输入图片标题") +### 3.5 GLUE模式(NodeJS) + +#### 步骤一:调度中心,新建调度任务 +参考上文“配置属性详细说明”对新建的任务进行参数配置,运行模式选中 "GLUE模式(NodeJS)"; + +#### 步骤二:开发任务代码: +选中指定任务,点击该任务右侧“GLUE”按钮,将会前往GLUE任务的Web IDE界面,在该界面支持对任务代码进行开发(也可以在IDE中开发完成后,复制粘贴到编辑中)。 + +该模式的任务实际上是一段 "nodejS" 脚本; + ## 四、任务管理 @@ -560,7 +588,7 @@ XXL-JOB首先定制了Quartz原生表结构前缀(XXL_JOB_QRTZ_)。 #### 5.3.2 系统组成 - **调度模块(调度中心)**: 负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。调度系统与任务解耦,提高了系统可用性和稳定性,同时调度系统性能不再受限于任务模块; - 支持可视化、简单且动态的维管理调度信息,包括任务新建,更新,删除,GLUE开发和任务报警等,所有上述操作都会实时生效,同时支持监控调度结果以及执行日志,支持执行器Failover。 + 支持可视化、简单且动态的管理调度信息,包括任务新建,更新,删除,GLUE开发和任务报警等,所有上述操作都会实时生效,同时支持监控调度结果以及执行日志,支持执行器Failover。 - **执行模块(执行器)**: 负责接收调度请求并执行任务逻辑。任务模块专注于任务的执行等操作,开发和维护更加简单和高效; 接收“调度中心”的执行请求、终止请求和日志请求等。 @@ -595,18 +623,24 @@ org.quartz.jobStore.clusterCheckinInterval: 1000 ``` #### 5.4.4 调度线程池 -默认线程池中线程的数量为10个,避免单线程因阻塞而引起任务调度延迟。 +调度采用线程池方式实现,避免单线程因阻塞而引起任务调度延迟。 ``` org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool -org.quartz.threadPool.threadCount: 10 +org.quartz.threadPool.threadCount: 15 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true ``` -XXL-JOB系统中业务逻辑在远程执行器执行,调度中心每次调度仅仅负责一次调度请求,执行器会将请求存入执行队列并且立即响应调度中心;相比直接在quartz的QuartzJobBean中执行业务逻辑,差别就像大象和羽毛; +XXL-JOB系统中业务逻辑在远程执行器执行,调度中心每次触发调度时仅发送一次调度请求,执行器会将请求存入执行队列并且立即响应调度中心;相比直接在quartz的QuartzJobBean中执行业务逻辑,极大的降低了调度线程占用; + +XXL-JOB调度中心中每个JOB逻辑非常 “轻”,单个JOB一次运行平均耗时基本在 "10ms" 之内(基本为一次请求的网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行; + +理论上采用推荐机器配置 "4核4G内存"情况下,单线程可以承担 100(quartz最小时间粒度1000ms/触发一次任务耗时10ms)个密集任务(每秒执行一次)的正常调度触发。因此,默认配置的15个线程理论上可以承担起1500个密集任务的正常运行。 -XXL-JOB调度中心中每个JOB逻辑非常 “轻”,单个JOB一次运行平均耗时基本在 "100ms" 之内(基本是网络开销);因此,可以保证使用有限的线程支撑大量的JOB并发运行;上面配置的10个线程至少可以支撑100个JOB正常运行; +实际场景中,调度请求网络耗时不同、DB读写耗时不同、任务密集或稀疏调度情况不同,会导致任务量上限会上下波动。 + +如若需要支撑更多的任务量,可以通过 "调大调度线程数" 和 "提升机器配置" 两种方式实现。 #### 5.4.5 @DisallowConcurrentExecution XXL-JOB调度模块的“调度中心”默认不使用该注解,即默认开启并行机制,因为RemoteHttpJobBean为公共QuartzJobBean,这样在多线程调度的情况下,调度模块被阻塞的几率很低,大大提高了调度系统的承载量。 @@ -691,13 +725,13 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback ### 5.5 任务 "运行模式" 剖析 #### 5.5.1 "Bean模式" 任务 开发步骤:可参考 "章节三" ; -原理:每个Bean模式任务都是一个Spring的Bean类实例,它被维护在“执行器”项目的Spring容器中。任务类需要加“@JobHander(value="名称")”注解,因为“执行器”会根据该注解识别Spring容器中的任务。任务类需要继承统一接口“IJobHandler”,任务逻辑在execute方法中开发,因为“执行器”在接收到调度中心的调度请求时,将会调用“IJobHandler”的execute方法,执行任务逻辑。 +原理:每个Bean模式任务都是一个Spring的Bean类实例,它被维护在“执行器”项目的Spring容器中。任务类需要加“@JobHandler(value="名称")”注解,因为“执行器”会根据该注解识别Spring容器中的任务。任务类需要继承统一接口“IJobHandler”,任务逻辑在execute方法中开发,因为“执行器”在接收到调度中心的调度请求时,将会调用“IJobHandler”的execute方法,执行任务逻辑。 #### 5.5.2 "GLUE模式(Java)" 任务 开发步骤:可参考 "章节三" ; 原理:每个 "GLUE模式(Java)" 任务的代码,实际上是“一个继承自“IJobHandler”的实现类的类代码”,“执行器”接收到“调度中心”的调度请求时,会通过Groovy类加载器加载此代码,实例化成Java对象,同时注入此代码中声明的Spring服务(请确保Glue代码中的服务和类引用在“执行器”项目中存在),然后调用该对象的execute方法,执行任务逻辑。 -#### 5.5.3 GLUE模式(Shell) + GLUE模式(Python) +#### 5.5.3 GLUE模式(Shell) + GLUE模式(Python) + GLUE模式(NodeJS) 开发步骤:可参考 "章节三" ; 原理:脚本任务的源码托管在调度中心,脚本逻辑在执行器运行。当触发脚本任务时,执行器会加载脚本源码在执行器机器上生成一份脚本文件,然后通过Java代码调用该脚本;并且实时将脚本输出日志写到任务日志文件中,从而在调度中心可以实时监控脚本运行情况;脚本返回码为0时表示执行成功,其他标示执行失败。 @@ -705,6 +739,7 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback - shell脚本:任务运行模式选择为 "GLUE模式(Shell)"时支持 "shell" 脚本任务; - python脚本:任务运行模式选择为 "GLUE模式(Python)"时支持 "python" 脚本任务; + - nodejs脚本:务运行模式选择为 "GLUE模式(NodeJS)"时支持 "nodejs" 脚本任务; #### 5.5.4 执行器 @@ -712,7 +747,7 @@ xxl-job-admin#com.xxl.job.admin.controller.JobApiController.callback ![输入图片说明](https://static.oschina.net/uploads/img/201703/10174923_TgNO.png "在这里输入图片标题") -在项目启动时,执行器会通过“@JobHander”识别Spring容器中“Bean模式任务”,以注解的value属性为key管理起来。 +在项目启动时,执行器会通过“@JobHandler”识别Spring容器中“Bean模式任务”,以注解的value属性为key管理起来。 “执行器”接收到“调度中心”的调度请求时,如果任务类型为“Bean模式”,将会匹配Spring容器中的“Bean模式任务”,然后调用其execute方法,执行任务逻辑。如果任务类型为“GLUE模式”,将会加载GLue代码,实例化Java对象,注入依赖的Spring服务(注意:Glue代码中注入的Spring服务,必须存在与该“执行器”项目的Spring容器中),然后调用execute方法,执行任务逻辑。 @@ -756,11 +791,21 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件,需要通过 "分片广播" 以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。 -"分片广播" 和普通任务开发流程一致,不同之处在于可以可以获取分片参数,获取分片参数对象的代码如下(可参考Sample示例执行器中的示例任务"ShardingJobHandler" ): +"分片广播" 和普通任务开发流程一致,不同之处在于可以可以获取分片参数,获取分片参数进行分片业务处理。 - ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo(); +- Java语言任务获取分片参数方式:BEAN、GLUE模式(Java) +``` +// 可参考Sample示例执行器中的示例任务"ShardingJobHandler"了解试用 +ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo(); +``` +- 脚本语言任务获取分片参数方式:GLUE模式(Shell)、GLUE模式(Python)、GLUE模式(Nodejs) +``` +// 脚本任务入参固定为三个,依次为:任务传参、分片序号、分片总数。以Shell模式任务为例,获取分片参数代码如下 +echo "分片序号 index = $2" +echo "分片总数 total = $3" +``` -该分片参数对象拥有两个属性: +分片参数属性说明: index:当前分片序号(从0开始),执行器集群列表中当前执行器的序号; total:总分片数,执行器集群的总机器数量; @@ -779,6 +824,39 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件,需要通过 - 设置一:调度中心和执行器,均不设置AccessToken;关闭安全性校验; - 设置二:调度中心和执行器,设置了相同的AccessToken; +### 5.11 调度中心API服务 +调度中心提供了API服务,供执行器和业务方选择使用,目前提供的API服务有: + + 1、任务结果回调服务; + 2、执行器注册服务; + 3、执行器注册摘除服务; + 4、触发任务单次执行服务,支持任务根据业务事件触发; + +调度中心API服务位置:com.xxl.job.core.biz.AdminBiz.java + +调度中心API服务请求参考代码:com.xxl.job.adminbiz.AdminBizTest.java + +### 5.12 执行器API服务 +执行器提供了API服务,供调度中心选择使用,目前提供的API服务有: + + 1、心跳检测 + 2、忙碌检测 + 3、触发任务执行 + 4、获取Rolling Log + 5、终止任务 + +执行器API服务位置:com.xxl.job.core.biz.ExecutorBiz + +执行器API服务请求参考代码:com.xxl.executor.test.DemoJobHandlerTest + +### 5.13 故障转移 & 失败重试 +一次完整任务流程包括"调度(调度中心) + 执行(执行器)"两个阶段。 + +- "故障转移"发生在调度阶段,在执行器集群部署时,如果某一台执行器发生故障,该策略支持自动进行Failover切换到一台正常的执行器机器并且完成调度请求流程。 +- "失败重试"发生在"调度 + 执行"两个阶段,如下: + - 调度中心调度失败时,任务失败处理策略选择"失败重试",将会自动重试一次; + - 执行器运行失败时,任务执行结果返回"失败重试(IJobHandler.FAIL_RETRY)"回调,将会自动重试一次; + ## 六、版本更新日志 ### 6.1 版本 V1.1.x,新特性[2015-12-05] @@ -811,13 +889,13 @@ XXL-JOB会为每次调度请求生成一个单独的日志文件,需要通过 - 稳定性; ### 6.3 版本 V1.3.0,新特性[2016-05-19] -- 1、遗弃“本地任务”模式,推荐使用“远程任务”,易于系统解耦,任务对应的JobHander统称为“执行器”; +- 1、遗弃“本地任务”模式,推荐使用“远程任务”,易于系统解耦,任务对应的JobHandler统称为“执行器”; - 2、遗弃“servlet”方式底层系统通讯,推荐使用JETTY方式,调度+回调双向通讯,重构通讯逻辑; - 3、UI交互优化:左侧菜单展开状态优化,菜单项选中状态优化,任务列表打开表格有压缩优化; - 4、【重要】“执行器”细分为:BEAN、GLUE两种开发模式,简介见下文: “执行器” 模式简介: - - BEAN模式执行器:每个执行器都是Spring的一个Bean实例,XXL-JOB通过注解@JobHander识别和调度执行器; + - BEAN模式执行器:每个执行器都是Spring的一个Bean实例,XXL-JOB通过注解@JobHandler识别和调度执行器; -GLUE模式执行器:每个执行器对应一段代码,在线Web编辑和维护,动态编译生效,执行器负责加载GLUE代码和执行; ### 6.4 版本 V1.3.1,新特性[2016-05-23] @@ -982,30 +1060,61 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段 - 10、springboot版本执行器,升级至1.5.6.RELEASE版本; - 11、统一maven依赖版本管理; -### 6.18 版本 V1.8.2 特性[Coding] -- 1、解决执行器回调URL不支持配置HTTPS时问题; -- 2、规范项目目录,方便扩展多执行器; -- 3、新增JFinal类型执行器sample示例项目; -- 4、执行器手动设置IP时将会绑定Host; -- 5、项目主页搭建,提供中英文文档(http://www.xuxueli.com/xxl-job); -- 6、执行器回调线程销毁前, 批量回调队列中数据,防止任务结果丢失; -- 7、执行器注册线程销毁时, 主动摘除注册机器信息,提高执行器注册的实时性; -- 8、调度中心任务监控线程销毁时,批量对失败任务告警,防止告警信息丢失; -- 9、调度中心API服务:支持API方式触发任务执行; -- 10、事件调度:系统支持Cron、子任务触发、事件触发(API)三种方式触发任务调度; +### 6.19 版本 V1.8.2 特性[2017-09-04] +- 1、项目主页搭建:提供中英文文档:http://www.xuxueli.com/xxl-job +- 2、JFinal执行器Sample示例项目; +- 3、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。 +- 4、执行器摘除:执行器销毁时,主动通知调度中心并摘除对应执行器节点,提高执行器状态感知的时效性。 +- 5、执行器手动设置IP时将会绑定Host; +- 6、规范项目目录,方便扩展多执行器; +- 7、解决执行器回调URL不支持配置HTTPS时问题; +- 8、执行器回调线程销毁前, 批量回调队列中数据,防止任务结果丢失; +- 9、调度中心任务监控线程销毁时,批量对失败任务告警,防止告警信息丢失; +- 10、任务日志文件路径时间戳格式化时SimpleDateFormat并发问题解决; + +### 6.20 版本 V1.9.0 特性[迭代中] +- 1、新增任务运行模式 "GLUE模式(NodeJS) ",支持NodeJS脚本任务; +- 2、脚本任务Shell、Python和Nodejs等支持获取分片参数; +- 3、失败重试,完整支持:调度中心调度失败且启用"失败重试"策略时,将会自动重试一次;执行器执行失败且回调失败重试状态(新增失败重试状态返回值)时,也将会自动重试一次; +- 4、失败告警策略扩展:默认提供邮件失败告警,可扩展短信等,扩展代码位置为 "JobFailMonitorHelper.failAlarm"; +- 5、执行器端口支持自动生成(小于等于0时),避免端口定义冲突; +- 6、调度报表优化,支持时间区间筛选; +- 7、Log组件支持输出异常栈信息,底层实现优化; +- 8、告警邮件样式优化,调整为表格形式,邮件组件调整为commons-email简化邮件操作; +- 9、项目依赖升级,如spring、jackson等; +- 10、任务日志,记录发起调度的机器信息; +- 11、交互优化,如登陆注销; +- 12、任务Cron长度扩展支持至128位,支持负责类型Cron设置; +- 13、执行器地址录入交互优化,地址长度扩展支持至512位,支持大规模执行器集群配置; +- 14、任务参数“IJobHandler.execute”入参改为“String params”,增强入参通用性。 +- 15、JobHandler提供init/destroy方法,支持在JobHandler初始化和销毁时进行附加操作; +- 16、任务注解调整为 “@JobHandler”,与任务抽象接口统一; +- 17、修复任务监控线程被耗时任务阻塞的问题; +- 18、修复任务监控线程无法监控任务触发和执行状态均未0的问题; +- 19、执行器动态代理对象,拦截非业务方法的执行; +- 20、修复JobThread捕获Error错误不更新JobLog的问题; +- 21、修复任务列表界面左侧菜单合并时样式错乱问题; +- 22、调度中心项目日志配置改为xml文件格式; +- 23、Log地址格式兼容,支持非"/"结尾路径配置; +- 24、底层系统日志级别规范调整,清理遗留代码; +- 25、建表SQL优化,支持同步创建制定编码的库和表; + ### TODO LIST - 1、任务权限管理:执行器为粒度分配权限,核心操作校验权限; - 2、任务分片路由:分片采用一致性Hash算法计算出尽量稳定的分片顺序,即使注册机器存在波动也不会引起分批分片顺序大的波动;目前采用IP自然排序,可以满足需求,待定; -- 3、失败重试优化:目前失败重试逻辑为,在本次调度请求失败后重新执行一次请求逻辑。优化点为针对调度和执行失败时均做失败重试,重试时重新触发一次完整调度,这将可能导致失败是调度死循环,待定。 -- 4、回调失败写文件,查看日志时读文件确认,重启后回调确认; +- 3、任务单机多线程:提升任务单机并行处理能力; +- 4、回调失败丢包问题:执行器回调失败写文件,重启或周期性回调重试;调度中心周期性请求并同步未回调的执行结果; - 5、任务依赖,流程图,子任务+会签任务,各节点日志; - 6、调度任务优先级; - 7、移除quartz依赖,重写调度模块:新增或恢复任务时将下次执行记录插入delayqueue,调度中心集群竞争分布式锁,成功节点批量加载到期delayqueue数据,批量执行。 - 8、springboot 和 docker镜像,并且推送docker镜像到中央仓库,更进一步实现产品开箱即用; - 9、国际化:调度中心界面。 -- 10、执行器摘除:执行器销毁时,主动通知调度中心并摘除对应执行器节点,提高执行器状态感知的时效性。 -- 11、任务类方法"IJobHandler.execute"的参数类型改为"string",进一步方便参数传递;任务注解和任务类统一并改为"JobHandler""; +- 10、任务告警逻辑调整:任务调度,以及任务回调失败时,均推送监控队列。后期考虑通过任务Log字段控制告警状态; +- 11、执行器Log清理功能:调度中心Log删除时同步删除执行器中的Log文件; +- 12、Bean模式任务,JobHandler自动从执行器中查询展示为下拉框,选择后自动填充任务名称等属性; +- 13、任务事件触发API服务优化,支持调用时动态传参; + ## 七、其他 @@ -1023,22 +1132,4 @@ Tips: 历史版本(V1.3.x)目前已经Release至稳定版本, 进入维护阶段 --- ### 捐赠 -无论金额多少都足够表达您这份心意,非常感谢 :) - -微信: -支付宝: - -> 自2017-03-29起XXL系列接收用户捐赠,捐赠记录如下, 感谢你们的支持!: - -捐赠时间 | 金额 | 名称 | 留言 ---- | --- | --- | --- -2017-08-17 | 10.00¥ | 微信 | 不行,还得感谢一把 -2017-08-17 | 10.00¥ | 微信 | 感谢,好项目,好作者 -2017-08-16 | 3.00¥ | 微信 | -2017-06-30 | 10.00¥ | 石头哥哥* | 支持下xxl开源系列 -2017-06-16 | 10.00¥ | 劳巴* | xxl-job对我的帮助很大,辛苦你们的帮助,辛苦了! -2017-06-12 | 6.66¥ | 凌浦* | -2017-06-12 | 10.00¥ | Henry* | 支持XXL -2017-06-12 | 10.00¥ | loioi* | 感谢您的开源项目! -2017-05-10 | 10.00¥ | 阿杜杜不是阿木木* | 感谢您的开源项目! - +无论金额多少都足够表达您这份心意,非常感谢 :) [前往捐赠](http://www.xuxueli.com/page/donate.html ) diff --git a/doc/db/tables_xxl_job.sql b/doc/db/tables_xxl_job.sql index 3441f147..6de19187 100644 --- a/doc/db/tables_xxl_job.sql +++ b/doc/db/tables_xxl_job.sql @@ -1,3 +1,7 @@ +CREATE database if NOT EXISTS `xxl-job` default character set utf8 collate utf8_general_ci; +use `xxl-job`; + + CREATE TABLE XXL_JOB_QRTZ_JOB_DETAILS ( @@ -156,7 +160,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_INFO` ( `alarm_email` varchar(255) DEFAULT NULL COMMENT '报警邮件', `executor_route_strategy` varchar(50) DEFAULT NULL COMMENT '执行器路由策略', `executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler', - `executor_param` varchar(255) DEFAULT NULL COMMENT '执行器任务参数', + `executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数', `executor_block_strategy` varchar(50) DEFAULT NULL COMMENT '阻塞处理策略', `executor_fail_strategy` varchar(50) DEFAULT NULL COMMENT '失败处理策略', `glue_type` varchar(50) NOT NULL COMMENT 'GLUE类型', @@ -174,7 +178,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_LOG` ( `glue_type` varchar(50) DEFAULT NULL COMMENT 'GLUE类型', `executor_address` varchar(255) DEFAULT NULL COMMENT '执行器地址,本次执行的地址', `executor_handler` varchar(255) DEFAULT NULL COMMENT '执行器任务handler', - `executor_param` varchar(255) DEFAULT NULL COMMENT 'executor_param', + `executor_param` varchar(512) DEFAULT NULL COMMENT '执行器任务参数', `trigger_time` datetime DEFAULT NULL COMMENT '调度-时间', `trigger_code` varchar(255) NOT NULL DEFAULT '0' COMMENT '调度-结果', `trigger_msg` varchar(2048) DEFAULT NULL COMMENT '调度-日志', @@ -210,7 +214,7 @@ CREATE TABLE `XXL_JOB_QRTZ_TRIGGER_GROUP` ( `title` varchar(12) NOT NULL COMMENT '执行器名称', `order` tinyint(4) NOT NULL DEFAULT '0' COMMENT '排序', `address_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '执行器地址类型:0=自动注册、1=手动录入', - `address_list` varchar(200) DEFAULT NULL COMMENT '执行器地址列表,多地址逗号分隔', + `address_list` varchar(512) DEFAULT NULL COMMENT '执行器地址列表,多地址逗号分隔', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/doc/images/donate-paypal.png b/doc/images/donate-paypal.png new file mode 100644 index 00000000..24e78a40 Binary files /dev/null and b/doc/images/donate-paypal.png differ diff --git a/doc/images/git-osc-热门排行-第一.png b/doc/images/git-osc-热门排行-第一.png deleted file mode 100644 index 1db893af..00000000 Binary files a/doc/images/git-osc-热门排行-第一.png and /dev/null differ diff --git a/doc/images/gitee-gvp.jpg b/doc/images/gitee-gvp.jpg new file mode 100644 index 00000000..dcc195b0 Binary files /dev/null and b/doc/images/gitee-gvp.jpg differ diff --git a/pom.xml b/pom.xml index cdaba16c..97c73c75 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.xuxueli xxl-job - 1.8.2-SNAPSHOT + 1.9.0-SNAPSHOT pom ${project.artifactId} @@ -20,31 +20,32 @@ 3.0.1 2.2 - 3.2.18.RELEASE - 1.9.13 - 1.8.7 + 4.3.13.RELEASE + 2.9.3 + 1.8.13 1.7.25 - 2.3.20 - 4.11 + 2.3.23 + 4.12 + + 9.2.22.v20170606 + 4.0.51 + 4.5.4 - 9.4.6.v20170531 - 4.0.38 - 4.3.6 1.3 - 1.9.2 - 2.6 + 4.1 + 3.7 + 1.5 0.9.5.2 - 5.1.29 - 1.2.2 - 3.2.8 + 5.1.45 + 1.3.1 + 3.4.5 - 2.4.5 - 1.4.6 + 2.4.13 2.3.0 - 1.5.6.RELEASE + 1.5.9.RELEASE diff --git a/xxl-job-admin/pom.xml b/xxl-job-admin/pom.xml index cbddeecc..2498c234 100644 --- a/xxl-job-admin/pom.xml +++ b/xxl-job-admin/pom.xml @@ -4,7 +4,7 @@ com.xuxueli xxl-job - 1.8.2-SNAPSHOT + 1.9.0-SNAPSHOT xxl-job-admin war @@ -40,18 +40,22 @@ - org.codehaus.jackson - jackson-mapper-asl - ${jackson-mapper-asl.version} + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} - + - org.slf4j - slf4j-log4j12 - ${slf4j-api.version} + javax.servlet + javax.servlet-api + ${javax.servlet-api.version} + + + javax.servlet.jsp + jsp-api + ${jsp-api.version} - org.freemarker @@ -59,37 +63,37 @@ ${freemarker.version} - + - commons-beanutils - commons-beanutils - ${commons-beanutils.version} + org.slf4j + slf4j-log4j12 + ${slf4j-api.version} - + - commons-lang - commons-lang - ${commons-lang.version} + junit + junit + ${junit.version} + test - + - javax.servlet - javax.servlet-api - ${javax.servlet-api.version} + org.apache.commons + commons-collections4 + ${commons-collections4.version} + - javax.servlet.jsp - jsp-api - ${jsp-api.version} + org.apache.commons + commons-lang3 + ${commons-lang3.version} - - + - junit - junit - ${junit.version} - test + org.apache.commons + commons-email + ${commons-email.version} @@ -116,7 +120,6 @@ ${mybatis.version} - org.apache.httpcomponents @@ -124,13 +127,6 @@ ${httpclient.version} - - - javax.mail - mail - ${mail.version} - - org.quartz-scheduler diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java index c63f9cf7..23055c2f 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/IndexController.java @@ -5,9 +5,12 @@ import com.xxl.job.admin.controller.interceptor.PermissionInterceptor; import com.xxl.job.admin.core.util.PropertiesUtil; import com.xxl.job.admin.service.XxlJobService; import com.xxl.job.core.biz.model.ReturnT; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.propertyeditors.CustomDateEditor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; @@ -15,6 +18,8 @@ import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.Map; /** @@ -38,8 +43,8 @@ public class IndexController { @RequestMapping("/triggerChartDate") @ResponseBody - public ReturnT> triggerChartDate() { - ReturnT> triggerChartDate = xxlJobService.triggerChartDate(); + public ReturnT> triggerChartDate(Date startDate, Date endDate) { + ReturnT> triggerChartDate = xxlJobService.triggerChartDate(startDate, endDate); return triggerChartDate; } @@ -91,5 +96,12 @@ public class IndexController { return "help"; } + + @InitBinder + public void initBinder(WebDataBinder binder) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormat.setLenient(false); + binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true)); + } } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java index 496d34e7..6211f3c7 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobGroupController.java @@ -4,7 +4,7 @@ import com.xxl.job.admin.core.model.XxlJobGroup; import com.xxl.job.admin.dao.XxlJobGroupDao; import com.xxl.job.admin.dao.XxlJobInfoDao; import com.xxl.job.core.biz.model.ReturnT; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java index f3192737..a31536cf 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/JobLogController.java @@ -11,8 +11,8 @@ import com.xxl.job.core.biz.ExecutorBiz; import com.xxl.job.core.biz.model.LogResult; import com.xxl.job.core.biz.model.ReturnT; import com.xxl.job.core.rpc.netcom.NetComClientProxy; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.time.DateUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java index 8072b458..bd84534f 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/CookieInterceptor.java @@ -1,17 +1,17 @@ package com.xxl.job.admin.controller.interceptor; -import java.util.HashMap; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.lang.ArrayUtils; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; +import java.util.HashMap; /** * push cookies to model as cookieMap + * * @author xuxueli 2015-12-12 18:09:04 */ public class CookieInterceptor extends HandlerInterceptorAdapter { diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java index ab565e1f..74633dd7 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/controller/interceptor/PermissionInterceptor.java @@ -12,11 +12,12 @@ import java.math.BigInteger; /** * 权限拦截, 简易版 + * * @author xuxueli 2015-12-12 18:09:04 */ public class PermissionInterceptor extends HandlerInterceptorAdapter { - public static final String LOGIN_IDENTITY_KEY = "LOGIN_IDENTITY"; + public static final String LOGIN_IDENTITY_KEY = "XXL_JOB_LOGIN_IDENTITY"; public static final String LOGIN_IDENTITY_TOKEN; static { String username = PropertiesUtil.getString("xxl.job.login.username"); diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/LocalNomalJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/LocalNomalJobBean.java deleted file mode 100644 index 067e10f4..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/LocalNomalJobBean.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.xxl.job.admin.core.jobbean; -//package com.xxl.job.action.job; -// -//import java.io.PrintWriter; -//import java.io.StringWriter; -//import java.util.Date; -//import java.util.HashMap; -//import java.util.Map; -// -//import org.apache.commons.lang.StringUtils; -//import org.quartz.JobExecutionContext; -//import org.quartz.JobExecutionException; -//import org.quartz.JobKey; -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -//import org.springframework.scheduling.quartz.QuartzJobBean; -// -//import com.xxl.job.client.handler.HandlerRouter; -//import com.xxl.job.client.util.XxlJobNetCommUtil.RemoteCallBack; -//import com.xxl.job.client.util.JacksonUtil; -//import com.xxl.job.core.model.XxlJobInfo; -//import com.xxl.job.core.model.XxlJobLog; -//import com.xxl.job.core.thread.JobFailMonitorHelper; -//import com.xxl.job.core.util.DynamicSchedulerUtil; -// -///** -// * http job bean -// * @author xuxueli 2015-12-17 18:20:34 -// */ -//@Deprecated -//public abstract class LocalNomalJobBean extends QuartzJobBean { -// private static Logger logger = LoggerFactory.getLogger(LocalNomalJobBean.class); -// -// @Override -// protected void executeInternal(JobExecutionContext context) -// throws JobExecutionException { -// JobKey jobKey = context.getTrigger().getJobKey(); -// -// XxlJobInfo jobInfo = DynamicSchedulerUtil.xxlJobInfoDao.load(jobKey.getGroup(), jobKey.getName()); -// @SuppressWarnings("unchecked") -// HashMap jobDataMap = (HashMap) JacksonUtil.readValueRefer(jobInfo.getJobData(), Map.class); -// -// // save log -// XxlJobLog jobLog = new XxlJobLog(); -// jobLog.setJobGroup(jobInfo.getJobGroup()); -// jobLog.setJobName(jobInfo.getJobName()); -// jobLog.setJobCron(jobInfo.getJobCron()); -// jobLog.setJobDesc(jobInfo.getJobDesc()); -// jobLog.setJobClass(jobInfo.getJobClass()); -// jobLog.setJobData(jobInfo.getJobData()); -// -// jobLog.setJobClass(RemoteHttpJobBean.class.getName()); -// jobLog.setJobData(jobInfo.getJobData()); -// DynamicSchedulerUtil.xxlJobLogDao.save(jobLog); -// logger.info(">>>>>>>>>>> xxl-job trigger start, jobLog:{}", jobLog); -// -// // trigger request -// String handler_params = jobDataMap.get(HandlerRouter.HANDLER_PARAMS); -// String[] handlerParams = null; -// if (StringUtils.isNotBlank(handler_params)) { -// handlerParams = handler_params.split(","); -// } -// -// jobLog.setTriggerTime(new Date()); -// jobLog.setTriggerStatus(RemoteCallBack.SUCCESS); -// jobLog.setTriggerMsg(null); -// -// try { -// Object responseMsg = this.handle(handlerParams); -// -// jobLog.setHandleTime(new Date()); -// jobLog.setHandleStatus(RemoteCallBack.SUCCESS); -// jobLog.setHandleMsg(JacksonUtil.writeValueAsString(responseMsg)); -// } catch (Exception e) { -// logger.info("JobThread Exception:", e); -// StringWriter out = new StringWriter(); -// e.printStackTrace(new PrintWriter(out)); -// -// jobLog.setHandleTime(new Date()); -// jobLog.setHandleStatus(RemoteCallBack.FAIL); -// jobLog.setHandleMsg(out.toString()); -// } -// -// // update trigger info -// DynamicSchedulerUtil.xxlJobLogDao.updateTriggerInfo(jobLog); -// DynamicSchedulerUtil.xxlJobLogDao.updateHandleInfo(jobLog); -// JobFailMonitorHelper.monitor(jobLog.getId()); -// logger.info(">>>>>>>>>>> xxl-job trigger end, jobLog.id:{}, jobLog:{}", jobLog.getId(), jobLog); -// -// } -// -// public abstract Object handle(String... param); -// -//} \ No newline at end of file diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/impl/DemoConcurrentJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/impl/DemoConcurrentJobBean.java deleted file mode 100644 index a64ef5b7..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/impl/DemoConcurrentJobBean.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.xxl.job.admin.core.jobbean.impl; -//package com.xxl.job.action.job.impl; -// -//import java.util.concurrent.TimeUnit; -// -//import org.quartz.DisallowConcurrentExecution; -// -//import com.xxl.job.action.job.LocalNomalJobBean; -// -///** -// * demo job bean for no-concurrent -// * @author xuxueli 2016-3-12 14:25:14 -// */ -//@Deprecated -//@DisallowConcurrentExecution // 串行;线程数要多配置几个,否则不生效; -//public class DemoConcurrentJobBean extends LocalNomalJobBean { -// -// @Override -// public Object handle(String... param) { -// -// try { -// TimeUnit.SECONDS.sleep(10); -// } catch (InterruptedException e) { -// logger.error(e.getMessage(), e); -// } -// -// return false; -// } -// -//} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/impl/DemoNomalJobBean.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/impl/DemoNomalJobBean.java deleted file mode 100644 index ccb7c41d..00000000 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/jobbean/impl/DemoNomalJobBean.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.xxl.job.admin.core.jobbean.impl; -//package com.xxl.job.action.job.impl; -// -//import java.util.concurrent.TimeUnit; -// -//import org.slf4j.Logger; -//import org.slf4j.LoggerFactory; -// -//import com.xxl.job.action.job.LocalNomalJobBean; -// -///** -// * demo job bean for concurrent -// * @author xuxueli 2016-3-12 14:25:57 -// */ -//@Deprecated -//public class DemoNomalJobBean extends LocalNomalJobBean { -// private static Logger Logger = LoggerFactory.getLogger(DemoNomalJobBean.class); -// -// @Override -// public Object handle(String... param) { -// Logger.info("DemoNomalJobBean run :" + param); -// -// try { -// TimeUnit.SECONDS.sleep(10); -// } catch (InterruptedException e) { -// logger.error(e.getMessage(), e); -// } -// -// return false; -// } -// -//} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java index 85fb4a10..040c9226 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/model/XxlJobGroup.java @@ -1,6 +1,6 @@ package com.xxl.job.admin.core.model; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.Arrays; diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java index 7d7838de..2695569c 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobFailMonitorHelper.java @@ -4,8 +4,11 @@ import com.xxl.job.admin.core.model.XxlJobGroup; import com.xxl.job.admin.core.model.XxlJobInfo; import com.xxl.job.admin.core.model.XxlJobLog; import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler; +import com.xxl.job.admin.core.util.JobKeyUtil; import com.xxl.job.admin.core.util.MailUtil; import com.xxl.job.core.biz.model.ReturnT; +import com.xxl.job.core.handler.IJobHandler; +import org.apache.commons.collections4.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,6 +29,8 @@ public class JobFailMonitorHelper { return instance; } + // ---------------------- monitor ---------------------- + private LinkedBlockingQueue queue = new LinkedBlockingQueue(0xfff8); private Thread monitorThread; @@ -35,33 +40,41 @@ public class JobFailMonitorHelper { @Override public void run() { - // monitor while (!toStop) { try { - Integer jobLogId = JobFailMonitorHelper.instance.queue.take(); - if (jobLogId != null && jobLogId > 0) { - XxlJobLog log = XxlJobDynamicScheduler.xxlJobLogDao.load(jobLogId); - if (log!=null) { - if (ReturnT.SUCCESS_CODE==log.getTriggerCode() && log.getHandleCode()==0) { - // job running, wait + again monitor - TimeUnit.SECONDS.sleep(10); + List jobLogIdList = new ArrayList(); + int drainToNum = JobFailMonitorHelper.instance.queue.drainTo(jobLogIdList); + if (CollectionUtils.isNotEmpty(jobLogIdList)) { + for (Integer jobLogId : jobLogIdList) { + if (jobLogId==null || jobLogId==0) { + continue; + } + XxlJobLog log = XxlJobDynamicScheduler.xxlJobLogDao.load(jobLogId); + if (log == null) { + continue; + } + if (IJobHandler.SUCCESS.getCode() == log.getTriggerCode() && log.getHandleCode() == 0) { JobFailMonitorHelper.monitor(jobLogId); logger.info(">>>>>>>>>>> job monitor, job running, JobLogId:{}", jobLogId); - } - if (ReturnT.SUCCESS_CODE==log.getTriggerCode() && ReturnT.SUCCESS_CODE==log.getHandleCode()) { + } else if (IJobHandler.SUCCESS.getCode() == log.getHandleCode()) { // job success, pass logger.info(">>>>>>>>>>> job monitor, job success, JobLogId:{}", jobLogId); - } - - if (ReturnT.FAIL_CODE == log.getTriggerCode()|| ReturnT.FAIL_CODE==log.getHandleCode()) { + } else if (IJobHandler.FAIL.getCode() == log.getTriggerCode() + || IJobHandler.FAIL.getCode() == log.getHandleCode() + || IJobHandler.FAIL_RETRY.getCode() == log.getHandleCode() ) { // job fail, - sendMonitorEmail(log); + failAlarm(log); logger.info(">>>>>>>>>>> job monitor, job fail, JobLogId:{}", jobLogId); + } else { + JobFailMonitorHelper.monitor(jobLogId); + logger.info(">>>>>>>>>>> job monitor, job status unknown, JobLogId:{}", jobLogId); } } } + + TimeUnit.SECONDS.sleep(10); } catch (Exception e) { logger.error("job monitor error:{}", e); } @@ -75,7 +88,7 @@ public class JobFailMonitorHelper { XxlJobLog log = XxlJobDynamicScheduler.xxlJobLogDao.load(jobLogId); if (ReturnT.FAIL_CODE == log.getTriggerCode()|| ReturnT.FAIL_CODE==log.getHandleCode()) { // job fail, - sendMonitorEmail(log); + failAlarm(log); logger.info(">>>>>>>>>>> job monitor last, job fail, JobLogId:{}", jobLogId); } } @@ -87,24 +100,6 @@ public class JobFailMonitorHelper { monitorThread.start(); } - /** - * send monitor email - * @param jobLog - */ - private void sendMonitorEmail(XxlJobLog jobLog){ - XxlJobInfo info = XxlJobDynamicScheduler.xxlJobInfoDao.loadById(jobLog.getJobId()); - if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) { - - Set emailSet = new HashSet(Arrays.asList(info.getAlarmEmail().split(","))); - for (String email: emailSet) { - String title = "《调度监控报警》(任务调度中心XXL-JOB)"; - XxlJobGroup group = XxlJobDynamicScheduler.xxlJobGroupDao.load(Integer.valueOf(info.getJobGroup())); - String content = MessageFormat.format("任务调度失败, 执行器名称:{0}, 任务描述:{1}.", group!=null?group.getTitle():"null", info.getJobDesc()); - MailUtil.sendMail(email, title, content, false, null); - } - } - } - public void toStop(){ toStop = true; // interrupt and wait @@ -120,5 +115,55 @@ public class JobFailMonitorHelper { public static void monitor(int jobLogId){ getInstance().queue.offer(jobLogId); } - + + + // ---------------------- alarm ---------------------- + + // email alarm template + private static final String mailBodyTemplate = "
监控告警明细:" + + "\n" + + " " + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
执行器JobKey任务描述告警类型
{0}{1}{2}调度失败
"; + + /** + * fail alarm + * + * @param jobLog + */ + private void failAlarm(XxlJobLog jobLog){ + + // send monitor email + XxlJobInfo info = XxlJobDynamicScheduler.xxlJobInfoDao.loadById(jobLog.getJobId()); + if (info!=null && info.getAlarmEmail()!=null && info.getAlarmEmail().trim().length()>0) { + + Set emailSet = new HashSet(Arrays.asList(info.getAlarmEmail().split(","))); + for (String email: emailSet) { + XxlJobGroup group = XxlJobDynamicScheduler.xxlJobGroupDao.load(Integer.valueOf(info.getJobGroup())); + + String title = "调度中心监控报警"; + String content = MessageFormat.format(mailBodyTemplate, group!=null?group.getTitle():"null", JobKeyUtil.formatJobKey(info), info.getJobDesc()); + + MailUtil.sendMail(email, title, content); + } + } + + // TODO, custom alarm strategy, such as sms + + } + } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java index 94f40a0e..647c02ed 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/thread/JobRegistryMonitorHelper.java @@ -4,8 +4,8 @@ import com.xxl.job.admin.core.model.XxlJobGroup; import com.xxl.job.admin.core.model.XxlJobRegistry; import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler; import com.xxl.job.core.enums.RegistryConfig; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java index 48fc850a..bf3c8567 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/trigger/XxlJobTrigger.java @@ -11,7 +11,8 @@ import com.xxl.job.core.biz.ExecutorBiz; import com.xxl.job.core.biz.model.ReturnT; import com.xxl.job.core.biz.model.TriggerParam; import com.xxl.job.core.enums.ExecutorBlockStrategyEnum; -import org.apache.commons.collections.CollectionUtils; +import com.xxl.job.core.util.IpUtil; +import org.apache.commons.collections4.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +36,7 @@ public class XxlJobTrigger { // load data XxlJobInfo jobInfo = XxlJobDynamicScheduler.xxlJobInfoDao.loadById(jobId); // job info if (jobInfo == null) { - logger.warn(">>>>>>>>>>>> xxl-job trigger fail, jobId invalid,jobId={}", jobId); + logger.warn(">>>>>>>>>>>> trigger fail, jobId invalid,jobId={}", jobId); return; } XxlJobGroup group = XxlJobDynamicScheduler.xxlJobGroupDao.load(jobInfo.getJobGroup()); // group info @@ -66,11 +67,12 @@ public class XxlJobTrigger { ReturnT triggerResult = new ReturnT(null); StringBuffer triggerMsgSb = new StringBuffer(); - triggerMsgSb.append("注册方式:").append( (group.getAddressType() == 0)?"自动注册":"手动录入" ); + triggerMsgSb.append("调度机器:").append(IpUtil.getIp()); + triggerMsgSb.append("
执行器-注册方式:").append( (group.getAddressType() == 0)?"自动注册":"手动录入" ); + triggerMsgSb.append("
执行器-地址列表:").append(group.getRegistryList()); + triggerMsgSb.append("
路由策略:").append(executorRouteStrategyEnum.getTitle()).append("("+i+"/"+addressList.size()+")"); // update01 triggerMsgSb.append("
阻塞处理策略:").append(blockStrategy.getTitle()); triggerMsgSb.append("
失败处理策略:").append(failStrategy.getTitle()); - triggerMsgSb.append("
地址列表:").append(group.getRegistryList()); - triggerMsgSb.append("
路由策略:").append(executorRouteStrategyEnum.getTitle()).append("("+i+"/"+addressList.size()+")"); // update01 // 3、trigger-valid if (triggerResult.getCode()==ReturnT.SUCCESS_CODE && CollectionUtils.isEmpty(addressList)) { @@ -115,72 +117,73 @@ public class XxlJobTrigger { logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId()); } - return; - } - - // 1、save log-id - XxlJobLog jobLog = new XxlJobLog(); - jobLog.setJobGroup(jobInfo.getJobGroup()); - jobLog.setJobId(jobInfo.getId()); - XxlJobDynamicScheduler.xxlJobLogDao.save(jobLog); - logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId()); - - // 2、prepare trigger-info - //jobLog.setExecutorAddress(executorAddress); - jobLog.setGlueType(jobInfo.getGlueType()); - jobLog.setExecutorHandler(jobInfo.getExecutorHandler()); - jobLog.setExecutorParam(jobInfo.getExecutorParam()); - jobLog.setTriggerTime(new Date()); - - ReturnT triggerResult = new ReturnT(null); - StringBuffer triggerMsgSb = new StringBuffer(); - triggerMsgSb.append("注册方式:").append( (group.getAddressType() == 0)?"自动注册":"手动录入" ); - triggerMsgSb.append("
阻塞处理策略:").append(blockStrategy.getTitle()); - triggerMsgSb.append("
失败处理策略:").append(failStrategy.getTitle()); - triggerMsgSb.append("
地址列表:").append(group.getRegistryList()); - triggerMsgSb.append("
路由策略:").append(executorRouteStrategyEnum.getTitle()); - - // 3、trigger-valid - if (triggerResult.getCode()==ReturnT.SUCCESS_CODE && CollectionUtils.isEmpty(addressList)) { - triggerResult.setCode(ReturnT.FAIL_CODE); - triggerMsgSb.append("
----------------------
").append("调度失败:").append("执行器地址为空"); - } + } else { + // 1、save log-id + XxlJobLog jobLog = new XxlJobLog(); + jobLog.setJobGroup(jobInfo.getJobGroup()); + jobLog.setJobId(jobInfo.getId()); + XxlJobDynamicScheduler.xxlJobLogDao.save(jobLog); + logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId()); + + // 2、prepare trigger-info + //jobLog.setExecutorAddress(executorAddress); + jobLog.setGlueType(jobInfo.getGlueType()); + jobLog.setExecutorHandler(jobInfo.getExecutorHandler()); + jobLog.setExecutorParam(jobInfo.getExecutorParam()); + jobLog.setTriggerTime(new Date()); + + ReturnT triggerResult = new ReturnT(null); + StringBuffer triggerMsgSb = new StringBuffer(); + triggerMsgSb.append("调度机器:").append(IpUtil.getIp()); + triggerMsgSb.append("
执行器-注册方式:").append( (group.getAddressType() == 0)?"自动注册":"手动录入" ); + triggerMsgSb.append("
执行器-地址列表:").append(group.getRegistryList()); + triggerMsgSb.append("
路由策略:").append(executorRouteStrategyEnum.getTitle()); + triggerMsgSb.append("
阻塞处理策略:").append(blockStrategy.getTitle()); + triggerMsgSb.append("
失败处理策略:").append(failStrategy.getTitle()); + + // 3、trigger-valid + if (triggerResult.getCode()==ReturnT.SUCCESS_CODE && CollectionUtils.isEmpty(addressList)) { + triggerResult.setCode(ReturnT.FAIL_CODE); + triggerMsgSb.append("
----------------------
").append("调度失败:").append("执行器地址为空"); + } - if (triggerResult.getCode() == ReturnT.SUCCESS_CODE) { - // 4.1、trigger-param - TriggerParam triggerParam = new TriggerParam(); - triggerParam.setJobId(jobInfo.getId()); - triggerParam.setExecutorHandler(jobInfo.getExecutorHandler()); - triggerParam.setExecutorParams(jobInfo.getExecutorParam()); - triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy()); - triggerParam.setLogId(jobLog.getId()); - triggerParam.setLogDateTim(jobLog.getTriggerTime().getTime()); - triggerParam.setGlueType(jobInfo.getGlueType()); - triggerParam.setGlueSource(jobInfo.getGlueSource()); - triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime()); - triggerParam.setBroadcastIndex(0); - triggerParam.setBroadcastTotal(1); - - // 4.2、trigger-run (route run / trigger remote executor) - triggerResult = executorRouteStrategyEnum.getRouter().routeRun(triggerParam, addressList); - triggerMsgSb.append("

>>>>>>>>>>>触发调度<<<<<<<<<<<
").append(triggerResult.getMsg()); - - // 4.3、trigger (fail retry) - if (triggerResult.getCode()!=ReturnT.SUCCESS_CODE && failStrategy == ExecutorFailStrategyEnum.FAIL_RETRY) { + if (triggerResult.getCode() == ReturnT.SUCCESS_CODE) { + // 4.1、trigger-param + TriggerParam triggerParam = new TriggerParam(); + triggerParam.setJobId(jobInfo.getId()); + triggerParam.setExecutorHandler(jobInfo.getExecutorHandler()); + triggerParam.setExecutorParams(jobInfo.getExecutorParam()); + triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy()); + triggerParam.setLogId(jobLog.getId()); + triggerParam.setLogDateTim(jobLog.getTriggerTime().getTime()); + triggerParam.setGlueType(jobInfo.getGlueType()); + triggerParam.setGlueSource(jobInfo.getGlueSource()); + triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime()); + triggerParam.setBroadcastIndex(0); + triggerParam.setBroadcastTotal(1); + + // 4.2、trigger-run (route run / trigger remote executor) triggerResult = executorRouteStrategyEnum.getRouter().routeRun(triggerParam, addressList); - triggerMsgSb.append("

>>>>>>>>>>>失败重试<<<<<<<<<<<
").append(triggerResult.getMsg()); + triggerMsgSb.append("

>>>>>>>>>>>触发调度<<<<<<<<<<<
").append(triggerResult.getMsg()); + + // 4.3、trigger (fail retry) + if (triggerResult.getCode()!=ReturnT.SUCCESS_CODE && failStrategy == ExecutorFailStrategyEnum.FAIL_RETRY) { + triggerResult = executorRouteStrategyEnum.getRouter().routeRun(triggerParam, addressList); + triggerMsgSb.append("

>>>>>>>>>>>调度失败重试<<<<<<<<<<<
").append(triggerResult.getMsg()); + } } - } - // 5、save trigger-info - jobLog.setExecutorAddress(triggerResult.getContent()); - jobLog.setTriggerCode(triggerResult.getCode()); - jobLog.setTriggerMsg(triggerMsgSb.toString()); - XxlJobDynamicScheduler.xxlJobLogDao.updateTriggerInfo(jobLog); + // 5、save trigger-info + jobLog.setExecutorAddress(triggerResult.getContent()); + jobLog.setTriggerCode(triggerResult.getCode()); + jobLog.setTriggerMsg(triggerMsgSb.toString()); + XxlJobDynamicScheduler.xxlJobLogDao.updateTriggerInfo(jobLog); + + // 6、monitor triger + JobFailMonitorHelper.monitor(jobLog.getId()); + logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId()); + } - // 6、monitor triger - JobFailMonitorHelper.monitor(jobLog.getId()); - logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId()); } /** @@ -195,7 +198,7 @@ public class XxlJobTrigger { ExecutorBiz executorBiz = XxlJobDynamicScheduler.getExecutorBiz(address); runResult = executorBiz.run(triggerParam); } catch (Exception e) { - logger.error(e.getMessage(), e); + logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e); runResult = new ReturnT(ReturnT.FAIL_CODE, ""+e ); } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JobKeyUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JobKeyUtil.java new file mode 100644 index 00000000..7eaea1f4 --- /dev/null +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/JobKeyUtil.java @@ -0,0 +1,44 @@ +package com.xxl.job.admin.core.util; + +import com.xxl.job.admin.core.model.XxlJobInfo; +import org.apache.commons.lang3.StringUtils; + +/** + * job key util + * + * @author xuxueli 2017-12-22 18:48:45 + */ +public class JobKeyUtil { + + /** + * format job key + * + * @param xxlJobInfo + * @return + */ + public static String formatJobKey(XxlJobInfo xxlJobInfo){ + return String.valueOf(xxlJobInfo.getJobGroup()) + .concat("_").concat(String.valueOf(xxlJobInfo.getId())); + } + + /** + * parse jobId from JobKey + * + * @param jobKey + * @return + */ + public static int parseJobId(String jobKey){ + if (jobKey!=null && jobKey.trim().length()>0) { + String[] jobKeyArr = jobKey.split("_"); + if (jobKeyArr.length == 2) { + String jobIdStr = jobKeyArr[1]; + if (StringUtils.isNotBlank(jobIdStr) && StringUtils.isNumeric(jobIdStr)) { + int jobId = Integer.valueOf(jobIdStr); + return jobId; + } + } + } + return -1; + } + +} diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/MailUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/MailUtil.java index 07e5cc21..e000b157 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/MailUtil.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/MailUtil.java @@ -1,21 +1,16 @@ package com.xxl.job.admin.core.util; -import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.mail.DefaultAuthenticator; +import org.apache.commons.mail.EmailException; +import org.apache.commons.mail.HtmlEmail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.JavaMailSenderImpl; -import org.springframework.mail.javamail.MimeMessageHelper; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeUtility; -import java.io.File; -import java.util.Properties; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.nio.charset.Charset; /** * 邮件发送.Util + * * @author xuxueli 2016-3-12 15:06:20 */ public class MailUtil { @@ -25,156 +20,52 @@ public class MailUtil { private static String port; private static String username; private static String password; - private static String sendFrom; private static String sendNick; static{ host = PropertiesUtil.getString("xxl.job.mail.host"); port = PropertiesUtil.getString("xxl.job.mail.port"); username = PropertiesUtil.getString("xxl.job.mail.username"); password = PropertiesUtil.getString("xxl.job.mail.password"); - sendFrom = PropertiesUtil.getString("xxl.job.mail.sendFrom"); sendNick = PropertiesUtil.getString("xxl.job.mail.sendNick"); } - - /** - - - - - - - - - true - true - - - - - */ + /** - * 发送邮件 (完整版)(结合Spring) - * - * //@param javaMailSender: 发送Bean - * //@param sendFrom : 发送人邮箱 - * //@param sendNick : 发送人昵称 - * @param toAddress : 收件人邮箱 - * @param mailSubject : 邮件主题 - * @param mailBody : 邮件正文 - * @param mailBodyIsHtml: 邮件正文格式,true:HTML格式;false:文本格式 - * @param attachments : 附件 + * + * @param toAddress 收件人邮箱 + * @param mailSubject 邮件主题 + * @param mailBody 邮件正文 + * @return */ - @SuppressWarnings("null") - public static boolean sendMailSpring(String toAddress, String mailSubject, String mailBody, boolean mailBodyIsHtml,File[] attachments) { - JavaMailSender javaMailSender = null;//ResourceBundle.getInstance().getJavaMailSender(); + public static boolean sendMail(String toAddress, String mailSubject, String mailBody){ + try { - MimeMessage mimeMessage = javaMailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, ArrayUtils.isNotEmpty(attachments), "UTF-8"); // 设置utf-8或GBK编码,否则邮件会有乱码;multipart,true表示文件上传 - - helper.setFrom(sendFrom, sendNick); - helper.setTo(toAddress); + // Create the email message + HtmlEmail email = new HtmlEmail(); - // 设置收件人抄送的名片和地址(相当于群发了) - //helper.setCc(InternetAddress.parse(MimeUtility.encodeText("邮箱001") + " <@163.com>," + MimeUtility.encodeText("邮箱002") + " <@foxmail.com>")); + //email.setDebug(true); // 将会打印一些log + //email.setTLS(true); // 是否TLS校验,,某些邮箱需要TLS安全校验,同理有SSL校验 + //email.setSSL(true); - helper.setSubject(mailSubject); - helper.setText(mailBody, mailBodyIsHtml); - - // 添加附件 - if (ArrayUtils.isNotEmpty(attachments)) { - for (File file : attachments) { - helper.addAttachment(MimeUtility.encodeText(file.getName()), file); - } - } - - // 群发 - //MimeMessage[] mailMessages = { mimeMessage }; - - javaMailSender.send(mimeMessage); - return true; - } catch (Exception e) { - logger.info("{}", e); - } - return false; - } - - /** - * 发送邮件 (完整版) (纯JavaMail) - * - * @param toAddress : 收件人邮箱 - * @param mailSubject : 邮件主题 - * @param mailBody : 邮件正文 - * @param mailBodyIsHtml: 邮件正文格式,true:HTML格式;false:文本格式 - * //@param inLineFile : 内嵌文件 - * @param attachments : 附件 - */ - public static boolean sendMail (String toAddress, String mailSubject, String mailBody, - boolean mailBodyIsHtml, File[] attachments){ - try { - // 创建邮件发送类 JavaMailSender (用于发送多元化邮件,包括附件,图片,html 等 ) - JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); - mailSender.setHost(host); // 设置邮件服务主机 - mailSender.setUsername(username); // 发送者邮箱的用户名 - mailSender.setPassword(password); // 发送者邮箱的密码 - - //配置文件,用于实例化java.mail.session - Properties pro = new Properties(); - pro.put("mail.smtp.auth", "true"); // 登录SMTP服务器,需要获得授权 (网易163邮箱新近注册的邮箱均不能授权,测试 sohu 的邮箱可以获得授权) - pro.put("mail.smtp.socketFactory.port", port); - pro.put("mail.smtp.socketFactory.fallback", "false"); - mailSender.setJavaMailProperties(pro); - - //创建多元化邮件 (创建 mimeMessage 帮助类,用于封装信息至 mimeMessage) - MimeMessage mimeMessage = mailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, ArrayUtils.isNotEmpty(attachments), "UTF-8"); - - helper.setFrom(sendFrom, sendNick); - helper.setTo(toAddress); + email.setHostName(host); + email.setSmtpPort(Integer.valueOf(port)); + //email.setSslSmtpPort(port); + email.setAuthenticator(new DefaultAuthenticator(username, password)); + email.setCharset(Charset.defaultCharset().name()); + + email.setFrom(username, sendNick); + email.addTo(toAddress); + email.setSubject(mailSubject); + email.setMsg(mailBody); - helper.setSubject(mailSubject); - helper.setText(mailBody, mailBodyIsHtml); - - // 添加内嵌文件,第1个参数为cid标识这个文件,第2个参数为资源 - //helper.addInline(MimeUtility.encodeText(inLineFile.getName()), inLineFile); - - // 添加附件 - if (ArrayUtils.isNotEmpty(attachments)) { - for (File file : attachments) { - helper.addAttachment(MimeUtility.encodeText(file.getName()), file); - } - } - - mailSender.send(mimeMessage); + //email.attach(attachment); // add the attachment + + email.send(); // send the email return true; - } catch (Exception e) { + } catch (EmailException e) { logger.error(e.getMessage(), e); + } return false; } - - static int total = 0; - public static void main(String[] args) { - - ExecutorService exec = Executors.newCachedThreadPool(); - for (int i = 0; i < 20; i++) { - exec.execute(new Thread(new Runnable() { - @Override - public void run() { - while(total < 10){ - String mailBody = "

新书快递通知

你的新书快递申请已推送新书,请到空间" - + "中查看"; - - sendMail("ovono802302@163.com", "测试邮件", mailBody, false, null); - System.out.println(total); - total++; - } - } - })); - } - } - + } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/PropertiesUtil.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/PropertiesUtil.java index ed80e708..a3c60077 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/PropertiesUtil.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/core/util/PropertiesUtil.java @@ -12,6 +12,7 @@ import java.util.Properties; /** * properties util + * * @author xuxueli 2015-8-28 10:35:53 */ public class PropertiesUtil { @@ -33,9 +34,5 @@ public class PropertiesUtil { } return null; } - - public static void main(String[] args) { - System.out.println(getString("xxl.job.login.username")); - } } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java index 64f9f57b..7a0c1607 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/XxlJobService.java @@ -4,6 +4,7 @@ package com.xxl.job.admin.service; import com.xxl.job.admin.core.model.XxlJobInfo; import com.xxl.job.core.biz.model.ReturnT; +import java.util.Date; import java.util.Map; /** @@ -29,6 +30,6 @@ public interface XxlJobService { public Map dashboardInfo(); - public ReturnT> triggerChartDate(); + public ReturnT> triggerChartDate(Date startDate, Date endDate); } diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java index a2f05cae..6f989b80 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/AdminBizImpl.java @@ -5,6 +5,7 @@ import com.xxl.job.admin.core.model.XxlJobInfo; import com.xxl.job.admin.core.model.XxlJobLog; import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler; import com.xxl.job.admin.core.trigger.XxlJobTrigger; +import com.xxl.job.admin.core.util.JobKeyUtil; import com.xxl.job.admin.dao.XxlJobInfoDao; import com.xxl.job.admin.dao.XxlJobLogDao; import com.xxl.job.admin.dao.XxlJobRegistryDao; @@ -13,7 +14,8 @@ import com.xxl.job.core.biz.AdminBiz; import com.xxl.job.core.biz.model.HandleCallbackParam; import com.xxl.job.core.biz.model.RegistryParam; import com.xxl.job.core.biz.model.ReturnT; -import org.apache.commons.lang.StringUtils; +import com.xxl.job.core.handler.IJobHandler; +import org.apache.commons.lang3.StringUtils; import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,8 +47,8 @@ public class AdminBizImpl implements AdminBiz { public ReturnT callback(List callbackParamList) { for (HandleCallbackParam handleCallbackParam: callbackParamList) { ReturnT callbackResult = callback(handleCallbackParam); - logger.info("JobApiController.callback {}, handleCallbackParam={}, callbackResult={}", - (callbackResult.getCode()==ReturnT.SUCCESS_CODE?"success":"fail"), handleCallbackParam, callbackResult); + logger.info(">>>>>>>>> JobApiController.callback {}, handleCallbackParam={}, callbackResult={}", + (callbackResult.getCode()==IJobHandler.SUCCESS.getCode()?"success":"fail"), handleCallbackParam, callbackResult); } return ReturnT.SUCCESS; @@ -58,28 +60,39 @@ public class AdminBizImpl implements AdminBiz { if (log == null) { return new ReturnT(ReturnT.FAIL_CODE, "log item not found."); } + if (log.getHandleCode() > 0) { + return new ReturnT(ReturnT.FAIL_CODE, "log repeate callback."); // avoid repeat callback, trigger child job etc + } - // trigger success, to trigger child job, and avoid repeat trigger child job - String childTriggerMsg = null; - if (ReturnT.SUCCESS_CODE==handleCallbackParam.getExecuteResult().getCode() && ReturnT.SUCCESS_CODE!=log.getHandleCode()) { + // trigger success, to trigger child job + String callbackMsg = null; + if (IJobHandler.SUCCESS.getCode() == handleCallbackParam.getExecuteResult().getCode()) { XxlJobInfo xxlJobInfo = xxlJobInfoDao.loadById(log.getJobId()); if (xxlJobInfo!=null && StringUtils.isNotBlank(xxlJobInfo.getChildJobKey())) { - childTriggerMsg = "
"; + callbackMsg = "

>>>>>>>>>>>触发子任务<<<<<<<<<<<
"; + String[] childJobKeys = xxlJobInfo.getChildJobKey().split(","); for (int i = 0; i < childJobKeys.length; i++) { - String[] jobKeyArr = childJobKeys[i].split("_"); - if (jobKeyArr!=null && jobKeyArr.length==2) { - ReturnT triggerChildResult = xxlJobService.triggerJob(Integer.valueOf(jobKeyArr[1])); + int childJobId = JobKeyUtil.parseJobId(childJobKeys[i]); + if (childJobId > 0) { + ReturnT triggerChildResult = xxlJobService.triggerJob(childJobId); + // add msg - childTriggerMsg += MessageFormat.format("
{0}/{1} 触发子任务{2}, 子任务Key: {3}, 子任务触发备注: {4}", - (i+1), childJobKeys.length, (triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?"成功":"失败"), childJobKeys[i], triggerChildResult.getMsg()); + callbackMsg += MessageFormat.format("{0}/{1} [JobKey={2}], 触发{3}, 触发备注: {4}
", + (i+1), childJobKeys.length, childJobKeys[i], (triggerChildResult.getCode()==ReturnT.SUCCESS_CODE?"成功":"失败"), triggerChildResult.getMsg()); } else { - childTriggerMsg += MessageFormat.format("
{0}/{1} 触发子任务失败, 子任务Key格式错误, 子任务Key: {2}", + callbackMsg += MessageFormat.format(" {0}/{1} [JobKey={2}], 触发失败, 触发备注: JobKey格式错误
", (i+1), childJobKeys.length, childJobKeys[i]); } } } + } else if (IJobHandler.FAIL_RETRY.getCode() == handleCallbackParam.getExecuteResult().getCode()){ + ReturnT retryTriggerResult = xxlJobService.triggerJob(log.getJobId()); + callbackMsg = "

>>>>>>>>>>>执行失败重试<<<<<<<<<<<
"; + + callbackMsg += MessageFormat.format("触发{0}, 触发备注: {1}", + (retryTriggerResult.getCode()==ReturnT.SUCCESS_CODE?"成功":"失败"), retryTriggerResult.getMsg()); } // handle msg @@ -90,8 +103,8 @@ public class AdminBizImpl implements AdminBiz { if (handleCallbackParam.getExecuteResult().getMsg() != null) { handleMsg.append(handleCallbackParam.getExecuteResult().getMsg()); } - if (childTriggerMsg !=null) { - handleMsg.append("
子任务触发备注:").append(childTriggerMsg); + if (callbackMsg != null) { + handleMsg.append(callbackMsg); } // success, save log diff --git a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java index 65d53971..820e8968 100644 --- a/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java +++ b/xxl-job-admin/src/main/java/com/xxl/job/admin/service/impl/XxlJobServiceImpl.java @@ -5,6 +5,7 @@ import com.xxl.job.admin.core.model.XxlJobGroup; import com.xxl.job.admin.core.model.XxlJobInfo; import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum; import com.xxl.job.admin.core.schedule.XxlJobDynamicScheduler; +import com.xxl.job.admin.core.util.JobKeyUtil; import com.xxl.job.admin.dao.XxlJobGroupDao; import com.xxl.job.admin.dao.XxlJobInfoDao; import com.xxl.job.admin.dao.XxlJobLogDao; @@ -13,10 +14,10 @@ import com.xxl.job.admin.service.XxlJobService; import com.xxl.job.core.biz.model.ReturnT; import com.xxl.job.core.enums.ExecutorBlockStrategyEnum; import com.xxl.job.core.glue.GlueTypeEnum; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.time.DateUtils; -import org.apache.commons.lang.time.FastDateFormat; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateUtils; +import org.apache.commons.lang3.time.FastDateFormat; import org.quartz.CronExpression; import org.quartz.SchedulerException; import org.slf4j.Logger; @@ -107,11 +108,11 @@ public class XxlJobServiceImpl implements XxlJobService { if (StringUtils.isNotBlank(jobInfo.getChildJobKey())) { String[] childJobKeys = jobInfo.getChildJobKey().split(","); for (String childJobKeyItem: childJobKeys) { - String[] childJobKeyArr = childJobKeyItem.split("_"); - if (childJobKeyArr.length!=2) { + int childJobId = JobKeyUtil.parseJobId(childJobKeyItem); + if (childJobId <= 0) { return new ReturnT(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})格式错误", childJobKeyItem)); } - XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.valueOf(childJobKeyArr[1])); + XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(childJobId); if (childJobInfo==null) { return new ReturnT(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})无效", childJobKeyItem)); } @@ -170,11 +171,11 @@ public class XxlJobServiceImpl implements XxlJobService { if (StringUtils.isNotBlank(jobInfo.getChildJobKey())) { String[] childJobKeys = jobInfo.getChildJobKey().split(","); for (String childJobKeyItem: childJobKeys) { - String[] childJobKeyArr = childJobKeyItem.split("_"); - if (childJobKeyArr.length!=2) { + int childJobId = JobKeyUtil.parseJobId(childJobKeyItem); + if (childJobId <= 0) { return new ReturnT(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})格式错误", childJobKeyItem)); } - XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(Integer.valueOf(childJobKeyArr[1])); + XxlJobInfo childJobInfo = xxlJobInfoDao.loadById(childJobId); if (childJobInfo==null) { return new ReturnT(ReturnT.FAIL_CODE, MessageFormat.format("子任务Key({0})无效", childJobKeyItem)); } @@ -310,18 +311,15 @@ public class XxlJobServiceImpl implements XxlJobService { } @Override - public ReturnT> triggerChartDate() { - Date from = DateUtils.addDays(new Date(), -30); - Date to = new Date(); - + public ReturnT> triggerChartDate(Date startDate, Date endDate) { List triggerDayList = new ArrayList(); List triggerDayCountSucList = new ArrayList(); List triggerDayCountFailList = new ArrayList(); int triggerCountSucTotal = 0; int triggerCountFailTotal = 0; - List> triggerCountMapAll = xxlJobLogDao.triggerCountByDay(from, to, -1); - List> triggerCountMapSuc = xxlJobLogDao.triggerCountByDay(from, to, ReturnT.SUCCESS_CODE); + List> triggerCountMapAll = xxlJobLogDao.triggerCountByDay(startDate, endDate, -1); + List> triggerCountMapSuc = xxlJobLogDao.triggerCountByDay(startDate, endDate, ReturnT.SUCCESS_CODE); if (CollectionUtils.isNotEmpty(triggerCountMapAll)) { for (Map item: triggerCountMapAll) { String day = String.valueOf(item.get("triggerDay")); diff --git a/xxl-job-admin/src/main/resources/log4j.properties b/xxl-job-admin/src/main/resources/log4j.properties deleted file mode 100644 index c9455ab0..00000000 --- a/xxl-job-admin/src/main/resources/log4j.properties +++ /dev/null @@ -1,10 +0,0 @@ -log4j.rootLogger=info,console,logFile - -log4j.appender.console=org.apache.log4j.ConsoleAppender -log4j.appender.console.layout=org.apache.log4j.PatternLayout -log4j.appender.console.layout.ConversionPattern=%d - xxl-job-admin - %p [%c] - <%m>%n - -log4j.appender.logFile=org.apache.log4j.DailyRollingFileAppender -log4j.appender.logFile.File=/data/applogs/xxl-job/xxl-job-admin.log -log4j.appender.logFile.layout=org.apache.log4j.PatternLayout -log4j.appender.logFile.layout.ConversionPattern=%d - xxl-job-admin - %p [%c] - <%m>%n diff --git a/xxl-job-admin/src/main/resources/log4j.xml b/xxl-job-admin/src/main/resources/log4j.xml new file mode 100644 index 00000000..b62da198 --- /dev/null +++ b/xxl-job-admin/src/main/resources/log4j.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/xxl-job-admin/src/main/resources/spring/applicationcontext-base.xml b/xxl-job-admin/src/main/resources/spring/applicationcontext-base.xml index 0e34f959..af486d7d 100644 --- a/xxl-job-admin/src/main/resources/spring/applicationcontext-base.xml +++ b/xxl-job-admin/src/main/resources/spring/applicationcontext-base.xml @@ -3,9 +3,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context - http://www.springframework.org/schema/context/spring-context-3.0.xsd"> + http://www.springframework.org/schema/context/spring-context.xsd"> diff --git a/xxl-job-admin/src/main/resources/spring/applicationcontext-xxl-job-admin.xml b/xxl-job-admin/src/main/resources/spring/applicationcontext-xxl-job-admin.xml index 79da65b3..c603f5f6 100644 --- a/xxl-job-admin/src/main/resources/spring/applicationcontext-xxl-job-admin.xml +++ b/xxl-job-admin/src/main/resources/spring/applicationcontext-xxl-job-admin.xml @@ -3,9 +3,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx - http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> + http://www.springframework.org/schema/tx/spring-tx.xsd"> diff --git a/xxl-job-admin/src/main/resources/spring/springmvc-context.xml b/xxl-job-admin/src/main/resources/spring/springmvc-context.xml index b1f11c89..a6285b90 100644 --- a/xxl-job-admin/src/main/resources/spring/springmvc-context.xml +++ b/xxl-job-admin/src/main/resources/spring/springmvc-context.xml @@ -4,11 +4,11 @@ xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans - http://www.springframework.org/schema/beans/spring-beans-3.0.xsd + http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context - http://www.springframework.org/schema/context/spring-context-3.0.xsd + http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc - http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> + http://www.springframework.org/schema/mvc/spring-mvc.xsd"> diff --git a/xxl-job-admin/src/main/resources/xxl-job-admin.properties b/xxl-job-admin/src/main/resources/xxl-job-admin.properties index ac5576f7..9c425aba 100644 --- a/xxl-job-admin/src/main/resources/xxl-job-admin.properties +++ b/xxl-job-admin/src/main/resources/xxl-job-admin.properties @@ -9,7 +9,6 @@ xxl.job.mail.host=smtp.163.com xxl.job.mail.port=25 xxl.job.mail.username=ovono802302@163.com xxl.job.mail.password=asdfzxcv -xxl.job.mail.sendFrom=ovono802302@163.com xxl.job.mail.sendNick=《任务调度平台XXL-JOB》 ### xxl-job login diff --git a/xxl-job-admin/src/main/webapp/WEB-INF/template/common/common.macro.ftl b/xxl-job-admin/src/main/webapp/WEB-INF/template/common/common.macro.ftl index fa84ceaf..c303d616 100644 --- a/xxl-job-admin/src/main/webapp/WEB-INF/template/common/common.macro.ftl +++ b/xxl-job-admin/src/main/webapp/WEB-INF/template/common/common.macro.ftl @@ -175,7 +175,7 @@ <#macro commonFooter >