├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs ├── org.eclipse.jdt.core.prefs ├── org.springframework.ide.eclipse.beans.core.prefs └── org.springframework.ide.eclipse.core.prefs ├── .springBeans ├── LICENSE ├── README.md ├── resources ├── applicationContext1.xml ├── applicationContext2.xml └── log4j.properties ├── settings.fatjar ├── src └── cn │ └── uncode │ └── schedule │ ├── ZKScheduleManager.java │ ├── quartz │ └── MethodInvokingJobDetailFactoryBean.java │ ├── util │ └── ScheduleUtil.java │ └── zk │ ├── IScheduleDataManager.java │ ├── ScheduleDataManager4ZK.java │ ├── ScheduleServer.java │ ├── ScheduleTask.java │ ├── Version.java │ ├── ZKManager.java │ └── ZKTools.java └── test └── cn └── uncode └── schedule └── test ├── SimpeTestNode_1.java ├── SimpeTestNode_2.java ├── SimpleTask.java └── ZookeeperTest.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | /bin/ 14 | /classes/ 15 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | uncode-schedule 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.springframework.ide.eclipse.core.springbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.springframework.ide.eclipse.core.springnature 21 | org.eclipse.jdt.core.javanature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.6 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.source=1.6 12 | -------------------------------------------------------------------------------- /.settings/org.springframework.ide.eclipse.beans.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.springframework.ide.eclipse.beans.core.ignoreMissingNamespaceHandler=false 3 | -------------------------------------------------------------------------------- /.settings/org.springframework.ide.eclipse.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.springframework.ide.eclipse.core.builders.enable.aopreferencemodelbuilder=true 3 | org.springframework.ide.eclipse.core.builders.enable.beanmetadatabuilder=true 4 | org.springframework.ide.eclipse.core.builders.enable.osgibundleupdater=false 5 | org.springframework.ide.eclipse.core.enable.project.preferences=false 6 | org.springframework.ide.eclipse.core.validator.enable.org.springframework.ide.eclipse.beans.core.beansvalidator=true 7 | org.springframework.ide.eclipse.core.validator.enable.org.springframework.ide.eclipse.bestpractices.beansvalidator=false 8 | org.springframework.ide.eclipse.core.validator.enable.org.springframework.ide.eclipse.core.springvalidator=false 9 | org.springframework.ide.eclipse.core.validator.enable.org.springframework.ide.eclipse.data.core.datavalidator=true 10 | org.springframework.ide.eclipse.core.validator.enable.org.springframework.ide.eclipse.webflow.core.validator=true 11 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.autowire.autowire-org.springframework.ide.eclipse.beans.core.beansvalidator=false 12 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanAlias-org.springframework.ide.eclipse.beans.core.beansvalidator=true 13 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanClass-org.springframework.ide.eclipse.beans.core.beansvalidator=true 14 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanConstructorArgument-org.springframework.ide.eclipse.beans.core.beansvalidator=true 15 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanDefinition-org.springframework.ide.eclipse.beans.core.beansvalidator=true 16 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanDefinitionHolder-org.springframework.ide.eclipse.beans.core.beansvalidator=true 17 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanFactory-org.springframework.ide.eclipse.beans.core.beansvalidator=true 18 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanInitDestroyMethod-org.springframework.ide.eclipse.beans.core.beansvalidator=true 19 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanProperty-org.springframework.ide.eclipse.beans.core.beansvalidator=true 20 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanReference-org.springframework.ide.eclipse.beans.core.beansvalidator=true 21 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.methodOverride-org.springframework.ide.eclipse.beans.core.beansvalidator=true 22 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.parsingProblems-org.springframework.ide.eclipse.beans.core.beansvalidator=true 23 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.requiredProperty-org.springframework.ide.eclipse.beans.core.beansvalidator=false 24 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.toolAnnotation-org.springframework.ide.eclipse.beans.core.beansvalidator=false 25 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.bestpractices.com.springsource.sts.bestpractices.AvoidDriverManagerDataSource-org.springframework.ide.eclipse.bestpractices.beansvalidator=false 26 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.bestpractices.com.springsource.sts.bestpractices.ImportElementsAtTopRulee-org.springframework.ide.eclipse.bestpractices.beansvalidator=false 27 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.bestpractices.com.springsource.sts.bestpractices.ParentBeanSpecifiesAbstractClassRule-org.springframework.ide.eclipse.bestpractices.beansvalidator=false 28 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.bestpractices.com.springsource.sts.bestpractices.RefElementRule-org.springframework.ide.eclipse.bestpractices.beansvalidator=false 29 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.bestpractices.com.springsource.sts.bestpractices.TooManyBeansInFileRule-org.springframework.ide.eclipse.bestpractices.beansvalidator=false 30 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.bestpractices.com.springsource.sts.bestpractices.UnnecessaryValueElementRule-org.springframework.ide.eclipse.bestpractices.beansvalidator=false 31 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.bestpractices.com.springsource.sts.bestpractices.UseBeanInheritance-org.springframework.ide.eclipse.bestpractices.beansvalidator=false 32 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.bestpractices.legacyxmlusage.jndiobjectfactory-org.springframework.ide.eclipse.bestpractices.beansvalidator=false 33 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.core.springClasspath-org.springframework.ide.eclipse.core.springvalidator=false 34 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.data.core.invalidDerivedQuery-org.springframework.ide.eclipse.data.core.datavalidator=true 35 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.data.core.invalidParameterType-org.springframework.ide.eclipse.data.core.datavalidator=true 36 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.action-org.springframework.ide.eclipse.webflow.core.validator=true 37 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.actionstate-org.springframework.ide.eclipse.webflow.core.validator=true 38 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.attribute-org.springframework.ide.eclipse.webflow.core.validator=true 39 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.attributemapper-org.springframework.ide.eclipse.webflow.core.validator=true 40 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.beanaction-org.springframework.ide.eclipse.webflow.core.validator=true 41 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.evaluationaction-org.springframework.ide.eclipse.webflow.core.validator=true 42 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.evaluationresult-org.springframework.ide.eclipse.webflow.core.validator=true 43 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.exceptionhandler-org.springframework.ide.eclipse.webflow.core.validator=true 44 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.import-org.springframework.ide.eclipse.webflow.core.validator=true 45 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.inputattribute-org.springframework.ide.eclipse.webflow.core.validator=true 46 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.mapping-org.springframework.ide.eclipse.webflow.core.validator=true 47 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.outputattribute-org.springframework.ide.eclipse.webflow.core.validator=true 48 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.set-org.springframework.ide.eclipse.webflow.core.validator=true 49 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.state-org.springframework.ide.eclipse.webflow.core.validator=true 50 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.subflowstate-org.springframework.ide.eclipse.webflow.core.validator=true 51 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.transition-org.springframework.ide.eclipse.webflow.core.validator=true 52 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.variable-org.springframework.ide.eclipse.webflow.core.validator=true 53 | org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.webflowstate-org.springframework.ide.eclipse.webflow.core.validator=true 54 | -------------------------------------------------------------------------------- /.springBeans: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 5 | 6 | 7 | 8 | 9 | 10 | resources/applicationContext2.xml 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uncode-schedule 2 | 基于Zookeeper+Quartz/SpringTask的分布式任务调度系统,非常小巧,零侵入,无需任何修改就可以使Quartz/SpringTask具备分布式特性,确保所有任务在集群中不重复,不遗漏的执行. 3 | 4 | # 功能概述 5 | 6 | 1. 基于Zookeeper+Quartz/SpringTask的分布任务调度系统. 7 | 2. 确保每个任务在集群中不同节点上不重复的执行. 8 | 3. 单个任务节点故障时自动转移到其他任务节点继续执行. 9 | 4. 任务节点启动时必须保证zookeeper可用,任务节点运行期zookeeper集群不可用时任务节点保持悬挂状态,直到zookeeper集群恢复正常运期. 10 | 5. 任务会在各个任务节点均衡的执行. 11 | 12 | 说明: 13 | * 所有任务节点的代码和task配置要完全一致! 14 | * 单节点故障时需要业务保障数据完整性或幂等性. 15 | * 具体使用方式和spring task/quartz相同,只需要配置ZKScheduleManager即可. 16 | * 当添加,删除task时,当修改了task的逻辑时,要停止所有的任务节点,全部更新配置和代码后,再依次启动任务节点! 17 | 18 | ------------------------------------------------------------------------ 19 | 20 | # 1. 基于Spring Task的XML配置 21 | 22 | ## 1.1 XML方式 23 | 24 | 1 Spring bean 25 | ```java 26 | public class SimpleTask { 27 | 28 | private static int i = 0; 29 | 30 | public void print() { 31 | System.out.println("===========start!========="); 32 | System.out.println("I:"+i);i++; 33 | System.out.println("=========== end !========="); 34 | } 35 | } 36 | ``` 37 | 2 xml配置 38 | ```xml 39 | 40 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 67 | 68 | 69 | 70 | 71 | 72 | ``` 73 | ------------------------------------------------------------------------ 74 | 75 | ## 1.2 Annotation方式 76 | 77 | 1 Spring bean 78 | ```java 79 | @Component 80 | public class SimpleTask { 81 | 82 | private static int i = 0; 83 | 84 | @Scheduled(fixedDelay = 1000) 85 | public void print() { 86 | System.out.println("===========start!========="); 87 | System.out.println("I:"+i);i++; 88 | System.out.println("=========== end !========="); 89 | } 90 | 91 | } 92 | ``` 93 | 94 | 2 xml配置 95 | ```xml 96 | 97 | 98 | 99 | 100 | 101 | 102 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | ``` 119 | 120 | ------------------------------------------------------------------------ 121 | 122 | # 2. 基于Quartz的XML配置 123 | ```xml 124 | 125 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 160 | 161 | 162 | 163 | 164 | 165 | 0/3 * * * * ? 166 | 167 | 168 | 169 | 170 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | ``` 181 | ------------------------------------------------------------------------ 182 | 183 | # 引用: 184 | 本代码基于`oschina:http://git.oschina.net/uncode/uncode-schedule`项目进行的改造,非常感谢! 185 | -------------------------------------------------------------------------------- /resources/applicationContext1.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /resources/applicationContext2.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 0/3 * * * * ? 43 | 44 | 45 | 46 | 47 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO, stdout 2 | 3 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 4 | log4j.appender.stdout.Target=System.out 5 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 6 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS}\:%p(%L)%t %C - %M - %m%n 7 | 8 | log4j.logger.cn.uncode.schedule=DEBUG 9 | -------------------------------------------------------------------------------- /settings.fatjar: -------------------------------------------------------------------------------- 1 | #Fat Jar Configuration File 2 | #Wed Dec 02 17:43:43 CST 2015 3 | onejar.license.required=true 4 | manifest.classpath= 5 | manifest.removesigners=true 6 | onejar.checkbox=false 7 | jarname=uncode-schedule.jar 8 | manifest.mergeall=true 9 | manifest.mainclass= 10 | manifest.file= 11 | jarname.isextern=false 12 | onejar.expand= 13 | excludes=~cn/~uncode/~schedule/~test/;~applicationContext1.xml;~applicationContext2.xml;~log4j.properties;;;;;;;;;;;;;;;;;;;; 14 | includes= 15 | -------------------------------------------------------------------------------- /src/cn/uncode/schedule/ZKScheduleManager.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule; 2 | 3 | import java.lang.reflect.Method; 4 | import java.sql.Timestamp; 5 | import java.util.Date; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Properties; 9 | import java.util.Timer; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | import java.util.concurrent.ScheduledFuture; 12 | import java.util.concurrent.TimeUnit; 13 | import java.util.concurrent.locks.Lock; 14 | import java.util.concurrent.locks.ReentrantLock; 15 | 16 | import org.apache.commons.lang3.StringUtils; 17 | import org.quartz.Scheduler; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | import org.springframework.beans.BeansException; 21 | import org.springframework.context.ApplicationContext; 22 | import org.springframework.context.ApplicationContextAware; 23 | import org.springframework.scheduling.Trigger; 24 | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 25 | import org.springframework.scheduling.quartz.SchedulerFactoryBean; 26 | import org.springframework.scheduling.support.ScheduledMethodRunnable; 27 | import org.springframework.util.Assert; 28 | 29 | import cn.uncode.schedule.util.ScheduleUtil; 30 | import cn.uncode.schedule.zk.IScheduleDataManager; 31 | import cn.uncode.schedule.zk.ScheduleDataManager4ZK; 32 | import cn.uncode.schedule.zk.ScheduleServer; 33 | import cn.uncode.schedule.zk.ScheduleTask; 34 | import cn.uncode.schedule.zk.ZKManager; 35 | 36 | /** 37 | * 调度器核心管理 38 | * 39 | * @author juny.ye 40 | * 41 | */ 42 | public class ZKScheduleManager extends ThreadPoolTaskScheduler implements ApplicationContextAware { 43 | 44 | private static final long serialVersionUID = 1L; 45 | 46 | protected static final transient Logger LOG = LoggerFactory.getLogger(ZKScheduleManager.class); 47 | 48 | private Map zkConfig; 49 | 50 | protected ZKManager zkManager; 51 | 52 | private IScheduleDataManager scheduleDataManager; 53 | 54 | private static ZKScheduleManager instance = null; 55 | 56 | /** 57 | * 当前调度服务的信息 58 | */ 59 | protected ScheduleServer currenScheduleServer; 60 | 61 | /** 62 | * 心跳间隔 63 | */ 64 | private int timerInterval = 3000; 65 | 66 | /** 67 | * 重新分配task的阀值 68 | */ 69 | private int reAssignTaskThreshold = 10; 70 | 71 | public int getReAssignTaskThreshold() { 72 | return reAssignTaskThreshold; 73 | } 74 | 75 | public void setReAssignTaskThreshold(int reAssignTaskThreshold) { 76 | Assert.isTrue((reAssignTaskThreshold > 0)); 77 | this.reAssignTaskThreshold = reAssignTaskThreshold; 78 | } 79 | 80 | /** 81 | * 删除垃圾Task延迟时间,单位:秒,缺省延迟30分钟. 82 | */ 83 | private int deleteGarbageTaskDelay = 30 * 60; //单位秒 84 | 85 | public int getDeleteGarbageTaskDelay() { 86 | return deleteGarbageTaskDelay; 87 | } 88 | 89 | public void setDeleteGarbageTaskDelay(int deleteGarbageTaskDelay) { 90 | this.deleteGarbageTaskDelay = deleteGarbageTaskDelay; 91 | } 92 | 93 | //ZKScheduleManager启动时间 94 | private long _startedTime = System.currentTimeMillis(); 95 | 96 | /** 97 | * 是否注册成功 98 | */ 99 | private boolean registed = false; 100 | 101 | private ApplicationContext applicationcontext; 102 | 103 | //task运行次数Map 104 | private Map _taskRunCountMap = new ConcurrentHashMap(); 105 | 106 | public Map getTaskRunCountMap() { 107 | return _taskRunCountMap; 108 | } 109 | 110 | private Timer hearBeatTimer; 111 | protected Lock initLock = new ReentrantLock(); 112 | protected Lock registerLock = new ReentrantLock(); 113 | 114 | volatile String errorMessage = "No config Zookeeper connect infomation"; 115 | private InitialThread initialThread; 116 | 117 | public ZKScheduleManager() { 118 | this.currenScheduleServer = ScheduleServer.createScheduleServer(null); 119 | } 120 | 121 | public static ZKScheduleManager getInstance() { 122 | return instance; 123 | } 124 | 125 | @Override 126 | public void afterPropertiesSet() { 127 | super.afterPropertiesSet(); 128 | 129 | try { //@wjw_note: 初始化_taskRunCountMap 130 | SchedulerFactoryBean schedulerFactoryBean = applicationcontext.getBean(org.springframework.scheduling.quartz.SchedulerFactoryBean.class); 131 | Scheduler scheduler = schedulerFactoryBean.getScheduler(); 132 | String[] triggerNames = scheduler.getTriggerNames(Scheduler.DEFAULT_GROUP); 133 | for (String triggerName : triggerNames) { 134 | org.quartz.Trigger trigger = scheduler.getTrigger(triggerName, Scheduler.DEFAULT_GROUP); 135 | String taskName = trigger.getName() + "." + trigger.getJobName(); 136 | _taskRunCountMap.put(taskName, 0); 137 | } 138 | } catch (org.springframework.beans.factory.NoSuchBeanDefinitionException e) { 139 | //Spring配置文件里没有使用Quartz,忽略! 140 | } catch (Exception e) { 141 | LOG.warn(e.getMessage(), e); 142 | } 143 | 144 | Properties properties = new Properties(); 145 | for (Map.Entry e : this.zkConfig.entrySet()) { 146 | properties.put(e.getKey(), e.getValue()); 147 | } 148 | 149 | try { 150 | this.init(properties); 151 | } catch (Exception e1) { 152 | throw new RuntimeException(e1); 153 | } 154 | 155 | _startedTime = System.currentTimeMillis(); 156 | } 157 | 158 | public void init(Properties properties) throws Exception { 159 | if (this.initialThread != null) { 160 | this.initialThread.stopThread(); 161 | } 162 | this.initLock.lock(); 163 | try { 164 | this.scheduleDataManager = null; 165 | instance = this; 166 | if (this.zkManager != null) { 167 | this.zkManager.close(); 168 | } 169 | this.zkManager = new ZKManager(properties); 170 | while (this.zkManager.isZookeeperConnected() == false) { 171 | LOG.info("等待连接上Zookeeper......"); 172 | TimeUnit.SECONDS.sleep(1); 173 | } 174 | 175 | this.errorMessage = "Zookeeper connecting[" + this.zkManager.getConnectStr() + "]"; 176 | initialThread = new InitialThread(this); 177 | initialThread.setName("ScheduleManager-initialThread"); 178 | initialThread.start(); 179 | } finally { 180 | this.initLock.unlock(); 181 | } 182 | } 183 | 184 | @Override 185 | public void destroy() { 186 | _startedTime = System.currentTimeMillis(); 187 | try { 188 | if (this.initialThread != null) { 189 | try { 190 | this.initialThread.stopThread(); 191 | } finally { 192 | this.initialThread = null; 193 | } 194 | } 195 | 196 | if (this.hearBeatTimer != null) { 197 | try { 198 | this.hearBeatTimer.cancel(); 199 | } finally { 200 | this.hearBeatTimer = null; 201 | } 202 | } 203 | 204 | if (this.scheduleDataManager != null) { 205 | try { 206 | this.scheduleDataManager.UnRegisterScheduleServer(this.currenScheduleServer); 207 | this.scheduleDataManager.clearExpireScheduleServer(); 208 | } finally { 209 | this.scheduleDataManager = null; 210 | } 211 | } 212 | 213 | if (this.zkManager != null) { 214 | try { 215 | this.zkManager.close(); 216 | } finally { 217 | this.zkManager = null; 218 | } 219 | } 220 | } catch (Exception e) { 221 | e.printStackTrace(); 222 | } finally { 223 | super.destroy(); 224 | } 225 | } 226 | 227 | public void rewriteScheduleInfo() throws Exception { 228 | registerLock.lock(); 229 | try { 230 | // 先发送心跳信息 231 | if (errorMessage != null) { 232 | this.currenScheduleServer.setDealInfoDesc(errorMessage); 233 | } 234 | if (this.scheduleDataManager.refreshScheduleServer(this.currenScheduleServer) == false) { 235 | // 更新信息失败,清除内存数据后重新注册 236 | this.clearMemoInfo(); 237 | this.scheduleDataManager.registerScheduleServer(this.currenScheduleServer); 238 | } 239 | this.registed = true; 240 | } finally { 241 | registerLock.unlock(); 242 | } 243 | } 244 | 245 | /** 246 | * 清除内存中所有的已经取得的数据和任务队列.在状态更新失败,或者发现注册中心的调度信息被删除时调用此方法! 247 | */ 248 | public void clearMemoInfo() { 249 | try { 250 | 251 | } finally { 252 | } 253 | 254 | } 255 | 256 | /** 257 | * 根据当前调度服务器的信息,重新计算分配所有的调度任务. 任务的分配是需要加锁,避免数据分配错误。为了避免数据锁带来的负面作用,通过版本号来达到锁的目的 258 | * 259 | * 1、获取任务状态的版本号 2、获取所有的服务器注册信息和任务队列信息 3、清除已经超过心跳周期的服务器注册信息 3、重新计算任务分配 260 | * 4、更新任务状态的版本号【乐观锁】 5、更新任务队列的分配信息 261 | * 262 | * @throws Exception 263 | */ 264 | public void assignScheduleTask() throws Exception { 265 | scheduleDataManager.clearExpireScheduleServer(); //@wjw_note: 先清理无效的ScheduleServer! 266 | 267 | List serverList = scheduleDataManager.loadScheduleServerNames(); 268 | if (scheduleDataManager.isLeader(this.currenScheduleServer.getUuid(), serverList) == false) { 269 | if (LOG.isDebugEnabled()) { 270 | LOG.debug(this.currenScheduleServer.getUuid() + ",不是负责任务分配的Leader,直接返回!"); 271 | } 272 | return; 273 | } 274 | 275 | //@wjw_note: 添加主动清理Quartz类型的遗留下来的垃圾task,这要求集群里的Job配置要完全一致! 276 | if (((System.currentTimeMillis() - _startedTime) > (deleteGarbageTaskDelay * 1000)) && (_taskRunCountMap.size() > 0)) { 277 | List zkTaskNames = scheduleDataManager.loadTaskNames(); 278 | for (String zkTaskName : zkTaskNames) { 279 | if (_taskRunCountMap.containsKey(zkTaskName) == false) { 280 | LOG.warn("删除垃圾Task:[" + zkTaskName + "]"); 281 | scheduleDataManager.deleteTask(zkTaskName); 282 | } 283 | } 284 | } 285 | 286 | //非常重要的,分配任务! 287 | scheduleDataManager.assignTask(this.currenScheduleServer.getUuid(), serverList); 288 | } 289 | 290 | /** 291 | * 定时向Zookeeper更新当前服务器的心跳信息。
292 | * 如果发现本次更新的时间如果已经超过了服务器死亡的心跳周期, 293 | * 则不能再向Zookeeper更新信息,而应该当作新的ScheduleServer,进行重新注册。 294 | * 295 | * @throws Exception 296 | */ 297 | public void refreshScheduleServer() throws Exception { 298 | try { 299 | this.rewriteScheduleInfo(); 300 | // 如果任务信息没有初始化成功,不做任务相关的处理 301 | if (this.registed == false) { 302 | return; 303 | } 304 | 305 | // 重新分配任务 306 | this.assignScheduleTask(); 307 | } catch (Throwable e) { 308 | // 清除内存中所有的已经取得的数据和任务队列,避免心跳线程失败时候导致的数据重复 309 | this.clearMemoInfo(); 310 | if (e instanceof Exception) { 311 | throw (Exception) e; 312 | } else { 313 | throw new Exception(e.getMessage(), e); 314 | } 315 | } 316 | } 317 | 318 | /** 319 | * 在Zk状态正常后回调数据初始化 320 | * 321 | * @throws Exception 322 | */ 323 | public void initialData() throws Exception { 324 | this.zkManager.initial(); 325 | this.scheduleDataManager = new ScheduleDataManager4ZK(this.zkManager); 326 | 327 | // 注册调度管理器 328 | this.scheduleDataManager.registerScheduleServer(this.currenScheduleServer); 329 | if (hearBeatTimer == null) { 330 | hearBeatTimer = new Timer("ScheduleManager-" + this.currenScheduleServer.getUuid() + "-HearBeat"); 331 | } 332 | hearBeatTimer.schedule(new HeartBeatTimerTask(this), 2000, this.timerInterval); 333 | } 334 | 335 | private Runnable taskWrapper(final Runnable task) { 336 | return new Runnable() { 337 | public void run() { 338 | ScheduledMethodRunnable scheduledMethodRunnable = (ScheduledMethodRunnable) task; 339 | Method targetMethod = scheduledMethodRunnable.getMethod(); 340 | String[] beanNames = applicationcontext.getBeanNamesForType(targetMethod.getDeclaringClass()); 341 | if (null != beanNames && StringUtils.isNotEmpty(beanNames[0])) { 342 | String taskName = "SpringScheduler." + ScheduleUtil.getTaskNameFormBean(beanNames[0], targetMethod.getName()); 343 | if (_taskRunCountMap.containsKey(taskName) == false) { 344 | _taskRunCountMap.put(taskName, 0); 345 | } 346 | boolean isOwner = false; 347 | try { 348 | if (registed == false) { 349 | Thread.sleep(1000); 350 | } 351 | if (zkManager.isZookeeperConnected()) { 352 | String taskDesc = "Spring:Task"; 353 | ScheduleTask scheduleTask = new ScheduleTask(taskName, currenScheduleServer.getUuid(), taskDesc, new Timestamp(System.currentTimeMillis())); 354 | 355 | isOwner = scheduleDataManager.isOwner(scheduleTask); 356 | } 357 | } catch (InterruptedException e) { 358 | LOG.debug("Check task owner error.", e); 359 | } catch (Exception e) { 360 | LOG.error("Check task owner error.", e); 361 | } 362 | 363 | if (isOwner) { //@wjw_note: 如果是task的owner,就执行! 364 | int fireCount = _taskRunCountMap.get(taskName); 365 | fireCount++; 366 | _taskRunCountMap.put(taskName, fireCount); 367 | 368 | task.run(); 369 | LOG.info("Cron job has been executed."); 370 | 371 | //@wjw_note: 添加让出逻辑! 372 | if ((fireCount % ZKScheduleManager.getInstance().getReAssignTaskThreshold()) == 0) { 373 | if (LOG.isDebugEnabled()) { 374 | LOG.debug("Task执行次数已经达到让出阀值:[" + fireCount + "],让出执行权给其他节点!"); 375 | } 376 | try { 377 | ZKScheduleManager.getInstance().getScheduleDataManager().deleteTaskOwner(taskName, ZKScheduleManager.getInstance().getScheduleServerUUid()); 378 | } catch (Exception ex) { 379 | LOG.error(ex.getMessage(), ex); 380 | } 381 | } 382 | } 383 | } 384 | } 385 | }; 386 | } 387 | 388 | class HeartBeatTimerTask extends java.util.TimerTask { 389 | private transient final Logger log = LoggerFactory.getLogger(HeartBeatTimerTask.class); 390 | ZKScheduleManager manager; 391 | 392 | public HeartBeatTimerTask(ZKScheduleManager aManager) { 393 | manager = aManager; 394 | } 395 | 396 | public void run() { 397 | try { 398 | Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 399 | manager.refreshScheduleServer(); 400 | } catch (Exception ex) { 401 | log.error(ex.getMessage(), ex); 402 | } 403 | } 404 | } 405 | 406 | class InitialThread extends Thread { 407 | private transient Logger log = LoggerFactory.getLogger(InitialThread.class); 408 | ZKScheduleManager sm; 409 | 410 | public InitialThread(ZKScheduleManager sm) { 411 | this.sm = sm; 412 | } 413 | 414 | boolean isStop = false; 415 | 416 | public void stopThread() { 417 | this.isStop = true; 418 | } 419 | 420 | @Override 421 | public void run() { 422 | sm.initLock.lock(); 423 | try { 424 | int count = 0; 425 | while (sm.zkManager.isZookeeperConnected() == false) { 426 | count = count + 1; 427 | if (count % 50 == 0) { 428 | sm.errorMessage = "Zookeeper connecting[" + sm.zkManager.getConnectStr() + "],spendTime:" + count * 20 + "(ms)"; 429 | log.error(sm.errorMessage); 430 | } 431 | Thread.sleep(20); 432 | if (this.isStop == true) { 433 | return; 434 | } 435 | } 436 | sm.initialData(); 437 | } catch (Throwable e) { 438 | log.error(e.getMessage(), e); 439 | } finally { 440 | sm.initLock.unlock(); 441 | } 442 | 443 | } 444 | 445 | } 446 | 447 | public IScheduleDataManager getScheduleDataManager() { 448 | return scheduleDataManager; 449 | } 450 | 451 | @Override 452 | public void setApplicationContext(ApplicationContext applicationcontext) 453 | throws BeansException { 454 | this.applicationcontext = applicationcontext; 455 | } 456 | 457 | public void setZkManager(ZKManager zkManager) { 458 | this.zkManager = zkManager; 459 | } 460 | 461 | public ZKManager getZkManager() { 462 | return zkManager; 463 | } 464 | 465 | public void setZkConfig(Map zkConfig) { 466 | this.zkConfig = zkConfig; 467 | } 468 | 469 | @Override 470 | public ScheduledFuture scheduleAtFixedRate(Runnable task, long period) { 471 | return super.scheduleAtFixedRate(taskWrapper(task), period); 472 | } 473 | 474 | @Override 475 | public ScheduledFuture schedule(Runnable task, Trigger trigger) { 476 | return super.schedule(taskWrapper(task), trigger); 477 | } 478 | 479 | @Override 480 | public ScheduledFuture schedule(Runnable task, Date startTime) { 481 | return super.schedule(taskWrapper(task), startTime); 482 | } 483 | 484 | @Override 485 | public ScheduledFuture scheduleAtFixedRate(Runnable task, Date startTime, long period) { 486 | return super.scheduleAtFixedRate(taskWrapper(task), startTime, period); 487 | } 488 | 489 | @Override 490 | public ScheduledFuture scheduleWithFixedDelay(Runnable task, Date startTime, long delay) { 491 | return super.scheduleWithFixedDelay(taskWrapper(task), startTime, delay); 492 | } 493 | 494 | @Override 495 | public ScheduledFuture scheduleWithFixedDelay(Runnable task, long delay) { 496 | return super.scheduleWithFixedDelay(taskWrapper(task), delay); 497 | } 498 | 499 | public String getScheduleServerUUid() { 500 | if (null != currenScheduleServer) { 501 | return currenScheduleServer.getUuid(); 502 | } 503 | return null; 504 | } 505 | 506 | public boolean isRegisted() { 507 | return registed; 508 | } 509 | } -------------------------------------------------------------------------------- /src/cn/uncode/schedule/quartz/MethodInvokingJobDetailFactoryBean.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.quartz; 2 | 3 | /* 4 | * Copyright 2002-2012 the original author or authors. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import java.lang.reflect.InvocationTargetException; 20 | import java.lang.reflect.Method; 21 | import java.sql.Timestamp; 22 | import java.util.concurrent.atomic.AtomicInteger; 23 | 24 | import org.apache.commons.logging.Log; 25 | import org.apache.commons.logging.LogFactory; 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.quartz.StatefulJob; 32 | import org.quartz.Trigger; 33 | import org.slf4j.Logger; 34 | import org.slf4j.LoggerFactory; 35 | import org.springframework.beans.BeanUtils; 36 | import org.springframework.beans.BeanWrapper; 37 | import org.springframework.beans.PropertyAccessorFactory; 38 | import org.springframework.beans.factory.BeanClassLoaderAware; 39 | import org.springframework.beans.factory.BeanFactory; 40 | import org.springframework.beans.factory.BeanFactoryAware; 41 | import org.springframework.beans.factory.BeanNameAware; 42 | import org.springframework.beans.factory.FactoryBean; 43 | import org.springframework.beans.factory.InitializingBean; 44 | import org.springframework.beans.support.ArgumentConvertingMethodInvoker; 45 | import org.springframework.scheduling.quartz.JobMethodInvocationFailedException; 46 | import org.springframework.scheduling.quartz.QuartzJobBean; 47 | import org.springframework.scheduling.quartz.SchedulerFactoryBean; 48 | import org.springframework.util.Assert; 49 | import org.springframework.util.ClassUtils; 50 | import org.springframework.util.MethodInvoker; 51 | import org.springframework.util.ReflectionUtils; 52 | 53 | import cn.uncode.schedule.ZKScheduleManager; 54 | import cn.uncode.schedule.zk.ScheduleTask; 55 | 56 | /** 57 | * {@link org.springframework.beans.factory.FactoryBean} that exposes a 58 | * {@link org.quartz.JobDetail} object which delegates job execution to a 59 | * specified (static or non-static) method. Avoids the need for implementing a 60 | * one-line Quartz Job that just invokes an existing service method on a 61 | * Spring-managed target bean. 62 | * 63 | *

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

72 | * Supports both concurrently running jobs and non-currently running jobs 73 | * through the "concurrent" property. Jobs created by this 74 | * MethodInvokingJobDetailFactoryBean are by default volatile and durable 75 | * (according to Quartz terminology). 76 | * 77 | *

78 | * NOTE: JobDetails created via this FactoryBean are not serializable 79 | * and thus not suitable for persistent job stores. You need to implement 80 | * your own Quartz Job as a thin wrapper for each case where you want a 81 | * persistent job to delegate to a specific service method. 82 | * 83 | *

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

141 | * Default is the bean name of this FactoryBean. 142 | * 143 | * @see org.quartz.JobDetail#setName 144 | */ 145 | public void setName(String name) { 146 | this.name = name; 147 | } 148 | 149 | /** 150 | * Set the group of the job. 151 | *

152 | * Default is the default group of the Scheduler. 153 | * 154 | * @see org.quartz.JobDetail#setGroup 155 | * @see org.quartz.Scheduler#DEFAULT_GROUP 156 | */ 157 | public void setGroup(String group) { 158 | this.group = group; 159 | } 160 | 161 | /** 162 | * Specify whether or not multiple jobs should be run in a concurrent fashion. 163 | * The behavior when one does not want concurrent jobs to be executed is 164 | * realized through adding the {@link StatefulJob} interface. More information 165 | * on stateful versus stateless jobs can be found here. 168 | *

169 | * The default setting is to run jobs concurrently. 170 | */ 171 | public void setConcurrent(boolean concurrent) { 172 | this.concurrent = concurrent; 173 | } 174 | 175 | /** 176 | * Set the name of the target bean in the Spring BeanFactory. 177 | *

178 | * This is an alternative to specifying {@link #setTargetObject 179 | * "targetObject"}, allowing for non-singleton beans to be invoked. Note that 180 | * specified "targetObject" and {@link #setTargetClass "targetClass"} values 181 | * will override the corresponding effect of this "targetBeanName" setting 182 | * (i.e. statically pre-define the bean type or even the bean object). 183 | */ 184 | public void setTargetBeanName(String targetBeanName) { 185 | this.targetBeanName = targetBeanName; 186 | } 187 | 188 | /** 189 | * Set a list of JobListener names for this job, referring to non-global 190 | * JobListeners registered with the Scheduler. 191 | *

192 | * A JobListener name always refers to the name returned by the JobListener 193 | * implementation. 194 | * 195 | * @see SchedulerFactoryBean#setJobListeners 196 | * @see org.quartz.JobListener#getName 197 | */ 198 | public void setJobListenerNames(String[] names) { 199 | this.jobListenerNames = names; 200 | } 201 | 202 | public void setBeanName(String beanName) { 203 | this.beanName = beanName; 204 | } 205 | 206 | public void setBeanClassLoader(ClassLoader classLoader) { 207 | this.beanClassLoader = classLoader; 208 | } 209 | 210 | public void setBeanFactory(BeanFactory beanFactory) { 211 | this.beanFactory = beanFactory; 212 | } 213 | 214 | @Override 215 | protected Class resolveClassName(String className) throws ClassNotFoundException { 216 | return ClassUtils.forName(className, this.beanClassLoader); 217 | } 218 | 219 | public void afterPropertiesSet() throws ClassNotFoundException, NoSuchMethodException { 220 | prepare(); 221 | 222 | // Use specific name if given, else fall back to bean name. 223 | String name = (this.name != null ? this.name : this.beanName); 224 | 225 | // Consider the concurrent flag to choose between stateful and stateless job. 226 | Class jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class); 227 | 228 | // Build JobDetail instance. 229 | if (jobDetailImplClass != null) { 230 | // Using Quartz 2.0 JobDetailImpl class... 231 | this.jobDetail = (JobDetail) BeanUtils.instantiate(jobDetailImplClass); 232 | BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this.jobDetail); 233 | bw.setPropertyValue("name", name); 234 | bw.setPropertyValue("group", this.group); 235 | bw.setPropertyValue("jobClass", jobClass); 236 | bw.setPropertyValue("durability", true); 237 | ((JobDataMap) bw.getPropertyValue("jobDataMap")).put("methodInvoker", this); 238 | } else { //@wjw_add: 添加对Quartz1.X的支持! 239 | // Using Quartz 1.x JobDetail class... 240 | this.jobDetail = new JobDetail(name, this.group, jobClass); 241 | this.jobDetail.setVolatility(true); 242 | this.jobDetail.setDurability(true); 243 | this.jobDetail.getJobDataMap().put("methodInvoker", this); 244 | } 245 | 246 | // Register job listener names. 247 | if (this.jobListenerNames != null) { 248 | for (String jobListenerName : this.jobListenerNames) { 249 | if (jobDetailImplClass != null) { 250 | throw new IllegalStateException("Non-global JobListeners not supported on Quartz 2 - " + 251 | "manually register a Matcher against the Quartz ListenerManager instead"); 252 | } 253 | //this.jobDetail.addJobListener(jobListenerName); 254 | } 255 | } 256 | 257 | postProcessJobDetail(this.jobDetail); 258 | } 259 | 260 | /** 261 | * Callback for post-processing the JobDetail to be exposed by this 262 | * FactoryBean. 263 | *

264 | * The default implementation is empty. Can be overridden in subclasses. 265 | * 266 | * @param jobDetail 267 | * the JobDetail prepared by this FactoryBean 268 | */ 269 | protected void postProcessJobDetail(JobDetail jobDetail) { 270 | } 271 | 272 | /** 273 | * Overridden to support the {@link #setTargetBeanName "targetBeanName"} 274 | * feature. 275 | */ 276 | @Override 277 | public Class getTargetClass() { 278 | Class targetClass = super.getTargetClass(); 279 | if (targetClass == null && this.targetBeanName != null) { 280 | Assert.state(this.beanFactory != null, "BeanFactory must be set when using 'targetBeanName'"); 281 | targetClass = this.beanFactory.getType(this.targetBeanName); 282 | } 283 | return targetClass; 284 | } 285 | 286 | /** 287 | * Overridden to support the {@link #setTargetBeanName "targetBeanName"} 288 | * feature. 289 | */ 290 | @Override 291 | public Object getTargetObject() { 292 | Object targetObject = super.getTargetObject(); 293 | if (targetObject == null && this.targetBeanName != null) { 294 | Assert.state(this.beanFactory != null, "BeanFactory must be set when using 'targetBeanName'"); 295 | targetObject = this.beanFactory.getBean(this.targetBeanName); 296 | } 297 | return targetObject; 298 | } 299 | 300 | public JobDetail getObject() { 301 | return this.jobDetail; 302 | } 303 | 304 | public Class getObjectType() { 305 | return (this.jobDetail != null ? this.jobDetail.getClass() : JobDetail.class); 306 | } 307 | 308 | public boolean isSingleton() { 309 | return true; 310 | } 311 | 312 | /** 313 | * Quartz Job implementation that invokes a specified method. Automatically 314 | * applied by MethodInvokingJobDetailFactoryBean. 315 | */ 316 | public static class MethodInvokingJob extends QuartzJobBean { 317 | 318 | protected static final Log logger = LogFactory.getLog(MethodInvokingJob.class); 319 | 320 | private MethodInvoker methodInvoker; 321 | 322 | /** 323 | * Set the MethodInvoker to use. 324 | */ 325 | public void setMethodInvoker(MethodInvoker methodInvoker) { 326 | this.methodInvoker = methodInvoker; 327 | } 328 | 329 | /** 330 | * Invoke the method via the MethodInvoker. 331 | */ 332 | @Override 333 | protected void executeInternal(JobExecutionContext context) throws JobExecutionException { 334 | try { 335 | Trigger trigger = context.getTrigger(); 336 | //String taskName = trigger.getGroup() + "." + trigger.getName() + "$" + trigger.getJobGroup() + "." + trigger.getJobName(); 337 | String taskName = trigger.getName() + "." + trigger.getJobName(); 338 | if (ZKScheduleManager.getInstance().getTaskRunCountMap().containsKey(taskName) == false) { 339 | ZKScheduleManager.getInstance().getTaskRunCountMap().put(taskName, 0); 340 | } 341 | 342 | boolean isOwner = false; 343 | try { 344 | if (ZKScheduleManager.getInstance().getZkManager().isZookeeperConnected() && ZKScheduleManager.getInstance().isRegisted()) { 345 | String taskDesc = null; 346 | if (trigger instanceof org.quartz.CronTrigger) { 347 | taskDesc = "Quartz:CronTrigger:" + ((org.quartz.CronTrigger) trigger).getCronExpression(); 348 | } else if (trigger instanceof org.quartz.SimpleTrigger) { 349 | taskDesc = "Quartz:SimpleTrigger"; 350 | } else { 351 | taskDesc = "Quartz:OtherTrigger"; 352 | } 353 | ScheduleTask scheduleTask = new ScheduleTask(taskName, ZKScheduleManager.getInstance().getScheduleServerUUid(), taskDesc, new Timestamp(System.currentTimeMillis())); 354 | isOwner = ZKScheduleManager.getInstance().getScheduleDataManager().isOwner(scheduleTask); 355 | } 356 | } catch (org.apache.zookeeper.KeeperException.NoNodeException ex) { //@wjw_note: NoNodeException异常说明系统还没有初始化好,忽略此异常! 357 | } catch (Exception e) { 358 | LOG.error("Check task owner error.", e); 359 | } 360 | if (isOwner) { 361 | int fireCount = ZKScheduleManager.getInstance().getTaskRunCountMap().get(taskName); 362 | fireCount++; 363 | ZKScheduleManager.getInstance().getTaskRunCountMap().put(taskName, fireCount); 364 | 365 | ReflectionUtils.invokeMethod(setResultMethod, context, this.methodInvoker.invoke()); 366 | if (LOG.isDebugEnabled()) { 367 | LOG.debug("Cron job has been executed."); 368 | } 369 | 370 | //@wjw_note: 添加让出逻辑! 371 | if ((fireCount % ZKScheduleManager.getInstance().getReAssignTaskThreshold()) == 0) { 372 | if (LOG.isDebugEnabled()) { 373 | LOG.debug("Task Owner[" + taskName + "/" + ZKScheduleManager.getInstance().getScheduleServerUUid() + "]执行次数已经达到让出阀值:[" + fireCount + "],让出执行权给其他节点!"); 374 | } 375 | ZKScheduleManager.getInstance().getScheduleDataManager().deleteTaskOwner(taskName, ZKScheduleManager.getInstance().getScheduleServerUUid()); 376 | } 377 | } 378 | } catch (InvocationTargetException ex) { 379 | if (ex.getTargetException() instanceof JobExecutionException) { 380 | // -> JobExecutionException, to be logged at info level by Quartz 381 | throw (JobExecutionException) ex.getTargetException(); 382 | } 383 | else { 384 | // -> "unhandled exception", to be logged at error level by Quartz 385 | throw new JobMethodInvocationFailedException(this.methodInvoker, ex.getTargetException()); 386 | } 387 | } catch (Exception ex) { 388 | // -> "unhandled exception", to be logged at error level by Quartz 389 | throw new JobMethodInvocationFailedException(this.methodInvoker, ex); 390 | } 391 | } 392 | } 393 | 394 | /** 395 | * Extension of the MethodInvokingJob, implementing the StatefulJob interface. 396 | * Quartz checks whether or not jobs are stateful and if so, won't let jobs 397 | * interfere with each other. 398 | */ 399 | public static class StatefulMethodInvokingJob extends MethodInvokingJob implements StatefulJob { 400 | 401 | // No implementation, just an addition of the tag interface StatefulJob 402 | // in order to allow stateful method invoking jobs. 403 | } 404 | 405 | } 406 | -------------------------------------------------------------------------------- /src/cn/uncode/schedule/util/ScheduleUtil.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.util; 2 | 3 | import java.net.InetAddress; 4 | import java.net.ServerSocket; 5 | import java.text.ParseException; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Date; 8 | 9 | /** 10 | * 调度处理工具类 11 | * 12 | * @author juny.ye 13 | * 14 | */ 15 | public class ScheduleUtil { 16 | public static final String OWN_SIGN_BASE = "BASE"; 17 | public static final String DATA_FORMAT_YYYYMMDDHHMMSS = "yyyy-MM-dd HH:mm:ss"; 18 | 19 | public static String getLocalHostName() { 20 | try { 21 | return InetAddress.getLocalHost().getHostName(); 22 | } catch (Exception e) { 23 | return ""; 24 | } 25 | } 26 | 27 | public static int getFreeSocketPort() { 28 | try { 29 | ServerSocket ss = new ServerSocket(0); 30 | int freePort = ss.getLocalPort(); 31 | ss.close(); 32 | return freePort; 33 | } catch (Exception ex) { 34 | throw new RuntimeException(ex); 35 | } 36 | } 37 | 38 | public static String getLocalIP() { 39 | try { 40 | return InetAddress.getLocalHost().getHostAddress(); 41 | } catch (Exception e) { 42 | return ""; 43 | } 44 | } 45 | 46 | public static String transferDataToString(Date d) { 47 | SimpleDateFormat DATA_FORMAT_yyyyMMddHHmmss = new SimpleDateFormat(ScheduleUtil.DATA_FORMAT_YYYYMMDDHHMMSS); 48 | return DATA_FORMAT_yyyyMMddHHmmss.format(d); 49 | } 50 | 51 | public static Date transferStringToDate(String d) throws ParseException { 52 | SimpleDateFormat DATA_FORMAT_yyyyMMddHHmmss = new SimpleDateFormat(ScheduleUtil.DATA_FORMAT_YYYYMMDDHHMMSS); 53 | return DATA_FORMAT_yyyyMMddHHmmss.parse(d); 54 | } 55 | 56 | public static Date transferStringToDate(String d, String formate) throws ParseException { 57 | SimpleDateFormat FORMAT = new SimpleDateFormat(formate); 58 | return FORMAT.parse(d); 59 | } 60 | 61 | public static String getTaskTypeByBaseAndOwnSign(String baseType, String ownSign) { 62 | if (ownSign.equals(OWN_SIGN_BASE) == true) { 63 | return baseType; 64 | } 65 | return baseType + "$" + ownSign; 66 | } 67 | 68 | public static String splitBaseTaskTypeFromTaskType(String taskType) { 69 | if (taskType.indexOf("$") >= 0) { 70 | return taskType.substring(0, taskType.indexOf("$")); 71 | } else { 72 | return taskType; 73 | } 74 | 75 | } 76 | 77 | public static String splitOwnsignFromTaskType(String taskType) { 78 | if (taskType.indexOf("$") >= 0) { 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 | * 92 | * @param serverNum 93 | * 总的服务器数量 94 | * @param taskItemNum 95 | * 任务项数量 96 | * @param maxNumOfOneServer 97 | * 每个server最大任务项数目 98 | * @param maxNum 99 | * 总的任务数量 100 | * @return 101 | */ 102 | public static int[] assignTaskNumber(int serverNum, int taskItemNum, int maxNumOfOneServer) { 103 | int[] taskNums = new int[serverNum]; 104 | int numOfSingle = taskItemNum / serverNum; 105 | int otherNum = taskItemNum % serverNum; 106 | if (maxNumOfOneServer > 0 && numOfSingle >= maxNumOfOneServer) { 107 | numOfSingle = maxNumOfOneServer; 108 | otherNum = 0; 109 | } 110 | for (int i = 0; i < taskNums.length; i++) { 111 | if (i < otherNum) { 112 | taskNums[i] = numOfSingle + 1; 113 | } else { 114 | taskNums[i] = numOfSingle; 115 | } 116 | } 117 | return taskNums; 118 | } 119 | 120 | private static String printArray(int[] items) { 121 | String s = ""; 122 | for (int i = 0; i < items.length; i++) { 123 | if (i > 0) { 124 | s = s + ","; 125 | } 126 | s = s + items[i]; 127 | } 128 | return s; 129 | } 130 | 131 | public static void main(String[] args) { 132 | System.out.println(printArray(assignTaskNumber(1, 10, 0))); 133 | System.out.println(printArray(assignTaskNumber(2, 10, 0))); 134 | System.out.println(printArray(assignTaskNumber(3, 10, 0))); 135 | System.out.println(printArray(assignTaskNumber(4, 10, 0))); 136 | System.out.println(printArray(assignTaskNumber(5, 10, 0))); 137 | System.out.println(printArray(assignTaskNumber(6, 10, 0))); 138 | System.out.println(printArray(assignTaskNumber(7, 10, 0))); 139 | System.out.println(printArray(assignTaskNumber(8, 10, 0))); 140 | System.out.println(printArray(assignTaskNumber(9, 10, 0))); 141 | System.out.println(printArray(assignTaskNumber(10, 10, 0))); 142 | 143 | System.out.println("-----------------"); 144 | 145 | System.out.println(printArray(assignTaskNumber(1, 10, 3))); 146 | System.out.println(printArray(assignTaskNumber(2, 10, 3))); 147 | System.out.println(printArray(assignTaskNumber(3, 10, 3))); 148 | System.out.println(printArray(assignTaskNumber(4, 10, 3))); 149 | System.out.println(printArray(assignTaskNumber(5, 10, 3))); 150 | System.out.println(printArray(assignTaskNumber(6, 10, 3))); 151 | System.out.println(printArray(assignTaskNumber(7, 10, 3))); 152 | System.out.println(printArray(assignTaskNumber(8, 10, 3))); 153 | System.out.println(printArray(assignTaskNumber(9, 10, 3))); 154 | System.out.println(printArray(assignTaskNumber(10, 10, 3))); 155 | 156 | } 157 | } -------------------------------------------------------------------------------- /src/cn/uncode/schedule/zk/IScheduleDataManager.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.zk; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 调度配置中心客户端接口,可以有基于数据库的实现,可以有基于ConfigServer的实现 7 | * 8 | * @author juny.ye 9 | * 10 | */ 11 | public interface IScheduleDataManager { 12 | 13 | /** 14 | * 发送心跳信息 15 | * 16 | * @param server 17 | * @throws Exception 18 | */ 19 | public boolean refreshScheduleServer(ScheduleServer server) throws Exception; 20 | 21 | public void refreshScheduleTask(ScheduleTask task); 22 | 23 | /** 24 | * 注册服务器 25 | * 26 | * @param server 27 | * @throws Exception 28 | */ 29 | public void registerScheduleServer(ScheduleServer server) throws Exception; 30 | 31 | /** 32 | * 注销服务器 33 | * 34 | * @param server 35 | * @throws Exception 36 | */ 37 | public void UnRegisterScheduleServer(ScheduleServer server) throws Exception; 38 | 39 | public boolean isLeader(String uuid, List serverList); 40 | 41 | /** 42 | * 清楚失效的ScheduleServer 43 | * 44 | * @throws Exception 45 | */ 46 | public void clearExpireScheduleServer() throws Exception; 47 | 48 | /** 49 | * 获取Zookeeper中的ScheduleServer列表 50 | * 51 | * @return 52 | * @throws Exception 53 | */ 54 | public List loadScheduleServerNames() throws Exception; 55 | 56 | /** 57 | * 获取Zookeeper中的Task列表 58 | * 59 | * @return 60 | * @throws Exception 61 | */ 62 | public List loadTaskNames() throws Exception; 63 | 64 | /** 65 | * 分配task给taskServerList中的随机一个! 66 | * 67 | * @param currentUuid 68 | * ScheduleServer的UUID 69 | * @param taskServerList 70 | * ScheduleServer列表 71 | * @throws Exception 72 | */ 73 | public void assignTask(String currentUuid, List taskServerList) throws Exception; 74 | 75 | public boolean isOwner(ScheduleTask scheduleTask) throws Exception; 76 | 77 | public void addTask(String name) throws Exception; 78 | 79 | public void deleteTask(String name) throws Exception; 80 | 81 | public void deleteTaskOwner(String taskName, String uuid) throws Exception; 82 | } -------------------------------------------------------------------------------- /src/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.zookeeper.CreateMode; 16 | import org.apache.zookeeper.ZooKeeper; 17 | import org.apache.zookeeper.data.Stat; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | import cn.uncode.schedule.util.ScheduleUtil; 22 | 23 | import com.google.gson.Gson; 24 | import com.google.gson.GsonBuilder; 25 | import com.google.gson.JsonDeserializationContext; 26 | import com.google.gson.JsonDeserializer; 27 | import com.google.gson.JsonElement; 28 | import com.google.gson.JsonParseException; 29 | import com.google.gson.JsonPrimitive; 30 | import com.google.gson.JsonSerializationContext; 31 | import com.google.gson.JsonSerializer; 32 | 33 | /** 34 | * 使用Zookeeper实现了IScheduleDataManager! 35 | * 36 | * @author juny.ye 37 | * 38 | */ 39 | public class ScheduleDataManager4ZK implements IScheduleDataManager { 40 | private static final transient Logger LOG = LoggerFactory.getLogger(ScheduleDataManager4ZK.class); 41 | 42 | public static final String NODE_SERVER = "server"; 43 | public static final String NODE_TASK = "task"; 44 | public static final long SERVER_EXPIRE_TIME = 5000 * 3; 45 | 46 | private ZKManager zkManager; 47 | 48 | private Gson gson; 49 | private String pathServer; 50 | private String pathTask; 51 | private long zkBaseTime = 0; 52 | private long loclaBaseTime = 0; 53 | private Random random; 54 | 55 | public ScheduleDataManager4ZK(ZKManager aZkManager) throws Exception { 56 | this.zkManager = aZkManager; 57 | this.gson = new GsonBuilder().registerTypeAdapter(Timestamp.class, new TimestampTypeAdapter()).setDateFormat(ScheduleUtil.DATA_FORMAT_YYYYMMDDHHMMSS).create(); 58 | this.pathServer = this.zkManager.getRootPath() + "/" + NODE_SERVER; 59 | this.pathTask = this.zkManager.getRootPath() + "/" + NODE_TASK; 60 | this.random = new Random(); 61 | if (this.getZooKeeper().exists(this.pathServer, false) == null) { 62 | ZKTools.createPath(getZooKeeper(), this.pathServer, CreateMode.PERSISTENT, this.zkManager.getAcl()); 63 | } 64 | 65 | loclaBaseTime = System.currentTimeMillis(); 66 | String tempPath = this.zkManager.getZooKeeper().create(this.zkManager.getRootPath() + "/systime", null, this.zkManager.getAcl(), CreateMode.EPHEMERAL_SEQUENTIAL); 67 | Stat tempStat = this.zkManager.getZooKeeper().exists(tempPath, false); 68 | zkBaseTime = tempStat.getCtime(); 69 | ZKTools.deleteTree(getZooKeeper(), tempPath); 70 | if (Math.abs(this.zkBaseTime - this.loclaBaseTime) > 5000) { 71 | LOG.error("请注意,Zookeeper服务器时间与本地时间相差 : " + Math.abs(this.zkBaseTime - this.loclaBaseTime) + " ms"); 72 | } 73 | } 74 | 75 | public ZooKeeper getZooKeeper() throws Exception { 76 | return this.zkManager.getZooKeeper(); 77 | } 78 | 79 | /** 80 | * x发送心跳信息 81 | * 82 | * @param server 83 | * @throws Exception 84 | */ 85 | @Override 86 | public boolean refreshScheduleServer(ScheduleServer server) throws Exception { 87 | Timestamp heartBeatTime = new Timestamp(this.getSystemTime()); 88 | String zkPath = this.pathServer + "/" + server.getUuid(); 89 | if (this.getZooKeeper().exists(zkPath, false) == null) { 90 | //数据可能被清除,先清除内存数据后,重新注册数据 91 | server.setRegisted(false); 92 | return false; 93 | } 94 | 95 | Timestamp oldHeartBeatTime = server.getHeartBeatTime(); 96 | server.setHeartBeatTime(heartBeatTime); 97 | server.setVersion(server.getVersion() + 1); 98 | String valueString = this.gson.toJson(server); 99 | try { 100 | this.getZooKeeper().setData(zkPath, valueString.getBytes(), -1); 101 | } catch (Exception e) { 102 | //恢复上次的心跳时间 103 | server.setHeartBeatTime(oldHeartBeatTime); 104 | server.setVersion(server.getVersion() - 1); 105 | throw e; 106 | } 107 | return true; 108 | } 109 | 110 | @Override 111 | //@wjw_note: 在Zookeeper上注册ScheduleServer 112 | public void registerScheduleServer(ScheduleServer server) throws Exception { 113 | if (server.isRegisted() == true) { 114 | throw new Exception(server.getUuid() + " 被重复注册"); 115 | } 116 | 117 | String realPath = null; 118 | //此处必须增加UUID作为唯一性保障 119 | StringBuilder id = new StringBuilder(); 120 | id.append(server.getIp()).append("$").append(UUID.randomUUID().toString().replaceAll("-", "").toUpperCase()); 121 | String zkServerPath = pathServer + "/" + id.toString() + "$"; 122 | realPath = this.getZooKeeper().create(zkServerPath, null, this.zkManager.getAcl(), CreateMode.PERSISTENT_SEQUENTIAL); 123 | server.setUuid(realPath.substring(realPath.lastIndexOf("/") + 1)); 124 | 125 | Timestamp heartBeatTime = new Timestamp(getSystemTime()); 126 | server.setHeartBeatTime(heartBeatTime); 127 | 128 | String valueString = this.gson.toJson(server); 129 | this.getZooKeeper().setData(realPath, valueString.getBytes(), -1); 130 | server.setRegisted(true); 131 | } 132 | 133 | @Override 134 | //@wjw_note: 删除此server下的所有zookeeper上的数据! 135 | public void UnRegisterScheduleServer(ScheduleServer server) throws Exception { 136 | //1. 删除server 137 | String zkPath = this.pathServer; 138 | if (this.getZooKeeper().exists(zkPath, false) != null) { 139 | ZKTools.deleteTree(this.getZooKeeper(), zkPath + "/" + server.getUuid()); 140 | } 141 | 142 | //2. 删除task 143 | zkPath = this.pathTask; 144 | if (this.getZooKeeper().exists(zkPath, false) == null) { 145 | return; 146 | } 147 | List children = this.getZooKeeper().getChildren(zkPath, false); 148 | if (null == children || children.size() == 0) { 149 | return; 150 | } 151 | 152 | for (int i = 0; i < children.size(); i++) { 153 | String taskName = children.get(i); 154 | String taskPath = zkPath + "/" + taskName + "/" + server.getUuid(); 155 | if (this.getZooKeeper().exists(taskPath, false) != null) { 156 | ZKTools.deleteTree(this.getZooKeeper(), taskPath); 157 | } 158 | } 159 | } 160 | 161 | public List loadAllScheduleServer() throws Exception { 162 | String zkPath = this.pathServer; 163 | List names = this.getZooKeeper().getChildren(zkPath, false); 164 | Collections.sort(names); 165 | return names; 166 | } 167 | 168 | public void clearExpireScheduleServer() throws Exception { 169 | String zkPath = this.pathServer; 170 | if (this.getZooKeeper().exists(zkPath, false) == null) { 171 | this.getZooKeeper().create(zkPath, null, this.zkManager.getAcl(), CreateMode.PERSISTENT); 172 | } 173 | for (String name : this.zkManager.getZooKeeper().getChildren(zkPath, false)) { 174 | try { 175 | Stat stat = new Stat(); 176 | this.getZooKeeper().getData(zkPath + "/" + name, null, stat); 177 | if (getSystemTime() - stat.getMtime() > SERVER_EXPIRE_TIME) { 178 | ZKTools.deleteTree(this.getZooKeeper(), zkPath + "/" + name); 179 | LOG.warn("清除过期ScheduleServer[" + zkPath + "/" + name + "]"); 180 | } 181 | } catch (Exception e) { 182 | // 当有多台服务器时,存在并发清理的可能,忽略异常 183 | } 184 | } 185 | } 186 | 187 | public List loadScheduleServerNames(String taskType) throws Exception { 188 | String zkPath = this.pathServer; 189 | if (this.getZooKeeper().exists(zkPath, false) == null) { 190 | return new ArrayList(); 191 | } 192 | List serverList = this.getZooKeeper().getChildren(zkPath, false); 193 | Collections.sort(serverList, new Comparator() { 194 | public int compare(String u1, String u2) { 195 | return u1.substring(u1.lastIndexOf("$") + 1).compareTo( 196 | u2.substring(u2.lastIndexOf("$") + 1)); 197 | } 198 | }); 199 | return serverList; 200 | } 201 | 202 | public List loadScheduleServerNames() throws Exception { 203 | String zkPath = this.pathServer; 204 | if (this.getZooKeeper().exists(zkPath, false) == null) { 205 | return new ArrayList(); 206 | } 207 | List serverList = this.getZooKeeper().getChildren(zkPath, false); 208 | Collections.sort(serverList, new Comparator() { 209 | public int compare(String u1, String u2) { 210 | return u1.substring(u1.lastIndexOf("$") + 1).compareTo( 211 | u2.substring(u2.lastIndexOf("$") + 1)); 212 | } 213 | }); 214 | return serverList; 215 | } 216 | 217 | public List loadTaskNames() throws Exception { 218 | String zkPath = this.pathTask; 219 | if (this.getZooKeeper().exists(zkPath, false) == null) { 220 | return new ArrayList(); 221 | } 222 | List taskList = this.getZooKeeper().getChildren(zkPath, false); 223 | return taskList; 224 | } 225 | 226 | @Override 227 | //@wjw_note: 非常重要的,分配任务的逻辑! 228 | public void assignTask(String currentUuid, List taskServerList) throws Exception { 229 | if (this.zkManager.isZookeeperConnected() == false) { 230 | return; 231 | } 232 | 233 | if (this.isLeader(currentUuid, taskServerList) == false) { 234 | if (LOG.isDebugEnabled()) { 235 | LOG.debug("节点[" + currentUuid + "],不是负责任务分配的Leader,直接返回"); 236 | } 237 | return; 238 | } 239 | if (LOG.isDebugEnabled()) { 240 | LOG.debug("Leader节点[" + currentUuid + "],开始重新分配任务......"); 241 | } 242 | 243 | if (taskServerList.size() <= 0) { 244 | //在服务器动态调整的时候,可能出现服务器列表为空的清空 245 | return; 246 | } 247 | 248 | String zkPath = this.pathTask; 249 | if (this.getZooKeeper().exists(zkPath, false) == null) { 250 | this.getZooKeeper().create(zkPath, null, this.zkManager.getAcl(), CreateMode.PERSISTENT); 251 | } 252 | List taskChildren = this.getZooKeeper().getChildren(zkPath, false); 253 | if (null == taskChildren || taskChildren.size() == 0) { 254 | if (LOG.isDebugEnabled()) { 255 | LOG.debug("节点[" + currentUuid + "],没有集群任务!"); 256 | } 257 | return; 258 | } 259 | 260 | for (int i = 0; i < taskChildren.size(); i++) { 261 | String taskName = taskChildren.get(i); 262 | String taskPath = zkPath + "/" + taskName; 263 | if (this.getZooKeeper().exists(taskPath, false) == null) { 264 | this.getZooKeeper().create(taskPath, null, this.zkManager.getAcl(), CreateMode.PERSISTENT); 265 | } 266 | List taskServerIds = this.getZooKeeper().getChildren(taskPath, false); 267 | if (null == taskServerIds || taskServerIds.size() == 0) { //执行task的节点是空的 268 | assignServer2Task(taskServerList, taskPath); 269 | } else { 270 | boolean hasAssignSuccess = false; 271 | for (String serverId : taskServerIds) { 272 | if (taskServerList.contains(serverId)) { 273 | hasAssignSuccess = true; 274 | continue; 275 | } 276 | 277 | LOG.warn("删除僵尸Task Owner[" + taskPath + "/" + serverId + "]"); 278 | ZKTools.deleteTree(this.getZooKeeper(), taskPath + "/" + serverId); //@wjw_note: 删除某一节点已经死掉的残留下来的僵尸task! 279 | } 280 | if (hasAssignSuccess == false) { 281 | assignServer2Task(taskServerList, taskPath); //@wjw_note: 把任务分配给taskServerList里随机的一个server! 282 | } 283 | } 284 | } 285 | } 286 | 287 | //@wjw_note: 把任务分配给taskServerList里随机的一个server! 288 | private void assignServer2Task(List taskServerList, String taskPath) throws Exception { 289 | int index = random.nextInt(taskServerList.size()); 290 | String serverId = taskServerList.get(index); 291 | this.getZooKeeper().create(taskPath + "/" + serverId, null, this.zkManager.getAcl(), CreateMode.PERSISTENT); 292 | 293 | if (LOG.isDebugEnabled()) { 294 | StringBuilder buffer = new StringBuilder(); 295 | buffer.append("Assign server [").append(serverId).append("]").append(" to task [").append(taskPath).append("]"); 296 | LOG.debug(buffer.toString()); 297 | } 298 | } 299 | 300 | public boolean isLeader(String uuid, List serverList) { 301 | return uuid.equals(getLeader(serverList)); 302 | } 303 | 304 | //@wjw_note: 返回序号最大的作为分配任务的Leader 305 | private String getLeader(List serverList) { 306 | if (serverList == null || serverList.size() == 0) { 307 | return ""; 308 | } 309 | long no = Long.MAX_VALUE; 310 | long tmpNo = -1; 311 | String leader = null; 312 | for (String server : serverList) { 313 | tmpNo = Long.parseLong(server.substring(server.lastIndexOf("$") + 1)); 314 | if (no > tmpNo) { 315 | no = tmpNo; 316 | leader = server; 317 | } 318 | } 319 | return leader; 320 | } 321 | 322 | public long getSystemTime() { 323 | return this.zkBaseTime + (System.currentTimeMillis() - this.loclaBaseTime); 324 | } 325 | 326 | @Override 327 | public boolean isOwner(ScheduleTask scheduleTask) throws Exception { 328 | Stat tempStat = null; 329 | //查看集群中是否注册当前任务,如果没有就自动注册 330 | String zkPath = this.pathTask + "/" + scheduleTask.getName(); 331 | if (this.zkManager.isAutoRegisterTask()) { 332 | tempStat = this.getZooKeeper().exists(zkPath, false); 333 | if (tempStat == null) { 334 | this.getZooKeeper().create(zkPath, null, this.zkManager.getAcl(), CreateMode.PERSISTENT); 335 | tempStat = this.getZooKeeper().exists(zkPath, false); 336 | if (LOG.isDebugEnabled()) { 337 | LOG.debug(scheduleTask.getUuid() + ":自动向集群注册任务[" + scheduleTask.getName() + "]"); 338 | } 339 | } 340 | } 341 | 342 | //@wjw_note: 当task下的子节点为空时,进行等待,防止错过执行! 343 | if (tempStat.getNumChildren() == 0) { 344 | int sleepCount = 0; 345 | Thread.sleep(1000); 346 | tempStat = this.zkManager.getZooKeeper().exists(zkPath, false); 347 | while (tempStat.getNumChildren() == 0 && sleepCount < 100) { 348 | sleepCount++; 349 | Thread.sleep(1000); 350 | tempStat = this.zkManager.getZooKeeper().exists(zkPath, false); 351 | } 352 | } 353 | 354 | //判断是否分配给当前节点 355 | zkPath = zkPath + "/" + scheduleTask.getUuid(); 356 | if (this.getZooKeeper().exists(zkPath, false) != null) { 357 | //@wjw_note: 写一些数据 358 | this.refreshScheduleTask(scheduleTask); 359 | 360 | return true; 361 | } 362 | return false; 363 | } 364 | 365 | @Override 366 | public void refreshScheduleTask(ScheduleTask task) { 367 | try { 368 | String zkPath = this.pathTask + "/" + task.getName(); 369 | if (this.getZooKeeper().exists(zkPath, false) != null) { 370 | String valueString = this.gson.toJson(task); 371 | this.getZooKeeper().setData(zkPath, valueString.getBytes(), -1); 372 | } 373 | } catch (Exception e) { 374 | LOG.error(e.getMessage(), e); 375 | } 376 | } 377 | 378 | @Override 379 | public void addTask(String name) throws Exception { 380 | String zkPath = this.pathTask; 381 | if (this.getZooKeeper().exists(zkPath, false) == null) { 382 | this.getZooKeeper().create(zkPath, null, this.zkManager.getAcl(), CreateMode.PERSISTENT); 383 | } 384 | if (this.getZooKeeper().exists(zkPath + "/" + name, false) == null) { 385 | this.getZooKeeper().create(zkPath + "/" + name, null, this.zkManager.getAcl(), CreateMode.PERSISTENT); 386 | } 387 | } 388 | 389 | @Override 390 | public void deleteTask(String taskName) throws Exception { 391 | String taskOwnerPath = this.pathTask + "/" + taskName; 392 | 393 | if (this.getZooKeeper().exists(taskOwnerPath, false) != null) { 394 | ZKTools.deleteTree(this.getZooKeeper(), taskOwnerPath); 395 | } 396 | } 397 | 398 | @Override 399 | public void deleteTaskOwner(String taskName, String uuid) throws Exception { 400 | String taskOwnerPath = this.pathTask + "/" + taskName + "/" + uuid; 401 | 402 | if (this.getZooKeeper().exists(taskOwnerPath, false) != null) { 403 | ZKTools.deleteTree(this.getZooKeeper(), taskOwnerPath); 404 | } 405 | } 406 | 407 | private static class TimestampTypeAdapter implements JsonSerializer, JsonDeserializer { 408 | public JsonElement serialize(Timestamp src, Type arg1, JsonSerializationContext arg2) { 409 | DateFormat format = new SimpleDateFormat(ScheduleUtil.DATA_FORMAT_YYYYMMDDHHMMSS); 410 | String dateFormatAsString = format.format(new Date(src.getTime())); 411 | return new JsonPrimitive(dateFormatAsString); 412 | } 413 | 414 | public Timestamp deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) 415 | throws JsonParseException { 416 | if (!(json instanceof JsonPrimitive)) { 417 | throw new JsonParseException("The date should be a string value"); 418 | } 419 | 420 | try { 421 | DateFormat format = new SimpleDateFormat(ScheduleUtil.DATA_FORMAT_YYYYMMDDHHMMSS); 422 | Date date = (Date) format.parse(json.getAsString()); 423 | return new Timestamp(date.getTime()); 424 | } catch (Exception e) { 425 | throw new JsonParseException(e); 426 | } 427 | } 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /src/cn/uncode/schedule/zk/ScheduleServer.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.zk; 2 | 3 | import java.sql.Timestamp; 4 | import java.util.UUID; 5 | 6 | import cn.uncode.schedule.util.ScheduleUtil; 7 | 8 | /** 9 | * 调度服务器信息定义 10 | * 11 | * @author juny.ye 12 | * 13 | */ 14 | public class ScheduleServer { 15 | /** 16 | * 全局唯一编号 17 | */ 18 | private String uuid; 19 | 20 | private String ownSign; 21 | 22 | /** 23 | * 机器IP地址 24 | */ 25 | private String ip; 26 | 27 | /** 28 | * 机器名称 29 | */ 30 | private String hostName; 31 | 32 | /** 33 | * 服务开始时间 34 | */ 35 | private Timestamp registedTime; 36 | 37 | /** 38 | * 最后一次心跳通知时间 39 | */ 40 | private Timestamp heartBeatTime; 41 | 42 | /** 43 | * 最后一次取数据时间 44 | */ 45 | private Timestamp lastFetchDataTime; 46 | 47 | /** 48 | * 处理描述信息,例如读取的任务数量,处理成功的任务数量,处理失败的数量,处理耗时 49 | * FetchDataCount=4430,FetcheDataNum=438570 50 | * ,DealDataSucess=438570,DealDataFail=0,DealSpendTime=651066 51 | */ 52 | private String dealInfoDesc; 53 | 54 | private String nextRunStartTime; 55 | 56 | private String nextRunEndTime; 57 | /** 58 | * 配置中心的当前时间 59 | */ 60 | private Timestamp centerServerTime; 61 | 62 | /** 63 | * 数据版本号 64 | */ 65 | private long version; 66 | 67 | private boolean isRegisted; 68 | 69 | public ScheduleServer() { 70 | 71 | } 72 | 73 | public static ScheduleServer createScheduleServer(String aOwnSign) { 74 | long currentTime = System.currentTimeMillis(); 75 | ScheduleServer result = new ScheduleServer(); 76 | result.ownSign = aOwnSign; 77 | result.ip = ScheduleUtil.getLocalIP(); 78 | result.hostName = ScheduleUtil.getLocalHostName(); 79 | result.registedTime = new Timestamp(currentTime); 80 | result.heartBeatTime = null; 81 | result.dealInfoDesc = "调度初始化"; 82 | result.version = 0; 83 | result.uuid = result.ip + "$" + (UUID.randomUUID().toString().replaceAll("-", "").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 | public Timestamp getRegistedTime() { 104 | return registedTime; 105 | } 106 | 107 | public void setRegistedTime(Timestamp registedTime) { 108 | this.registedTime = registedTime; 109 | } 110 | 111 | public Timestamp getHeartBeatTime() { 112 | return heartBeatTime; 113 | } 114 | 115 | public void setHeartBeatTime(Timestamp heartBeatTime) { 116 | this.heartBeatTime = heartBeatTime; 117 | } 118 | 119 | public Timestamp getLastFetchDataTime() { 120 | return lastFetchDataTime; 121 | } 122 | 123 | public void setLastFetchDataTime(Timestamp lastFetchDataTime) { 124 | this.lastFetchDataTime = lastFetchDataTime; 125 | } 126 | 127 | public String getDealInfoDesc() { 128 | return dealInfoDesc; 129 | } 130 | 131 | public void setDealInfoDesc(String dealInfoDesc) { 132 | this.dealInfoDesc = dealInfoDesc; 133 | } 134 | 135 | public String getIp() { 136 | return ip; 137 | } 138 | 139 | public void setIp(String ip) { 140 | this.ip = ip; 141 | } 142 | 143 | public String getHostName() { 144 | return hostName; 145 | } 146 | 147 | public void setHostName(String hostName) { 148 | this.hostName = hostName; 149 | } 150 | 151 | public Timestamp getCenterServerTime() { 152 | return centerServerTime; 153 | } 154 | 155 | public void setCenterServerTime(Timestamp centerServerTime) { 156 | this.centerServerTime = centerServerTime; 157 | } 158 | 159 | public String getNextRunStartTime() { 160 | return nextRunStartTime; 161 | } 162 | 163 | public void setNextRunStartTime(String nextRunStartTime) { 164 | this.nextRunStartTime = nextRunStartTime; 165 | } 166 | 167 | public String getNextRunEndTime() { 168 | return nextRunEndTime; 169 | } 170 | 171 | public void setNextRunEndTime(String nextRunEndTime) { 172 | this.nextRunEndTime = nextRunEndTime; 173 | } 174 | 175 | public String getOwnSign() { 176 | return ownSign; 177 | } 178 | 179 | public void setOwnSign(String ownSign) { 180 | this.ownSign = ownSign; 181 | } 182 | 183 | public void setRegisted(boolean isRegisted) { 184 | this.isRegisted = isRegisted; 185 | } 186 | 187 | public boolean isRegisted() { 188 | return isRegisted; 189 | } 190 | 191 | } -------------------------------------------------------------------------------- /src/cn/uncode/schedule/zk/ScheduleTask.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.zk; 2 | 3 | import java.sql.Timestamp; 4 | 5 | /** 6 | * Task信息定义 7 | * 8 | * @author wjw465150@gmail.com 9 | * 10 | */ 11 | public class ScheduleTask { 12 | private String name; 13 | private String uuid; 14 | private String desc; 15 | private Timestamp lastfireTime; 16 | 17 | public ScheduleTask(String name, String uuid, String desc, Timestamp lastfireTime) { 18 | super(); 19 | this.name = name; 20 | this.uuid = uuid; 21 | this.desc = desc; 22 | this.lastfireTime = lastfireTime; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public void setName(String name) { 30 | this.name = name; 31 | } 32 | 33 | public String getUuid() { 34 | return uuid; 35 | } 36 | 37 | public void setUuid(String uuid) { 38 | this.uuid = uuid; 39 | } 40 | 41 | public String getDesc() { 42 | return desc; 43 | } 44 | 45 | public void setDesc(String desc) { 46 | this.desc = desc; 47 | } 48 | 49 | public Timestamp getLastfireTime() { 50 | return lastfireTime; 51 | } 52 | 53 | public void setLastfireTime(Timestamp lastfireTime) { 54 | this.lastfireTime = lastfireTime; 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | StringBuilder builder = new StringBuilder(); 60 | builder.append("ScheduleTask [name="); 61 | builder.append(name); 62 | builder.append(", uuid="); 63 | builder.append(uuid); 64 | builder.append(", desc="); 65 | builder.append(desc); 66 | builder.append(", lastfireTime="); 67 | builder.append(lastfireTime); 68 | builder.append("]"); 69 | return builder.toString(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/cn/uncode/schedule/zk/Version.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.zk; 2 | 3 | /** 4 | * 5 | * @author juny.ye 6 | * 7 | */ 8 | public class Version { 9 | 10 | public final static String version = "uncode-schedule-1.0.0"; 11 | 12 | public static String getVersion() { 13 | return version; 14 | } 15 | 16 | public static boolean isCompatible(String dataVersion) { 17 | if (version.compareTo(dataVersion) >= 0) { 18 | return true; 19 | } else { 20 | return false; 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/cn/uncode/schedule/zk/ZKManager.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.zk; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Properties; 6 | import java.util.concurrent.CountDownLatch; 7 | 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.apache.zookeeper.CreateMode; 10 | import org.apache.zookeeper.WatchedEvent; 11 | import org.apache.zookeeper.Watcher; 12 | import org.apache.zookeeper.Watcher.Event.KeeperState; 13 | import org.apache.zookeeper.ZooDefs; 14 | import org.apache.zookeeper.ZooDefs.Ids; 15 | import org.apache.zookeeper.ZooKeeper; 16 | import org.apache.zookeeper.ZooKeeper.States; 17 | import org.apache.zookeeper.data.ACL; 18 | import org.apache.zookeeper.data.Id; 19 | import org.apache.zookeeper.server.auth.DigestAuthenticationProvider; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | /** 24 | * 25 | * @author juny.ye 26 | * 27 | */ 28 | public class ZKManager { 29 | private static transient Logger LOG = LoggerFactory.getLogger(ZKManager.class); 30 | 31 | private ZooKeeper zk; 32 | private List acl = new ArrayList(); 33 | private Properties properties; 34 | private boolean isCheckParentPath = true; 35 | 36 | public enum keys { 37 | zkConnectString, rootPath, userName, password, zkSessionTimeout, autoRegisterTask 38 | } 39 | 40 | public ZKManager(Properties aProperties) throws Exception { 41 | this.properties = aProperties; 42 | this.connect(); 43 | } 44 | 45 | /** 46 | * 重连zookeeper 47 | * 48 | * @throws Exception 49 | */ 50 | public synchronized void reConnection() throws Exception { 51 | if (this.zk != null) { 52 | this.zk.close(); 53 | this.zk = null; 54 | this.connect(); 55 | } 56 | } 57 | 58 | private void connect() throws Exception { 59 | CountDownLatch connectionLatch = new CountDownLatch(1); 60 | createZookeeper(connectionLatch); 61 | connectionLatch.await(); 62 | } 63 | 64 | private void createZookeeper(final CountDownLatch connectionLatch) throws Exception { 65 | zk = new ZooKeeper(this.properties.getProperty(keys.zkConnectString 66 | .toString()), Integer.parseInt(this.properties 67 | .getProperty(keys.zkSessionTimeout.toString())), 68 | new Watcher() { 69 | public void process(WatchedEvent event) { 70 | sessionEvent(connectionLatch, event); 71 | } 72 | }); 73 | String authString = this.properties.getProperty(keys.userName.toString()) 74 | + ":" + this.properties.getProperty(keys.password.toString()); 75 | zk.addAuthInfo("digest", authString.getBytes()); 76 | acl.clear(); 77 | acl.add(new ACL(ZooDefs.Perms.ALL, new Id("digest", 78 | DigestAuthenticationProvider.generateDigest(authString)))); 79 | acl.add(new ACL(ZooDefs.Perms.READ, Ids.ANYONE_ID_UNSAFE)); 80 | } 81 | 82 | private void sessionEvent(CountDownLatch connectionLatch, WatchedEvent event) { 83 | if (event.getState() == KeeperState.SyncConnected) { 84 | LOG.info("收到ZK连接成功事件!"); 85 | connectionLatch.countDown(); 86 | } else if (event.getState() == KeeperState.Expired) { 87 | LOG.error("会话超时,等待重新建立ZK连接..."); 88 | try { 89 | reConnection(); 90 | } catch (Exception e) { 91 | LOG.error(e.getMessage(), e); 92 | } 93 | } // Disconnected:Zookeeper会自动处理Disconnected状态重连 94 | } 95 | 96 | public void close() throws InterruptedException { 97 | LOG.info("关闭zookeeper连接"); 98 | this.zk.close(); 99 | } 100 | 101 | public static Properties createProperties() { 102 | Properties result = new Properties(); 103 | result.setProperty(keys.zkConnectString.toString(), "localhost:2181"); 104 | result.setProperty(keys.rootPath.toString(), "/schedule/dev"); 105 | result.setProperty(keys.userName.toString(), "ScheduleAdmin"); 106 | result.setProperty(keys.password.toString(), "123456"); 107 | result.setProperty(keys.zkSessionTimeout.toString(), "60000"); 108 | result.setProperty(keys.autoRegisterTask.toString(), "true"); 109 | 110 | return result; 111 | } 112 | 113 | public String getRootPath() { 114 | return this.properties.getProperty(keys.rootPath.toString()); 115 | } 116 | 117 | public String getConnectStr() { 118 | return this.properties.getProperty(keys.zkConnectString.toString()); 119 | } 120 | 121 | public boolean isAutoRegisterTask() { 122 | String autoRegisterTask = this.properties.getProperty(keys.autoRegisterTask.toString()); 123 | if (StringUtils.isNotEmpty(autoRegisterTask)) { 124 | return Boolean.valueOf(autoRegisterTask); 125 | } 126 | return true; 127 | } 128 | 129 | public boolean isZookeeperConnected() throws Exception { 130 | return zk != null && zk.getState() == States.CONNECTED; 131 | } 132 | 133 | public void initial() throws Exception { 134 | //当zk状态正常后才能调用 135 | if (zk.exists(this.getRootPath(), false) == null) { 136 | ZKTools.createPath(zk, this.getRootPath(), CreateMode.PERSISTENT, acl); 137 | if (isCheckParentPath == true) { 138 | checkParent(zk, this.getRootPath()); 139 | } 140 | //设置版本信息 141 | zk.setData(this.getRootPath(), Version.getVersion().getBytes(), -1); 142 | } else { 143 | //先校验父亲节点,本身是否已经是schedule的目录 144 | if (isCheckParentPath == true) { 145 | checkParent(zk, this.getRootPath()); 146 | } 147 | byte[] value = zk.getData(this.getRootPath(), false, null); 148 | if (value == null) { 149 | zk.setData(this.getRootPath(), Version.getVersion().getBytes(), -1); 150 | } else { 151 | String dataVersion = new String(value); 152 | if (Version.isCompatible(dataVersion) == false) { 153 | throw new Exception("Schedule程序版本[ " + Version.getVersion() + "],不兼容Zookeeper中的数据版本 [" + dataVersion + "]"); 154 | } 155 | LOG.info("当前的程序版本[" + Version.getVersion() + "],数据版本[" + dataVersion + "]"); 156 | } 157 | } 158 | } 159 | 160 | public static void checkParent(ZooKeeper zk, String path) throws Exception { 161 | String[] list = path.split("/"); 162 | String zkPath = ""; 163 | for (int i = 0; i < list.length - 1; i++) { 164 | String str = list[i]; 165 | if (str.equals("") == false) { 166 | zkPath = zkPath + "/" + str; 167 | if (zk.exists(zkPath, false) != null) { 168 | byte[] value = zk.getData(zkPath, false, null); 169 | if (value != null) { 170 | String tmpVersion = new String(value); 171 | if (tmpVersion.indexOf("uncode-schedule-") >= 0) { 172 | throw new Exception("\"" + zkPath + "\" is already a schedule instance's root directory, its any subdirectory cannot as the root directory of others"); 173 | } 174 | } 175 | } 176 | } 177 | } 178 | } 179 | 180 | public List getAcl() { 181 | return acl; 182 | } 183 | 184 | public ZooKeeper getZooKeeper() throws Exception { 185 | if (this.isZookeeperConnected() == false) { 186 | reConnection(); 187 | } 188 | return this.zk; 189 | } 190 | 191 | } -------------------------------------------------------------------------------- /src/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.Deque; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | import org.apache.zookeeper.CreateMode; 11 | import org.apache.zookeeper.KeeperException; 12 | import org.apache.zookeeper.ZooKeeper; 13 | import org.apache.zookeeper.data.ACL; 14 | import org.apache.zookeeper.data.Stat; 15 | 16 | /** 17 | * zk工具类 18 | * 19 | * @author juny.ye 20 | * 21 | */ 22 | public class ZKTools { 23 | public static void createPath(ZooKeeper zk, String path, CreateMode createMode, List acl) throws Exception { 24 | String[] list = path.split("/"); 25 | String zkPath = ""; 26 | for (String str : list) { 27 | if (str.equals("") == false) { 28 | zkPath = zkPath + "/" + str; 29 | if (zk.exists(zkPath, false) == null) { 30 | zk.create(zkPath, null, acl, createMode); 31 | } 32 | } 33 | } 34 | } 35 | 36 | public static void printTree(ZooKeeper zk, String path, Writer writer, String lineSplitChar) throws Exception { 37 | String[] list = getSortedTree(zk, path); 38 | Stat stat = new Stat(); 39 | for (String name : list) { 40 | byte[] value = zk.getData(name, false, stat); 41 | if (value == null) { 42 | writer.write(name + lineSplitChar); 43 | } else { 44 | writer.write(name + "[v." + stat.getVersion() + "]" + "[" + new String(value) + "]" + lineSplitChar); 45 | } 46 | } 47 | } 48 | 49 | //返回排序好的Path数组 50 | public static String[] getSortedTree(ZooKeeper zk, String path) throws Exception { 51 | if (zk.exists(path, false) == null) { 52 | return new String[0]; 53 | } 54 | 55 | List dealList = new ArrayList(); 56 | dealList.add(path); 57 | 58 | int index = 0; 59 | while (index < dealList.size()) { 60 | String tempPath = dealList.get(index); 61 | List children = zk.getChildren(tempPath, false); 62 | if (tempPath.equalsIgnoreCase("/") == false) { 63 | tempPath = tempPath + "/"; 64 | } 65 | Collections.sort(children); 66 | for (int i = children.size() - 1; i >= 0; i--) { 67 | dealList.add(index + 1, tempPath + children.get(i)); 68 | } 69 | index++; 70 | } 71 | return (String[]) dealList.toArray(new String[0]); 72 | } 73 | 74 | public static void deleteTree(ZooKeeper zk, String path) throws Exception { 75 | // String[] list = getSortedTree(zk, path); 76 | // for (int i = list.length - 1; i >= 0; i--) { 77 | // zk.delete(list[i], -1); 78 | // } 79 | 80 | List tree = listSubTreeBFS(zk, path); 81 | for (int i = tree.size() - 1; i >= 0; i--) { //@wjw_note: 必须倒序! 82 | zk.delete(tree.get(i), -1); 83 | } 84 | } 85 | 86 | /** 87 | * BFS Traversal of the system under pathRoot, with the entries in the list, 88 | * in the same order as that of the traversal. 89 | *

90 | * Important: This is not an atomic snapshot of the tree ever, 91 | * but the state as it exists across multiple RPCs from zkClient to the 92 | * ensemble. For practical purposes, it is suggested to bring the clients to 93 | * the ensemble down (i.e. prevent writes to pathRoot) to 'simulate' a 94 | * snapshot behavior. 95 | * 96 | * @param zk 97 | * @param pathRoot 98 | * The znode path, for which the entire subtree needs to be listed. 99 | * @throws Exception 100 | */ 101 | public static List listSubTreeBFS(ZooKeeper zk, final String pathRoot) throws Exception { 102 | Deque queue = new LinkedList(); 103 | List tree = new ArrayList(); 104 | queue.add(pathRoot); 105 | tree.add(pathRoot); 106 | 107 | while (true) { 108 | String node = queue.pollFirst(); 109 | if (node == null) { 110 | break; 111 | } 112 | 113 | List children = zk.getChildren(node, false); 114 | for (final String child : children) { 115 | final String childPath = node + "/" + child; 116 | queue.add(childPath); 117 | tree.add(childPath); 118 | } 119 | } 120 | return tree; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /test/cn/uncode/schedule/test/SimpeTestNode_1.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.test; 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(30 * 1000); 13 | 14 | context.stop(); 15 | context.close(); 16 | 17 | System.exit(0); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /test/cn/uncode/schedule/test/SimpeTestNode_2.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.test; 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("applicationContext2.xml"); 12 | 13 | Thread.sleep(30 * 1000); 14 | 15 | context.stop(); 16 | context.close(); 17 | 18 | System.exit(0); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /test/cn/uncode/schedule/test/SimpleTask.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.test; 2 | 3 | import org.springframework.scheduling.annotation.Scheduled; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * @author juny.ye 8 | */ 9 | @Component 10 | public class SimpleTask { 11 | 12 | private static int i = 0; 13 | 14 | @Scheduled(fixedDelay = 1000) 15 | public void print() { 16 | System.out.println("===========start!========="); 17 | System.out.println("I:" + i); 18 | i++; 19 | System.out.println("=========== end !========="); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /test/cn/uncode/schedule/test/ZookeeperTest.java: -------------------------------------------------------------------------------- 1 | package cn.uncode.schedule.test; 2 | 3 | import java.io.StringWriter; 4 | 5 | import org.apache.zookeeper.WatchedEvent; 6 | import org.apache.zookeeper.Watcher; 7 | import org.apache.zookeeper.ZooKeeper; 8 | import org.junit.Test; 9 | 10 | import cn.uncode.schedule.zk.ZKTools; 11 | 12 | /** 13 | * @author juny.ye 14 | */ 15 | public class ZookeeperTest { 16 | private PrintWatcher watcher = new PrintWatcher(); 17 | 18 | @Test 19 | public void testCloseStatus() throws Exception { 20 | ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, watcher); 21 | int i = 1; 22 | while (true) { 23 | try { 24 | StringWriter writer = new StringWriter(); 25 | ZKTools.printTree(zk, "/schedule/dev", writer, ""); 26 | System.out 27 | .println(i++ + "----" + writer.getBuffer().toString()); 28 | Thread.sleep(2000); 29 | } catch (Exception e) { 30 | System.out.println(e.getMessage()); 31 | } 32 | } 33 | } 34 | 35 | @Test 36 | public void testPrint() throws Exception { 37 | ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, watcher); 38 | StringWriter writer = new StringWriter(); 39 | ZKTools.printTree(zk, "/", writer, "\n"); 40 | System.out.println(writer.getBuffer().toString()); 41 | 42 | zk.close(); 43 | } 44 | 45 | @Test 46 | public void deletePath() throws Exception { 47 | ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, watcher); 48 | zk.addAuthInfo("digest", "ScheduleAdmin:123456".getBytes()); 49 | 50 | ZKTools.deleteTree(zk, "/schedule/dev"); 51 | 52 | Thread.sleep(10 * 1000); 53 | zk.close(); 54 | } 55 | 56 | private static class PrintWatcher implements Watcher { 57 | 58 | @Override 59 | public void process(WatchedEvent event) { 60 | System.out.println(event); 61 | } 62 | } 63 | } --------------------------------------------------------------------------------