, 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 extends JobDetail> 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 | }
--------------------------------------------------------------------------------