├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── cn │ └── uncode │ └── schedule │ ├── ConsoleManager.java │ ├── DynamicTaskManager.java │ ├── ZKScheduleManager.java │ ├── core │ ├── IScheduleDataManager.java │ ├── ScheduleServer.java │ ├── ScheduledMethodRunnable.java │ ├── TaskDefine.java │ └── Version.java │ ├── quartz │ └── MethodInvokingJobDetailFactoryBean.java │ ├── util │ └── ScheduleUtil.java │ ├── web │ ├── ManagerServlet.java │ └── ManualServlet.java │ └── zk │ ├── ScheduleDataManager4ZK.java │ ├── ZKManager.java │ └── ZKTools.java └── test ├── java └── cn │ └── uncode │ └── schedule │ ├── SimpeTestNode_1.java │ ├── SimpeTestNode_2.java │ ├── SimpeTestNode_3.java │ ├── SimpleTask.java │ └── ZookeeperTest.java └── resources ├── applicationContext.xml ├── applicationContext1.xml ├── applicationContext2.xml └── log4j.properties /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | .classpath 3 | .project 4 | .settings/ 5 | .idea/ 6 | *.iml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., [http://fsf.org/] 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) 2015 uncode 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uncode-schedule 2 | 3 | >我对原作者开源的框架做了一部分的修改,包括bug的修复和新功能的添加. 4 | 1. 修改注册在zk上的server的code为可配置. 5 | 2. 修改ip黑名单配置可起作用. 6 | 3. 使spring/quartz task 也能将任务信息存储在zk上,这样管理界面就能看到具体的任务信息,并且区分任务类型,添加delay的spring task类型. 7 | 4. 修改任务的period和delay的时间单位为毫秒. 8 | 5. 添加手动执行任务的功能,并且可传参数. 9 | 10 | *以下是框架的原文说明* 11 | 12 | 13 | 基于zookeeper的分布式任务调度组件,非常小巧,使用简单,只需要引入jar包,不需要单独部署服务端。确保所有任务在集群中不重复,不遗漏的执行。支持动态添加和删除任务。 14 | 15 | 16 | # 功能概述 17 | 18 | 1. 基于zookeeper+spring task/quartz的分布任务调度系统。 19 | 2. 确保每个任务在集群中不同节点上不重复的执行。 20 | 3. 单个任务节点故障时自动转移到其他任务节点继续执行。 21 | 4. 任务节点启动时必须保证zookeeper可用,任务节点运行期zookeeper集群不可用时任务节点保持可用前状态运行,zookeeper集群恢复正常运期。 22 | 5. 支持动态添加和删除任务。 23 | 6. 添加ip黑名单,过滤不需要执行任务的节点。 24 | 7. 简单管理后台 25 | 26 | 27 | 说明: 28 | * 单节点故障时需要业务保障数据完整性或幂等性 29 | * 具体使用方式和spring task相同k 30 | 31 | 32 | ------------------------------------------------------------------------ 33 | 34 | # 模块架构 35 | 36 | ![模块架构](http://git.oschina.net/uploads/images/2016/0513/180808_6a6c1046_277761.png "模块架构") 37 | ![Worker构成](http://git.oschina.net/uploads/images/2016/0513/180912_8c9a24ec_277761.png "Worker构成") 38 | 39 | 40 | 41 | ------------------------------------------------------------------------ 42 | 43 | # Uncode-Schedule 44 | 45 | ## Spring bean 46 | 47 | public class SimpleTask { 48 | 49 | private static int i = 0; 50 | 51 | public void print() { 52 | System.out.println("===========start!========="); 53 | System.out.println("I:"+i);i++; 54 | System.out.println("=========== end !========="); 55 | } 56 | } 57 | 58 | ## xml配置 59 | 60 | 61 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | ## API 77 | 78 | 1 动态添加任务 79 | 80 | ConsoleManager.addScheduleTask(TaskDefine taskDefine); 81 | 82 | 2 动态删除任务 83 | 84 | ConsoleManager.delScheduleTask(String targetBean, String targetMethod); 85 | 86 | 3 查询任务列表 87 | 88 | ConsoleManager.queryScheduleTask(); 89 | 90 | ------------------------------------------------------------------------ 91 | 92 | # 基于Spring Task的XML配置 93 | 94 | ## XML方式 95 | 96 | 1 Spring bean 97 | 98 | public class SimpleTask { 99 | 100 | private static int i = 0; 101 | 102 | public void print() { 103 | System.out.println("===========start!========="); 104 | System.out.println("I:"+i);i++; 105 | System.out.println("=========== end !========="); 106 | } 107 | } 108 | 109 | 2 xml配置 110 | 111 | 112 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | ------------------------------------------------------------------------ 134 | 135 | ## Annotation方式 136 | 137 | 1 Spring bean 138 | 139 | @Component 140 | public class SimpleTask { 141 | 142 | private static int i = 0; 143 | 144 | @Scheduled(fixedDelay = 1000) 145 | public void print() { 146 | System.out.println("===========start!========="); 147 | System.out.println("I:"+i);i++; 148 | System.out.println("=========== end !========="); 149 | } 150 | 151 | } 152 | 153 | 2 xml配置 154 | 155 | 156 | 157 | 158 | 159 | 160 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | ------------------------------------------------------------------------ 178 | 179 | # 基于Quartz的XML配置 180 | 181 | 注意:spring的MethodInvokingJobDetailFactoryBean改成cn.uncode.schedule.quartz.MethodInvokingJobDetailFactoryBean 182 | 183 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 0/3 * * * * ? 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | ------------------------------------------------------------------------ 228 | 229 | # uncode-schedule管理后台 230 | 231 | 访问URL:项目名称/uncode/schedule,如果servlet3.x以下,请手动配置web.xml文件 232 | ``` 233 | 234 | UncodeSchedule 235 | cn.uncode.schedule.web.ManagerServlet 236 | 237 | 238 | UncodeSchedule 239 | /uncode/schedule 240 | 241 | ``` 242 | 243 | ![img1](http://git.oschina.net/uploads/images/2016/0811/161906_b5720cac_277761.png "img1") 244 | ![img2](http://git.oschina.net/uploads/images/2016/0512/162217_0043832a_277761.png) 245 | 246 | ------------------------------------------------------------------------ 247 | 248 | # 大家都在使用uncode-schedule 249 | 250 | - [快速递](http://www.ksudi.com) 251 | - [优酷](http://www.youku.com/) 252 | - [更多](https://git.oschina.net/uncode/uncode-schedule/issues/3) 253 | 254 | ------------------------------------------------------------------------ 255 | 256 | # 关于 257 | 258 | 作者:冶卫军(ywj_316@qq.com,微信:yeweijun) 259 | 260 | 技术支持QQ群:47306892 261 | 262 | Copyright 2013 www.uncode.cn -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | cn.uncode 6 | uncode-schedule 7 | 0.8.0 8 | jar 9 | 10 | uncode-schedule 11 | Job scheduler. 12 | https://git.oschina.net/uncode/uncode-schedule 13 | 14 | 15 | 16 | The Apache Software License, Version 2.0 17 | http://www.apache.org/licenses/LICENSE-2.0.txt 18 | 19 | 20 | 21 | 22 | 23 | yeweijun 24 | ywj_316@qq.com 25 | http://www.uncode.cn 26 | +8 27 | 28 | 29 | 30 | 31 | scm:git:git@git.oschina.net:uncode/uncode-schedule.git 32 | scm:git:git@git.oschina.net:uncode/uncode-schedule.git 33 | git@git.oschina.net:uncode/uncode-schedule.git 34 | 35 | 36 | 37 | UTF-8 38 | UTF-8 39 | UTF-8 40 | -Dfile.encoding=UTF-8 41 | true 42 | true 43 | 1.7 44 | 4.0.0.RELEASE 45 | 2.2.2 46 | 1.2.16 47 | 4.8.1 48 | 2.5 49 | 3.1.0 50 | 1.6.4 51 | 3.1 52 | 3.1 53 | 2.3 54 | 2.9.1 55 | 1.5 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | org.springframework 65 | spring-core 66 | ${spring.version} 67 | 68 | 69 | org.springframework 70 | spring-context 71 | ${spring.version} 72 | 73 | 74 | org.springframework 75 | spring-context-support 76 | ${spring.version} 77 | 78 | 79 | org.springframework 80 | spring-beans 81 | ${spring.version} 82 | 83 | 84 | 85 | 86 | 87 | 88 | org.springframework 89 | spring-web 90 | ${spring.version} 91 | 92 | 93 | 94 | 95 | 96 | 97 | javax.servlet 98 | javax.servlet-api 99 | ${javax.servlet.version} 100 | 101 | 102 | 103 | 104 | 105 | 106 | log4j 107 | log4j 108 | ${log4j.version} 109 | 110 | 111 | org.slf4j 112 | slf4j-api 113 | ${slf4j.version} 114 | 115 | 116 | org.slf4j 117 | slf4j-log4j12 118 | ${slf4j.version} 119 | 120 | 121 | org.perf4j 122 | perf4j 123 | 0.9.16 124 | 125 | 126 | 127 | 128 | 129 | org.apache.commons 130 | commons-lang3 131 | ${commons.lang3.version} 132 | 133 | 134 | 135 | org.apache.zookeeper 136 | zookeeper 137 | 3.4.6 138 | 139 | 140 | 141 | com.google.code.gson 142 | gson 143 | 2.1 144 | 145 | 146 | 147 | org.quartz-scheduler 148 | quartz 149 | 2.2.1 150 | 151 | 152 | 153 | 154 | 155 | 156 | junit 157 | junit 158 | ${junit.version} 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | uncode 167 | https://oss.sonatype.org/content/repositories/snapshots 168 | 169 | 170 | uncode 171 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 172 | 173 | 174 | 175 | 176 | 177 | 178 | org.apache.maven.plugins 179 | maven-compiler-plugin 180 | 2.3.2 181 | 182 | ${jdk.version} 183 | ${jdk.version} 184 | UTF-8 185 | 186 | 187 | 188 | org.apache.maven.plugins 189 | maven-source-plugin 190 | ${version.source-plugin} 191 | 192 | 193 | attach-sources 194 | 195 | jar-no-fork 196 | 197 | 198 | 199 | 200 | true 201 | 202 | 203 | 204 | org.apache.maven.plugins 205 | maven-javadoc-plugin 206 | ${version.javadoc-plugin} 207 | 208 | 209 | package 210 | 211 | jar 212 | 213 | 214 | 215 | 216 | 217 | http://docs.oracle.com/javase/7/docs/api 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | release 228 | 229 | 230 | 231 | 232 | 233 | org.apache.maven.plugins 234 | maven-compiler-plugin 235 | 3.0 236 | 237 | 1.7 238 | 1.7 239 | 240 | 241 | 242 | 243 | org.apache.maven.plugins 244 | maven-source-plugin 245 | 2.2.1 246 | 247 | 248 | package 249 | 250 | jar-no-fork 251 | 252 | 253 | 254 | 255 | 256 | 257 | org.apache.maven.plugins 258 | maven-javadoc-plugin 259 | 2.9.1 260 | 261 | 262 | package 263 | 264 | jar 265 | 266 | 267 | 268 | 269 | 270 | 271 | org.apache.maven.plugins 272 | maven-gpg-plugin 273 | 1.5 274 | 275 | 276 | sign-artifacts 277 | verify 278 | 279 | sign 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | uncode 289 | https://oss.sonatype.org/content/repositories/snapshots/ 290 | 291 | 292 | uncode 293 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 294 | 295 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/ConsoleManager.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import cn.uncode.schedule.core.TaskDefine; 12 | 13 | 14 | 15 | public class ConsoleManager { 16 | 17 | private static transient Logger log = LoggerFactory.getLogger(ConsoleManager.class); 18 | 19 | // private static Gson GSON = new GsonBuilder().create(); 20 | 21 | private static ZKScheduleManager scheduleManager; 22 | 23 | public static ZKScheduleManager getScheduleManager() throws Exception { 24 | if(null == ConsoleManager.scheduleManager){ 25 | synchronized(ConsoleManager.class) { 26 | ConsoleManager.scheduleManager = ZKScheduleManager.getApplicationcontext().getBean(ZKScheduleManager.class); 27 | } 28 | } 29 | return ConsoleManager.scheduleManager; 30 | } 31 | 32 | public static void addScheduleTask(TaskDefine taskDefine) throws Exception{ 33 | ConsoleManager.getScheduleManager().getScheduleDataManager().addTask(taskDefine); 34 | } 35 | 36 | public static void delScheduleTask(TaskDefine taskDefine) { 37 | try { 38 | ConsoleManager.scheduleManager.getScheduleDataManager().delTask(taskDefine); 39 | } catch (Exception e) { 40 | log.error(e.getMessage(), e); 41 | } 42 | } 43 | 44 | public static List queryScheduleTask() { 45 | List taskDefines = new ArrayList(); 46 | try { 47 | List tasks = ConsoleManager.getScheduleManager().getScheduleDataManager().selectTask(); 48 | taskDefines.addAll(tasks); 49 | } catch (Exception e) { 50 | log.error(e.getMessage(), e); 51 | } 52 | return taskDefines; 53 | } 54 | 55 | public static boolean isExistsTask(TaskDefine taskDefine) throws Exception{ 56 | return ConsoleManager.scheduleManager.getScheduleDataManager().isExistsTask(taskDefine); 57 | } 58 | 59 | /** 60 | * 手动执行定时任务 61 | * @param task 62 | */ 63 | public static void runTask(TaskDefine task) throws Exception{ 64 | Object object = null; 65 | if (StringUtils.isNotEmpty(task.getTargetBean())) { 66 | object = ZKScheduleManager.getApplicationcontext().getBean(task.getTargetBean()); 67 | } 68 | if (object == null) { 69 | log.error("任务名称 = [{}]---------------未启动成功,targetBean不存在,请检查是否配置正确!!!", task.stringKey()); 70 | throw new Exception("targetBean:"+task.getTargetBean()+"不存在"); 71 | } 72 | Method method = null; 73 | try { 74 | if(StringUtils.isNotEmpty(task.getParams())){ 75 | method = object.getClass().getDeclaredMethod(task.getTargetMethod(), String.class); 76 | }else{ 77 | method = object.getClass().getDeclaredMethod(task.getTargetMethod()); 78 | } 79 | } catch (Exception e) { 80 | log.error(String.format("定时任务bean[%s],method[%s]初始化失败.", task.getTargetBean(), task.getTargetMethod()), e); 81 | throw new Exception("定时任务:"+task.stringKey()+"初始化失败"); 82 | } 83 | if (method != null) { 84 | try { 85 | if(StringUtils.isNotEmpty(task.getParams())){ 86 | method.invoke(object, task.getParams()); 87 | }else{ 88 | method.invoke(object); 89 | } 90 | } catch (Exception e) { 91 | log.error(String.format("定时任务bean[%s],method[%s]调用失败.", task.getTargetBean(), task.getTargetMethod()), e); 92 | throw new Exception("定时任务:"+task.stringKey()+"调用失败"); 93 | } 94 | } 95 | log.info("任务名称 = [{}]----------启动成功", task.stringKey()); 96 | } 97 | 98 | public static List getServerIps() throws Exception{ 99 | return scheduleManager.loadScheduleServerIps(); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/DynamicTaskManager.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Date; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.concurrent.ScheduledFuture; 9 | 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.aop.framework.AopProxyUtils; 14 | import org.springframework.aop.support.AopUtils; 15 | import org.springframework.scheduling.Trigger; 16 | import org.springframework.scheduling.support.CronTrigger; 17 | import org.springframework.util.Assert; 18 | import org.springframework.util.ReflectionUtils; 19 | 20 | import cn.uncode.schedule.core.ScheduledMethodRunnable; 21 | import cn.uncode.schedule.core.TaskDefine; 22 | 23 | 24 | 25 | public class DynamicTaskManager { 26 | 27 | private static final transient Logger LOGGER = LoggerFactory.getLogger(DynamicTaskManager.class); 28 | 29 | 30 | private static final Map> SCHEDULE_FUTURES = new ConcurrentHashMap>(); 31 | 32 | 33 | /** 34 | * 启动定时任务 35 | * @param taskDefine 36 | * @param currentTime 37 | */ 38 | public static void scheduleTask(TaskDefine taskDefine, Date currentTime){ 39 | scheduleTask(taskDefine.getTargetBean(), taskDefine.getTargetMethod(), 40 | taskDefine.getCronExpression(), taskDefine.getStartTime(), taskDefine.getPeriod(), taskDefine.getParams()); 41 | } 42 | 43 | public static void clearLocalTask(List existsTaskName){ 44 | for(String name:SCHEDULE_FUTURES.keySet()){ 45 | if(!existsTaskName.contains(name)){ 46 | SCHEDULE_FUTURES.get(name).cancel(true); 47 | SCHEDULE_FUTURES.remove(name); 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * 启动定时任务 54 | * 支持: 55 | * 1 cron时间表达式,立即执行 56 | * 2 startTime + period,指定时间,定时进行 57 | * 3 period,定时进行,立即开始 58 | * 4 startTime,指定时间执行 59 | * 60 | * @param targetBean 61 | * @param targetMethod 62 | * @param cronExpression 63 | * @param startTime 64 | * @param period 65 | */ 66 | public static void scheduleTask(String targetBean, String targetMethod, String cronExpression, Date startTime, long period, String params){ 67 | String scheduleKey = buildScheduleKey(targetBean, targetMethod); 68 | try { 69 | ScheduledFuture scheduledFuture = null; 70 | ScheduledMethodRunnable scheduledMethodRunnable = buildScheduledRunnable(targetBean, targetMethod, params); 71 | if(scheduledMethodRunnable != null){ 72 | if (!SCHEDULE_FUTURES.containsKey(scheduleKey)) { 73 | if(StringUtils.isNotEmpty(cronExpression)){ 74 | Trigger trigger = new CronTrigger(cronExpression); 75 | scheduledFuture = ConsoleManager.getScheduleManager().schedule(scheduledMethodRunnable, trigger); 76 | }else if(startTime != null){ 77 | if(period > 0){ 78 | scheduledFuture = ConsoleManager.getScheduleManager().scheduleAtFixedRate(scheduledMethodRunnable, startTime, period); 79 | }else{ 80 | scheduledFuture = ConsoleManager.getScheduleManager().schedule(scheduledMethodRunnable, startTime); 81 | } 82 | }else if(period > 0){ 83 | scheduledFuture = ConsoleManager.getScheduleManager().scheduleAtFixedRate(scheduledMethodRunnable, period); 84 | } 85 | SCHEDULE_FUTURES.put(scheduleKey, scheduledFuture); 86 | LOGGER.debug("Building new schedule task, target bean "+ targetBean + " target method " + targetMethod + "."); 87 | } 88 | }else{ 89 | LOGGER.debug("Bean name is not exists."); 90 | } 91 | } catch (Exception e) { 92 | LOGGER.error(e.getMessage(), e); 93 | } 94 | } 95 | 96 | 97 | private static String buildScheduleKey(String targetBean, String targetMethod){ 98 | return targetBean + "#" + targetMethod; 99 | } 100 | 101 | /** 102 | * 封装任务对象 103 | * @param targetBean 104 | * @param targetMethod 105 | * @return 106 | */ 107 | private static ScheduledMethodRunnable buildScheduledRunnable(String targetBean, String targetMethod, String params){ 108 | Object bean; 109 | ScheduledMethodRunnable scheduledMethodRunnable = null; 110 | try { 111 | bean = ZKScheduleManager.getApplicationcontext().getBean(targetBean); 112 | scheduledMethodRunnable = _buildScheduledRunnable(bean, targetMethod, params); 113 | } catch (Exception e) { 114 | LOGGER.debug(e.getLocalizedMessage(), e); 115 | } 116 | return scheduledMethodRunnable; 117 | } 118 | 119 | private static ScheduledMethodRunnable buildScheduledRunnable(Object bean, String targetMethod, String params){ 120 | ScheduledMethodRunnable scheduledMethodRunnable = null; 121 | try { 122 | scheduledMethodRunnable = _buildScheduledRunnable(bean, targetMethod, params); 123 | }catch (Exception e){ 124 | LOGGER.debug(e.getLocalizedMessage(), e); 125 | } 126 | return scheduledMethodRunnable; 127 | } 128 | 129 | 130 | private static ScheduledMethodRunnable _buildScheduledRunnable(Object bean, String targetMethod, String params) throws Exception { 131 | 132 | Assert.notNull(bean, "target object must not be null"); 133 | Assert.hasLength(targetMethod, "Method name must not be empty"); 134 | 135 | Method method; 136 | ScheduledMethodRunnable scheduledMethodRunnable; 137 | 138 | Class clazz; 139 | if (AopUtils.isAopProxy(bean)) { 140 | clazz = AopProxyUtils.ultimateTargetClass(bean); 141 | } else { 142 | clazz = bean.getClass(); 143 | } 144 | if (params != null) { 145 | method = ReflectionUtils.findMethod(clazz, targetMethod, String.class); 146 | } else { 147 | method = ReflectionUtils.findMethod(clazz, targetMethod); 148 | } 149 | 150 | Assert.notNull(method, "can not find method named " + targetMethod); 151 | scheduledMethodRunnable = new ScheduledMethodRunnable(bean, method, params); 152 | return scheduledMethodRunnable; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/ZKScheduleManager.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Date; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Properties; 8 | import java.util.Timer; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | import java.util.concurrent.ScheduledFuture; 11 | import java.util.concurrent.locks.Lock; 12 | import java.util.concurrent.locks.ReentrantLock; 13 | 14 | import org.apache.commons.lang3.StringUtils; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | import org.springframework.beans.BeansException; 18 | import org.springframework.context.ApplicationContext; 19 | import org.springframework.context.ApplicationContextAware; 20 | import org.springframework.scheduling.Trigger; 21 | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 22 | import org.springframework.scheduling.support.CronTrigger; 23 | 24 | import cn.uncode.schedule.core.IScheduleDataManager; 25 | import cn.uncode.schedule.core.ScheduleServer; 26 | import cn.uncode.schedule.core.ScheduledMethodRunnable; 27 | import cn.uncode.schedule.core.TaskDefine; 28 | import cn.uncode.schedule.util.ScheduleUtil; 29 | import cn.uncode.schedule.zk.ScheduleDataManager4ZK; 30 | import cn.uncode.schedule.zk.ZKManager; 31 | 32 | /** 33 | * 调度器核心管理 34 | * 35 | * @author juny.ye 36 | * 37 | */ 38 | public class ZKScheduleManager extends ThreadPoolTaskScheduler implements ApplicationContextAware { 39 | 40 | /** 41 | * 42 | */ 43 | private static final long serialVersionUID = 1L; 44 | 45 | private static final transient Logger LOGGER = LoggerFactory.getLogger(ZKScheduleManager.class); 46 | 47 | private Map zkConfig; 48 | 49 | protected ZKManager zkManager; 50 | 51 | private IScheduleDataManager scheduleDataManager; 52 | 53 | /** 54 | * 当前调度服务的信息 55 | */ 56 | protected ScheduleServer currenScheduleServer; 57 | 58 | /** 59 | * 是否启动调度管理,如果只是做系统管理,应该设置为false 60 | */ 61 | public boolean start = true; 62 | 63 | /** 64 | * 心跳间隔 65 | */ 66 | private int timerInterval = 2000; 67 | 68 | /** 69 | * 是否注册成功 70 | */ 71 | private boolean isScheduleServerRegister = false; 72 | 73 | private static ApplicationContext applicationcontext; 74 | 75 | private Map isOwnerMap = new ConcurrentHashMap(); 76 | 77 | private Timer hearBeatTimer; 78 | private Lock initLock = new ReentrantLock(); 79 | private boolean isStopSchedule = false; 80 | private Lock registerLock = new ReentrantLock(); 81 | 82 | private volatile String errorMessage = "No config Zookeeper connect information"; 83 | private InitialThread initialThread; 84 | 85 | public ZKScheduleManager() { 86 | this.currenScheduleServer = ScheduleServer.createScheduleServer(null); 87 | } 88 | 89 | public void init() throws Exception { 90 | Properties properties = new Properties(); 91 | for (Map.Entry e : this.zkConfig.entrySet()) { 92 | properties.put(e.getKey(), e.getValue()); 93 | } 94 | this.init(properties); 95 | } 96 | 97 | public void reInit(Properties p) throws Exception { 98 | if (this.start || this.hearBeatTimer != null) { 99 | throw new Exception("调度器有任务处理,不能重新初始化"); 100 | } 101 | this.init(p); 102 | } 103 | 104 | public void init(Properties p) throws Exception { 105 | if (this.initialThread != null) { 106 | this.initialThread.stopThread(); 107 | } 108 | this.initLock.lock(); 109 | try { 110 | this.scheduleDataManager = null; 111 | if (this.zkManager != null) { 112 | this.zkManager.close(); 113 | } 114 | this.zkManager = new ZKManager(p); 115 | this.errorMessage = "Zookeeper connecting ......" 116 | + this.zkManager.getConnectStr(); 117 | initialThread = new InitialThread(this); 118 | initialThread.setName("ScheduleManager-initialThread"); 119 | initialThread.start(); 120 | } finally { 121 | this.initLock.unlock(); 122 | } 123 | } 124 | 125 | private void rewriteScheduleInfo() throws Exception { 126 | registerLock.lock(); 127 | try { 128 | if (this.isStopSchedule) { 129 | if (LOGGER.isDebugEnabled()) { 130 | LOGGER.debug("外部命令终止调度,不在注册调度服务,避免遗留垃圾数据:" 131 | + currenScheduleServer.getUuid()); 132 | } 133 | return; 134 | } 135 | // 先发送心跳信息 136 | if (errorMessage != null) { 137 | this.currenScheduleServer.setDealInfoDesc(errorMessage); 138 | } 139 | if (!this.scheduleDataManager 140 | .refreshScheduleServer(this.currenScheduleServer)) { 141 | // 更新信息失败,清除内存数据后重新注册 142 | this.clearMemoInfo(); 143 | this.scheduleDataManager.registerScheduleServer(this.currenScheduleServer); 144 | } 145 | isScheduleServerRegister = true; 146 | } finally { 147 | registerLock.unlock(); 148 | } 149 | } 150 | 151 | /** 152 | * 清除内存中所有的已经取得的数据和任务队列,在心态更新失败,或者发现注册中心的调度信息被删除 153 | */ 154 | public void clearMemoInfo() { 155 | try { 156 | 157 | } finally { 158 | } 159 | 160 | } 161 | 162 | /** 163 | * 根据当前调度服务器的信息,重新计算分配所有的调度任务 164 | * 任务的分配是需要加锁,避免数据分配错误。为了避免数据锁带来的负面作用,通过版本号来达到锁的目的 165 | * 166 | * 1、获取任务状态的版本号 2、获取所有的服务器注册信息和任务队列信息 3、清除已经超过心跳周期的服务器注册信息 3、重新计算任务分配 167 | * 4、更新任务状态的版本号【乐观锁】 5、根系任务队列的分配信息 168 | * 169 | * @throws Exception 170 | */ 171 | public void assignScheduleTask() throws Exception { 172 | scheduleDataManager.clearExpireScheduleServer(); 173 | List serverList = scheduleDataManager.loadScheduleServerNames(); 174 | if (!scheduleDataManager.isLeader(this.currenScheduleServer.getUuid(), 175 | serverList)) { 176 | if (LOGGER.isDebugEnabled()) { 177 | LOGGER.debug(this.currenScheduleServer.getUuid() 178 | + ":不是负责任务分配的Leader,直接返回"); 179 | } 180 | return; 181 | } 182 | //黑名单 183 | List serverIpList = ScheduleUtil.getServerIpList(serverList); 184 | for(String ip:zkManager.getIpBlacklist()){ 185 | int index = serverIpList.indexOf(ip); 186 | if (index > -1){ 187 | serverList.remove(index); 188 | } 189 | } 190 | // 设置初始化成功标准,避免在leader转换的时候,新增的线程组初始化失败 191 | scheduleDataManager.assignTask(this.currenScheduleServer.getUuid(), serverList); 192 | } 193 | 194 | /** 195 | * 1. 定时向数据配置中心更新当前服务器的心跳信息。 如果发现本次更新的时间如果已经超过了,服务器死亡的心跳周期,则不能在向服务器更新信息。 196 | * 而应该当作新的服务器,进行重新注册。 197 | * 2. 任务分配 198 | * 3. 检查任务是否属于本机,是否添加到调度器 199 | * 200 | * @throws Exception 201 | */ 202 | public void refreshScheduleServer() throws Exception { 203 | try { 204 | // 更新或者注册服务器信息 205 | rewriteScheduleInfo(); 206 | // 如果任务信息没有初始化成功,不做任务相关的处理 207 | if (!this.isScheduleServerRegister) { 208 | return; 209 | } 210 | 211 | // 重新分配任务 212 | this.assignScheduleTask(); 213 | // 检查本地任务 214 | this.checkLocalTask(); 215 | } catch (Throwable e) { 216 | // 清除内存中所有的已经取得的数据和任务队列,避免心跳线程失败时候导致的数据重复 217 | this.clearMemoInfo(); 218 | if (e instanceof Exception) { 219 | throw (Exception) e; 220 | } else { 221 | throw new Exception(e.getMessage(), e); 222 | } 223 | } 224 | } 225 | 226 | public void checkLocalTask() throws Exception { 227 | // 检查系统任务执行情况 228 | scheduleDataManager.checkLocalTask(this.currenScheduleServer.getUuid()); 229 | } 230 | 231 | /** 232 | * 在Zk状态正常后回调数据初始化 233 | * 234 | * @throws Exception 235 | */ 236 | public void initialData() throws Exception { 237 | this.zkManager.initial(); 238 | this.scheduleDataManager = new ScheduleDataManager4ZK(this.zkManager); 239 | if (this.start) { 240 | // 注册调度管理器 241 | this.scheduleDataManager.registerScheduleServer(this.currenScheduleServer); 242 | if (hearBeatTimer == null) { 243 | hearBeatTimer = new Timer("ScheduleManager-" 244 | + this.currenScheduleServer.getUuid() + "-HearBeat"); 245 | } 246 | hearBeatTimer.schedule(new HeartBeatTimerTask(this), 2000, this.timerInterval); 247 | } 248 | } 249 | 250 | /** 251 | * 将Spring的定时任务进行包装,决定任务是否在本机执行。 252 | * @param task 253 | * @return 254 | */ 255 | private Runnable taskWrapper(final Runnable task){ 256 | return new Runnable(){ 257 | public void run(){ 258 | Method targetMethod = null; 259 | if(task instanceof ScheduledMethodRunnable){ 260 | ScheduledMethodRunnable uncodeScheduledMethodRunnable = (ScheduledMethodRunnable)task; 261 | targetMethod = uncodeScheduledMethodRunnable.getMethod(); 262 | }else{ 263 | org.springframework.scheduling.support.ScheduledMethodRunnable springScheduledMethodRunnable = (org.springframework.scheduling.support.ScheduledMethodRunnable)task; 264 | targetMethod = springScheduledMethodRunnable.getMethod(); 265 | } 266 | String[] beanNames = applicationcontext.getBeanNamesForType(targetMethod.getDeclaringClass()); 267 | if(null != beanNames && StringUtils.isNotEmpty(beanNames[0])){ 268 | String name = ScheduleUtil.getTaskNameFormBean(beanNames[0], targetMethod.getName()); 269 | boolean isOwner = false; 270 | try { 271 | if(!isScheduleServerRegister){ 272 | Thread.sleep(1000); 273 | } 274 | if(zkManager.checkZookeeperState()){ 275 | isOwner = scheduleDataManager.isOwner(name, currenScheduleServer.getUuid()); 276 | isOwnerMap.put(name, isOwner); 277 | }else{ 278 | // 如果zk不可用,使用历史数据 279 | if(null != isOwnerMap){ 280 | isOwner = isOwnerMap.get(name); 281 | } 282 | } 283 | if(isOwner){ 284 | task.run(); 285 | scheduleDataManager.saveRunningInfo(name, currenScheduleServer.getUuid()); 286 | LOGGER.info("Cron job has been executed."); 287 | } 288 | } catch (Exception e) { 289 | LOGGER.error("Check task owner error.", e); 290 | } 291 | } 292 | } 293 | }; 294 | } 295 | 296 | class HeartBeatTimerTask extends java.util.TimerTask { 297 | private transient final Logger log = LoggerFactory.getLogger(HeartBeatTimerTask.class); 298 | ZKScheduleManager manager; 299 | 300 | public HeartBeatTimerTask(ZKScheduleManager aManager) { 301 | manager = aManager; 302 | } 303 | 304 | public void run() { 305 | try { 306 | Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 307 | manager.refreshScheduleServer(); 308 | } catch (Exception ex) { 309 | log.error(ex.getMessage(), ex); 310 | } 311 | } 312 | } 313 | 314 | class InitialThread extends Thread { 315 | private transient Logger log = LoggerFactory.getLogger(InitialThread.class); 316 | ZKScheduleManager sm; 317 | 318 | public InitialThread(ZKScheduleManager sm) { 319 | this.sm = sm; 320 | } 321 | 322 | boolean isStop = false; 323 | 324 | public void stopThread() { 325 | this.isStop = true; 326 | } 327 | 328 | @Override 329 | public void run() { 330 | sm.initLock.lock(); 331 | try { 332 | int count = 0; 333 | while (!sm.zkManager.checkZookeeperState()) { 334 | count = count + 1; 335 | if (count % 50 == 0) { 336 | sm.errorMessage = "Zookeeper connecting ......" 337 | + sm.zkManager.getConnectStr() + " spendTime:" 338 | + count * 20 + "(ms)"; 339 | log.error(sm.errorMessage); 340 | } 341 | Thread.sleep(20); 342 | if (this.isStop) { 343 | return; 344 | } 345 | } 346 | sm.initialData(); 347 | } catch (Throwable e) { 348 | log.error(e.getMessage(), e); 349 | } finally { 350 | sm.initLock.unlock(); 351 | } 352 | 353 | } 354 | 355 | } 356 | 357 | public IScheduleDataManager getScheduleDataManager() { 358 | return scheduleDataManager; 359 | } 360 | 361 | @Override 362 | public void setApplicationContext(ApplicationContext applicationcontext) 363 | throws BeansException { 364 | ZKScheduleManager.applicationcontext = applicationcontext; 365 | } 366 | 367 | public void setZkManager(ZKManager zkManager) { 368 | this.zkManager = zkManager; 369 | } 370 | 371 | public ZKManager getZkManager() { 372 | return zkManager; 373 | } 374 | 375 | public void setZkConfig(Map zkConfig) { 376 | this.zkConfig = zkConfig; 377 | } 378 | 379 | private void addTask(Runnable task, TaskDefine taskDefine){ 380 | if(task instanceof org.springframework.scheduling.support.ScheduledMethodRunnable){ 381 | try { 382 | scheduleDataManager.addTask(taskDefine); 383 | } catch (Exception e) { 384 | LOGGER.error(String.format("add task exception, taskName:%s", taskDefine.stringKey()), e); 385 | } 386 | } 387 | } 388 | 389 | @Override 390 | public ScheduledFuture scheduleAtFixedRate(Runnable task, long period) { 391 | TaskDefine taskDefine = getTaskDefine(task); 392 | LOGGER.info("spring task init------taskName:{}, period:{}", taskDefine.stringKey(), period); 393 | taskDefine.setPeriod(period); 394 | addTask(task, taskDefine); 395 | return super.scheduleAtFixedRate(taskWrapper(task), period); 396 | } 397 | 398 | public ScheduledFuture schedule(Runnable task, Trigger trigger) { 399 | TaskDefine taskDefine = getTaskDefine(task); 400 | if(trigger instanceof CronTrigger){ 401 | CronTrigger cronTrigger = (CronTrigger)trigger; 402 | taskDefine.setCronExpression(cronTrigger.getExpression()); 403 | LOGGER.info("spring task init------trigger:" + cronTrigger.getExpression()); 404 | } 405 | addTask(task, taskDefine); 406 | return super.schedule(taskWrapper(task), trigger); 407 | } 408 | 409 | public ScheduledFuture schedule(Runnable task, Date startTime) { 410 | TaskDefine taskDefine = getTaskDefine(task); 411 | LOGGER.info("spring task init------taskName:{}, period:{}", taskDefine.stringKey(), startTime); 412 | taskDefine.setStartTime(startTime); 413 | addTask(task, taskDefine); 414 | return super.schedule(taskWrapper(task), startTime); 415 | } 416 | 417 | public ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period) { 418 | TaskDefine taskDefine = getTaskDefine(task); 419 | LOGGER.info("spring task init------taskName:{}, period:{}", taskDefine.stringKey(), period); 420 | taskDefine.setStartTime(startTime); 421 | taskDefine.setPeriod(period); 422 | addTask(task, taskDefine); 423 | return super.scheduleAtFixedRate(taskWrapper(task), startTime, period); 424 | } 425 | 426 | public ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay) { 427 | TaskDefine taskDefine = getTaskDefine(task); 428 | LOGGER.info("spring task init------taskName:{}, delay:{}", taskDefine.stringKey(), delay); 429 | taskDefine.setStartTime(startTime); 430 | taskDefine.setPeriod(delay); 431 | taskDefine.setType(TaskDefine.TASK_TYPE_QSD); 432 | addTask(task, taskDefine); 433 | return super.scheduleWithFixedDelay(taskWrapper(task), startTime, delay); 434 | } 435 | 436 | public ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay) { 437 | TaskDefine taskDefine = getTaskDefine(task); 438 | LOGGER.info("spring task init------taskName:{}, delay:{}", taskDefine.stringKey(), delay); 439 | taskDefine.setPeriod(delay); 440 | taskDefine.setType(TaskDefine.TASK_TYPE_QSD); 441 | addTask(task, taskDefine); 442 | return super.scheduleWithFixedDelay(taskWrapper(task), delay); 443 | } 444 | 445 | public String getScheduleServerUUid(){ 446 | if(null != currenScheduleServer){ 447 | return currenScheduleServer.getUuid(); 448 | } 449 | return null; 450 | } 451 | 452 | public Map getIsOwnerMap() { 453 | return isOwnerMap; 454 | } 455 | 456 | public List loadScheduleServerIps() throws Exception{ 457 | return scheduleDataManager.loadScheduleServerIps(); 458 | } 459 | 460 | public static ApplicationContext getApplicationcontext() { 461 | return ZKScheduleManager.applicationcontext; 462 | } 463 | 464 | private TaskDefine getTaskDefine(Runnable task){ 465 | TaskDefine taskDefine = new TaskDefine(); 466 | if(task instanceof org.springframework.scheduling.support.ScheduledMethodRunnable){ 467 | taskDefine.setType(TaskDefine.TASK_TYPE_QS); 468 | taskDefine.setStartTime(new Date()); 469 | org.springframework.scheduling.support.ScheduledMethodRunnable springScheduledMethodRunnable = (org.springframework.scheduling.support.ScheduledMethodRunnable)task; 470 | Method targetMethod = springScheduledMethodRunnable.getMethod(); 471 | String[] beanNames = applicationcontext.getBeanNamesForType(targetMethod.getDeclaringClass()); 472 | if(null != beanNames && StringUtils.isNotEmpty(beanNames[0])){ 473 | taskDefine.setTargetBean(beanNames[0]); 474 | taskDefine.setTargetMethod(targetMethod.getName()); 475 | LOGGER.info("----------------------name:" + taskDefine.stringKey()); 476 | } 477 | } 478 | return taskDefine; 479 | } 480 | 481 | 482 | } -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/core/IScheduleDataManager.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.core; 2 | 3 | import java.util.List; 4 | 5 | 6 | /** 7 | * 调度配置中心客户端接口,可以有基于数据库的实现,可以有基于ConfigServer的实现 8 | * 9 | * @author juny.ye 10 | * 11 | */ 12 | public interface IScheduleDataManager{ 13 | 14 | /** 15 | * 发送心跳信息 16 | * 17 | * @param server 18 | * @throws Exception 19 | */ 20 | public boolean refreshScheduleServer(ScheduleServer server) throws Exception; 21 | 22 | /** 23 | * 注册服务器 24 | * 25 | * @param server 26 | * @throws Exception 27 | */ 28 | public void registerScheduleServer(ScheduleServer server) throws Exception; 29 | 30 | 31 | public boolean isLeader(String uuid,List serverList); 32 | 33 | public void unRegisterScheduleServer(ScheduleServer server) throws Exception; 34 | 35 | public void clearExpireScheduleServer() throws Exception; 36 | 37 | 38 | public List loadScheduleServerNames() throws Exception; 39 | public List loadScheduleServerIps() throws Exception; 40 | 41 | public void assignTask(String currentUuid, List taskServerList) throws Exception; 42 | 43 | public boolean isOwner(String name, String uuid)throws Exception; 44 | 45 | public void addTask(TaskDefine taskDefine)throws Exception; 46 | 47 | /** 48 | * addTask中存储的Key由对象本身的字符串组成,此方法实现重载 49 | * @param targetBean 50 | * @param targetMethod 51 | * @throws Exception 52 | */ 53 | @Deprecated 54 | public void delTask(String targetBean, String targetMethod)throws Exception; 55 | 56 | public void delTask(TaskDefine taskDefine) throws Exception; 57 | 58 | public List selectTask()throws Exception; 59 | 60 | public boolean checkLocalTask(String currentUuid)throws Exception; 61 | 62 | public boolean isExistsTask(TaskDefine taskDefine) throws Exception; 63 | 64 | public boolean saveRunningInfo(String name, String uuid)throws Exception; 65 | 66 | 67 | } -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/core/ScheduleServer.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.core; 2 | 3 | import java.sql.Timestamp; 4 | import java.util.UUID; 5 | 6 | import cn.uncode.schedule.util.ScheduleUtil; 7 | 8 | 9 | /** 10 | * 调度服务器信息定义 11 | * 12 | * @author juny.ye 13 | * 14 | */ 15 | public class ScheduleServer { 16 | /** 17 | * 全局唯一编号 18 | */ 19 | private String uuid; 20 | 21 | 22 | private String ownSign; 23 | /** 24 | * 机器IP地址 25 | */ 26 | private String ip; 27 | 28 | /** 29 | * 机器名称 30 | */ 31 | private String hostName; 32 | 33 | /** 34 | * 服务开始时间 35 | */ 36 | private Timestamp registerTime; 37 | /** 38 | * 最后一次心跳通知时间 39 | */ 40 | private Timestamp heartBeatTime; 41 | /** 42 | * 最后一次取数据时间 43 | */ 44 | private Timestamp lastFetchDataTime; 45 | /** 46 | * 处理描述信息,例如读取的任务数量,处理成功的任务数量,处理失败的数量,处理耗时 47 | * FetchDataCount=4430,FetcheDataNum=438570,DealDataSucess=438570,DealDataFail=0,DealSpendTime=651066 48 | */ 49 | private String dealInfoDesc; 50 | 51 | private String nextRunStartTime; 52 | 53 | private String nextRunEndTime; 54 | /** 55 | * 配置中心的当前时间 56 | */ 57 | private Timestamp centerServerTime; 58 | 59 | /** 60 | * 数据版本号 61 | */ 62 | private long version; 63 | 64 | private boolean isRegister; 65 | 66 | public ScheduleServer() { 67 | 68 | } 69 | 70 | public static ScheduleServer createScheduleServer(String aOwnSign){ 71 | long currentTime = System.currentTimeMillis(); 72 | ScheduleServer result = new ScheduleServer(); 73 | result.ownSign = aOwnSign; 74 | result.ip = ScheduleUtil.getLocalIP(); 75 | result.hostName = ScheduleUtil.getLocalHostName(); 76 | result.registerTime = new Timestamp(currentTime); 77 | result.heartBeatTime = null; 78 | result.dealInfoDesc = "调度初始化"; 79 | result.version = 0; 80 | result.uuid = result.ip 81 | + "$" 82 | + (UUID.randomUUID().toString().replaceAll("-", "") 83 | .toUpperCase()); 84 | return result; 85 | } 86 | 87 | public String getUuid() { 88 | return uuid; 89 | } 90 | 91 | public void setUuid(String uuid) { 92 | this.uuid = uuid; 93 | } 94 | 95 | public long getVersion() { 96 | return version; 97 | } 98 | 99 | public void setVersion(long version) { 100 | this.version = version; 101 | } 102 | 103 | 104 | public Timestamp getRegisterTime() { 105 | return registerTime; 106 | } 107 | 108 | public void setRegisterTime(Timestamp registerTime) { 109 | this.registerTime = registerTime; 110 | } 111 | 112 | public Timestamp getHeartBeatTime() { 113 | return heartBeatTime; 114 | } 115 | 116 | public void setHeartBeatTime(Timestamp heartBeatTime) { 117 | this.heartBeatTime = heartBeatTime; 118 | } 119 | 120 | public Timestamp getLastFetchDataTime() { 121 | return lastFetchDataTime; 122 | } 123 | 124 | public void setLastFetchDataTime(Timestamp lastFetchDataTime) { 125 | this.lastFetchDataTime = lastFetchDataTime; 126 | } 127 | 128 | public String getDealInfoDesc() { 129 | return dealInfoDesc; 130 | } 131 | 132 | public void setDealInfoDesc(String dealInfoDesc) { 133 | this.dealInfoDesc = dealInfoDesc; 134 | } 135 | 136 | public String getIp() { 137 | return ip; 138 | } 139 | 140 | public void setIp(String ip) { 141 | this.ip = ip; 142 | } 143 | 144 | public String getHostName() { 145 | return hostName; 146 | } 147 | 148 | public void setHostName(String hostName) { 149 | this.hostName = hostName; 150 | } 151 | 152 | 153 | public Timestamp getCenterServerTime() { 154 | return centerServerTime; 155 | } 156 | 157 | public void setCenterServerTime(Timestamp centerServerTime) { 158 | this.centerServerTime = centerServerTime; 159 | } 160 | 161 | public String getNextRunStartTime() { 162 | return nextRunStartTime; 163 | } 164 | 165 | public void setNextRunStartTime(String nextRunStartTime) { 166 | this.nextRunStartTime = nextRunStartTime; 167 | } 168 | 169 | public String getNextRunEndTime() { 170 | return nextRunEndTime; 171 | } 172 | 173 | public void setNextRunEndTime(String nextRunEndTime) { 174 | this.nextRunEndTime = nextRunEndTime; 175 | } 176 | 177 | public String getOwnSign() { 178 | return ownSign; 179 | } 180 | 181 | public void setOwnSign(String ownSign) { 182 | this.ownSign = ownSign; 183 | } 184 | 185 | 186 | public void setRegister(boolean isRegister) { 187 | this.isRegister = isRegister; 188 | } 189 | 190 | public boolean isRegister() { 191 | return isRegister; 192 | } 193 | 194 | 195 | } -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/core/ScheduledMethodRunnable.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.core; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.lang.reflect.Method; 5 | import java.lang.reflect.UndeclaredThrowableException; 6 | 7 | import org.springframework.util.ReflectionUtils; 8 | 9 | public class ScheduledMethodRunnable implements Runnable { 10 | 11 | private final Object target; 12 | 13 | private final Method method; 14 | 15 | private final String params; 16 | 17 | 18 | public ScheduledMethodRunnable(Object target, Method method, String params) { 19 | this.target = target; 20 | this.method = method; 21 | this.params = params; 22 | } 23 | 24 | public ScheduledMethodRunnable(Object target, String methodName, String params) throws NoSuchMethodException { 25 | this.target = target; 26 | this.method = target.getClass().getMethod(methodName); 27 | this.params = params; 28 | } 29 | 30 | 31 | public Object getTarget() { 32 | return this.target; 33 | } 34 | 35 | public Method getMethod() { 36 | return this.method; 37 | } 38 | 39 | public String getParams() { 40 | return params; 41 | } 42 | 43 | @Override 44 | public void run() { 45 | try { 46 | ReflectionUtils.makeAccessible(this.method); 47 | if(this.getParams() != null){ 48 | this.method.invoke(this.target, this.getParams()); 49 | }else{ 50 | this.method.invoke(this.target); 51 | } 52 | } 53 | catch (InvocationTargetException ex) { 54 | ReflectionUtils.rethrowRuntimeException(ex.getTargetException()); 55 | } 56 | catch (IllegalAccessException ex) { 57 | throw new UndeclaredThrowableException(ex); 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/core/TaskDefine.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.core; 2 | 3 | import java.util.Date; 4 | 5 | /** 6 | * 任务定义,提供关键信息给使用者 7 | * @author juny.ye 8 | * 9 | */ 10 | public class TaskDefine { 11 | 12 | public static final String TASK_TYPE_UNCODE="uncode task"; 13 | public static final String TASK_TYPE_QS="quartz/spring task"; 14 | public static final String TASK_TYPE_QSD="quartz/spring task delay"; 15 | 16 | /** 17 | * 目标bean 18 | */ 19 | private String targetBean; 20 | 21 | /** 22 | * 目标方法 23 | */ 24 | private String targetMethod; 25 | 26 | /** 27 | * cron表达式 28 | */ 29 | private String cronExpression; 30 | 31 | /** 32 | * 开始时间 33 | */ 34 | private Date startTime; 35 | 36 | /** 37 | * 周期(毫秒) 38 | */ 39 | private long period; 40 | 41 | private String currentServer; 42 | 43 | /** 44 | * 参数 45 | */ 46 | private String params; 47 | 48 | /** 49 | * 类型 50 | */ 51 | private String type; 52 | 53 | private int runTimes; 54 | 55 | private long lastRunningTime; 56 | 57 | public boolean begin(Date sysTime) { 58 | return null != sysTime && sysTime.after(startTime); 59 | } 60 | 61 | public String getTargetBean() { 62 | return targetBean; 63 | } 64 | 65 | public void setTargetBean(String targetBean) { 66 | this.targetBean = targetBean; 67 | } 68 | 69 | public String getTargetMethod() { 70 | return targetMethod; 71 | } 72 | 73 | public void setTargetMethod(String targetMethod) { 74 | this.targetMethod = targetMethod; 75 | } 76 | 77 | public String getCronExpression() { 78 | return cronExpression; 79 | } 80 | 81 | public void setCronExpression(String cronExpression) { 82 | this.cronExpression = cronExpression; 83 | } 84 | 85 | public Date getStartTime() { 86 | return startTime; 87 | } 88 | 89 | public void setStartTime(Date startTime) { 90 | this.startTime = startTime; 91 | } 92 | 93 | public long getPeriod() { 94 | return period; 95 | } 96 | 97 | public void setPeriod(long period) { 98 | this.period = period; 99 | } 100 | 101 | public String getCurrentServer() { 102 | return currentServer; 103 | } 104 | 105 | public void setCurrentServer(String currentServer) { 106 | this.currentServer = currentServer; 107 | } 108 | 109 | public String stringKey(){ 110 | return getTargetBean() + "#" + getTargetMethod(); 111 | } 112 | 113 | public String getParams() { 114 | return params; 115 | } 116 | 117 | public void setParams(String params) { 118 | this.params = params; 119 | } 120 | 121 | public String getType() { 122 | return type; 123 | } 124 | 125 | public void setType(String type) { 126 | this.type = type; 127 | } 128 | 129 | public int getRunTimes() { 130 | return runTimes; 131 | } 132 | 133 | public void setRunTimes(int runTimes) { 134 | this.runTimes = runTimes; 135 | } 136 | 137 | public long getLastRunningTime() { 138 | return lastRunningTime; 139 | } 140 | 141 | public void setLastRunningTime(long lastRunningTime) { 142 | this.lastRunningTime = lastRunningTime; 143 | } 144 | 145 | 146 | } -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/core/Version.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.core; 2 | 3 | /** 4 | * 5 | * @author juny.ye 6 | * 7 | */ 8 | public class Version { 9 | 10 | private final static String version="uncode-schedule-1.0.0"; 11 | 12 | public static String getVersion(){ 13 | return version; 14 | } 15 | public static boolean isCompatible(String dataVersion){ 16 | return version.compareTo(dataVersion) >= 0; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/quartz/MethodInvokingJobDetailFactoryBean.java: -------------------------------------------------------------------------------- 1 | 2 | package cn.uncode.schedule.quartz; 3 | 4 | /* 5 | * Copyright 2002-2012 the original author or authors. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | import java.lang.reflect.InvocationTargetException; 21 | import java.lang.reflect.Method; 22 | 23 | import org.apache.commons.logging.Log; 24 | import org.apache.commons.logging.LogFactory; 25 | import org.quartz.Job; 26 | import org.quartz.JobDataMap; 27 | import org.quartz.JobDetail; 28 | import org.quartz.JobExecutionContext; 29 | import org.quartz.JobExecutionException; 30 | import org.quartz.Scheduler; 31 | import org.slf4j.Logger; 32 | import org.slf4j.LoggerFactory; 33 | import org.springframework.beans.BeanUtils; 34 | import org.springframework.beans.BeanWrapper; 35 | import org.springframework.beans.PropertyAccessorFactory; 36 | import org.springframework.beans.factory.BeanClassLoaderAware; 37 | import org.springframework.beans.factory.BeanFactory; 38 | import org.springframework.beans.factory.BeanFactoryAware; 39 | import org.springframework.beans.factory.BeanNameAware; 40 | import org.springframework.beans.factory.FactoryBean; 41 | import org.springframework.beans.factory.InitializingBean; 42 | import org.springframework.beans.support.ArgumentConvertingMethodInvoker; 43 | import org.springframework.scheduling.quartz.JobMethodInvocationFailedException; 44 | import org.springframework.scheduling.quartz.QuartzJobBean; 45 | import org.springframework.util.Assert; 46 | import org.springframework.util.ClassUtils; 47 | import org.springframework.util.MethodInvoker; 48 | import org.springframework.util.ReflectionUtils; 49 | 50 | import cn.uncode.schedule.ConsoleManager; 51 | import cn.uncode.schedule.util.ScheduleUtil; 52 | 53 | /** 54 | * {@link org.springframework.beans.factory.FactoryBean} that exposes a 55 | * {@link org.quartz.JobDetail} object which delegates job execution to a 56 | * specified (static or non-static) method. Avoids the need for implementing 57 | * a one-line Quartz Job that just invokes an existing service method on a 58 | * Spring-managed target bean. 59 | * 60 | *

Inherits common configuration properties from the {@link MethodInvoker} 61 | * base class, such as {@link #setTargetObject "targetObject"} and 62 | * {@link #setTargetMethod "targetMethod"}, adding support for lookup of the target 63 | * bean by name through the {@link #setTargetBeanName "targetBeanName"} property 64 | * (as alternative to specifying a "targetObject" directly, allowing for 65 | * non-singleton target objects). 66 | * 67 | *

Supports both concurrently running jobs and non-currently running 68 | * jobs through the "concurrent" property. Jobs created by this 69 | * MethodInvokingJobDetailFactoryBean are by default volatile and durable 70 | * (according to Quartz terminology). 71 | * 72 | *

NOTE: JobDetails created via this FactoryBean are not 73 | * serializable and thus not suitable for persistent job stores. 74 | * You need to implement your own Quartz Job as a thin wrapper for each case 75 | * where you want a persistent job to delegate to a specific service method. 76 | * 77 | *

Compatible with Quartz 1.5+ as well as Quartz 2.0/2.1, as of Spring 3.1. 78 | * 79 | * @author Juergen Hoeller 80 | * @author Alef Arendsen 81 | * @since 18.02.2004 82 | * @see #setTargetBeanName 83 | * @see #setTargetObject 84 | * @see #setTargetMethod 85 | * @see #setConcurrent 86 | */ 87 | public class MethodInvokingJobDetailFactoryBean extends ArgumentConvertingMethodInvoker 88 | implements FactoryBean, BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean { 89 | 90 | private static final transient Logger LOGGER = LoggerFactory.getLogger(MethodInvokingJobDetailFactoryBean.class); 91 | 92 | private static Class jobDetailImplClass; 93 | 94 | private static Method setResultMethod; 95 | 96 | static { 97 | try { 98 | jobDetailImplClass = Class.forName("org.quartz.impl.JobDetailImpl"); 99 | } 100 | catch (ClassNotFoundException ex) { 101 | jobDetailImplClass = null; 102 | } 103 | try { 104 | Class jobExecutionContextClass = 105 | QuartzJobBean.class.getClassLoader().loadClass("org.quartz.JobExecutionContext"); 106 | setResultMethod = jobExecutionContextClass.getMethod("setResult", Object.class); 107 | } 108 | catch (Exception ex) { 109 | throw new IllegalStateException("Incompatible Quartz API: " + ex); 110 | } 111 | } 112 | 113 | 114 | private String name; 115 | 116 | private String group = Scheduler.DEFAULT_GROUP; 117 | 118 | private boolean concurrent = true; 119 | 120 | private String targetBeanName; 121 | 122 | private String[] jobListenerNames; 123 | 124 | private String beanName; 125 | 126 | private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); 127 | 128 | private BeanFactory beanFactory; 129 | 130 | private JobDetail jobDetail; 131 | 132 | 133 | /** 134 | * Set the name of the job. 135 | * Default is the bean name of this FactoryBean. 136 | */ 137 | public void setName(String name) { 138 | this.name = name; 139 | } 140 | 141 | /** 142 | * Set the group of the job. 143 | *

Default is the default group of the Scheduler. 144 | */ 145 | public void setGroup(String group) { 146 | this.group = group; 147 | } 148 | 149 | /** 150 | * Specify whether or not multiple jobs should be run in a concurrent 151 | * fashion. The behavior when one does not want concurrent jobs to be 152 | * executed is realized through adding the {@link StatefulJob} interface. 153 | * More information on stateful versus stateless jobs can be found 154 | * here. 155 | *

The default setting is to run jobs concurrently. 156 | */ 157 | public void setConcurrent(boolean concurrent) { 158 | this.concurrent = concurrent; 159 | } 160 | 161 | /** 162 | * Set the name of the target bean in the Spring BeanFactory. 163 | *

This is an alternative to specifying {@link #setTargetObject "targetObject"}, 164 | * allowing for non-singleton beans to be invoked. Note that specified 165 | * "targetObject" and {@link #setTargetClass "targetClass"} values will 166 | * override the corresponding effect of this "targetBeanName" setting 167 | * (i.e. statically pre-define the bean type or even the bean object). 168 | */ 169 | public void setTargetBeanName(String targetBeanName) { 170 | this.targetBeanName = targetBeanName; 171 | } 172 | 173 | /** 174 | * Set a list of JobListener names for this job, referring to 175 | * non-global JobListeners registered with the Scheduler. 176 | *

A JobListener name always refers to the name returned 177 | * by the JobListener implementation. 178 | */ 179 | public void setJobListenerNames(String[] names) { 180 | this.jobListenerNames = names; 181 | } 182 | 183 | public void setBeanName(String beanName) { 184 | this.beanName = beanName; 185 | } 186 | 187 | public void setBeanClassLoader(ClassLoader classLoader) { 188 | this.beanClassLoader = classLoader; 189 | } 190 | 191 | public void setBeanFactory(BeanFactory beanFactory) { 192 | this.beanFactory = beanFactory; 193 | } 194 | 195 | @Override 196 | protected Class resolveClassName(String className) throws ClassNotFoundException { 197 | return ClassUtils.forName(className, this.beanClassLoader); 198 | } 199 | 200 | 201 | public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException { 202 | prepare(); 203 | 204 | // Use specific name if given, else fall back to bean name. 205 | String name = (this.name != null ? this.name : this.beanName); 206 | 207 | // Consider the concurrent flag to choose between stateful and stateless job. 208 | Class jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class); 209 | 210 | // Build JobDetail instance. 211 | if (jobDetailImplClass != null) { 212 | // Using Quartz 2.0 JobDetailImpl class... 213 | this.jobDetail = (JobDetail) BeanUtils.instantiate(jobDetailImplClass); 214 | BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this.jobDetail); 215 | bw.setPropertyValue("name", name); 216 | bw.setPropertyValue("group", this.group); 217 | bw.setPropertyValue("jobClass", jobClass); 218 | bw.setPropertyValue("durability", true); 219 | ((JobDataMap) bw.getPropertyValue("jobDataMap")).put("methodInvoker", this); 220 | } 221 | else { 222 | // Using Quartz 1.x JobDetail class... 223 | /*this.jobDetail = new JobDetail(name, this.group, jobClass); 224 | this.jobDetail.setVolatility(true); 225 | this.jobDetail.setDurability(true); 226 | this.jobDetail.getJobDataMap().put("methodInvoker", this);*/ 227 | } 228 | 229 | // Register job listener names. 230 | if (this.jobListenerNames != null) { 231 | for (String jobListenerName : this.jobListenerNames) { 232 | if (jobListenerName != null) { 233 | throw new IllegalStateException("Non-global JobListeners not supported on Quartz 2 - " + 234 | "manually register a Matcher against the Quartz ListenerManager instead"); 235 | } 236 | //this.jobDetail.addJobListener(jobListenerName); 237 | } 238 | } 239 | 240 | postProcessJobDetail(this.jobDetail); 241 | } 242 | 243 | /** 244 | * Callback for post-processing the JobDetail to be exposed by this FactoryBean. 245 | *

The default implementation is empty. Can be overridden in subclasses. 246 | * @param jobDetail the JobDetail prepared by this FactoryBean 247 | */ 248 | protected void postProcessJobDetail(JobDetail jobDetail) { 249 | } 250 | 251 | 252 | /** 253 | * Overridden to support the {@link #setTargetBeanName "targetBeanName"} feature. 254 | */ 255 | @Override 256 | public Class getTargetClass() { 257 | Class targetClass = super.getTargetClass(); 258 | if (targetClass == null && this.targetBeanName != null) { 259 | Assert.state(this.beanFactory != null, "BeanFactory must be set when using 'targetBeanName'"); 260 | targetClass = this.beanFactory.getType(this.targetBeanName); 261 | } 262 | return targetClass; 263 | } 264 | 265 | /** 266 | * Overridden to support the {@link #setTargetBeanName "targetBeanName"} feature. 267 | */ 268 | @Override 269 | public Object getTargetObject() { 270 | Object targetObject = super.getTargetObject(); 271 | if (targetObject == null && this.targetBeanName != null) { 272 | Assert.state(this.beanFactory != null, "BeanFactory must be set when using 'targetBeanName'"); 273 | targetObject = this.beanFactory.getBean(this.targetBeanName); 274 | } 275 | return targetObject; 276 | } 277 | 278 | 279 | public JobDetail getObject() { 280 | return this.jobDetail; 281 | } 282 | 283 | public Class getObjectType() { 284 | return (this.jobDetail != null ? this.jobDetail.getClass() : JobDetail.class); 285 | } 286 | 287 | public boolean isSingleton() { 288 | return true; 289 | } 290 | 291 | 292 | /** 293 | * Quartz Job implementation that invokes a specified method. 294 | * Automatically applied by MethodInvokingJobDetailFactoryBean. 295 | */ 296 | public static class MethodInvokingJob extends QuartzJobBean { 297 | 298 | protected static final Log logger = LogFactory.getLog(MethodInvokingJob.class); 299 | 300 | private MethodInvoker methodInvoker; 301 | 302 | /** 303 | * Set the MethodInvoker to use. 304 | */ 305 | public void setMethodInvoker(MethodInvoker methodInvoker) { 306 | this.methodInvoker = methodInvoker; 307 | } 308 | 309 | /** 310 | * Invoke the method via the MethodInvoker. 311 | */ 312 | @Override 313 | protected void executeInternal(JobExecutionContext context) throws JobExecutionException { 314 | try { 315 | String name = ScheduleUtil.getTaskNameFormBean(context.getJobDetail().getKey().getName(), this.methodInvoker.getTargetMethod()); 316 | boolean isOwner = false; 317 | try { 318 | if(ConsoleManager.getScheduleManager().getZkManager().checkZookeeperState()){ 319 | isOwner = ConsoleManager.getScheduleManager().getScheduleDataManager().isOwner(name, ConsoleManager.getScheduleManager().getScheduleServerUUid()); 320 | ConsoleManager.getScheduleManager().getIsOwnerMap().put(name, isOwner); 321 | }else{ 322 | // 如果zk不可用,使用历史数据 323 | if(null != ConsoleManager.getScheduleManager().getIsOwnerMap()){ 324 | isOwner = ConsoleManager.getScheduleManager().getIsOwnerMap().get(name); 325 | } 326 | } 327 | } catch (Exception e) { 328 | LOGGER.error("Check task owner error.", e); 329 | } 330 | if(isOwner){ 331 | ReflectionUtils.invokeMethod(setResultMethod, context, this.methodInvoker.invoke()); 332 | ConsoleManager.getScheduleManager().getScheduleDataManager().saveRunningInfo(name, ConsoleManager.getScheduleManager().getScheduleServerUUid()); 333 | LOGGER.info("Cron job has been executed."); 334 | } 335 | } 336 | catch (InvocationTargetException ex) { 337 | if (ex.getTargetException() instanceof JobExecutionException) { 338 | // -> JobExecutionException, to be logged at info level by Quartz 339 | throw (JobExecutionException) ex.getTargetException(); 340 | } 341 | else { 342 | // -> "unhandled exception", to be logged at error level by Quartz 343 | throw new JobMethodInvocationFailedException(this.methodInvoker, ex.getTargetException()); 344 | } 345 | } 346 | catch (Exception ex) { 347 | // -> "unhandled exception", to be logged at error level by Quartz 348 | throw new JobMethodInvocationFailedException(this.methodInvoker, ex); 349 | } 350 | } 351 | } 352 | 353 | 354 | /** 355 | * Extension of the MethodInvokingJob, implementing the StatefulJob interface. 356 | * Quartz checks whether or not jobs are stateful and if so, 357 | * won't let jobs interfere with each other. 358 | */ 359 | public static class StatefulMethodInvokingJob extends MethodInvokingJob implements Job { 360 | 361 | // No implementation, just an addition of the tag interface StatefulJob 362 | // in order to allow stateful method invoking jobs. 363 | } 364 | 365 | } 366 | -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/util/ScheduleUtil.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.util; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.net.InetAddress; 6 | import java.net.ServerSocket; 7 | import java.text.ParseException; 8 | import java.text.SimpleDateFormat; 9 | import java.util.ArrayList; 10 | import java.util.Date; 11 | import java.util.List; 12 | import java.util.Properties; 13 | 14 | 15 | /** 16 | * 调度处理工具类 17 | * 18 | * @author juny.ye 19 | * 20 | */ 21 | public class ScheduleUtil { 22 | public static String OWN_SIGN_BASE ="BASE"; 23 | 24 | public static String getLocalHostName() { 25 | try { 26 | return InetAddress.getLocalHost().getHostName(); 27 | } catch (Exception e) { 28 | return ""; 29 | } 30 | } 31 | 32 | public static int getFreeSocketPort() { 33 | try { 34 | ServerSocket ss = new ServerSocket(0); 35 | int freePort = ss.getLocalPort(); 36 | ss.close(); 37 | return freePort; 38 | } catch (Exception ex) { 39 | throw new RuntimeException(ex); 40 | } 41 | } 42 | 43 | public static String getLocalIP() { 44 | try { 45 | return InetAddress.getLocalHost().getHostAddress(); 46 | } catch (Exception e) { 47 | return ""; 48 | } 49 | } 50 | 51 | public static String transferDataToString(Date d){ 52 | SimpleDateFormat DATA_FORMAT_yyyyMMddHHmmss = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 53 | return DATA_FORMAT_yyyyMMddHHmmss.format(d); 54 | } 55 | public static Date transferStringToDate(String d) throws ParseException{ 56 | SimpleDateFormat DATA_FORMAT_yyyyMMddHHmmss = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 57 | return DATA_FORMAT_yyyyMMddHHmmss.parse(d); 58 | } 59 | public static Date transferStringToDate(String d,String formate) throws ParseException{ 60 | SimpleDateFormat FORMAT = new SimpleDateFormat(formate); 61 | return FORMAT.parse(d); 62 | } 63 | public static String getTaskTypeByBaseAndOwnSign(String baseType,String ownSign){ 64 | if(ownSign.equals(OWN_SIGN_BASE)){ 65 | return baseType; 66 | } 67 | return baseType+"$" + ownSign; 68 | } 69 | public static String splitBaseTaskTypeFromTaskType(String taskType){ 70 | if(taskType.contains("$")){ 71 | return taskType.substring(0,taskType.indexOf("$")); 72 | }else{ 73 | return taskType; 74 | } 75 | 76 | } 77 | public static String splitOwnsignFromTaskType(String taskType){ 78 | if(taskType.contains("$")){ 79 | return taskType.substring(taskType.indexOf("$")+1); 80 | }else{ 81 | return OWN_SIGN_BASE; 82 | } 83 | } 84 | 85 | public static String getTaskNameFormBean(String beanName, String methodName){ 86 | return beanName + "#" + methodName; 87 | } 88 | 89 | /** 90 | * 分配任务数量 91 | * @param serverNum 总的服务器数量 92 | * @param taskItemNum 任务项数量 93 | * @param maxNumOfOneServer 每个server最大任务项数目 94 | * @return null 95 | */ 96 | public static int[] assignTaskNumber(int serverNum,int taskItemNum,int maxNumOfOneServer){ 97 | int[] taskNums = new int[serverNum]; 98 | int numOfSingle = taskItemNum / serverNum; 99 | int otherNum = taskItemNum % serverNum; 100 | if (maxNumOfOneServer >0 && numOfSingle >= maxNumOfOneServer) { 101 | numOfSingle = maxNumOfOneServer; 102 | otherNum = 0; 103 | } 104 | for (int i = 0; i < taskNums.length; i++) { 105 | if (i < otherNum) { 106 | taskNums[i] = numOfSingle + 1; 107 | } else { 108 | taskNums[i] = numOfSingle; 109 | } 110 | } 111 | return taskNums; 112 | } 113 | private static String printArray(int[] items){ 114 | String s=""; 115 | for(int i=0;i0){s = s +",";} 117 | s = s + items[i]; 118 | } 119 | return s; 120 | } 121 | 122 | static Properties prop = new Properties(); 123 | static { 124 | InputStream in = ScheduleUtil.class.getResourceAsStream("/schedule.properties"); 125 | try { 126 | prop.load(in); 127 | } catch (IOException e) { 128 | e.printStackTrace(); 129 | } 130 | } 131 | 132 | /** 133 | * 从配置文件获取server code 134 | * @return 135 | */ 136 | public static String getServerCode(){ 137 | return prop.getProperty("uncode.schedule.server.code"); 138 | } 139 | 140 | /** 141 | * 从zk的server list中获取server ip list ,保持元素顺序 142 | * @param serverList 143 | * @return 144 | */ 145 | public static List getServerIpList(List serverList){ 146 | List serverIpList = new ArrayList(serverList.size()); 147 | for(String ser:serverList){ 148 | serverIpList.add(ser.substring(0, ser.indexOf("$"))); 149 | } 150 | return serverIpList; 151 | } 152 | 153 | public static void main(String[] args) { 154 | System.out.println(printArray(assignTaskNumber(1,10,0))); 155 | System.out.println(printArray(assignTaskNumber(2,10,0))); 156 | System.out.println(printArray(assignTaskNumber(3,10,0))); 157 | System.out.println(printArray(assignTaskNumber(4,10,0))); 158 | System.out.println(printArray(assignTaskNumber(5,10,0))); 159 | System.out.println(printArray(assignTaskNumber(6,10,0))); 160 | System.out.println(printArray(assignTaskNumber(7,10,0))); 161 | System.out.println(printArray(assignTaskNumber(8,10,0))); 162 | System.out.println(printArray(assignTaskNumber(9,10,0))); 163 | System.out.println(printArray(assignTaskNumber(10,10,0))); 164 | 165 | System.out.println("-----------------"); 166 | 167 | System.out.println(printArray(assignTaskNumber(1,10,3))); 168 | System.out.println(printArray(assignTaskNumber(2,10,3))); 169 | System.out.println(printArray(assignTaskNumber(3,10,3))); 170 | System.out.println(printArray(assignTaskNumber(4,10,3))); 171 | System.out.println(printArray(assignTaskNumber(5,10,3))); 172 | System.out.println(printArray(assignTaskNumber(6,10,3))); 173 | System.out.println(printArray(assignTaskNumber(7,10,3))); 174 | System.out.println(printArray(assignTaskNumber(8,10,3))); 175 | System.out.println(printArray(assignTaskNumber(9,10,3))); 176 | System.out.println(printArray(assignTaskNumber(10,10,3))); 177 | 178 | } 179 | } -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/web/ManagerServlet.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.web; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintWriter; 5 | import java.text.SimpleDateFormat; 6 | import java.util.Date; 7 | import java.util.List; 8 | 9 | import javax.servlet.ServletException; 10 | import javax.servlet.annotation.WebServlet; 11 | import javax.servlet.http.HttpServlet; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | 15 | import org.apache.commons.lang3.StringUtils; 16 | 17 | import cn.uncode.schedule.ConsoleManager; 18 | import cn.uncode.schedule.core.TaskDefine; 19 | 20 | 21 | @WebServlet(name="schedule",urlPatterns="/uncode/schedule") 22 | public class ManagerServlet extends HttpServlet{ 23 | 24 | /** 25 | * 26 | */ 27 | private static final long serialVersionUID = 8160082230341182715L; 28 | 29 | private static final String HEAD = 30 | "\n"+ 31 | "\n"+ 32 | "\n"+ 33 | "\n"+ 34 | "\t Uncode-Schedule管理\n"+ 35 | "\t \n"+ 36 | "\t \n"+ 37 | "\t \n"+ 38 | "\t \n"+ 39 | "\t \n"+ 40 | "\t \n"+ 41 | "\t \n"+ 42 | "\n"; 43 | 44 | private static final String SCRIPT = 45 | "\t "; 58 | 59 | private static final String PAGE = 60 | "\t \n"+ 61 | "\t

\n"+ 62 | "\t
\n"+ 63 | "\t \n"+ 64 | "\t
\n"+ 65 | "\t
\n"+ 66 | "\t
\n"+ 67 | "\t
\n"+ 68 | "\t
\n"+ 69 | "\t \n"+ 70 | "\t

Modal Window

\n"+ 71 | "\t
\n"+ 72 | "\t
\n"+ 73 | "\t
\n"+ 74 | "\t
\n"+ 75 | "\t
\n"+ 76 | "\t
\n"+ 77 | "\t
\n"+ 78 | "\t \n"+ 79 | "\t
\n"+ 80 | "\t \n"+ 81 | "\t
\n"+ 82 | "\t
\n"+ 83 | "\t
\n"+ 84 | "\t \n"+ 85 | "\t
\n"+ 86 | "\t \n"+ 87 | "\t
\n"+ 88 | "\t
\n"+ 89 | "\t
\n"+ 90 | "\t \n"+ 91 | "\t
\n"+ 92 | "\t \n"+ 93 | "\t
\n"+ 94 | "\t
\n"+ 95 | "\t
\n"+ 96 | "\t \n"+ 97 | "\t
\n"+ 98 | "\t \n"+ 99 | "\t
\n"+ 100 | "\t
\n"+ 101 | "\t
\n"+ 102 | "\t \n"+ 103 | "\t
\n"+ 104 | "\t \n"+ 105 | "\t
\n"+ 106 | "\t
\n"+ 107 | "\t
\n"+ 108 | "\t \n"+ 109 | "\t
\n"+ 110 | "\t \n"+ 111 | "\t
\n"+ 112 | "\t
\n"+ 113 | "\t
\n"+ 114 | "\t \n"+ 115 | "\t \n"+ 116 | "\t
\n"+ 117 | "\t
\n"+ 118 | "\t
\n"+ 119 | 120 | "\t
\n"+ 121 | "\t
\n"+ 122 | "\t
\n"+ 123 | "\t
\n"+ 124 | "\t
\n"+ 125 | "\t
\n"+ 126 | "\t
\n"+ 127 | "\t

Uncode-Schedule管理页面

\n"+ 128 | "\t
\n"+ 129 | "\t
\n"+ 130 | "\t
\n"+ 131 | "\t

集群节点

\n"+ 132 | "\t \n"+ 133 | "\t \n"+ 134 | "\t \n"+ 135 | "\t \n"+ 136 | "\t \n"+ 137 | "\t \n"+ 138 | "\t \n"+ 139 | "\t \n"+ 140 | "\t \n"+ 141 | "\t %s \n"+ 142 | "\t \n"+ 143 | "\t
序号名称调度节点
\n"+ 144 | "\t
\n"+ 145 | "\t
\n"+ 146 | "\t

定时任务列表

\n"+ 147 | "\t \n"+ 148 | "\t \n"+ 149 | "\t \n"+ 150 | "\t \n"+ 151 | "\t \n"+ 152 | "\t \n"+ 153 | "\t \n"+ 154 | "\t \n"+ 155 | "\t \n"+ 156 | "\t \n"+ 157 | "\t \n"+ 158 | "\t \n"+ 159 | "\t \n"+ 160 | "\t \n"+ 161 | "\t \n"+ 162 | "\t \n"+ 163 | "\t \n"+ 164 | "\t %s\n "+ 165 | "\t \n"+ 166 | "\t
序号目标bean目标方法类型cron表达式开始时间周期(毫秒)执行节点执行次数最近执行时间操作
\n"+ 167 | "\t
\n"+ 168 | "\t
\n"+ 169 | "\t
\n"+ 170 | "\t "; 171 | 172 | 173 | @Override 174 | protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 175 | String del = request.getParameter("del"); 176 | String bean = request.getParameter("bean"); 177 | String method = request.getParameter("method"); 178 | if(StringUtils.isNotEmpty(del)){ 179 | TaskDefine taskDefine = new TaskDefine(); 180 | String[] dels = del.split("_"); 181 | taskDefine.setTargetBean(dels[0]); 182 | taskDefine.setTargetMethod(dels[1]); 183 | ConsoleManager.delScheduleTask(taskDefine); 184 | response.sendRedirect(request.getSession().getServletContext().getContextPath()+"/uncode/schedule"); 185 | }else if(StringUtils.isNotEmpty(bean) && StringUtils.isNotEmpty(method)){ 186 | TaskDefine taskDefine = new TaskDefine(); 187 | taskDefine.setTargetBean(bean); 188 | taskDefine.setTargetMethod(method); 189 | taskDefine.setType(TaskDefine.TASK_TYPE_UNCODE); 190 | String cronExpression = request.getParameter("cronExpression"); 191 | if(StringUtils.isNotEmpty(cronExpression)){ 192 | taskDefine.setCronExpression(cronExpression); 193 | } 194 | String period = request.getParameter("period"); 195 | if(StringUtils.isNotEmpty(period)){ 196 | taskDefine.setPeriod(Long.valueOf(period)); 197 | } 198 | String startTime = request.getParameter("startTime"); 199 | if(StringUtils.isNotEmpty(startTime)){ 200 | taskDefine.setStartTime(new Date()); 201 | } 202 | String param = request.getParameter("param"); 203 | if(StringUtils.isNotEmpty(param)){ 204 | taskDefine.setParams(param); 205 | } 206 | if(StringUtils.isNotEmpty(cronExpression) || StringUtils.isNotEmpty(period)){ 207 | try { 208 | ConsoleManager.addScheduleTask(taskDefine); 209 | } catch (Exception e) { 210 | e.printStackTrace(); 211 | } 212 | } 213 | response.sendRedirect(request.getSession().getServletContext().getContextPath()+"/uncode/schedule"); 214 | } 215 | try { 216 | List servers = ConsoleManager.getScheduleManager().getScheduleDataManager().loadScheduleServerNames(); 217 | if(servers != null){ 218 | response.setContentType("text/html;charset=UTF-8"); 219 | PrintWriter out = response.getWriter(); 220 | StringBuffer sb = new StringBuffer(); 221 | for(int i=0; i< servers.size();i++){ 222 | String ser = servers.get(i); 223 | sb.append("") 224 | .append("").append(i+1).append("") 225 | .append("").append(ser).append(""); 226 | if( ConsoleManager.getScheduleManager().getScheduleDataManager().isLeader(ser, servers)){ 227 | sb.append("").append("是").append(""); 228 | }else{ 229 | sb.append("").append("否").append(""); 230 | } 231 | sb.append(""); 232 | } 233 | 234 | List tasks = ConsoleManager.queryScheduleTask(); 235 | StringBuffer sbTask = new StringBuffer(); 236 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 237 | for(int i=0; i< tasks.size();i++){ 238 | TaskDefine taskDefine = tasks.get(i); 239 | sbTask.append("") 240 | .append("").append(i+1).append("") 241 | .append("").append(taskDefine.getTargetBean()).append("") 242 | .append("").append(taskDefine.getTargetMethod()).append("") 243 | .append("").append(taskDefine.getType()).append("") 244 | .append("").append(taskDefine.getCronExpression()).append("") 245 | .append("").append(taskDefine.getStartTime()).append("") 246 | .append("").append(taskDefine.getPeriod()).append("") 247 | .append("").append(taskDefine.getCurrentServer()).append("") 248 | .append("").append(taskDefine.getRunTimes()).append(""); 249 | if(taskDefine.getLastRunningTime() > 0){ 250 | Date date = new Date(taskDefine.getLastRunningTime()); 251 | sbTask.append("").append(sdf.format(date)).append(""); 252 | }else{ 253 | sbTask.append("").append("-").append(""); 254 | } 255 | sbTask.append("").append("删除") 261 | .append(""); 262 | sbTask.append(""); 263 | } 264 | out.write(HEAD); 265 | out.write(SCRIPT); 266 | out.write(String.format(PAGE, request.getSession().getServletContext().getContextPath()+"/uncode/schedule", 267 | sb.toString(), sbTask.toString())); 268 | } 269 | } catch (Exception e) { 270 | e.printStackTrace(); 271 | } 272 | 273 | } 274 | 275 | } 276 | -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/web/ManualServlet.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.web; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintWriter; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import javax.servlet.ServletException; 9 | import javax.servlet.annotation.WebServlet; 10 | import javax.servlet.http.HttpServlet; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | import org.apache.commons.lang3.StringUtils; 15 | 16 | import com.google.gson.Gson; 17 | 18 | import cn.uncode.schedule.ConsoleManager; 19 | import cn.uncode.schedule.core.TaskDefine; 20 | 21 | @WebServlet(name="schedule",urlPatterns="/schedule/manual") 22 | public class ManualServlet extends HttpServlet{ 23 | 24 | private static final long serialVersionUID = -1293802185335109372L; 25 | 26 | private Gson gson = new Gson(); 27 | 28 | @Override 29 | protected void service(HttpServletRequest request, HttpServletResponse response) 30 | throws ServletException, IOException { 31 | response.setCharacterEncoding("UTF-8"); 32 | response.setContentType("application/json; charset=utf-8"); 33 | Map result = new HashMap(); 34 | String bean = request.getParameter("bean"); 35 | String method = request.getParameter("method"); 36 | if(StringUtils.isNotEmpty(bean) && StringUtils.isNotEmpty(method)){ 37 | TaskDefine taskDefine = new TaskDefine(); 38 | taskDefine.setTargetBean(bean); 39 | taskDefine.setTargetMethod(method); 40 | String param = request.getParameter("param"); 41 | if(StringUtils.isNotEmpty(param)){ 42 | taskDefine.setParams(param); 43 | } 44 | try { 45 | ConsoleManager.runTask(taskDefine); 46 | result.put("returnCode", "0000"); 47 | result.put("returnMsg", "调用成功"); 48 | } catch (Exception e) { 49 | result.put("returnCode", "8888"); 50 | result.put("returnMsg", e.getMessage()); 51 | } 52 | } 53 | 54 | PrintWriter out = null; 55 | try { 56 | out = response.getWriter(); 57 | out.write(gson.toJson(result)); 58 | } catch (IOException e) { 59 | e.printStackTrace(); 60 | } finally { 61 | if (out != null) { 62 | out.close(); 63 | } 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/zk/ScheduleDataManager4ZK.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.zk; 2 | 3 | import java.lang.reflect.Type; 4 | import java.sql.Timestamp; 5 | import java.text.DateFormat; 6 | import java.text.SimpleDateFormat; 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.Comparator; 10 | import java.util.Date; 11 | import java.util.List; 12 | import java.util.Random; 13 | import java.util.UUID; 14 | 15 | import org.apache.commons.lang3.StringUtils; 16 | import org.apache.zookeeper.CreateMode; 17 | import org.apache.zookeeper.ZooKeeper; 18 | import org.apache.zookeeper.data.Stat; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import com.google.gson.Gson; 23 | import com.google.gson.GsonBuilder; 24 | import com.google.gson.JsonDeserializationContext; 25 | import com.google.gson.JsonDeserializer; 26 | import com.google.gson.JsonElement; 27 | import com.google.gson.JsonParseException; 28 | import com.google.gson.JsonPrimitive; 29 | import com.google.gson.JsonSerializationContext; 30 | import com.google.gson.JsonSerializer; 31 | 32 | import cn.uncode.schedule.DynamicTaskManager; 33 | import cn.uncode.schedule.core.IScheduleDataManager; 34 | import cn.uncode.schedule.core.ScheduleServer; 35 | import cn.uncode.schedule.core.TaskDefine; 36 | import cn.uncode.schedule.util.ScheduleUtil; 37 | 38 | /** 39 | * zk实现类 40 | * 41 | * @author juny.ye 42 | * 43 | */ 44 | public class ScheduleDataManager4ZK implements IScheduleDataManager { 45 | private static final transient Logger LOG = LoggerFactory.getLogger(ScheduleDataManager4ZK.class); 46 | 47 | private static final String NODE_SERVER = "server"; 48 | private static final String NODE_TASK = "task"; 49 | private static final long SERVER_EXPIRE_TIME = 5000 * 3; 50 | private Gson gson ; 51 | private ZKManager zkManager; 52 | private String pathServer; 53 | private String pathTask; 54 | private long zkBaseTime = 0; 55 | private long loclaBaseTime = 0; 56 | private Random random; 57 | 58 | public ScheduleDataManager4ZK(ZKManager aZkManager) throws Exception { 59 | this.zkManager = aZkManager; 60 | gson = new GsonBuilder().registerTypeAdapter(Timestamp.class,new TimestampTypeAdapter()).setDateFormat("yyyy-MM-dd HH:mm:ss").create(); 61 | this.pathServer = this.zkManager.getRootPath() +"/" + NODE_SERVER; 62 | this.pathTask = this.zkManager.getRootPath() +"/" + NODE_TASK; 63 | this.random = new Random(); 64 | if (this.getZooKeeper().exists(this.pathServer, false) == null) { 65 | ZKTools.createPath(getZooKeeper(),this.pathServer, CreateMode.PERSISTENT, this.zkManager.getAcl()); 66 | } 67 | loclaBaseTime = System.currentTimeMillis(); 68 | String tempPath = this.zkManager.getZooKeeper().create(this.zkManager.getRootPath() + "/systime",null, this.zkManager.getAcl(), CreateMode.EPHEMERAL_SEQUENTIAL); 69 | Stat tempStat = this.zkManager.getZooKeeper().exists(tempPath, false); 70 | zkBaseTime = tempStat.getCtime(); 71 | ZKTools.deleteTree(getZooKeeper(), tempPath); 72 | if(Math.abs(this.zkBaseTime - this.loclaBaseTime) > 5000){ 73 | LOG.error("请注意,Zookeeper服务器时间与本地时间相差 : " + Math.abs(this.zkBaseTime - this.loclaBaseTime) +" ms"); 74 | } 75 | } 76 | 77 | public ZooKeeper getZooKeeper() throws Exception { 78 | return this.zkManager.getZooKeeper(); 79 | } 80 | 81 | public boolean refreshScheduleServer(ScheduleServer server) throws Exception { 82 | Timestamp heartBeatTime = new Timestamp(this.getSystemTime()); 83 | String zkPath = this.pathServer +"/" + server.getUuid(); 84 | if(this.getZooKeeper().exists(zkPath, false)== null){ 85 | //数据可能被清除,先清除内存数据后,重新注册数据 86 | server.setRegister(false); 87 | return false; 88 | }else{ 89 | Timestamp oldHeartBeatTime = server.getHeartBeatTime(); 90 | server.setHeartBeatTime(heartBeatTime); 91 | server.setVersion(server.getVersion() + 1); 92 | String valueString = this.gson.toJson(server); 93 | try{ 94 | this.getZooKeeper().setData(zkPath,valueString.getBytes(),-1); 95 | }catch(Exception e){ 96 | //恢复上次的心跳时间 97 | server.setHeartBeatTime(oldHeartBeatTime); 98 | server.setVersion(server.getVersion() - 1); 99 | throw e; 100 | } 101 | return true; 102 | } 103 | } 104 | 105 | @Override 106 | public void registerScheduleServer(ScheduleServer server) throws Exception { 107 | if(server.isRegister()){ 108 | throw new Exception(server.getUuid() + " 被重复注册"); 109 | } 110 | //clearExpireScheduleServer(); 111 | String realPath; 112 | //此处必须增加UUID作为唯一性保障 113 | StringBuffer id = new StringBuffer(); 114 | id.append(server.getIp()).append("$") 115 | .append(UUID.randomUUID().toString().replaceAll("-", "").toUpperCase()); 116 | 117 | String serverCode = ScheduleUtil.getServerCode(); 118 | if(serverCode != null){ //如果配置文件schedule.properties中配置server code 119 | String zkServerPath = pathServer + "/" + id.toString() + "$" + serverCode; 120 | realPath = this.getZooKeeper().create(zkServerPath, null, this.zkManager.getAcl(),CreateMode.PERSISTENT); 121 | }else{ 122 | String zkServerPath = pathServer + "/" + id.toString() +"$"; 123 | realPath = this.getZooKeeper().create(zkServerPath, null, this.zkManager.getAcl(),CreateMode.PERSISTENT_SEQUENTIAL); 124 | } 125 | 126 | server.setUuid(realPath.substring(realPath.lastIndexOf("/") + 1)); 127 | 128 | Timestamp heartBeatTime = new Timestamp(getSystemTime()); 129 | server.setHeartBeatTime(heartBeatTime); 130 | 131 | String valueString = this.gson.toJson(server); 132 | this.getZooKeeper().setData(realPath,valueString.getBytes(),-1); 133 | server.setRegister(true); 134 | } 135 | 136 | public List loadAllScheduleServer() throws Exception { 137 | String zkPath = this.pathServer; 138 | List names = this.getZooKeeper().getChildren(zkPath,false); 139 | Collections.sort(names); 140 | return names; 141 | } 142 | 143 | public void clearExpireScheduleServer() throws Exception{ 144 | String zkPath = this.pathServer; 145 | if(this.getZooKeeper().exists(zkPath,false)== null){ 146 | this.getZooKeeper().create(zkPath, null, this.zkManager.getAcl(), CreateMode.PERSISTENT); 147 | } 148 | for (String name : this.zkManager.getZooKeeper().getChildren(zkPath, false)) { 149 | try { 150 | Stat stat = new Stat(); 151 | this.getZooKeeper().getData(zkPath + "/" + name, null, stat); 152 | if (getSystemTime() - stat.getMtime() > SERVER_EXPIRE_TIME) { 153 | ZKTools.deleteTree(this.getZooKeeper(), zkPath + "/" + name); 154 | LOG.debug("ScheduleServer[" + zkPath + "/" + name + "]过期清除"); 155 | } 156 | } catch (Exception e) { 157 | // 当有多台服务器时,存在并发清理的可能,忽略异常 158 | } 159 | } 160 | } 161 | 162 | 163 | @Override 164 | public void unRegisterScheduleServer(ScheduleServer server) throws Exception { 165 | List serverList = this.loadScheduleServerNames(); 166 | 167 | if(server.isRegister() && this.isLeader(server.getUuid(), serverList)){ 168 | //delete task 169 | String zkPath = this.pathTask; 170 | String serverPath = this.pathServer; 171 | 172 | if(this.getZooKeeper().exists(zkPath,false)== null){ 173 | this.getZooKeeper().create(zkPath, null, this.zkManager.getAcl(), CreateMode.PERSISTENT); 174 | } 175 | 176 | //get all task 177 | List children = this.getZooKeeper().getChildren(zkPath, false); 178 | if(null != children && children.size() > 0){ 179 | for (String taskName : children) { 180 | String taskPath = zkPath + "/" + taskName; 181 | if (this.getZooKeeper().exists(taskPath, false) != null) { 182 | ZKTools.deleteTree(this.getZooKeeper(), taskPath + "/" + server.getUuid()); 183 | } 184 | } 185 | } 186 | 187 | //删除(此处修改==为!=) 188 | if (this.getZooKeeper().exists(this.pathServer, false) != null) { 189 | ZKTools.deleteTree(this.getZooKeeper(), serverPath + serverPath + "/" + server.getUuid()); 190 | } 191 | server.setRegister(false); 192 | } 193 | } 194 | /** 195 | * 从zk上获取目录/uncode/schedule/server/下面的所有子节点 196 | * 并按照最后一部分的serverCode排序(从小到大)之后返回 197 | */ 198 | public List loadScheduleServerNames() throws Exception { 199 | String zkPath = this.pathServer; 200 | if (this.getZooKeeper().exists(zkPath, false) == null) { 201 | return new ArrayList(); 202 | } 203 | List serverList = this.getZooKeeper() 204 | .getChildren(zkPath, false); 205 | Collections.sort(serverList, new Comparator() { 206 | public int compare(String u1, String u2) { 207 | return u1.substring(u1.lastIndexOf("$") + 1).compareTo( 208 | u2.substring(u2.lastIndexOf("$") + 1)); 209 | } 210 | }); 211 | return serverList; 212 | } 213 | 214 | public List loadScheduleServerIps() throws Exception{ 215 | String zkPath = this.pathServer; 216 | if (this.getZooKeeper().exists(zkPath, false) == null) { 217 | return new ArrayList(); 218 | } 219 | List serverList = this.getZooKeeper() 220 | .getChildren(zkPath, false); 221 | List serverIpList = new ArrayList(serverList.size()); 222 | for(String ser:serverList){ 223 | serverIpList.add(ser.substring(0, ser.indexOf("$"))); 224 | } 225 | return serverIpList; 226 | } 227 | 228 | 229 | @Override 230 | public void assignTask(String currentUuid, List taskServerList) throws Exception { 231 | if(!this.isLeader(currentUuid, taskServerList)){ 232 | if(LOG.isDebugEnabled()){ 233 | LOG.debug(currentUuid +":不是负责任务分配的Leader,直接返回"); 234 | } 235 | return; 236 | } 237 | if(LOG.isDebugEnabled()){ 238 | LOG.debug(currentUuid +":开始重新分配任务......"); 239 | } 240 | if(taskServerList.size()<=0){ 241 | //在服务器动态调整的时候,可能出现服务器列表为空的清空 242 | return; 243 | } 244 | if(this.zkManager.checkZookeeperState()){ 245 | String zkPath = this.pathTask; 246 | if(this.getZooKeeper().exists(zkPath,false)== null){ 247 | this.getZooKeeper().create(zkPath, null, this.zkManager.getAcl(), CreateMode.PERSISTENT); 248 | } 249 | List children = this.getZooKeeper().getChildren(zkPath, false); 250 | if(null != children && children.size() > 0){ 251 | for (String taskName : children) { 252 | String taskPath = zkPath + "/" + taskName; 253 | if (this.getZooKeeper().exists(taskPath, false) == null) { 254 | this.getZooKeeper().create(taskPath, null, this.zkManager.getAcl(), CreateMode.PERSISTENT); 255 | } 256 | 257 | List taskServerIds = this.getZooKeeper().getChildren(taskPath, false); 258 | if (null == taskServerIds || taskServerIds.size() == 0) { 259 | assignServer2Task(taskServerList, taskPath); 260 | } else { 261 | boolean hasAssignSuccess = false; 262 | for (String serverId : taskServerIds) { 263 | if (taskServerList.contains(serverId)) { 264 | hasAssignSuccess = true; 265 | continue; 266 | } 267 | ZKTools.deleteTree(this.getZooKeeper(), taskPath + "/" + serverId); 268 | } 269 | if (!hasAssignSuccess) { 270 | assignServer2Task(taskServerList, taskPath); 271 | } 272 | } 273 | 274 | } 275 | }else{ 276 | if(LOG.isDebugEnabled()){ 277 | LOG.debug(currentUuid +":没有集群任务"); 278 | } 279 | } 280 | } 281 | 282 | } 283 | 284 | private void assignServer2Task(List taskServerList, String taskPath)throws Exception { 285 | int index = random.nextInt(taskServerList.size()); 286 | String serverId = taskServerList.get(index); 287 | this.getZooKeeper().create(taskPath + "/" + serverId, null, this.zkManager.getAcl(),CreateMode.PERSISTENT); 288 | if(LOG.isDebugEnabled()){ 289 | LOG.debug("Assign server [" + serverId + "]" + " to task [" + taskPath + "]"); 290 | } 291 | } 292 | 293 | public boolean isLeader(String uuid,List serverList){ 294 | return uuid.equals(getLeader(serverList)); 295 | } 296 | 297 | private String getLeader(List serverList){ 298 | if(serverList == null || serverList.size() ==0){ 299 | return ""; 300 | } 301 | long no = Long.MAX_VALUE; 302 | long tmpNo = -1; 303 | String leader = null; 304 | for(String server:serverList){ 305 | tmpNo =Long.parseLong( server.substring(server.lastIndexOf("$")+1)); 306 | if(no > tmpNo){ 307 | no = tmpNo; 308 | leader = server; 309 | } 310 | } 311 | return leader; 312 | } 313 | 314 | private long getSystemTime(){ 315 | return this.zkBaseTime + ( System.currentTimeMillis() - this.loclaBaseTime); 316 | } 317 | 318 | private class TimestampTypeAdapter implements JsonSerializer, JsonDeserializer{ 319 | public JsonElement serialize(Timestamp src, Type arg1, JsonSerializationContext arg2) { 320 | DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 321 | String dateFormatAsString = format.format(new Date(src.getTime())); 322 | return new JsonPrimitive(dateFormatAsString); 323 | } 324 | 325 | public Timestamp deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 326 | if (!(json instanceof JsonPrimitive)) { 327 | throw new JsonParseException("The date should be a string value"); 328 | } 329 | 330 | try { 331 | DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 332 | Date date = (Date) format.parse(json.getAsString()); 333 | return new Timestamp(date.getTime()); 334 | } catch (Exception e) { 335 | throw new JsonParseException(e); 336 | } 337 | } 338 | } 339 | 340 | @Override 341 | public boolean isOwner(String name, String uuid) throws Exception { 342 | boolean isOwner = false; 343 | //查看集群中是否注册当前任务,如果没有就自动注册 344 | String zkPath = this.pathTask + "/" + name; 345 | if(this.zkManager.isAutoRegisterTask()){ 346 | if(this.getZooKeeper().exists(zkPath,false) == null){ 347 | this.getZooKeeper().create(zkPath, null, this.zkManager.getAcl(),CreateMode.PERSISTENT); 348 | if(LOG.isDebugEnabled()){ 349 | LOG.debug(uuid +":自动向集群注册任务[" + name + "]"); 350 | } 351 | // 重新分配任务 352 | assignServer2Task(loadScheduleServerNames(), zkPath); 353 | } 354 | } 355 | //判断是否分配给当前节点 356 | zkPath = zkPath + "/" + uuid; 357 | if(this.getZooKeeper().exists(zkPath,false) != null){ 358 | isOwner = true; 359 | } 360 | return isOwner; 361 | } 362 | 363 | @Override 364 | public boolean saveRunningInfo(String name, String uuid) throws Exception { 365 | //查看集群中是否注册当前任务,如果没有就自动注册 366 | String zkPath = this.pathTask + "/" + name; 367 | //判断是否分配给当前节点 368 | zkPath = zkPath + "/" + uuid; 369 | if(this.getZooKeeper().exists(zkPath,false) != null){ 370 | try { 371 | int times = 0; 372 | byte[] dataVal = this.getZooKeeper().getData(zkPath, null, null); 373 | if(dataVal != null){ 374 | String val = new String(dataVal); 375 | String[] vals = val.split(":"); 376 | times = Integer.parseInt(vals[0]); 377 | } 378 | times++; 379 | String newVal = times+":"+System.currentTimeMillis(); 380 | this.getZooKeeper().setData(zkPath, newVal.getBytes(), -1); 381 | } catch (Exception e) { 382 | // TODO: handle exception 383 | } 384 | } 385 | return true; 386 | } 387 | 388 | @Override 389 | public boolean isExistsTask(TaskDefine taskDefine) throws Exception{ 390 | String zkPath = this.pathTask+ "/" + taskDefine.stringKey(); 391 | return this.getZooKeeper().exists(zkPath, false) != null; 392 | } 393 | 394 | @Override 395 | public void addTask(TaskDefine taskDefine) throws Exception { 396 | String zkPath = this.pathTask; 397 | zkPath = zkPath + "/" + taskDefine.stringKey(); 398 | if(this.getZooKeeper().exists(zkPath, false) == null){ 399 | this.getZooKeeper().create(zkPath, null, this.zkManager.getAcl(), CreateMode.PERSISTENT); 400 | } 401 | String json = this.gson.toJson(taskDefine); 402 | this.getZooKeeper().setData(zkPath, json.getBytes(), -1); 403 | } 404 | 405 | @Override 406 | public void delTask(String targetBean, String targetMethod) throws Exception { 407 | String zkPath = this.pathTask; 408 | if(this.getZooKeeper().exists(zkPath,false) != null){ 409 | zkPath = zkPath + "/" + targetBean + "#" + targetMethod; 410 | if(this.getZooKeeper().exists(zkPath, false) != null){ 411 | ZKTools.deleteTree(this.getZooKeeper(), zkPath); 412 | } 413 | } 414 | } 415 | 416 | @Override 417 | public void delTask(TaskDefine taskDefine) throws Exception { 418 | String zkPath = this.pathTask; 419 | if(this.getZooKeeper().exists(zkPath,false) != null){ 420 | zkPath = zkPath + "/" + taskDefine.stringKey(); 421 | if(this.getZooKeeper().exists(zkPath, false) != null){ 422 | ZKTools.deleteTree(this.getZooKeeper(), zkPath); 423 | } 424 | } 425 | } 426 | 427 | 428 | @Override 429 | public List selectTask() throws Exception { 430 | String zkPath = this.pathTask; 431 | List taskDefines = new ArrayList(); 432 | if(this.getZooKeeper().exists(zkPath,false) != null){ 433 | List childes = this.getZooKeeper().getChildren(zkPath, false); 434 | for(String child:childes){ 435 | byte[] data = this.getZooKeeper().getData(zkPath+"/"+child, null, null); 436 | TaskDefine taskDefine = null; 437 | if (null != data) { 438 | String json = new String(data); 439 | taskDefine = this.gson.fromJson(json, TaskDefine.class); 440 | //taskDefine.setType("uncode task"); 441 | }else{ 442 | String[] names = child.split("#"); 443 | if(StringUtils.isNotEmpty(names[0])){ 444 | taskDefine = new TaskDefine(); 445 | taskDefine.setTargetBean(names[0]); 446 | taskDefine.setTargetMethod(names[1]); 447 | taskDefine.setType("quartz/spring task"); 448 | } 449 | } 450 | 451 | List sers = this.getZooKeeper().getChildren(zkPath+"/"+child, false); 452 | if(taskDefine != null && sers != null && sers.size() > 0){ 453 | taskDefine.setCurrentServer(sers.get(0)); 454 | byte[] dataVal = this.getZooKeeper().getData(zkPath+"/"+child+"/"+sers.get(0), null, null); 455 | if(dataVal != null){ 456 | String val = new String(dataVal); 457 | String[] vals = val.split(":"); 458 | taskDefine.setRunTimes(Integer.valueOf(vals[0])); 459 | taskDefine.setLastRunningTime(Long.valueOf(vals[1])); 460 | } 461 | } 462 | taskDefines.add(taskDefine); 463 | } 464 | } 465 | return taskDefines; 466 | } 467 | 468 | @Override 469 | public boolean checkLocalTask(String currentUuid) throws Exception { 470 | if(this.zkManager.checkZookeeperState()){ 471 | String zkPath = this.pathTask; 472 | List children = this.getZooKeeper().getChildren(zkPath, false); 473 | List ownerTask = new ArrayList(); 474 | if(null != children && children.size() > 0){ 475 | for (String taskName : children) { 476 | if (isOwner(taskName, currentUuid)) { 477 | String taskPath = zkPath + "/" + taskName; 478 | byte[] data = this.getZooKeeper().getData(taskPath, null, null); 479 | if (null != data) { 480 | String json = new String(data); 481 | TaskDefine taskDefine = this.gson.fromJson(json, TaskDefine.class); 482 | if(TaskDefine.TASK_TYPE_UNCODE.equals(taskDefine.getType())){ 483 | ownerTask.add(taskName); 484 | DynamicTaskManager.scheduleTask(taskDefine, new Date(getSystemTime())); 485 | } 486 | } 487 | } 488 | } 489 | } 490 | DynamicTaskManager.clearLocalTask(ownerTask); 491 | } 492 | return false; 493 | } 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | } -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/zk/ZKManager.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.zk; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Properties; 7 | import java.util.concurrent.CountDownLatch; 8 | 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.apache.zookeeper.CreateMode; 11 | import org.apache.zookeeper.WatchedEvent; 12 | import org.apache.zookeeper.Watcher; 13 | import org.apache.zookeeper.Watcher.Event.KeeperState; 14 | import org.apache.zookeeper.ZooDefs; 15 | import org.apache.zookeeper.ZooDefs.Ids; 16 | import org.apache.zookeeper.ZooKeeper; 17 | import org.apache.zookeeper.ZooKeeper.States; 18 | import org.apache.zookeeper.data.ACL; 19 | import org.apache.zookeeper.data.Id; 20 | import org.apache.zookeeper.server.auth.DigestAuthenticationProvider; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import cn.uncode.schedule.core.Version; 25 | 26 | 27 | /** 28 | * 29 | * @author juny.ye 30 | * 31 | */ 32 | public class ZKManager{ 33 | 34 | private static transient Logger log = LoggerFactory.getLogger(ZKManager.class); 35 | private ZooKeeper zk; 36 | private List acl = new ArrayList(); 37 | private Properties properties; 38 | 39 | private enum keys { 40 | zkConnectString, rootPath, userName, password, zkSessionTimeout, autoRegisterTask, ipBlacklist 41 | } 42 | 43 | public ZKManager(Properties aProperties) throws Exception{ 44 | this.properties = aProperties; 45 | this.connect(); 46 | } 47 | 48 | /** 49 | * 重连zookeeper 50 | * @throws Exception 51 | */ 52 | private synchronized void reConnection() throws Exception{ 53 | if (this.zk != null) { 54 | this.zk.close(); 55 | this.zk = null; 56 | this.connect() ; 57 | } 58 | } 59 | 60 | private void connect() throws Exception { 61 | CountDownLatch connectionLatch = new CountDownLatch(1); 62 | createZookeeper(connectionLatch); 63 | connectionLatch.await(); 64 | } 65 | 66 | private void createZookeeper(final CountDownLatch connectionLatch) throws Exception { 67 | zk = new ZooKeeper(this.properties.getProperty(keys.zkConnectString 68 | .toString()), Integer.parseInt(this.properties 69 | .getProperty(keys.zkSessionTimeout.toString())), 70 | new Watcher() { 71 | public void process(WatchedEvent event) { 72 | sessionEvent(connectionLatch, event); 73 | } 74 | }); 75 | String authString = this.properties.getProperty(keys.userName.toString()) 76 | + ":"+ this.properties.getProperty(keys.password.toString()); 77 | zk.addAuthInfo("digest", authString.getBytes()); 78 | acl.clear(); 79 | acl.add(new ACL(ZooDefs.Perms.ALL, new Id("digest", 80 | DigestAuthenticationProvider.generateDigest(authString)))); 81 | acl.add(new ACL(ZooDefs.Perms.READ, Ids.ANYONE_ID_UNSAFE)); 82 | } 83 | 84 | private void sessionEvent(CountDownLatch connectionLatch, WatchedEvent event) { 85 | if (event.getState() == KeeperState.SyncConnected) { 86 | log.info("收到ZK连接成功事件!"); 87 | connectionLatch.countDown(); 88 | } else if (event.getState() == KeeperState.Expired) { 89 | log.error("会话超时,等待重新建立ZK连接..."); 90 | try { 91 | reConnection(); 92 | } catch (Exception e) { 93 | log.error(e.getMessage(),e); 94 | } 95 | } // Disconnected:Zookeeper会自动处理Disconnected状态重连 96 | } 97 | 98 | public void close() throws InterruptedException { 99 | log.info("关闭zookeeper连接"); 100 | this.zk.close(); 101 | } 102 | 103 | String getRootPath(){ 104 | return this.properties.getProperty(keys.rootPath.toString()); 105 | } 106 | 107 | public List getIpBlacklist(){ 108 | List ips = new ArrayList(); 109 | String list = this.properties.getProperty(keys.ipBlacklist.toString()); 110 | if(StringUtils.isNotEmpty(list)){ 111 | ips = Arrays.asList(list.split(",")); 112 | } 113 | return ips; 114 | } 115 | public String getConnectStr(){ 116 | return this.properties.getProperty(keys.zkConnectString.toString()); 117 | } 118 | 119 | boolean isAutoRegisterTask(){ 120 | String autoRegisterTask = this.properties.getProperty(keys.autoRegisterTask.toString()); 121 | if(StringUtils.isNotEmpty(autoRegisterTask)){ 122 | return Boolean.valueOf(autoRegisterTask); 123 | } 124 | return true; 125 | } 126 | 127 | public boolean checkZookeeperState() throws Exception{ 128 | return zk != null && zk.getState() == States.CONNECTED; 129 | } 130 | 131 | public void initial() throws Exception { 132 | //当zk状态正常后才能调用 133 | checkParent(zk,this.getRootPath()); 134 | if(zk.exists(this.getRootPath(), false) == null){ 135 | ZKTools.createPath(zk, this.getRootPath(), CreateMode.PERSISTENT, acl); 136 | //设置版本信息 137 | zk.setData(this.getRootPath(),Version.getVersion().getBytes(),-1); 138 | }else{ 139 | //先校验父亲节点,本身是否已经是schedule的目录 140 | byte[] value = zk.getData(this.getRootPath(), false, null); 141 | if(value == null){ 142 | zk.setData(this.getRootPath(),Version.getVersion().getBytes(),-1); 143 | }else{ 144 | String dataVersion = new String(value); 145 | if(!Version.isCompatible(dataVersion)){ 146 | throw new Exception("TBSchedule程序版本 "+ Version.getVersion() +" 不兼容Zookeeper中的数据版本 " + dataVersion ); 147 | } 148 | log.info("当前的程序版本:" + Version.getVersion() + " 数据版本: " + dataVersion); 149 | } 150 | } 151 | } 152 | private static void checkParent(ZooKeeper zk, String path) throws Exception { 153 | String[] list = path.split("/"); 154 | String zkPath = ""; 155 | for (int i =0;i< list.length -1;i++){ 156 | String str = list[i]; 157 | if (StringUtils.isNotEmpty(str)) { 158 | zkPath = zkPath + "/" + str; 159 | if (zk.exists(zkPath, false) != null) { 160 | byte[] value = zk.getData(zkPath, false, null); 161 | if(value != null && new String(value).contains("uncode-schedule-")){ 162 | throw new Exception("\"" + zkPath +"\" is already a schedule instance's root directory, its any subdirectory cannot as the root directory of others"); 163 | } 164 | } 165 | } 166 | } 167 | } 168 | 169 | List getAcl() { 170 | return acl; 171 | } 172 | 173 | ZooKeeper getZooKeeper() throws Exception { 174 | if(!this.checkZookeeperState()){ 175 | reConnection(); 176 | } 177 | return this.zk; 178 | } 179 | 180 | } -------------------------------------------------------------------------------- /src/main/java/cn/uncode/schedule/zk/ZKTools.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.zk; 2 | 3 | import java.io.Writer; 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.apache.zookeeper.CreateMode; 10 | import org.apache.zookeeper.ZooKeeper; 11 | import org.apache.zookeeper.data.ACL; 12 | import org.apache.zookeeper.data.Stat; 13 | 14 | /** 15 | * zk工具类 16 | * 17 | * @author juny.ye 18 | * 19 | */ 20 | public class ZKTools { 21 | static void createPath(ZooKeeper zk, String path, CreateMode createMode, List acl) throws Exception { 22 | String[] list = path.split("/"); 23 | String zkPath = ""; 24 | for (String str : list) { 25 | if (StringUtils.isNotEmpty(str)) { 26 | zkPath = zkPath + "/" + str; 27 | if (zk.exists(zkPath, false) == null) { 28 | zk.create(zkPath, null, acl, createMode); 29 | } 30 | } 31 | } 32 | } 33 | 34 | public static void printTree(ZooKeeper zk,String path,Writer writer,String lineSplitChar) throws Exception{ 35 | String[] list = getTree(zk,path); 36 | Stat stat = new Stat(); 37 | for(String name:list){ 38 | byte[] value = zk.getData(name, false, stat); 39 | if(value == null){ 40 | writer.write(name + lineSplitChar); 41 | }else{ 42 | writer.write(name+"[v."+ stat.getVersion() +"]"+"["+ new String(value) +"]" + lineSplitChar); 43 | } 44 | } 45 | } 46 | public static void deleteTree(ZooKeeper zk,String path) throws Exception{ 47 | String[] list = getTree(zk,path); 48 | for(int i= list.length -1;i>=0; i--){ 49 | zk.delete(list[i],-1); 50 | } 51 | } 52 | 53 | private static String[] getTree(ZooKeeper zk, String path) throws Exception{ 54 | if(zk.exists(path, false) == null){ 55 | return new String[0]; 56 | } 57 | List dealList = new ArrayList(); 58 | dealList.add(path); 59 | int index =0; 60 | while(index < dealList.size()){ 61 | String tempPath = dealList.get(index); 62 | List children = zk.getChildren(tempPath, false); 63 | if(!tempPath.equalsIgnoreCase("/")){ 64 | tempPath = tempPath +"/"; 65 | } 66 | Collections.sort(children); 67 | for (int i = children.size() - 1; i >= 0; i--) { 68 | dealList.add(index + 1, tempPath + children.get(i)); 69 | } 70 | index++; 71 | } 72 | return dealList.toArray(new String[0]); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/test/java/cn/uncode/schedule/SimpeTestNode_1.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule; 2 | 3 | import org.springframework.context.support.ClassPathXmlApplicationContext; 4 | 5 | /** 6 | * @author juny.ye 7 | */ 8 | public class SimpeTestNode_1 { 9 | 10 | public static void main(String[] args) throws InterruptedException { 11 | ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml"); 12 | Thread.sleep(Long.MAX_VALUE); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/cn/uncode/schedule/SimpeTestNode_2.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule; 2 | 3 | import org.springframework.context.support.ClassPathXmlApplicationContext; 4 | 5 | /** 6 | * @author juny.ye 7 | */ 8 | public class SimpeTestNode_2 { 9 | 10 | public static void main(String[] args) throws InterruptedException { 11 | ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml"); 12 | Thread.sleep(Long.MAX_VALUE); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/cn/uncode/schedule/SimpeTestNode_3.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule; 2 | 3 | import org.springframework.context.support.ClassPathXmlApplicationContext; 4 | 5 | /** 6 | * @author juny.ye 7 | */ 8 | public class SimpeTestNode_3 { 9 | 10 | public static void main(String[] args) throws InterruptedException { 11 | ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext2.xml"); 12 | Thread.sleep(Long.MAX_VALUE); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/cn/uncode/schedule/SimpleTask.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule; 2 | 3 | import org.springframework.scheduling.annotation.Scheduled; 4 | import org.springframework.stereotype.Component; 5 | 6 | 7 | /** 8 | * @author juny.ye 9 | */ 10 | @Component 11 | public class SimpleTask { 12 | 13 | private static int i = 0; 14 | 15 | @Scheduled(fixedDelay = 1000) 16 | public void print() { 17 | System.out.println("===========start!========="); 18 | System.out.println("I:"+i);i++; 19 | System.out.println("=========== end !========="); 20 | } 21 | 22 | public void print1() { 23 | System.out.println("===========start!========="); 24 | System.out.println("print1:"+i);i++; 25 | System.out.println("=========== end !========="); 26 | } 27 | 28 | public void print2() { 29 | System.out.println("===========start!========="); 30 | System.out.println("print2:"+i);i++; 31 | System.out.println("=========== end !========="); 32 | } 33 | 34 | public void print3() { 35 | System.out.println("===========start!========="); 36 | System.out.println("print3:"+i);i++; 37 | System.out.println("=========== end !========="); 38 | } 39 | 40 | public void print4() { 41 | System.out.println("===========start!========="); 42 | System.out.println("print4:"+i);i++; 43 | System.out.println("=========== end !========="); 44 | } 45 | 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/cn/uncode/schedule/ZookeeperTest.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule; 2 | 3 | import java.io.StringWriter; 4 | import java.util.ArrayList; 5 | import java.util.Date; 6 | import java.util.List; 7 | 8 | import org.apache.zookeeper.CreateMode; 9 | import org.apache.zookeeper.ZooDefs; 10 | import org.apache.zookeeper.ZooDefs.Ids; 11 | import org.apache.zookeeper.ZooKeeper; 12 | import org.apache.zookeeper.data.ACL; 13 | import org.apache.zookeeper.data.Id; 14 | import org.apache.zookeeper.server.auth.DigestAuthenticationProvider; 15 | import org.junit.Test; 16 | import org.springframework.context.support.ClassPathXmlApplicationContext; 17 | 18 | import cn.uncode.schedule.core.TaskDefine; 19 | import cn.uncode.schedule.zk.ZKTools; 20 | 21 | 22 | /** 23 | * @author juny.ye 24 | */ 25 | public class ZookeeperTest { 26 | @Test 27 | public void testCloseStatus() throws Exception { 28 | ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null); 29 | int i = 1; 30 | while (true) { 31 | try { 32 | StringWriter writer = new StringWriter(); 33 | ZKTools.printTree(zk, "/uncode/schedule", writer, ""); 34 | System.out.println(i++ + "----" + writer.toString()); 35 | Thread.sleep(2000); 36 | } catch (Exception e) { 37 | System.out.println(e.getMessage()); 38 | } 39 | } 40 | } 41 | 42 | @Test 43 | public void testPrint() throws Exception { 44 | ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null); 45 | StringWriter writer = new StringWriter(); 46 | ZKTools.printTree(zk, "/", writer, "\n"); 47 | System.out.println(writer.toString()); 48 | } 49 | 50 | @Test 51 | public void deletePath() throws Exception { 52 | ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 3000, null); 53 | zk.addAuthInfo("digest", "ScheduleAdmin:password".getBytes()); 54 | ZKTools.deleteTree(zk, "/uncode/schedule/task/taskObj#print"); 55 | //ZKTools.deleteTree(zk, "/uncode/schedule"); 56 | StringWriter writer = new StringWriter(); 57 | ZKTools.printTree(zk, "/", writer, "\n"); 58 | System.out.println(writer.getBuffer().toString()); 59 | } 60 | 61 | @Test 62 | public void testCreateTask() throws Exception { 63 | ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null); 64 | List acls = new ArrayList(); 65 | zk.addAuthInfo("digest", "ScheduleAdmin:password".getBytes()); 66 | acls.add(new ACL(ZooDefs.Perms.ALL, new Id("digest", 67 | DigestAuthenticationProvider.generateDigest("ScheduleAdmin:password")))); 68 | acls.add(new ACL(ZooDefs.Perms.READ, Ids.ANYONE_ID_UNSAFE)); 69 | zk.create("/uncode/schedule/task/taskObj#print", new byte[0], acls, CreateMode.PERSISTENT); 70 | zk.getData("/uncode/schedule/task/taskObj#print", false, null); 71 | } 72 | 73 | @Test 74 | public void testCreateLocalTask() throws Exception { 75 | ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext1.xml"); 76 | Thread.sleep(1000); 77 | //print1 78 | TaskDefine taskDefine1 = new TaskDefine(); 79 | taskDefine1.setTargetBean("taskObj"); 80 | taskDefine1.setTargetMethod("print1"); 81 | taskDefine1.setCronExpression("0/3 * * * * ?"); 82 | ConsoleManager.addScheduleTask(taskDefine1); 83 | //print2 84 | TaskDefine taskDefine2 = new TaskDefine(); 85 | taskDefine2.setTargetBean("taskObj"); 86 | taskDefine2.setStartTime(new Date(System.currentTimeMillis()+1000)); 87 | taskDefine2.setTargetMethod("print2"); 88 | ConsoleManager.addScheduleTask(taskDefine2); 89 | //print3 90 | TaskDefine taskDefine3 = new TaskDefine(); 91 | taskDefine3.setTargetBean("taskObj"); 92 | taskDefine3.setTargetMethod("print3"); 93 | taskDefine3.setStartTime(new Date(System.currentTimeMillis()+1000*2)); 94 | taskDefine3.setPeriod(1000); 95 | ConsoleManager.addScheduleTask(taskDefine3); 96 | //print4 97 | TaskDefine taskDefine4 = new TaskDefine(); 98 | taskDefine4.setTargetBean("taskObj"); 99 | taskDefine4.setTargetMethod("print4"); 100 | taskDefine4.setPeriod(1000); 101 | ConsoleManager.addScheduleTask(taskDefine4); 102 | 103 | } 104 | } -------------------------------------------------------------------------------- /src/test/resources/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/resources/applicationContext1.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | -------------------------------------------------------------------------------- /src/test/resources/applicationContext2.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 0/3 * * * * ? 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootCategory=INFO, stdout 2 | 3 | log4j.appender.stdout.encoding=UTF-8 4 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 5 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 6 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss}\:%p(%L)%t %C - %M - %m%n 7 | 8 | 9 | --------------------------------------------------------------------------------