├── .gitignore ├── src ├── test │ ├── resources │ │ └── log4j.properties │ └── java │ │ └── com │ │ └── api6 │ │ └── zkclient │ │ ├── util │ │ ├── TestSystem.java │ │ ├── TestUtil.java │ │ └── ZKServer.java │ │ ├── ZKDistributedQueueTest.java │ │ ├── ZKDistributedLockTest.java │ │ ├── ZKDistributedDelayLockTest.java │ │ ├── ZKHALockTest.java │ │ ├── ZKClientSessionTimeoutTest.java │ │ ├── ZKLeaderSelectorTest.java │ │ └── ZKLeaderDelaySelectorTest.java └── main │ └── java │ └── com │ └── api6 │ └── zkclient │ ├── serializer │ ├── ZKSerializer.java │ ├── BytesSerializer.java │ └── SerializableSerializer.java │ ├── leader │ ├── ZKLeaderSelectorListener.java │ ├── LeaderSelector.java │ ├── ZKLeaderSelector.java │ └── ZKLeaderDelySelector.java │ ├── exception │ ├── ZKInterruptedException.java │ ├── ZKNoNodeException.java │ ├── ZKTimeoutException.java │ ├── ZKNodeExistsException.java │ └── ZKException.java │ ├── listener │ ├── ZKListener.java │ ├── ZKStateListener.java │ ├── ZKChildCountListener.java │ ├── ZKChildDataListener.java │ └── ZKNodeListener.java │ ├── lock │ ├── ZKLock.java │ ├── ZKHALock.java │ ├── ZKDistributedLock.java │ └── ZKDistributedDelayLock.java │ ├── event │ ├── ZKEvent.java │ ├── ZKEventLock.java │ └── ZKEventThreadPool.java │ ├── ZKNode.java │ ├── util │ └── ExceptionUtil.java │ ├── connection │ ├── ZKConnection.java │ └── ZKConnectionImpl.java │ ├── watcher │ ├── ZKWatcher.java │ └── ZKWatcherProcess.java │ ├── ZKClientBuilder.java │ └── queue │ └── ZKDistributedQueue.java ├── pom.xml ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | # eclipse ignore 3 | .metadata/ 4 | .settings/ 5 | .project 6 | .classpath -------------------------------------------------------------------------------- /src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=info, console 2 | log4j.logger.org.apache.zookeeper=error 3 | 4 | log4j.appender.console=org.apache.log4j.ConsoleAppender 5 | log4j.appender.console.target=System.out 6 | log4j.appender.console.layout=org.apache.log4j.PatternLayout 7 | log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %5p [%t] (%F:%L) - %m%n 8 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/serializer/ZKSerializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.serializer; 17 | 18 | public interface ZKSerializer { 19 | 20 | public byte[] serialize(Object data); 21 | 22 | public Object deserialize(byte[] bytes); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/leader/ZKLeaderSelectorListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.leader; 17 | 18 | import com.api6.zkclient.ZKClient; 19 | 20 | public interface ZKLeaderSelectorListener { 21 | void takeLeadership(ZKClient client, LeaderSelector selector); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/exception/ZKInterruptedException.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.exception; 17 | 18 | public class ZKInterruptedException extends ZKException { 19 | private static final long serialVersionUID = 1L; 20 | 21 | public ZKInterruptedException(InterruptedException e) { 22 | super(e); 23 | Thread.currentThread().interrupt(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/listener/ZKListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.listener; 17 | 18 | import org.apache.zookeeper.Watcher.Event.EventType; 19 | 20 | /** 21 | * 监听基类 22 | * @author: zhaojie/zh_jie@163.com.com 23 | */ 24 | public interface ZKListener { 25 | void handle(String path,EventType eventType, Object data) throws Exception; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/leader/LeaderSelector.java: -------------------------------------------------------------------------------- 1 | package com.api6.zkclient.leader; 2 | 3 | import java.util.List; 4 | 5 | public interface LeaderSelector { 6 | /** 7 | * 启动参与选举Leader 8 | * @return void 9 | */ 10 | void start(); 11 | 12 | /** 13 | * 重新添加当前线程到选举队列 14 | * @return void 15 | */ 16 | void requeue(); 17 | 18 | /** 19 | * 获得Leader ID 20 | * @return 21 | * @return String 22 | */ 23 | String getLeader(); 24 | 25 | /** 26 | * 是否是Leader 27 | * @return 28 | * @return boolean 29 | */ 30 | boolean isLeader(); 31 | /** 32 | * 获得当前的所有参与者的路径名 33 | * @return 34 | * @return List 35 | */ 36 | List getParticipantNodes(); 37 | 38 | 39 | /** 40 | * 终止等待成为Leader 41 | * @return void 42 | */ 43 | void interruptLeadership(); 44 | 45 | /** 46 | * 关闭Leader选举 47 | * @return void 48 | */ 49 | void close(); 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/lock/ZKLock.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.lock; 17 | 18 | public interface ZKLock { 19 | /** 20 | * 获得锁 21 | 22 | * @return 23 | * @return boolean 成功获得锁返回true,否则返回false 24 | */ 25 | boolean lock(); 26 | 27 | /** 28 | * 释放锁 29 | * @return 30 | * @return boolean 31 | * 如果释放锁成功返回true,否则返回false 32 | */ 33 | boolean unlock(); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/event/ZKEvent.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.event; 17 | 18 | public abstract class ZKEvent { 19 | private String description; 20 | 21 | public ZKEvent(String description) { 22 | this.description = description; 23 | } 24 | 25 | public abstract void run() throws Exception; 26 | 27 | @Override 28 | public String toString() { 29 | return "ZKEvent[" + description + "]"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/serializer/BytesSerializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.serializer; 17 | 18 | /** 19 | * 对Byte数组序列化,只是简单的原样返回 20 | * @author: zhaojie/zh_jie@163.com.com 21 | */ 22 | public class BytesSerializer implements ZKSerializer { 23 | 24 | @Override 25 | public Object deserialize(byte[] bytes){ 26 | return bytes; 27 | } 28 | 29 | @Override 30 | public byte[] serialize(Object bytes){ 31 | return (byte[]) bytes; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/exception/ZKNoNodeException.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.exception; 17 | 18 | import org.apache.zookeeper.KeeperException; 19 | 20 | public class ZKNoNodeException extends ZKException { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | public ZKNoNodeException() { 25 | super(); 26 | } 27 | 28 | public ZKNoNodeException(KeeperException cause) { 29 | super(cause); 30 | } 31 | 32 | public ZKNoNodeException(String message, KeeperException cause) { 33 | super(message, cause); 34 | } 35 | 36 | public ZKNoNodeException(String message) { 37 | super(message); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/exception/ZKTimeoutException.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.exception; 17 | 18 | import org.apache.zookeeper.KeeperException; 19 | 20 | public class ZKTimeoutException extends ZKException { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | public ZKTimeoutException() { 25 | super(); 26 | } 27 | 28 | public ZKTimeoutException(KeeperException cause) { 29 | super(cause); 30 | } 31 | 32 | public ZKTimeoutException(String message, KeeperException cause) { 33 | super(message, cause); 34 | } 35 | 36 | public ZKTimeoutException(String message) { 37 | super(message); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/exception/ZKNodeExistsException.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.exception; 17 | 18 | import org.apache.zookeeper.KeeperException; 19 | 20 | public class ZKNodeExistsException extends ZKException { 21 | private static final long serialVersionUID = 1L; 22 | 23 | public ZKNodeExistsException() { 24 | super(); 25 | } 26 | 27 | public ZKNodeExistsException(KeeperException cause) { 28 | super(cause); 29 | } 30 | 31 | public ZKNodeExistsException(String message, KeeperException cause) { 32 | super(message, cause); 33 | } 34 | 35 | public ZKNodeExistsException(String message) { 36 | super(message); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/exception/ZKException.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.exception; 17 | 18 | import org.apache.zookeeper.KeeperException; 19 | 20 | public class ZKException extends RuntimeException { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | public ZKException() { 25 | super(); 26 | } 27 | 28 | public ZKException(String message, Throwable cause) { 29 | super(message, cause); 30 | } 31 | 32 | public ZKException(String message) { 33 | super(message); 34 | } 35 | 36 | public ZKException(Throwable cause) { 37 | super(cause); 38 | } 39 | 40 | public static ZKException create(KeeperException e) { 41 | switch (e.code()) { 42 | case NONODE: 43 | return new ZKNoNodeException(e); 44 | case NODEEXISTS: 45 | return new ZKNodeExistsException(e); 46 | default: 47 | return new ZKException(e); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/listener/ZKStateListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.listener; 17 | 18 | import org.apache.zookeeper.Watcher.Event.KeeperState; 19 | 20 | /** 21 | * ZooKeeper状态监听类 22 | * @author: zhaojie/zh_jie@163.com.com 23 | */ 24 | public interface ZKStateListener { 25 | 26 | /** 27 | * 状态改变的回调函数 28 | * @param state 29 | * @throws Exception 30 | * @return void 31 | * @author: zhaojie/zh_jie@163.com 32 | */ 33 | public void handleStateChanged(KeeperState state) throws Exception; 34 | 35 | /** 36 | * 会话创建的回调函数 37 | * @throws Exception 38 | * @return void 39 | * @author: zhaojie/zh_jie@163.com 40 | */ 41 | public void handleNewSession() throws Exception; 42 | 43 | /** 44 | * 会话出错的回调函数 45 | * @param error 46 | * @throws Exception 47 | * @return void 48 | * @author: zhaojie/zh_jie@163.com 49 | */ 50 | public void handleSessionError(final Throwable error) throws Exception; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/ZKNode.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient; 17 | 18 | import org.apache.zookeeper.CreateMode; 19 | 20 | class ZKNode { 21 | private String path; 22 | private Object data; 23 | private CreateMode createMode; 24 | 25 | public ZKNode() { 26 | } 27 | 28 | public ZKNode(String path,Object data,CreateMode createMode) { 29 | this.path = path; 30 | this.data = data; 31 | this.createMode = createMode; 32 | } 33 | 34 | public String getPath() { 35 | return path; 36 | } 37 | public void setPath(String path) { 38 | this.path = path; 39 | } 40 | public Object getData() { 41 | return data; 42 | } 43 | public void setData(Object data) { 44 | this.data = data; 45 | } 46 | public CreateMode getCreateMode() { 47 | return createMode; 48 | } 49 | public void setCreateMode(CreateMode createMode) { 50 | this.createMode = createMode; 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/util/ExceptionUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.util; 17 | 18 | import com.api6.zkclient.exception.ZKInterruptedException; 19 | 20 | public class ExceptionUtil { 21 | 22 | public static RuntimeException convertToRuntimeException(Throwable e) { 23 | if (e instanceof RuntimeException) { 24 | return (RuntimeException) e; 25 | } 26 | retainInterruptFlag(e); 27 | return new RuntimeException(e); 28 | } 29 | 30 | /** 31 | * 如果catch {@link InterruptedException}异常,重置当前线程interrupt标记 32 | * @param catchedException 33 | */ 34 | public static void retainInterruptFlag(Throwable catchedException) { 35 | if (catchedException instanceof InterruptedException) { 36 | Thread.currentThread().interrupt(); 37 | } 38 | } 39 | 40 | public static void rethrowInterruptedException(Throwable e) throws InterruptedException { 41 | if (e instanceof InterruptedException) { 42 | throw (InterruptedException) e; 43 | } 44 | if (e instanceof ZKInterruptedException) { 45 | throw (ZKInterruptedException) e; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/event/ZKEventLock.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.event; 17 | 18 | import java.util.concurrent.locks.Condition; 19 | import java.util.concurrent.locks.ReentrantLock; 20 | 21 | /** 22 | * ZooKeeper事件锁 23 | * @author: zhaojie/zh_jie@163.com.com 24 | */ 25 | public class ZKEventLock extends ReentrantLock { 26 | 27 | private static final long serialVersionUID = 1L; 28 | 29 | private Condition nodeOrChildChangedCondition = newCondition(); 30 | private Condition stateChangedCondition = newCondition(); 31 | private Condition nodeEventCondition = newCondition(); 32 | 33 | 34 | /** 35 | * 条件在节点改变(节点新增、修改、删除)或者子节点数量改变时被标记。 36 | * @return 37 | * @return Condition 38 | * @author: zhaojie/zh_jie@163.com 39 | */ 40 | public Condition getNodeOrChildChangedCondition() { 41 | return nodeOrChildChangedCondition; 42 | } 43 | 44 | 45 | /** 46 | * 条件在ZooKeeper状态发生改变时被标记,包括,连接成功,断开连接,会话失效等。 47 | * @return 48 | * @return Condition 49 | * @author: zhaojie/zh_jie@163.com 50 | * @version: 2016年5月26日 上午11:04:20 51 | */ 52 | public Condition getStateChangedCondition() { 53 | return stateChangedCondition; 54 | } 55 | 56 | /** 57 | * 该条件在节点发生变化时会被标记 58 | * @return 59 | * @return Condition 60 | * @author: zhaojie/zh_jie@163.com 61 | * @version: 2016年5月26日 上午11:03:19 62 | */ 63 | public Condition getNodeEventCondition() { 64 | return nodeEventCondition; 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/listener/ZKChildCountListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.listener; 17 | 18 | import java.util.List; 19 | 20 | import org.apache.zookeeper.Watcher.Event.EventType; 21 | 22 | /** 23 | * 监听子节点数量变化,不监听子节点内容的变化 24 | * @author: zhaojie/zh_jie@163.com.com 25 | */ 26 | public abstract class ZKChildCountListener implements ZKListener { 27 | 28 | @Override 29 | public void handle(String path, EventType eventType, Object data) throws Exception { 30 | List children = null; 31 | if(data!=null){ 32 | children = (List)data; 33 | } 34 | if(eventType == eventType.None){ 35 | handleSessionExpired(path,children); 36 | }else{ 37 | 38 | handleChildCountChanged(path,children); 39 | } 40 | 41 | } 42 | 43 | /** 44 | * 子节点数量变化的回调函数 45 | * @param path 46 | * @param children 47 | * @throws Exception 48 | * @return void 49 | * @author: zhaojie/zh_jie@163.com 50 | */ 51 | public abstract void handleChildCountChanged(String path, List children) throws Exception; 52 | 53 | /** 54 | * 会话失效并重新连接后会回调此方法。 55 | * 因为在会话失效时,服务端会注销Wather监听, 56 | * 所以在会话失效后到连接成功这段时间内,数据可能发生变化,会触发此方法 57 | * @param path 58 | * @throws Exception 59 | * @return void 60 | * @author: zhaojie/zh_jie@163.com 61 | */ 62 | public abstract void handleSessionExpired(String path, List children) throws Exception; 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/com/api6/zkclient/util/TestSystem.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.util; 17 | 18 | import java.util.List; 19 | 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import com.api6.zkclient.ZKClient; 24 | 25 | public class TestSystem { 26 | private static Logger LOG = LoggerFactory.getLogger(TestSystem.class); 27 | private static TestSystem testSystem; 28 | private final ZKServer zKserver; 29 | private int port = 1009; 30 | private String serverAddress = "localhost"; 31 | 32 | private TestSystem() { 33 | zKserver = TestUtil.startServer(serverAddress, port); 34 | } 35 | 36 | 37 | public static TestSystem getInstance(){ 38 | synchronized (TestSystem.class) { 39 | if(testSystem == null) { 40 | testSystem = new TestSystem(); 41 | Runtime.getRuntime().addShutdownHook(new Thread() { 42 | @Override 43 | public void run() { 44 | LOG.info("shutting zkserver down"); 45 | getInstance().getZKserver().shutdown(); 46 | } 47 | }); 48 | } 49 | } 50 | 51 | return testSystem; 52 | } 53 | 54 | public void cleanup(ZKClient zKClient) { 55 | LOG.info("unlisten all listeners"); 56 | zKClient.unlistenAll(); 57 | 58 | LOG.info("cleanup zkserver namespace"); 59 | List children = zKClient.getChildren("/"); 60 | for (String child : children) { 61 | if (!child.equals("zookeeper")) { 62 | zKClient.deleteRecursive("/" + child); 63 | } 64 | } 65 | zKClient.close(); 66 | } 67 | 68 | public ZKServer getZKserver() { 69 | return zKserver; 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/listener/ZKChildDataListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.listener; 17 | 18 | import java.util.List; 19 | 20 | import org.apache.zookeeper.Watcher.Event.EventType; 21 | 22 | /** 23 | * 子节点监听器,监听子节点数量和子节点内容的变化 24 | * @author: zhaojie/zh_jie@163.com.com 25 | */ 26 | public abstract class ZKChildDataListener implements ZKListener { 27 | 28 | @Override 29 | public void handle(String path, EventType eventType, Object data) throws Exception { 30 | //子节点个数变化 31 | if (eventType == EventType.NodeChildrenChanged 32 | || eventType == EventType.NodeCreated 33 | || eventType == EventType.NodeDeleted) { 34 | handleChildCountChanged(path,(List)data); 35 | } 36 | //子节点数据变化 37 | if(eventType == EventType.NodeDataChanged){ 38 | handleChildDataChanged(path,data); 39 | } 40 | //Session失效 41 | if(eventType == eventType.None){ 42 | handleSessionExpired(path,data); 43 | } 44 | } 45 | 46 | /** 47 | * 子节点数量变化的回调函数 48 | * @param path 49 | * @param children 50 | * @throws Exception 51 | * @return void 52 | * @author: zhaojie/zh_jie@163.com 53 | */ 54 | public abstract void handleChildCountChanged(String path, List children) throws Exception; 55 | /** 56 | * 子节点内容变化回调函数 57 | * @param path 58 | * @param object 59 | * @throws Exception 60 | * @return void 61 | * @author: zhaojie/zh_jie@163.com 62 | */ 63 | public abstract void handleChildDataChanged(String path, Object data) throws Exception; 64 | /** 65 | * 会话失效并重新连接后会回调此方法。 66 | * 因为在会话失效时,服务端会注销Wather监听, 67 | * 所以在会话失效后到连接成功这段时间内,数据可能发生变化,会触发此方法 68 | * @param path 69 | * @throws Exception 70 | * @return void 71 | * @author: zhaojie/zh_jie@163.com 72 | */ 73 | public abstract void handleSessionExpired(String path,Object data) throws Exception; 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/listener/ZKNodeListener.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.listener; 17 | 18 | import org.apache.zookeeper.Watcher.Event.EventType; 19 | 20 | /** 21 | * 节点变化监听,监听节点的创建,数据的修改,以及节点的删除。 22 | * @author: zhaojie/zh_jie@163.com.com 23 | */ 24 | public abstract class ZKNodeListener implements ZKListener { 25 | 26 | @Override 27 | public void handle(String path, EventType eventType, Object data) throws Exception { 28 | 29 | if (eventType == EventType.NodeCreated) { 30 | handleDataCreated(path,data); 31 | } 32 | 33 | if (eventType == EventType.NodeDataChanged) { 34 | handleDataChanged(path,data); 35 | } 36 | 37 | if(eventType == EventType.NodeDeleted ){ 38 | handleDataDeleted(path); 39 | } 40 | 41 | if(eventType == eventType.None){ 42 | handleSessionExpired(path); 43 | } 44 | } 45 | 46 | /** 47 | * 节点创建后回调此方法 48 | * @param path 49 | * @param data 50 | * @throws Exception 51 | * @return void 52 | * @author: zhaojie/zh_jie@163.com 53 | */ 54 | public abstract void handleDataCreated(String path, Object data) throws Exception; 55 | /** 56 | * 节点内容变化的回调函数 57 | * @param path 58 | * @param data 59 | * @throws Exception 60 | * @return void 61 | * @author: zhaojie/zh_jie@163.com 62 | */ 63 | public abstract void handleDataChanged(String path, Object data) throws Exception; 64 | /** 65 | * 节点删除的回调函数 66 | * @param path 67 | * @throws Exception 68 | * @return void 69 | * @author: zhaojie/zh_jie@163.com 70 | * @version: 2016年5月26日 下午4:00:37 71 | */ 72 | public abstract void handleDataDeleted(String path) throws Exception; 73 | /** 74 | * 会话失效并重新连接后会回调此方法。 75 | * 因为在会话失效时,服务端会注销Wather监听, 76 | * 所以在会话失效后到连接成功这段时间内,数据可能发生变化,会触发此方法 77 | * @param path 78 | * @throws Exception 79 | * @return void 80 | * @author: zhaojie/zh_jie@163.com 81 | */ 82 | public abstract void handleSessionExpired(String path) throws Exception; 83 | 84 | } 85 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.api6 4 | zkclient 5 | 1.0.0 6 | 7 | 8 | 9 | 1.7 10 | 3.4.8 11 | 4.12 12 | 1.7.21 13 | 1.1.3 14 | 2.5 15 | 2.0.0 16 | 17 | 18 | 19 | 20 | 21 | 22 | org.apache.maven.plugins 23 | maven-compiler-plugin 24 | 25 | ${java.version} 26 | ${java.version} 27 | 28 | 29 | 30 | 31 | org.codehaus.mojo 32 | cobertura-maven-plugin 33 | 2.7 34 | 35 | UTF-8 36 | 37 | html 38 | xml 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | junit 49 | junit 50 | ${junit.version} 51 | test 52 | 53 | 54 | commons-io 55 | commons-io 56 | ${commons-io.version} 57 | test 58 | 59 | 60 | org.assertj 61 | assertj-core 62 | ${assertj.version} 63 | test 64 | 65 | 66 | 67 | 68 | org.apache.zookeeper 69 | zookeeper 70 | ${zookeeper.version} 71 | 72 | 73 | org.slf4j 74 | slf4j-api 75 | ${slf4j.version} 76 | 77 | 78 | org.slf4j 79 | log4j-over-slf4j 80 | ${slf4j.version} 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/test/java/com/api6/zkclient/util/TestUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.util; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.util.concurrent.Callable; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | import org.apache.commons.io.FileUtils; 24 | import org.junit.rules.TemporaryFolder; 25 | 26 | import com.api6.zkclient.exception.ZKException; 27 | 28 | /** 29 | * 测试工具类 30 | * @author: zhaojie/zh_jie@163.com.com 31 | * @version: 2016年5月27日 上午10:13:46 32 | */ 33 | public class TestUtil { 34 | 35 | /** 36 | * 等待直到callable返回值等于期望值expectedValue,或者直到超时。 37 | * @param expectedValue 38 | * @param callable 39 | * @param timeUnit 40 | * @param timeout 41 | * @throws Exception 42 | * @return T 返回回调函数callable的返回值 43 | */ 44 | public static T waitUntil(T expectedValue, Callable callable, TimeUnit timeUnit, long timeout) throws Exception { 45 | long startTime = System.currentTimeMillis(); 46 | do { 47 | T actual = callable.call(); 48 | if (expectedValue.equals(actual)) { 49 | System.out.println("TestUtil.waitUntil expected"); 50 | return actual; 51 | } 52 | if (System.currentTimeMillis() > startTime + timeUnit.toMillis(timeout)) { 53 | System.out.println("TestUtil.waitUntil timeout!"); 54 | return actual; 55 | } 56 | Thread.sleep(300); 57 | } while (true); 58 | } 59 | 60 | /** 61 | * 启动一个单实例的ZooKeeper server 62 | * @param serverName 63 | * @param port 64 | * @return 65 | * @return ZKServer 66 | * @author: zhaojie/zh_jie@163.com 67 | */ 68 | public static ZKServer startServer(String serverName,int port) { 69 | String dataPath = "./target/test-classes/" + serverName + "/data"; 70 | String logPath = "./target/test-classes/" + serverName + "/log"; 71 | try { 72 | FileUtils.deleteDirectory(new File(dataPath)); 73 | FileUtils.deleteDirectory(new File(logPath)); 74 | } catch (IOException e) { 75 | throw new ZKException("start server error!",e); 76 | } 77 | return startServer(dataPath, logPath ,port); 78 | } 79 | 80 | public static ZKServer startZkServer(TemporaryFolder temporaryFolder, int port) throws IOException { 81 | File dataFolder = temporaryFolder.newFolder("data"); 82 | File logFolder = temporaryFolder.newFolder("log"); 83 | return startServer(dataFolder.getAbsolutePath(), logFolder.getAbsolutePath(),port); 84 | } 85 | 86 | private static ZKServer startServer(String dataPath,String logPath, int port){ 87 | ZKServer zkServer = new ZKServer(dataPath, logPath, port, ZKServer.DEFAULT_TICK_TIME, 100); 88 | zkServer.start(); 89 | return zkServer; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/event/ZKEventThreadPool.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.event; 17 | 18 | import java.util.concurrent.LinkedBlockingQueue; 19 | import java.util.concurrent.ThreadFactory; 20 | import java.util.concurrent.ThreadPoolExecutor; 21 | import java.util.concurrent.TimeUnit; 22 | import java.util.concurrent.atomic.AtomicInteger; 23 | 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | 27 | /** 28 | * 事件处理线程池 29 | * 阻塞队列无限制 30 | * @author: zhaojie/zh_jie@163.com.com 31 | */ 32 | public class ZKEventThreadPool { 33 | private static Logger LOG = LoggerFactory.getLogger(ZKEventThreadPool.class); 34 | 35 | private ThreadPoolExecutor pool = null; 36 | private static AtomicInteger index = new AtomicInteger(0); 37 | 38 | /** 39 | * 初始化线程池,这里采用corePoolSize=maximumPoolSize, 40 | * 并且使用LinkedBlockingQueue无限大小的阻塞队列来处理事件 41 | * ZKEventThreadPoolExecutor. 42 | * 43 | * @param poolSize 44 | */ 45 | public ZKEventThreadPool(Integer poolSize){ 46 | pool = new ThreadPoolExecutor( 47 | poolSize, //corePoolSize 核心线程池大小 48 | poolSize, //aximumPoolSize 最大线程池大小 49 | 30, //keepAliveTime 线程池中超过corePoolSize数目的空闲线程最大存活时间 50 | TimeUnit.MINUTES, //TimeUnit keepAliveTime时间单位 这里是秒 51 | new LinkedBlockingQueue(), //workQueue 阻塞队列 52 | new ZKEventThreadFactory()); //线程工厂 53 | } 54 | 55 | /** 56 | * 销毁线程池 57 | * @return void 58 | */ 59 | public void destory() { 60 | if(pool != null) { 61 | pool.shutdown(); 62 | } 63 | } 64 | 65 | /** 66 | * 处理事件 67 | * @param zkEvent 68 | * @return void 69 | * @author: zhaojie/zh_jie@163.com 70 | */ 71 | public void submit(final ZKEvent zkEvent){ 72 | pool.submit(new Runnable() { 73 | @Override 74 | public void run() { 75 | int eventId = index.incrementAndGet(); 76 | try { 77 | LOG.debug("Handling event-" + eventId + " " + zkEvent); 78 | zkEvent.run(); 79 | } catch (Exception e) { 80 | LOG.error("Error handling event [" + zkEvent+"]", e); 81 | } 82 | LOG.debug("Handled the event-" + eventId); 83 | } 84 | }); 85 | } 86 | 87 | 88 | /** 89 | * 私有内部类,线程创建工厂 90 | * @author: zhaojie/zh_jie@163.com.com 91 | * @version: 2016年5月26日 下午9:06:53 92 | */ 93 | private class ZKEventThreadFactory implements ThreadFactory { 94 | private AtomicInteger count = new AtomicInteger(0); 95 | @Override 96 | public Thread newThread(Runnable r) { 97 | Thread t = new Thread(r); 98 | String threadName = "ZkClient-EventThread-" + count.addAndGet(1); 99 | t.setName(threadName); 100 | return t; 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/serializer/SerializableSerializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.serializer; 17 | 18 | import java.io.ByteArrayInputStream; 19 | import java.io.ByteArrayOutputStream; 20 | import java.io.ObjectInputStream; 21 | import java.io.ObjectOutputStream; 22 | import java.io.Serializable; 23 | 24 | import org.apache.zookeeper.server.util.SerializeUtils; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | /** 29 | * 序列化反序列化工具类 30 | * @author: zhaojie/zh_jie@163.com.com 31 | */ 32 | public class SerializableSerializer implements ZKSerializer { 33 | 34 | Logger logger = LoggerFactory.getLogger(SerializableSerializer.class); 35 | /** 36 | * 反序列化 37 | * @param bytes 38 | * @return 39 | * @return Object 40 | */ 41 | public Object deserialize(byte[] bytes) { 42 | 43 | Object result = null; 44 | 45 | if (isEmpty(bytes)) { 46 | return null; 47 | } 48 | 49 | try { 50 | ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes); 51 | try { 52 | ObjectInputStream objectInputStream = new ObjectInputStream(byteStream); 53 | try { 54 | result = objectInputStream.readObject(); 55 | } 56 | catch (ClassNotFoundException ex) { 57 | throw new Exception("Failed to deserialize object type", ex); 58 | } 59 | } 60 | catch (Throwable ex) { 61 | throw new Exception("Failed to deserialize", ex); 62 | } 63 | } catch (Exception e) { 64 | logger.error("Failed to deserialize",e); 65 | } 66 | return result; 67 | } 68 | 69 | public boolean isEmpty(byte[] data) { 70 | return (data == null || data.length == 0); 71 | } 72 | 73 | /** 74 | * 序列化 75 | * @param object 76 | * @return 77 | * @return byte[] 78 | */ 79 | public byte[] serialize(Object object) { 80 | byte[] result = null; 81 | 82 | if (object == null) { 83 | return new byte[0]; 84 | } 85 | try { 86 | ByteArrayOutputStream byteStream = new ByteArrayOutputStream(128); 87 | try { 88 | if (!(object instanceof Serializable)) { 89 | throw new IllegalArgumentException(SerializeUtils.class.getSimpleName() + " requires a Serializable payload " + 90 | "but received an object of type [" + object.getClass().getName() + "]"); 91 | } 92 | ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream); 93 | objectOutputStream.writeObject(object); 94 | objectOutputStream.flush(); 95 | result = byteStream.toByteArray(); 96 | } 97 | catch (Throwable ex) { 98 | throw new Exception("Failed to serialize", ex); 99 | } 100 | } catch (Exception ex) { 101 | logger.error("Failed to serialize",ex); 102 | } 103 | return result; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/test/java/com/api6/zkclient/ZKDistributedQueueTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.concurrent.Callable; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | import org.apache.zookeeper.CreateMode; 26 | import org.junit.After; 27 | import org.junit.Before; 28 | import org.junit.Test; 29 | 30 | import com.api6.zkclient.queue.ZKDistributedQueue; 31 | import com.api6.zkclient.util.TestSystem; 32 | import com.api6.zkclient.util.TestUtil; 33 | import com.api6.zkclient.util.ZKServer; 34 | 35 | public class ZKDistributedQueueTest { 36 | 37 | private TestSystem testSystem = TestSystem.getInstance(); 38 | private ZKServer zkServer = null; 39 | private ZKClient zkClient = null; 40 | @Before 41 | public void before() { 42 | zkServer = testSystem.getZKserver(); 43 | zkClient = ZKClientBuilder.newZKClient() 44 | .servers("localhost:"+zkServer.getPort()) 45 | .sessionTimeout(1000) 46 | .build(); 47 | } 48 | 49 | @After 50 | public void after(){ 51 | testSystem.cleanup(zkClient); 52 | } 53 | 54 | /** 55 | * 测试分布式队列 56 | * @throws Exception 57 | * @return void 58 | */ 59 | @Test 60 | public void testDistributedQueue() throws Exception{ 61 | final String rootPath = "/zk/queue"; 62 | zkClient.createRecursive(rootPath, null, CreateMode.PERSISTENT); 63 | 64 | final List list1 = new ArrayList(); 65 | final List list2 = new ArrayList(); 66 | for(int i=0;i<21;i++){ 67 | Thread thread1 = new Thread(new Runnable() { 68 | public void run() { 69 | ZKDistributedQueue queue = new ZKDistributedQueue(zkClient, rootPath); 70 | queue.offer(Thread.currentThread().getName()); 71 | list1.add(Thread.currentThread().getName()); 72 | } 73 | }); 74 | thread1.start(); 75 | } 76 | 77 | //等待事件到达 78 | int size1 = TestUtil.waitUntil(21, new Callable() { 79 | @Override 80 | public Integer call() throws Exception { 81 | return list1.size(); 82 | } 83 | 84 | }, TimeUnit.SECONDS, 100); 85 | System.out.println(zkClient.getChildren(rootPath)); 86 | 87 | for(int i=0;i<20;i++){ 88 | Thread thread = new Thread(new Runnable() { 89 | public void run() { 90 | ZKDistributedQueue queue = new ZKDistributedQueue(zkClient, rootPath); 91 | list2.add(queue.poll()); 92 | } 93 | }); 94 | thread.start(); 95 | } 96 | //等待事件到达 97 | int size2 = TestUtil.waitUntil(20, new Callable() { 98 | @Override 99 | public Integer call() throws Exception { 100 | return list2.size(); 101 | } 102 | 103 | }, TimeUnit.SECONDS, 100); 104 | assertThat(size2).isEqualTo(20); 105 | boolean flag = true; 106 | for(int i =0;i<20;i++){ 107 | if(!list1.get(i).equals(list2.get(i))){ 108 | flag = false; 109 | break; 110 | } 111 | } 112 | assertThat(flag).isTrue(); 113 | 114 | ZKDistributedQueue queue = new ZKDistributedQueue(zkClient, rootPath); 115 | assertThat(queue.peek()).isEqualTo(queue.poll()); 116 | 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/connection/ZKConnection.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.connection; 17 | 18 | import java.util.concurrent.Callable; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | import org.apache.zookeeper.Watcher; 22 | import org.apache.zookeeper.Watcher.Event.KeeperState; 23 | import org.apache.zookeeper.ZooKeeper; 24 | 25 | import com.api6.zkclient.event.ZKEventLock; 26 | import com.api6.zkclient.exception.ZKException; 27 | import com.api6.zkclient.exception.ZKInterruptedException; 28 | import com.api6.zkclient.exception.ZKTimeoutException; 29 | 30 | /** 31 | * ZK客户端连接接口,定义了ZKConnection所需实现的方法 32 | * @author: zhaojie/zh_jie@163.com.com 33 | */ 34 | public interface ZKConnection { 35 | /** 36 | * 连接zookeeper服务器 37 | * @param watcher 38 | * @return void 39 | */ 40 | void connect(Watcher watcher); 41 | 42 | /** 43 | * 重新连接ZooKeeper服务器 44 | * @param watcher 45 | * @return void 46 | */ 47 | void reconnect(Watcher watcher); 48 | 49 | 50 | /** 51 | * 尝试连接,只到连接成功为止 52 | * 对于连接失败,或者会话超时,会发起重新连接的请求,只到连接成功。 53 | * @param callable 54 | * @return 55 | * @throws ZKInterruptedException 如果操作被Interrupted抛出异常 56 | * @throws ZKException 所有的ZooKeeper异常发生会抛出此异常 57 | * @throws RuntimeException 执行时异常 58 | * @throws ZKTimeoutException 如果设置了超时时间operationRetryTimeoutInMillis, 59 | * 则会在尝试时间超过operationRetryTimeoutInMillis时 60 | * 抛出异常 61 | * @return T 62 | */ 63 | T retryUntilConnected(Callable callable) 64 | throws ZKInterruptedException, ZKTimeoutException, ZKException, RuntimeException; 65 | 66 | /** 67 | * 等待直到连接成功 68 | * @param timeout 尝试的超时时间 69 | * @param timeUnit 时间单位 70 | * @return 71 | * @throws ZKInterruptedException 72 | * 如果操作被interrupted抛出中断异常 73 | * @return boolean 74 | */ 75 | boolean waitUntilConnected(long timeout, TimeUnit timeUnit) throws ZKInterruptedException; 76 | 77 | /** 78 | * 关闭与zookeeper服务端的连接 79 | * @throws InterruptedException 80 | * @return void 81 | */ 82 | void close() throws InterruptedException; 83 | 84 | /** 85 | * 获取到当前ZooKeeper客户端的状态 86 | * @return 87 | * @return KeeperState 88 | */ 89 | KeeperState getCurrentState(); 90 | /** 91 | * 设置ZooKeeper客户端的状态 92 | * @param currentState 93 | * @return void 94 | */ 95 | void setCurrentState(KeeperState currentState); 96 | 97 | /** 98 | * 获得EventLock 99 | * @return 100 | * @return ZKEventLock 101 | */ 102 | ZKEventLock getEventLock(); 103 | 104 | /** 105 | * 尝试获得EventLock 106 | * @return void 107 | */ 108 | void acquireEventLock(); 109 | /** 110 | * 释放EventLock 111 | * @return void 112 | */ 113 | void releaseEventLock(); 114 | /** 115 | * 获得可中断的EventLock,在获取锁的时候被阻塞后,如果当前线程收到interrupt信号, 116 | * 此线程会被唤醒并处理InterruptedException,不会一直阻塞下去 117 | * @return void 118 | */ 119 | void acquireEventLockInterruptibly(); 120 | 121 | 122 | /** 123 | * 添加指定的scheme:auth认证信息连接服务器 124 | * @param scheme 125 | * @param auth 126 | * @return void 127 | */ 128 | void addAuthInfo(String scheme, byte[] auth); 129 | 130 | /** 131 | * 返回服务连接 132 | * @return 133 | * @return String 134 | */ 135 | String getServers(); 136 | 137 | /** 138 | * 获得原生ZooKeeper客户端 139 | * @return 140 | * @return ZooKeeper 141 | */ 142 | ZooKeeper getZooKeeper(); 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/watcher/ZKWatcher.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.watcher; 17 | 18 | import org.apache.zookeeper.WatchedEvent; 19 | import org.apache.zookeeper.Watcher; 20 | import org.apache.zookeeper.Watcher.Event.EventType; 21 | import org.apache.zookeeper.Watcher.Event.KeeperState; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import com.api6.zkclient.ZKClient; 26 | 27 | /** 28 | * 事件监听类,用于事件分发 29 | * @author: zhaojie/zh_jie@163.com.com 30 | */ 31 | public class ZKWatcher implements Watcher { 32 | private static final Logger LOG = LoggerFactory.getLogger(ZKWatcher.class); 33 | private final ZKClient client; 34 | private final ZKWatcherProcess process; 35 | 36 | public ZKWatcher(ZKClient client) { 37 | this.client = client; 38 | this.process = new ZKWatcherProcess(client); 39 | } 40 | 41 | /** 42 | * 事件处理 43 | * @see org.apache.zookeeper.Watcher#process(org.apache.zookeeper.WatchedEvent) 44 | */ 45 | @Override 46 | public void process(WatchedEvent event) { 47 | LOG.debug("ZooKeeper event is arrived [" + event+" ]..."); 48 | EventType eventType = event.getType(); 49 | //状态更新 50 | boolean stateChanged = event.getPath() == null; 51 | //节点相关的所有事件 52 | boolean znodeChanged = event.getPath() != null; 53 | 54 | //节点创建、删除和数据改变的事件 55 | boolean nodeChanged = eventType == EventType.NodeDataChanged 56 | || eventType == EventType.NodeDeleted 57 | || eventType == EventType.NodeCreated; 58 | 59 | //子节点数量改变相关的事件,包括节点创建和删除(都会影响子节点数量的变化),以及子节点数量的改变 60 | boolean childChanged = eventType == EventType.NodeDeleted 61 | || eventType == EventType.NodeCreated 62 | || eventType == EventType.NodeChildrenChanged; 63 | 64 | client.acquireEventLock(); 65 | try { 66 | if (client.getShutdownTrigger()) { 67 | LOG.debug("client will shutdown,ignore the event [" + eventType + " | " + event.getPath() + "]"); 68 | return; 69 | } 70 | if (stateChanged) {//ZooKeeper状态改变的处理 71 | process.processStateChanged(event); 72 | } 73 | if (nodeChanged) {//节点改变事件处理,包括节点的创建、删除、数据改变 74 | process.processNodeChanged(event); 75 | } 76 | if (childChanged) {//造成子节点数量改变的事件的处理,包括节点的创建、删除、子节点数量改变 77 | process.processChildChanged(event); 78 | } 79 | } finally { 80 | if (stateChanged) { 81 | client.getEventLock().getStateChangedCondition().signalAll(); 82 | // 在会话失效后,服务端会取消watch. 83 | // 如果在会话失效后与重连这段时间内有数据发生变化,监听器是无法监听到的, 84 | // 所以要唤醒等待的监听,并触发所有的监听事件 85 | if (event.getState() == KeeperState.Expired) { 86 | client.getEventLock().getNodeEventCondition().signalAll(); 87 | client.getEventLock().getNodeOrChildChangedCondition().signalAll(); 88 | 89 | // 通知所有的监听器,可能存在数据变化 90 | process.processAllNodeAndChildListeners(event); 91 | } 92 | } 93 | if (znodeChanged) { 94 | client.getEventLock().getNodeEventCondition().signalAll(); 95 | } 96 | if (nodeChanged || childChanged) { 97 | client.getEventLock().getNodeOrChildChangedCondition().signalAll(); 98 | } 99 | client.releaseEventLock(); 100 | } 101 | } 102 | 103 | /** 104 | * 停止监听 105 | * @return void 106 | */ 107 | public void stop(){ 108 | process.stop(); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/ZKClientBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient; 17 | 18 | import com.api6.zkclient.exception.ZKException; 19 | import com.api6.zkclient.serializer.SerializableSerializer; 20 | import com.api6.zkclient.serializer.ZKSerializer; 21 | 22 | /** 23 | * ZKClient辅助创建类 24 | * @author: zhaojie/zh_jie@163.com.com 25 | */ 26 | public class ZKClientBuilder { 27 | private int connectionTimeout = Integer.MAX_VALUE; 28 | private ZKSerializer zkSerializer = new SerializableSerializer(); 29 | private int eventThreadPoolSize = 1; 30 | private String servers; 31 | private int sessionTimeout = 30000; 32 | private int retryTimeout = -1; 33 | 34 | /** 35 | * 创建ZClient 36 | * @return 37 | * @return ZKClientBuilder 38 | * @author: zhaojie/zh_jie@163.com 39 | */ 40 | public static ZKClientBuilder newZKClient(){ 41 | ZKClientBuilder builder = new ZKClientBuilder(); 42 | return builder; 43 | } 44 | 45 | /** 46 | * 创建ZClient 47 | * @param servers 48 | * @return 49 | * @return ZKClientBuilder 50 | * @author: zhaojie/zh_jie@163.com 51 | */ 52 | public static ZKClientBuilder newZKClient(String servers){ 53 | ZKClientBuilder builder = new ZKClientBuilder(); 54 | builder.servers(servers); 55 | return builder; 56 | } 57 | 58 | /** 59 | * 组件并初始化ZKClient 60 | * @return 61 | * @return ZKClient 62 | */ 63 | public ZKClient build(){ 64 | if(servers==null || servers.trim().equals("")){ 65 | throw new ZKException("Servers can not be empty !"); 66 | } 67 | ZKClient zkClient = new ZKClient(servers,sessionTimeout,retryTimeout,zkSerializer,connectionTimeout,eventThreadPoolSize); 68 | return zkClient; 69 | } 70 | 71 | /** 72 | * 设置服务器地址 73 | * @param servers 74 | * @return 75 | * @return ZKClientBuilder 76 | */ 77 | public ZKClientBuilder servers(String servers){ 78 | this.servers = servers; 79 | return this; 80 | } 81 | 82 | /** 83 | * 设置序列化类,可选. 84 | * (默认实现:{@link SerializableSerializer}) 85 | * @param zkSerializer 86 | * @return 87 | * @return ZKClientBuilder 88 | */ 89 | public ZKClientBuilder serializer(ZKSerializer zkSerializer){ 90 | this.zkSerializer = zkSerializer; 91 | return this; 92 | } 93 | 94 | /** 95 | * 设置会话失效时间,可选 96 | * (默认值:30000,实际大小ZooKeeper会重新计算大概在2 * tickTime ~ 20 * tickTime) 97 | * @param sessionTimeout 98 | * @return 99 | * @return ZKClientBuilder 100 | * @author: zhaojie/zh_jie@163.com 101 | */ 102 | public ZKClientBuilder sessionTimeout(int sessionTimeout){ 103 | this.sessionTimeout = sessionTimeout; 104 | return this; 105 | } 106 | 107 | /** 108 | * 连接超时时间,可选。 109 | * 默认值Integer.MAX_VALUE 110 | * @param connectionTimeout 111 | * @return 112 | * @return ZKClientBuilder 113 | * @author: zhaojie/zh_jie@163.com 114 | */ 115 | public ZKClientBuilder connectionTimeout(int connectionTimeout){ 116 | this.connectionTimeout = connectionTimeout; 117 | return this; 118 | } 119 | 120 | /** 121 | * 重试超时时间,可选,主要用于ZooKeeper与服务器断开后的重连。 122 | * 默认值-1,也就是没有超时限制 123 | * @param retryTimeout 124 | * @return 125 | * @return ZKClientBuilder 126 | * @author: zhaojie/zh_jie@163.com 127 | */ 128 | public ZKClientBuilder retryTimeout(int retryTimeout){ 129 | this.retryTimeout = retryTimeout; 130 | return this; 131 | } 132 | 133 | /** 134 | * 处理事件的线程数,可选,默认值为1 135 | * @param eventThreadPoolSize 136 | * @return 137 | * @return ZKClientBuilder 138 | * @author: zhaojie/zh_jie@163.com 139 | */ 140 | public ZKClientBuilder eventThreadPoolSize(int eventThreadPoolSize){ 141 | this.eventThreadPoolSize = eventThreadPoolSize; 142 | return this; 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/test/java/com/api6/zkclient/ZKDistributedLockTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient; 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.concurrent.Callable; 22 | import java.util.concurrent.TimeUnit; 23 | import java.util.concurrent.atomic.AtomicInteger; 24 | 25 | import org.apache.zookeeper.CreateMode; 26 | import org.junit.After; 27 | import org.junit.Before; 28 | import org.junit.Test; 29 | 30 | import com.api6.zkclient.lock.ZKDistributedLock; 31 | import com.api6.zkclient.util.TestSystem; 32 | import com.api6.zkclient.util.TestUtil; 33 | import com.api6.zkclient.util.ZKServer; 34 | 35 | public class ZKDistributedLockTest { 36 | 37 | private TestSystem testSystem = TestSystem.getInstance(); 38 | private ZKServer zkServer = null; 39 | private ZKClient zkClient = null; 40 | @Before 41 | public void before() { 42 | zkServer = testSystem.getZKserver(); 43 | zkClient = ZKClientBuilder.newZKClient() 44 | .servers("localhost:"+zkServer.getPort()) 45 | .sessionTimeout(1000) 46 | .build(); 47 | } 48 | 49 | @After 50 | public void after(){ 51 | testSystem.cleanup(zkClient); 52 | } 53 | 54 | /** 55 | * 测试分布式锁 56 | * @throws Exception 57 | * @return void 58 | */ 59 | @Test 60 | public void testDistributedLock() throws Exception{ 61 | final String lockPath = "/zk/lock"; 62 | zkClient.createRecursive(lockPath, null, CreateMode.PERSISTENT); 63 | final AtomicInteger integer = new AtomicInteger(0); 64 | final List msgList = new ArrayList(); 65 | for(int i=0;i<20;i++){ 66 | Thread thread1 = new Thread(new Runnable() { 67 | public void run() { 68 | try { 69 | ZKDistributedLock lock = ZKDistributedLock.newInstance(zkClient,lockPath); 70 | lock.lock(0); 71 | integer.getAndIncrement(); 72 | msgList.add("thread "+integer); 73 | System.out.println("Thread "+integer+" got lock........"); 74 | System.out.println(lock.getParticipantNodes()); 75 | if(integer.get()==3){ 76 | Thread.sleep(1000); 77 | } 78 | if(integer.get()==5){ 79 | Thread.sleep(700); 80 | } 81 | 82 | if(integer.get()==6 || integer.get()==11){ 83 | Thread.sleep(500); 84 | } 85 | 86 | if(integer.get()==10){ 87 | Thread.sleep(500); 88 | } 89 | if(integer.get()==15){ 90 | Thread.sleep(400); 91 | } 92 | lock.unlock(); 93 | System.out.println("thread "+integer+" unlock........"); 94 | 95 | } catch (InterruptedException e) { 96 | e.printStackTrace(); 97 | } finally { 98 | 99 | } 100 | } 101 | }); 102 | thread1.start(); 103 | } 104 | 105 | //等待事件到达 106 | int size = TestUtil.waitUntil(20, new Callable() { 107 | @Override 108 | public Integer call() throws Exception { 109 | return msgList.size(); 110 | } 111 | 112 | }, TimeUnit.SECONDS, 100); 113 | assertThat(size).isEqualTo(20); 114 | boolean flag = true; 115 | for(int i =0;i<20;i++){ 116 | if(!msgList.get(i).equals("thread "+(i+1))){ 117 | flag = false; 118 | } 119 | } 120 | assertThat(flag).isTrue(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/test/java/com/api6/zkclient/ZKDistributedDelayLockTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient; 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.concurrent.Callable; 22 | import java.util.concurrent.CountDownLatch; 23 | import java.util.concurrent.TimeUnit; 24 | import java.util.concurrent.atomic.AtomicInteger; 25 | 26 | import org.apache.zookeeper.CreateMode; 27 | import org.junit.After; 28 | import org.junit.Before; 29 | import org.junit.Test; 30 | 31 | import com.api6.zkclient.lock.ZKDistributedDelayLock; 32 | import com.api6.zkclient.util.TestSystem; 33 | import com.api6.zkclient.util.TestUtil; 34 | import com.api6.zkclient.util.ZKServer; 35 | 36 | public class ZKDistributedDelayLockTest { 37 | 38 | private TestSystem testSystem = TestSystem.getInstance(); 39 | private ZKServer zkServer = null; 40 | private ZKClient zkClient = null; 41 | @Before 42 | public void before() { 43 | zkServer = testSystem.getZKserver(); 44 | zkClient = ZKClientBuilder.newZKClient() 45 | .servers("localhost:"+zkServer.getPort()) 46 | .sessionTimeout(1000) 47 | .build(); 48 | } 49 | 50 | @After 51 | public void after(){ 52 | testSystem.cleanup(zkClient); 53 | } 54 | 55 | /** 56 | * 测试分布式锁 57 | * @throws Exception 58 | * @return void 59 | */ 60 | @Test 61 | public void testDistributedDelayLock() throws Exception{ 62 | final String lockPach = "/zk/delylock1"; 63 | final List msgList = new ArrayList(); 64 | final CountDownLatch latch = new CountDownLatch(5); 65 | zkClient.createRecursive(lockPach, null, CreateMode.PERSISTENT); 66 | final AtomicInteger index = new AtomicInteger(0); 67 | for(int i=0;i<5;i++){ 68 | 69 | Thread thread1 = new Thread(new Runnable() { 70 | public void run() { 71 | final ZKClient zkClient1 = ZKClientBuilder.newZKClient() 72 | .servers("localhost:"+zkServer.getPort()) 73 | .sessionTimeout(1000) 74 | .build(); 75 | ZKDistributedDelayLock lock = ZKDistributedDelayLock.newInstance(zkClient1, lockPach); 76 | 77 | try { 78 | latch.await(); 79 | } catch (InterruptedException e1) { 80 | e1.printStackTrace(); 81 | } 82 | lock.lock(); 83 | System.out.println(Thread.currentThread().getName()+":lock...."); 84 | msgList.add(Thread.currentThread().getName()+":unlock"); 85 | try { 86 | Thread.sleep(1000); 87 | } catch (InterruptedException e) { 88 | e.printStackTrace(); 89 | } 90 | System.out.println(Thread.currentThread().getName()+":unlock...."); 91 | lock.unlock(); 92 | } 93 | }); 94 | thread1.start(); 95 | latch.countDown(); 96 | index.getAndIncrement(); 97 | } 98 | 99 | 100 | //等待事件到达 101 | int size = TestUtil.waitUntil(5, new Callable() { 102 | @Override 103 | public Integer call() throws Exception { 104 | return msgList.size(); 105 | } 106 | 107 | }, TimeUnit.SECONDS, 100); 108 | 109 | assertThat(size).isEqualTo(5); 110 | } 111 | 112 | 113 | 114 | /* @Test 115 | public void testDistributedDelayLock1() throws Exception{ 116 | final String lockPach = "/zk/delylock1"; 117 | ZKClient client = ZKClientBuilder.newZKClient() 118 | .servers("192.168.1.102:2181") 119 | .sessionTimeout(10000) 120 | .build(); 121 | client.createRecursive(lockPach, null, CreateMode.PERSISTENT); 122 | ZKDistributedDelayLock lock = ZKDistributedDelayLock.newInstance(client, lockPach); 123 | lock.lock(); 124 | System.out.println("sleep"); 125 | Thread.sleep(20000); 126 | System.out.println(client.getData(lockPach+"/lock")); 127 | 128 | }*/ 129 | 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/queue/ZKDistributedQueue.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.queue; 17 | 18 | import java.io.Serializable; 19 | import java.util.List; 20 | 21 | import org.apache.zookeeper.CreateMode; 22 | 23 | import com.api6.zkclient.ZKClient; 24 | import com.api6.zkclient.exception.ZKNoNodeException; 25 | import com.api6.zkclient.util.ExceptionUtil; 26 | 27 | /** 28 | * 分布式队列 29 | * @author: zhaojie/zh_jie@163.com.com 30 | * @version: 2016年5月31日 下午6:19:11 31 | */ 32 | public class ZKDistributedQueue { 33 | private ZKClient zkClient; 34 | private String rootPath; 35 | private static final String ELEMENT_NAME = "element"; 36 | 37 | private class Element { 38 | private String name; 39 | private T data; 40 | 41 | public Element(String name, T data) { 42 | this.name = name; 43 | this.data = data; 44 | } 45 | 46 | public String getName() { 47 | return name; 48 | } 49 | 50 | public T getData() { 51 | return data; 52 | } 53 | } 54 | 55 | /** 56 | * 创建分布式队列 57 | * ZKDistributedQueue. 58 | * 59 | * @param zkClient ZKClient客户端 60 | * @param root 分布式队列根路径 61 | */ 62 | public ZKDistributedQueue(ZKClient zkClient, String root) { 63 | this.zkClient = zkClient; 64 | this.rootPath = root; 65 | if(!zkClient.exists(root)){ 66 | throw new ZKNoNodeException("The root is not exists!,please create the node.[path:"+root+"]"); 67 | } 68 | } 69 | 70 | 71 | /** 72 | * 添加一个元素 73 | * @param element 74 | * @return 75 | * @return boolean 76 | */ 77 | public boolean offer(T element) { 78 | try { 79 | zkClient.create(rootPath + "/" + ELEMENT_NAME + "-", element, CreateMode.PERSISTENT_SEQUENTIAL); 80 | } catch (Exception e) { 81 | throw ExceptionUtil.convertToRuntimeException(e); 82 | } 83 | return true; 84 | } 85 | 86 | /** 87 | * 删除并返回顶部元素 88 | * @return 89 | * @return T 90 | */ 91 | public T poll() { 92 | while (true) { 93 | Element element = getFirstElement(); 94 | if (element == null) { 95 | return null; 96 | } 97 | 98 | try { 99 | boolean flag = zkClient.delete(element.getName()); 100 | if(flag){ 101 | return element.getData(); 102 | }else{ 103 | //如果删除失败,证明已被其他线程获取 104 | //重新获取最新的元素,直到获取成功为止。 105 | } 106 | } catch (Exception e) { 107 | throw ExceptionUtil.convertToRuntimeException(e); 108 | } 109 | } 110 | } 111 | 112 | 113 | /** 114 | * 获取顶部元素 115 | * @return 116 | * @return T 117 | */ 118 | public T peek() { 119 | Element element = getFirstElement(); 120 | if (element == null) { 121 | return null; 122 | } 123 | return element.getData(); 124 | } 125 | 126 | 127 | /** 128 | * 获取队列顶部元素 129 | * @return 130 | * @return Element 131 | */ 132 | @SuppressWarnings("unchecked") 133 | private Element getFirstElement() { 134 | try { 135 | while (true) { 136 | List list = zkClient.getChildren(rootPath); 137 | if (list.size() == 0) { 138 | return null; 139 | } 140 | String elementName = getSmallestElement(list); 141 | 142 | try { 143 | return new Element(rootPath + "/" + elementName, (T) zkClient.getData(rootPath + "/" + elementName)); 144 | } catch (ZKNoNodeException e) { 145 | //如果抛出此异常,证明该节点已被其他线程获取。 146 | //此时忽略异常,重新获取最新的元素,直到获取成功为止。 147 | } 148 | } 149 | } catch (Exception e) { 150 | throw ExceptionUtil.convertToRuntimeException(e); 151 | } 152 | } 153 | 154 | /** 155 | * 获得最小节点 156 | * @param list 157 | * @return 158 | * @return String 159 | */ 160 | private String getSmallestElement(List list) { 161 | String smallestElement = list.get(0); 162 | for (String element : list) { 163 | if (element.compareTo(smallestElement) < 0) { 164 | smallestElement = element; 165 | } 166 | } 167 | return smallestElement; 168 | } 169 | 170 | /** 171 | * 判断队列是否为空 172 | * @return 173 | * @return boolean 174 | */ 175 | public boolean isEmpty() { 176 | return zkClient.getChildren(rootPath).size() == 0; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/test/java/com/api6/zkclient/ZKHALockTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.concurrent.Callable; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | import org.apache.zookeeper.CreateMode; 26 | import org.junit.After; 27 | import org.junit.Before; 28 | import org.junit.Test; 29 | 30 | import com.api6.zkclient.lock.ZKHALock; 31 | import com.api6.zkclient.util.TestSystem; 32 | import com.api6.zkclient.util.TestUtil; 33 | import com.api6.zkclient.util.ZKServer; 34 | 35 | public class ZKHALockTest { 36 | 37 | private TestSystem testSystem = TestSystem.getInstance(); 38 | private ZKServer zkServer = null; 39 | private ZKClient zkClient = null; 40 | @Before 41 | public void before() { 42 | zkServer = testSystem.getZKserver(); 43 | zkClient = ZKClientBuilder.newZKClient() 44 | .servers("localhost:"+zkServer.getPort()) 45 | .sessionTimeout(1000) 46 | .build(); 47 | } 48 | 49 | @After 50 | public void after(){ 51 | testSystem.cleanup(zkClient); 52 | } 53 | 54 | /** 55 | * 测试主从服务锁 56 | * @throws Exception 57 | * @return void 58 | */ 59 | @Test 60 | public void testZKHALock() throws Exception{ 61 | final String lockPach = "/zk/halock"; 62 | final List msgList = new ArrayList(); 63 | zkClient.createRecursive(lockPach, null, CreateMode.PERSISTENT); 64 | 65 | 66 | Thread thread1 = new Thread(new Runnable() { 67 | public void run() { 68 | final ZKClient zkClient1 = ZKClientBuilder.newZKClient() 69 | .servers("localhost:"+zkServer.getPort()) 70 | .sessionTimeout(1000) 71 | .build(); 72 | ZKHALock lock = ZKHALock.newInstance(zkClient1, lockPach); 73 | //尝试获取锁,如果获取成功则变为主服务 74 | lock.lock(); 75 | msgList.add("thread1 is master"); 76 | System.out.println("thread1 now is master!"); 77 | try { 78 | Thread.sleep(1000*1); 79 | zkClient1.reconnect(); 80 | } catch (InterruptedException e) { 81 | e.printStackTrace(); 82 | } 83 | 84 | 85 | } 86 | }); 87 | 88 | Thread thread2 = new Thread(new Runnable() { 89 | public void run() { 90 | final ZKClient zkClient2 = ZKClientBuilder.newZKClient() 91 | .servers("localhost:"+zkServer.getPort()) 92 | .sessionTimeout(1000) 93 | .build(); 94 | ZKHALock lock = ZKHALock.newInstance(zkClient2, lockPach); 95 | //尝试获取锁,如果获取成功则变为主服务 96 | lock.lock(); 97 | msgList.add("thread2 is master"); 98 | System.out.println("thread2 now is master!"); 99 | try { 100 | Thread.sleep(1000*2); 101 | lock.unlock(); 102 | zkClient2.unlistenAll(); 103 | zkClient2.close(); 104 | } catch (InterruptedException e) { 105 | e.printStackTrace(); 106 | } 107 | 108 | 109 | } 110 | }); 111 | 112 | Thread thread3 = new Thread(new Runnable() { 113 | public void run() { 114 | final ZKClient zkClient3 = ZKClientBuilder.newZKClient() 115 | .servers("localhost:"+zkServer.getPort()) 116 | .sessionTimeout(1000) 117 | .build(); 118 | ZKHALock lock = ZKHALock.newInstance(zkClient3, lockPach); 119 | //尝试获取锁,如果获取成功则变为主服务 120 | lock.lock(); 121 | msgList.add("thread3 is master"); 122 | System.out.println("thread3 now is master!"); 123 | try { 124 | Thread.sleep(1000*3); 125 | zkClient3.unlistenAll(); 126 | zkClient3.close(); 127 | } catch (InterruptedException e) { 128 | e.printStackTrace(); 129 | } 130 | } 131 | }); 132 | thread1.start(); 133 | thread2.start(); 134 | thread3.start(); 135 | 136 | //等待事件到达 137 | int size = TestUtil.waitUntil(3, new Callable() { 138 | @Override 139 | public Integer call() throws Exception { 140 | return msgList.size(); 141 | } 142 | 143 | }, TimeUnit.SECONDS, 100); 144 | assertThat(size).isEqualTo(3); 145 | 146 | 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/test/java/com/api6/zkclient/util/ZKServer.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.util; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.net.ConnectException; 21 | import java.net.InetSocketAddress; 22 | import java.net.Socket; 23 | import java.net.SocketException; 24 | import java.net.UnknownHostException; 25 | 26 | import javax.annotation.PostConstruct; 27 | import javax.annotation.PreDestroy; 28 | 29 | import org.apache.log4j.Logger; 30 | import org.apache.zookeeper.server.NIOServerCnxnFactory; 31 | import org.apache.zookeeper.server.ZooKeeperServer; 32 | 33 | import com.api6.zkclient.exception.ZKException; 34 | import com.api6.zkclient.exception.ZKInterruptedException; 35 | 36 | public class ZKServer { 37 | 38 | private final static Logger LOG = Logger.getLogger(ZKServer.class); 39 | 40 | public static final int DEFAULT_PORT = 2181; 41 | public static final int DEFAULT_TICK_TIME = 1000; 42 | public static final int DEFAULT_MIN_SESSION_TIMEOUT = 2 * DEFAULT_TICK_TIME; 43 | 44 | private String dataPath; 45 | private String logPath; 46 | private ZooKeeperServer zooKeeperServer; 47 | private NIOServerCnxnFactory nioFactory; 48 | private int port; 49 | private int tickTime; 50 | private int minSessionTimeout; 51 | 52 | public ZKServer(String dataPath, String logPath) { 53 | this(dataPath, logPath, DEFAULT_PORT); 54 | } 55 | 56 | public ZKServer(String dataPath, String logPath, int port) { 57 | this(dataPath, logPath, port, DEFAULT_TICK_TIME); 58 | } 59 | 60 | public ZKServer(String dataPath, String logPath, int port, int tickTime) { 61 | this(dataPath, logPath, port, tickTime, DEFAULT_MIN_SESSION_TIMEOUT); 62 | } 63 | 64 | public ZKServer(String dataPath, String logPath, int port, int tickTime, int minSessionTimeout) { 65 | this.dataPath = dataPath; 66 | this.logPath = logPath; 67 | this.port = port; 68 | this.tickTime = tickTime; 69 | this.minSessionTimeout = minSessionTimeout; 70 | } 71 | 72 | public int getPort() { 73 | return port; 74 | } 75 | 76 | @PostConstruct 77 | public void start() { 78 | LOG.info("Start single zookeeper server on: localhost:" + port + "..."); 79 | try { 80 | startZooKeeperServer(); 81 | } catch (RuntimeException e) { 82 | shutdown(); 83 | throw e; 84 | } 85 | } 86 | 87 | private void startZooKeeperServer() { 88 | //检查端口是否被占用 89 | if (isPortFree(port)) { 90 | final File dataDir = new File(dataPath); 91 | final File dataLogDir = new File(logPath); 92 | dataDir.mkdirs(); 93 | dataLogDir.mkdirs(); 94 | //单机版 zk server 95 | LOG.info("data dir: " + dataDir.getAbsolutePath()); 96 | LOG.info("data log dir: " + dataLogDir.getAbsolutePath()); 97 | LOG.info("JAAS login file: " + System.getProperty("java.security.auth.login.config", "none")); 98 | 99 | try { 100 | zooKeeperServer = new ZooKeeperServer(dataDir, dataLogDir, tickTime); 101 | zooKeeperServer.setMinSessionTimeout(minSessionTimeout); 102 | nioFactory = new NIOServerCnxnFactory(); 103 | int maxClientConnections = 0; // 0 means unlimited 104 | nioFactory.configure(new InetSocketAddress(port), maxClientConnections); 105 | nioFactory.startup(zooKeeperServer); 106 | } catch (IOException e) { 107 | throw new ZKException("Unable to start single ZooKeeper server.", e); 108 | } catch (InterruptedException e) { 109 | throw new ZKInterruptedException(e); 110 | } 111 | 112 | } else { 113 | throw new IllegalStateException("Zookeeper port " + port + " was already in use."); 114 | } 115 | } 116 | 117 | @PreDestroy 118 | public void shutdown() { 119 | LOG.info("Shutting down ZkServer..."); 120 | if (nioFactory != null) { 121 | nioFactory.shutdown(); 122 | try { 123 | nioFactory.join(); 124 | } catch (InterruptedException e) { 125 | Thread.currentThread().interrupt(); 126 | } 127 | nioFactory = null; 128 | } 129 | if (zooKeeperServer != null) { 130 | zooKeeperServer.shutdown(); 131 | zooKeeperServer = null; 132 | } 133 | LOG.info("Shutting down ZkServer...done"); 134 | } 135 | 136 | /** 137 | * 检查端口是否空闲 138 | * @param port 139 | * @return 140 | * @return boolean 空闲返回true,否则返回false 141 | */ 142 | private static boolean isPortFree(int port) { 143 | try { 144 | Socket socket = new Socket("localhost", port); 145 | socket.close(); 146 | return false; 147 | } catch (ConnectException e) { 148 | return true; 149 | } catch (SocketException e) { 150 | if (e.getMessage().equals("Connection reset by peer")) { 151 | return true; 152 | } 153 | throw new RuntimeException(e); 154 | } catch (UnknownHostException e) { 155 | throw new RuntimeException(e); 156 | } catch (IOException e) { 157 | throw new RuntimeException(e); 158 | } 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /src/test/java/com/api6/zkclient/ZKClientSessionTimeoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient; 17 | 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.concurrent.Callable; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | import org.apache.zookeeper.Watcher.Event.KeeperState; 27 | import org.junit.After; 28 | import org.junit.Before; 29 | import org.junit.Test; 30 | 31 | import com.api6.zkclient.listener.ZKChildCountListener; 32 | import com.api6.zkclient.listener.ZKChildDataListener; 33 | import com.api6.zkclient.listener.ZKNodeListener; 34 | import com.api6.zkclient.listener.ZKStateListener; 35 | import com.api6.zkclient.util.TestSystem; 36 | import com.api6.zkclient.util.TestUtil; 37 | import com.api6.zkclient.util.ZKServer; 38 | 39 | public class ZKClientSessionTimeoutTest { 40 | 41 | private TestSystem testSystem = TestSystem.getInstance(); 42 | private ZKServer zkServer = null; 43 | private ZKClient zkClient = null; 44 | @Before 45 | public void before() { 46 | zkServer = testSystem.getZKserver(); 47 | zkClient = ZKClientBuilder.newZKClient() 48 | .servers("localhost:"+zkServer.getPort()) 49 | .sessionTimeout(1000) 50 | .build(); 51 | //zkClient = new ZKClient("192.168.1.104:2181"); 52 | } 53 | 54 | @After 55 | public void after(){ 56 | testSystem.cleanup(zkClient); 57 | } 58 | 59 | @Test 60 | public void testZKClentExpried() throws Exception { 61 | String path = "/test-expried"; 62 | final List msgList = new ArrayList(); 63 | 64 | 65 | 66 | zkClient.listenNodeChanges(path, new ZKNodeListener() { 67 | 68 | @Override 69 | public void handleSessionExpired(String path) throws Exception { 70 | msgList.add("session expried"); 71 | } 72 | 73 | @Override 74 | public void handleDataDeleted(String path) throws Exception { 75 | //ignore 76 | } 77 | 78 | @Override 79 | public void handleDataCreated(String path, Object data) throws Exception { 80 | //ignore 81 | } 82 | 83 | @Override 84 | public void handleDataChanged(String path, Object data) throws Exception { 85 | //ignore 86 | } 87 | }); 88 | 89 | zkClient.listenChildCountChanges(path, new ZKChildCountListener() { 90 | 91 | @Override 92 | public void handleSessionExpired(String path, List children) throws Exception { 93 | msgList.add("session expried"); 94 | } 95 | 96 | @Override 97 | public void handleChildCountChanged(String path, List children) throws Exception { 98 | //ignore 99 | } 100 | }); 101 | 102 | zkClient.listenChildDataChanges(path, new ZKChildDataListener() { 103 | 104 | @Override 105 | public void handleSessionExpired(String path, Object data) throws Exception { 106 | msgList.add("session expried"); 107 | } 108 | 109 | @Override 110 | public void handleChildDataChanged(String path, Object data) throws Exception { 111 | //ignore 112 | } 113 | 114 | @Override 115 | public void handleChildCountChanged(String path, List children) throws Exception { 116 | //ignore 117 | } 118 | }); 119 | 120 | zkClient.listenStateChanges(new ZKStateListener() { 121 | 122 | @Override 123 | public void handleStateChanged(KeeperState state) throws Exception { 124 | if(state==KeeperState.Expired){ 125 | msgList.add("session expried"); 126 | } 127 | } 128 | 129 | @Override 130 | public void handleSessionError(Throwable error) throws Exception { 131 | //ignore 132 | } 133 | 134 | @Override 135 | public void handleNewSession() throws Exception { 136 | msgList.add("new session"); 137 | } 138 | }); 139 | 140 | //创建节点 141 | //zkClient.create(path, "123", CreateMode.PERSISTENT); 142 | 143 | zkServer.shutdown(); 144 | 145 | 146 | //20秒后重启server; 147 | Thread thread = new Thread(new Runnable() { 148 | 149 | @Override 150 | public void run() { 151 | try { 152 | Thread.sleep(1000*40); 153 | zkServer.start(); 154 | } catch (InterruptedException e) { 155 | e.printStackTrace(); 156 | } 157 | } 158 | }); 159 | 160 | thread.start(); 161 | thread.join(); 162 | 163 | //等待事件到达 164 | int size = TestUtil.waitUntil(5, new Callable() { 165 | @Override 166 | public Integer call() throws Exception { 167 | return msgList.size(); 168 | } 169 | 170 | }, TimeUnit.SECONDS, 60); 171 | assertThat(size).isEqualTo(5); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/lock/ZKHALock.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.lock; 17 | 18 | import java.util.List; 19 | import java.util.concurrent.Semaphore; 20 | 21 | import org.apache.zookeeper.CreateMode; 22 | import org.apache.zookeeper.Watcher.Event.KeeperState; 23 | import org.slf4j.Logger; 24 | import org.slf4j.LoggerFactory; 25 | 26 | import com.api6.zkclient.ZKClient; 27 | import com.api6.zkclient.exception.ZKInterruptedException; 28 | import com.api6.zkclient.exception.ZKNoNodeException; 29 | import com.api6.zkclient.listener.ZKChildCountListener; 30 | import com.api6.zkclient.listener.ZKStateListener; 31 | 32 | /** 33 | * 主从服务锁,主服务一直持有锁,断开连接,从服务获得锁 34 | * 非线程安全,每个线程请单独创建实例 35 | * @author: zhaojie/zh_jie@163.com.com 36 | * @version: 2016年5月31日 下午3:48:36 37 | */ 38 | public class ZKHALock implements ZKLock{ 39 | private final static Logger logger = LoggerFactory.getLogger(ZKHALock.class); 40 | 41 | private final ZKChildCountListener countListener; 42 | private final ZKStateListener stateListener; 43 | private final ZKClient client; 44 | private final String lockPath; 45 | private String currentSeq; 46 | private Semaphore semaphore; 47 | 48 | private ZKHALock(final ZKClient client,final String lockPach) { 49 | this.client = client; 50 | this.lockPath = lockPach; 51 | 52 | countListener = new ZKChildCountListener() { 53 | @Override 54 | public void handleSessionExpired(String path, List children) throws Exception { 55 | //ignore 56 | } 57 | 58 | @Override 59 | public void handleChildCountChanged(String path, List children) throws Exception { 60 | if(check(currentSeq, children)){ 61 | semaphore.release(); 62 | } 63 | } 64 | }; 65 | 66 | stateListener = new ZKStateListener() { 67 | @Override 68 | public void handleStateChanged(KeeperState state) throws Exception { 69 | if(state == KeeperState.SyncConnected){//如果重新连接 70 | //如果重连后之前的节点已删除,并且lock处于等待状态,则重新创建节点,等待获得lock 71 | if(!client.exists(lockPach+"/"+currentSeq) || semaphore.availablePermits()==0){ 72 | String newPath = client.create(lockPath+"/1", null, CreateMode.EPHEMERAL_SEQUENTIAL); 73 | String[] paths = newPath.split("/"); 74 | currentSeq = paths[paths.length - 1]; 75 | } 76 | } 77 | } 78 | 79 | @Override 80 | public void handleSessionError(Throwable error) throws Exception { 81 | //ignore 82 | } 83 | 84 | @Override 85 | public void handleNewSession() throws Exception { 86 | //ignore 87 | } 88 | }; 89 | } 90 | 91 | /** 92 | * 创建ZKHALock实例的工厂方法 93 | * @param client 94 | * @param lockPach 95 | * @return 96 | * @return ZKHALock 97 | */ 98 | public static ZKHALock newInstance(final ZKClient client,final String lockPach){ 99 | if(!client.exists(lockPach)){ 100 | throw new ZKNoNodeException("The lockPath is not exists!,please create the node.[path:"+lockPach+"]"); 101 | } 102 | 103 | ZKHALock zkhaLock = new ZKHALock(client, lockPach); 104 | //对lockPath进行子节点数量的监听 105 | client.listenChildCountChanges(lockPach, zkhaLock.countListener); 106 | //对客户端连接状态进行监听 107 | client.listenStateChanges(zkhaLock.stateListener); 108 | return zkhaLock; 109 | } 110 | 111 | @Override 112 | public boolean lock() { 113 | //信号量为0,线程就会一直等待直到数据变成正数 114 | semaphore = new Semaphore(0); 115 | String newPath = client.create(lockPath+"/1", null, CreateMode.EPHEMERAL_SEQUENTIAL); 116 | String[] paths = newPath.split("/"); 117 | currentSeq = paths[paths.length - 1]; 118 | boolean getLock = false; 119 | try { 120 | semaphore.acquire(); 121 | getLock = true; 122 | } catch (InterruptedException e) { 123 | throw new ZKInterruptedException(e); 124 | } 125 | if (getLock) { 126 | logger.debug("get halock successful."); 127 | } else { 128 | logger.debug("failed to get halock."); 129 | } 130 | return getLock; 131 | } 132 | 133 | /** 134 | * 释放锁 135 | * @return 136 | * @return boolean 137 | * 如果释放锁成功返回true,否则返回false 138 | * 释放锁失败只有一种情况,就是线程正好获得锁,在释放之前, 139 | * 与服务器断开连接,并且会话过期,这时候服务器会自动删除EPHEMERAL_SEQUENTIAL节点。 140 | * 在会话过期之后再删除节点就会删除失败,因为路径已经不存在了。 141 | */ 142 | @Override 143 | public boolean unlock() { 144 | return client.delete(lockPath+"/"+currentSeq); 145 | } 146 | 147 | 148 | /** 149 | * 判断路径是否可以获得锁,如果checkPath 对应的序列是所有子节点中最小的,则可以获得锁。 150 | * @param checkPath 需要判断的路径 151 | * @param children 所有的子路径 152 | * @return boolean 如果可以获得锁返回true,否则,返回false; 153 | */ 154 | private boolean check(String checkPath, List children) { 155 | if(children==null || !children.contains(checkPath)){ 156 | return false; 157 | } 158 | //判断checkPath 是否是children中的最小值,如果是返回true,不是返回false 159 | Long chePathSeq = Long.parseLong(checkPath); 160 | boolean isLock = true; 161 | for (String path : children) { 162 | Long pathSeq = Long.parseLong(path); 163 | if (chePathSeq > pathSeq) { 164 | isLock = false; 165 | break; 166 | } 167 | } 168 | return isLock; 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/lock/ZKDistributedLock.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.lock; 17 | 18 | import java.util.Collections; 19 | import java.util.Comparator; 20 | import java.util.List; 21 | import java.util.concurrent.Semaphore; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | import org.apache.zookeeper.CreateMode; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | import com.api6.zkclient.ZKClient; 29 | import com.api6.zkclient.exception.ZKInterruptedException; 30 | import com.api6.zkclient.exception.ZKNoNodeException; 31 | import com.api6.zkclient.listener.ZKChildCountListener; 32 | 33 | /** 34 | * 分布式锁 35 | * 非线程安全,每个线程请单独创建实例 36 | * @author: zhaojie/zh_jie@163.com.com 37 | * @version: 2016年5月31日 下午3:48:36 38 | */ 39 | public class ZKDistributedLock implements ZKLock { 40 | private final static Logger logger = LoggerFactory.getLogger(ZKDistributedLock.class); 41 | private final ZKChildCountListener countListener; 42 | private final ZKClient client; 43 | private final String lockPath; 44 | private String currentSeq; 45 | private Semaphore semaphore; 46 | private String lockNodeData; 47 | 48 | private ZKDistributedLock(ZKClient client,String lockPach) { 49 | this.client = client; 50 | this.lockPath = lockPach; 51 | this.countListener = new ZKChildCountListener() { 52 | @Override 53 | public void handleSessionExpired(String path, List children) throws Exception { 54 | //ignore 55 | } 56 | 57 | @Override 58 | public void handleChildCountChanged(String path, List children) throws Exception { 59 | if(check(currentSeq, children)){ 60 | semaphore.release(); 61 | } 62 | } 63 | }; 64 | } 65 | 66 | /** 67 | * 创建分布式锁实例的工厂方法 68 | * @param client 69 | * @param lockPach 70 | * @return 71 | * @return ZKDistributedLock 72 | */ 73 | public static ZKDistributedLock newInstance(ZKClient client,String lockPach) { 74 | if(!client.exists(lockPach)){ 75 | throw new ZKNoNodeException("The lockPath is not exists!,please create the node.[path:"+lockPach+"]"); 76 | } 77 | ZKDistributedLock zkDistributedLock = new ZKDistributedLock(client, lockPach); 78 | //对lockPath进行子节点数量的监听 79 | client.listenChildCountChanges(lockPach,zkDistributedLock.countListener); 80 | return zkDistributedLock; 81 | } 82 | 83 | @Override 84 | public boolean lock(){ 85 | return lock(0); 86 | } 87 | 88 | /** 89 | * 获得锁 90 | * @param timeout 超时时间 91 | * 如果超时间大于0,则会在超时后直接返回false。 92 | * 如果超时时间小于等于0,则会等待直到获取锁为止。 93 | * @return 94 | * @return boolean 成功获得锁返回true,否则返回false 95 | */ 96 | public boolean lock(int timeout) { 97 | //信号量为0,线程就会一直等待直到数据变成正数 98 | semaphore = new Semaphore(0); 99 | String newPath = client.create(lockPath+"/1", lockNodeData, CreateMode.EPHEMERAL_SEQUENTIAL); 100 | String[] paths = newPath.split("/"); 101 | currentSeq = paths[paths.length - 1]; 102 | boolean getLock = false; 103 | try { 104 | if(timeout>0){ 105 | getLock = semaphore.tryAcquire(timeout, TimeUnit.MICROSECONDS); 106 | }else{ 107 | semaphore.acquire(); 108 | getLock = true; 109 | } 110 | } catch (InterruptedException e) { 111 | throw new ZKInterruptedException(e); 112 | } 113 | if (getLock) { 114 | logger.debug("get lock successful."); 115 | } else { 116 | logger.debug("failed to get lock."); 117 | } 118 | return getLock; 119 | } 120 | 121 | 122 | public void setLockNodeData(String lockNodeData){ 123 | this.lockNodeData = lockNodeData; 124 | } 125 | 126 | /** 127 | * 释放锁 128 | * @return 129 | * @return boolean 130 | * 如果释放锁成功返回true,否则返回false 131 | * 释放锁失败只有一种情况,就是线程正好获得锁,在释放之前, 132 | * 与服务器断开连接,这时候服务器会自动删除EPHEMERAL_SEQUENTIAL节点。 133 | * 在会话过期之后再删除节点就会删除失败,因为路径已经不存在了。 134 | */ 135 | @Override 136 | public boolean unlock() { 137 | client.unlistenChildChanges(lockPath, countListener); 138 | return client.delete(lockPath+"/"+currentSeq); 139 | } 140 | 141 | /** 142 | * 获得所有参与者的节点名称 143 | * @return 144 | * @return List 145 | */ 146 | public List getParticipantNodes(){ 147 | List children = client.getChildren(lockPath); 148 | Collections.sort 149 | ( 150 | children, 151 | new Comparator() 152 | { 153 | @Override 154 | public int compare(String lhs, String rhs) 155 | { 156 | return lhs.compareTo(rhs); 157 | } 158 | } 159 | ); 160 | return children; 161 | } 162 | 163 | /** 164 | * 判断路径是否可以获得锁,如果checkPath 对应的序列是所有子节点中最小的,则可以获得锁。 165 | * @param checkPath 需要判断的路径 166 | * @param children 所有的子路径 167 | * @return boolean 如果可以获得锁返回true,否则,返回false; 168 | */ 169 | private boolean check(String checkPath, List children) { 170 | if(children==null || !children.contains(checkPath)){ 171 | return false; 172 | } 173 | //判断checkPath 是否是children中的最小值,如果是返回true,不是返回false 174 | Long chePathSeq = Long.parseLong(checkPath); 175 | boolean isLock = true; 176 | for (String path : children) { 177 | Long pathSeq = Long.parseLong(path); 178 | if (chePathSeq > pathSeq) { 179 | isLock = false; 180 | break; 181 | } 182 | } 183 | return isLock; 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/leader/ZKLeaderSelector.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.leader; 17 | 18 | import java.util.List; 19 | import java.util.concurrent.Callable; 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.concurrent.Executors; 22 | import java.util.concurrent.Future; 23 | import java.util.concurrent.atomic.AtomicBoolean; 24 | import java.util.concurrent.atomic.AtomicReference; 25 | 26 | import org.apache.zookeeper.Watcher.Event.KeeperState; 27 | 28 | import com.api6.zkclient.ZKClient; 29 | import com.api6.zkclient.exception.ZKException; 30 | import com.api6.zkclient.listener.ZKStateListener; 31 | import com.api6.zkclient.lock.ZKDistributedLock; 32 | 33 | /** 34 | * 选举Leader 35 | * @author: zhaojie/zh_jie@163.com.com 36 | * @version: 2016年6月29日 下午8:51:03 37 | */ 38 | public class ZKLeaderSelector implements LeaderSelector { 39 | private final String id; 40 | private final ZKClient client; 41 | private final ZKDistributedLock lock; 42 | private final String leaderPath; 43 | private final ExecutorService executorService; 44 | private final ZKLeaderSelectorListener listener; 45 | private final ZKStateListener stateListener; 46 | private final AtomicBoolean isInterrupted = new AtomicBoolean(false); 47 | private final AtomicBoolean autoRequeue = new AtomicBoolean(false); 48 | private final AtomicReference> ourTask = new AtomicReference>(null); 49 | private final AtomicReference state = new AtomicReference(State.LATENT); 50 | 51 | private enum State 52 | { 53 | LATENT, 54 | STARTED, 55 | CLOSED 56 | } 57 | 58 | /** 59 | * 创建Leader选举对象 60 | * ZKLeaderSelector. 61 | * 62 | * @param id 每个Leader选举的参与者都有一个ID标识,用于区分各个参与者。 63 | * @param autoRequue 是否在由于网络问题造成与服务器断开连接后,自动参与到选举队列中。 64 | * @param client ZKClient 65 | * @param leaderPath 选举的路径 66 | * @param listener 成为Leader后执行的的监听器 67 | */ 68 | public ZKLeaderSelector(String id,Boolean autoRequue,ZKClient client, String leaderPath, ZKLeaderSelectorListener listener) { 69 | this.id = id; 70 | this.client = client; 71 | this.autoRequeue.set(autoRequue); 72 | this.leaderPath = leaderPath; 73 | this.lock = ZKDistributedLock.newInstance(client, leaderPath); 74 | this.lock.setLockNodeData(this.id); 75 | this.executorService = Executors.newSingleThreadExecutor(); 76 | this.listener = listener; 77 | 78 | this.stateListener = new ZKStateListener() { 79 | @Override 80 | public void handleStateChanged(KeeperState state) throws Exception { 81 | if(state == KeeperState.SyncConnected){//如果重新连接 82 | if(isInterrupted.get() == false) { 83 | requeue(); 84 | } 85 | } 86 | } 87 | 88 | @Override 89 | public void handleSessionError(Throwable error) throws Exception { 90 | //ignore 91 | } 92 | 93 | @Override 94 | public void handleNewSession() throws Exception { 95 | //ignore 96 | } 97 | }; 98 | } 99 | 100 | /** 101 | * 启动参与选举Leader 102 | * @return void 103 | */ 104 | @Override 105 | public void start() { 106 | if (!state.compareAndSet(State.LATENT, State.STARTED)) { 107 | throw new ZKException("Cannot be started more than once"); 108 | } 109 | client.listenStateChanges(stateListener); 110 | requeue(); 111 | } 112 | 113 | /** 114 | * 重新添加当前线程到选举队列 115 | * @return void 116 | */ 117 | @Override 118 | public synchronized void requeue() { 119 | if (state.get() != State.STARTED) { 120 | throw new ZKException("close() has already been called"); 121 | } 122 | 123 | 124 | isInterrupted.set(false); 125 | if(ourTask.get() != null) { 126 | ourTask.get().cancel(true); 127 | } 128 | 129 | Future task = executorService.submit(new Callable() { 130 | @Override 131 | public Void call() throws Exception { 132 | lock.lock(); 133 | listener.takeLeadership(client,ZKLeaderSelector.this); 134 | return null; 135 | } 136 | 137 | }); 138 | ourTask.set(task); 139 | } 140 | 141 | /** 142 | * 获得 143 | * @return 144 | * @return String 145 | */ 146 | @Override 147 | public String getLeader() { 148 | if(lock.getParticipantNodes().size()>0){ 149 | return client.getData(leaderPath+"/"+lock.getParticipantNodes().get(0)); 150 | } 151 | return null; 152 | } 153 | 154 | @Override 155 | public boolean isLeader(){ 156 | if (client.getCurrentState() == KeeperState.SyncConnected ){ 157 | if(lock.getParticipantNodes().size()>0){ 158 | return id.equals(client.getData(leaderPath+"/"+lock.getParticipantNodes().get(0))); 159 | } 160 | } 161 | 162 | return false; 163 | } 164 | /** 165 | * 获得当前的所有参与者的路径名 166 | * @return 167 | * @return List 168 | */ 169 | @Override 170 | public List getParticipantNodes(){ 171 | return lock.getParticipantNodes(); 172 | } 173 | 174 | /** 175 | * 终止等待成为Leader 176 | * @return void 177 | */ 178 | @Override 179 | public synchronized void interruptLeadership(){ 180 | Future task = ourTask.get(); 181 | if ( task != null ) { 182 | task.cancel(true); 183 | } 184 | isInterrupted.set(true); 185 | } 186 | 187 | /** 188 | * 关闭Leader选举 189 | * @return void 190 | */ 191 | @Override 192 | public synchronized void close() { 193 | if(!state.compareAndSet(State.STARTED, State.CLOSED)){ 194 | throw new ZKException("Already closed or has not been started"); 195 | } 196 | lock.unlock(); 197 | client.unlistenStateChanges(stateListener); 198 | executorService.shutdown(); 199 | ourTask.set(null); 200 | } 201 | 202 | 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/leader/ZKLeaderDelySelector.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.leader; 17 | 18 | import java.util.List; 19 | import java.util.concurrent.Callable; 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.concurrent.Executors; 22 | import java.util.concurrent.Future; 23 | import java.util.concurrent.atomic.AtomicBoolean; 24 | import java.util.concurrent.atomic.AtomicInteger; 25 | import java.util.concurrent.atomic.AtomicReference; 26 | 27 | import org.apache.zookeeper.CreateMode; 28 | import org.apache.zookeeper.Watcher.Event.KeeperState; 29 | 30 | import com.api6.zkclient.ZKClient; 31 | import com.api6.zkclient.exception.ZKException; 32 | import com.api6.zkclient.listener.ZKStateListener; 33 | import com.api6.zkclient.lock.ZKDistributedDelayLock; 34 | 35 | /** 36 | * 选举Leader 37 | * @author: zhaojie/zh_jie@163.com.com 38 | * @version: 2016年6月29日 下午8:51:03 39 | */ 40 | public class ZKLeaderDelySelector implements LeaderSelector { 41 | private final String id; 42 | private final ZKClient client; 43 | private final AtomicInteger delayTimeMillisAtomic = new AtomicInteger(5000); 44 | private final ZKDistributedDelayLock lock; 45 | private final String leaderPath; 46 | private final ExecutorService executorService; 47 | private final ZKLeaderSelectorListener listener; 48 | private final ZKStateListener stateListener; 49 | private final AtomicBoolean isInterrupted = new AtomicBoolean(false); 50 | private final AtomicBoolean autoRequeue = new AtomicBoolean(false); 51 | private final AtomicReference state = new AtomicReference(State.LATENT); 52 | private final AtomicReference> ourTask = new AtomicReference>(null); 53 | private String curentNodePath; 54 | 55 | 56 | private enum State 57 | { 58 | LATENT, 59 | STARTED, 60 | CLOSED 61 | } 62 | 63 | /** 64 | * 创建Leader选举对象 65 | * ZKLeaderSelector. 66 | * 67 | * @param id 每个Leader选举的参与者都有一个ID标识,用于区分各个参与者。 68 | * @param autoRequue 是否在由于网络问题造成与服务器断开连接后,自动参与到选举队列中。 69 | * @param delayTimeMillis 延迟选举的时间,主要是针对网络闪断的情况,给Leader以重连并继续成为Leader的机会,一般5秒合适。 70 | * @param client ZKClient 71 | * @param leaderPath 选举的路径 72 | * @param listener 成为Leader后执行的的监听器 73 | */ 74 | public ZKLeaderDelySelector(String id,Boolean autoRequue,Integer delayTimeMillis, ZKClient client, String leaderPath, ZKLeaderSelectorListener listener) { 75 | this.delayTimeMillisAtomic.set(delayTimeMillis); 76 | this.id = id; 77 | this.client = client; 78 | this.autoRequeue.set(autoRequue); 79 | this.leaderPath = leaderPath; 80 | this.lock = ZKDistributedDelayLock.newInstance(client, leaderPath); 81 | this.lock.setLockNodeData(this.id); 82 | this.executorService = Executors.newSingleThreadExecutor(); 83 | this.listener = listener; 84 | 85 | this.stateListener = new ZKStateListener() { 86 | @Override 87 | public void handleStateChanged(KeeperState state) throws Exception { 88 | if(state == KeeperState.SyncConnected){//如果重新连接 89 | if(isInterrupted.get() == false) { 90 | requeue(); 91 | } 92 | } 93 | } 94 | 95 | @Override 96 | public void handleSessionError(Throwable error) throws Exception { 97 | //ignore 98 | } 99 | 100 | @Override 101 | public void handleNewSession() throws Exception { 102 | //ignore 103 | } 104 | }; 105 | } 106 | 107 | /** 108 | * 启动参与选举Leader 109 | * @return void 110 | */ 111 | @Override 112 | public void start() { 113 | if (!state.compareAndSet(State.LATENT, State.STARTED)) { 114 | throw new ZKException("Cannot be started more than once"); 115 | } 116 | client.listenStateChanges(stateListener); 117 | requeue(); 118 | } 119 | 120 | /** 121 | * 重新添加当前线程到选举队列 122 | * @return void 123 | */ 124 | @Override 125 | public synchronized void requeue() { 126 | if (state.get() != State.STARTED) { 127 | throw new ZKException("close() has already been called"); 128 | } 129 | 130 | isInterrupted.set(false); 131 | 132 | if(ourTask.get() != null) { 133 | ourTask.get().cancel(true); 134 | } 135 | 136 | //添加到参与者节点中 137 | addParticipantNode(); 138 | 139 | Future task = executorService.submit(new Callable() { 140 | @Override 141 | public Void call() throws Exception { 142 | lock.lock(0,delayTimeMillisAtomic.get()); 143 | listener.takeLeadership(client,ZKLeaderDelySelector.this); 144 | return null; 145 | } 146 | 147 | }); 148 | ourTask.set(task); 149 | } 150 | 151 | /** 152 | * 获得Leader ID 153 | * @return 154 | * @return String 155 | */ 156 | @Override 157 | public String getLeader() { 158 | return client.getData(lock.getLockPath()); 159 | } 160 | 161 | /** 162 | * 是否是Leader 163 | * @return 164 | * @return boolean 165 | */ 166 | @Override 167 | public boolean isLeader(){ 168 | return lock.hasLock(); 169 | } 170 | /** 171 | * 获得当前的所有参与者的路径名 172 | * @return 173 | * @return List 174 | */ 175 | @Override 176 | public List getParticipantNodes(){ 177 | return client.getChildren(leaderPath+"/nodes"); 178 | } 179 | 180 | private void addParticipantNode() { 181 | String path = client.create(leaderPath+"/nodes/1", id, CreateMode.EPHEMERAL_SEQUENTIAL); 182 | curentNodePath = path; 183 | } 184 | 185 | private void removeParticipantNode() { 186 | client.delete(curentNodePath); 187 | } 188 | 189 | /** 190 | * 终止等待成为Leader 191 | * @return void 192 | */ 193 | @Override 194 | public synchronized void interruptLeadership(){ 195 | Future task = ourTask.get(); 196 | if ( task != null ) { 197 | task.cancel(true); 198 | } 199 | isInterrupted.set(true); 200 | } 201 | 202 | /** 203 | * 关闭Leader选举 204 | * @return void 205 | */ 206 | @Override 207 | public synchronized void close() { 208 | if(!state.compareAndSet(State.STARTED, State.CLOSED)){ 209 | throw new ZKException("Already closed or has not been started"); 210 | } 211 | client.unlistenStateChanges(stateListener); 212 | lock.unlock(); 213 | //从参与者节点列表中移除 214 | removeParticipantNode(); 215 | executorService.shutdown(); 216 | ourTask.set(null); 217 | } 218 | 219 | 220 | } 221 | -------------------------------------------------------------------------------- /src/test/java/com/api6/zkclient/ZKLeaderSelectorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient; 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.concurrent.Callable; 22 | import java.util.concurrent.CountDownLatch; 23 | import java.util.concurrent.TimeUnit; 24 | import java.util.concurrent.atomic.AtomicInteger; 25 | 26 | import org.apache.zookeeper.CreateMode; 27 | import org.junit.After; 28 | import org.junit.Before; 29 | import org.junit.Test; 30 | 31 | import com.api6.zkclient.leader.LeaderSelector; 32 | import com.api6.zkclient.leader.ZKLeaderSelector; 33 | import com.api6.zkclient.leader.ZKLeaderSelectorListener; 34 | import com.api6.zkclient.util.TestSystem; 35 | import com.api6.zkclient.util.TestUtil; 36 | import com.api6.zkclient.util.ZKServer; 37 | 38 | public class ZKLeaderSelectorTest { 39 | 40 | private TestSystem testSystem = TestSystem.getInstance(); 41 | private ZKServer zkServer = null; 42 | private ZKClient zkClient = null; 43 | @Before 44 | public void before() { 45 | zkServer = testSystem.getZKserver(); 46 | zkClient = ZKClientBuilder.newZKClient() 47 | .servers("localhost:"+zkServer.getPort()) 48 | .sessionTimeout(1000) 49 | .build(); 50 | } 51 | 52 | @After 53 | public void after(){ 54 | testSystem.cleanup(zkClient); 55 | } 56 | 57 | /** 58 | * 测试分布式锁 59 | * @throws Exception 60 | * @return void 61 | */ 62 | @Test 63 | public void testZKLeaderSeletor() throws Exception{ 64 | final String leaderPath = "/zk/leader"; 65 | final List msgList = new ArrayList(); 66 | final CountDownLatch latch = new CountDownLatch(20); 67 | final CountDownLatch latch1 = new CountDownLatch(20); 68 | zkClient.createRecursive(leaderPath, null, CreateMode.PERSISTENT); 69 | final AtomicInteger index = new AtomicInteger(0); 70 | for(int i=0;i<20;i++){ 71 | final String name = "server:"+index.get(); 72 | 73 | Thread thread1 = new Thread(new Runnable() { 74 | public void run() { 75 | final ZKClient zkClient1 = ZKClientBuilder.newZKClient() 76 | .servers("localhost:"+zkServer.getPort()) 77 | .sessionTimeout(1000) 78 | .build(); 79 | final ZKLeaderSelector selector = new ZKLeaderSelector(name, true, zkClient1, leaderPath, new ZKLeaderSelectorListener() { 80 | 81 | @Override 82 | public void takeLeadership(ZKClient client, LeaderSelector selector) { 83 | msgList.add(name+" I am the leader"); 84 | System.out.println(name+": I am the leader-"+selector.getLeader()); 85 | selector.close(); 86 | latch1.countDown(); 87 | } 88 | }); 89 | 90 | try { 91 | System.out.println(name+":waiting"); 92 | latch.await(); 93 | } catch (InterruptedException e1) { 94 | e1.printStackTrace(); 95 | } 96 | selector.start(); 97 | 98 | try { 99 | latch1.await(); 100 | 101 | } catch (InterruptedException e) { 102 | e.printStackTrace(); 103 | } 104 | } 105 | }); 106 | thread1.start(); 107 | latch.countDown(); 108 | index.getAndIncrement(); 109 | } 110 | 111 | 112 | //等待事件到达 113 | int size = TestUtil.waitUntil(20, new Callable() { 114 | @Override 115 | public Integer call() throws Exception { 116 | return msgList.size(); 117 | } 118 | 119 | }, TimeUnit.SECONDS, 100); 120 | 121 | assertThat(size).isEqualTo(20); 122 | } 123 | 124 | @Test 125 | public void testZKLeaderSeletor1() throws Exception{ 126 | final String leaderPath = "/zk/leader"; 127 | final List msgList = new ArrayList(); 128 | zkClient.createRecursive(leaderPath, null, CreateMode.PERSISTENT); 129 | 130 | final LeaderSelector selector = new ZKLeaderSelector("server1", true, zkClient, leaderPath, new ZKLeaderSelectorListener() { 131 | 132 | @Override 133 | public void takeLeadership(ZKClient client, LeaderSelector selector) { 134 | try { 135 | Thread.sleep(1000); 136 | } catch (InterruptedException e) { 137 | e.printStackTrace(); 138 | } 139 | msgList.add("server1 I am the leader"); 140 | System.out.println("server1: I am the leader-"+selector.getLeader()); 141 | } 142 | }); 143 | 144 | 145 | 146 | Thread thread1 = new Thread(new Runnable() { 147 | public void run() { 148 | final ZKClient zkClient1 = ZKClientBuilder.newZKClient() 149 | .servers("localhost:"+zkServer.getPort()) 150 | .sessionTimeout(1000) 151 | .build(); 152 | final LeaderSelector selector = new ZKLeaderSelector("server2", true, zkClient1, leaderPath, new ZKLeaderSelectorListener() { 153 | 154 | @Override 155 | public void takeLeadership(ZKClient client, LeaderSelector selector) { 156 | try { 157 | Thread.sleep(1000); 158 | } catch (InterruptedException e) { 159 | e.printStackTrace(); 160 | } 161 | 162 | msgList.add("server2 I am the leader"); 163 | System.out.println("server2: I am the leader-"+selector.getLeader()); 164 | 165 | selector.close(); 166 | } 167 | }); 168 | selector.start(); 169 | } 170 | }); 171 | 172 | selector.start(); 173 | thread1.start(); 174 | Thread.sleep(1000); 175 | zkClient.reconnect(); 176 | 177 | 178 | 179 | //等待事件到达 180 | int size = TestUtil.waitUntil(3, new Callable() { 181 | @Override 182 | public Integer call() throws Exception { 183 | return msgList.size(); 184 | } 185 | 186 | }, TimeUnit.SECONDS, 100); 187 | System.out.println(msgList); 188 | assertThat(size).isEqualTo(3); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/lock/ZKDistributedDelayLock.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.lock; 17 | 18 | import java.util.UUID; 19 | import java.util.concurrent.Callable; 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.concurrent.Executors; 22 | import java.util.concurrent.Semaphore; 23 | import java.util.concurrent.atomic.AtomicBoolean; 24 | import java.util.concurrent.atomic.AtomicInteger; 25 | 26 | import org.apache.zookeeper.CreateMode; 27 | import org.apache.zookeeper.Watcher.Event.KeeperState; 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | 31 | import com.api6.zkclient.ZKClient; 32 | import com.api6.zkclient.exception.ZKException; 33 | import com.api6.zkclient.exception.ZKNoNodeException; 34 | import com.api6.zkclient.exception.ZKNodeExistsException; 35 | import com.api6.zkclient.listener.ZKNodeListener; 36 | import com.api6.zkclient.listener.ZKStateListener; 37 | 38 | /** 39 | * 带延迟获取的分布式锁 40 | * 此分布式锁主要针对网络闪断的情况。 41 | * 不带延迟功能的分布式锁:某个线程获取了分布式锁,在网络发生闪断,ZooKeeper删除了临时节点,那么就会释放锁。 42 | * 带延迟功能的分布式锁:例如设置了delayTimeMillis的值为5000,那么在发生网络闪断ZooKeeper删除了临时节点后5秒内重新连上,则当前线程还有依旧可以重新获得锁。 43 | * 44 | * 非线程安全,每个线程请单独创建实例 45 | * @author: zhaojie/zh_jie@163.com.com 46 | * @version: 2016年5月31日 下午3:48:36 47 | */ 48 | public class ZKDistributedDelayLock implements ZKLock { 49 | private final static Logger logger = LoggerFactory.getLogger(ZKDistributedDelayLock.class); 50 | private final ExecutorService executorService; 51 | private final ZKNodeListener nodeListener; 52 | private final ZKStateListener stateListener; 53 | private final ZKClient client; 54 | private final String lockPath; 55 | private Semaphore semaphore; 56 | private final AtomicBoolean hasLock = new AtomicBoolean(false); 57 | //锁的值一定要唯一,且不允许为null,这里采用UUID 58 | private String lockNodeData = UUID.randomUUID().toString(); 59 | private final AtomicInteger delayTimeMillis = new AtomicInteger(0); 60 | 61 | private ZKDistributedDelayLock(final ZKClient client,String lockPach) { 62 | this.client = client; 63 | this.lockPath = lockPach; 64 | this.executorService = Executors.newSingleThreadExecutor(); 65 | this.nodeListener = new ZKNodeListener() { 66 | 67 | @Override 68 | public void handleSessionExpired(String path) throws Exception {} 69 | 70 | @Override 71 | public void handleDataDeleted(String path) throws Exception { 72 | if( !executorService.isTerminated() ){//如果没有取消获取锁 73 | //异步方式 74 | executorService.submit(new Callable() { 75 | @Override 76 | public Void call() throws Exception { 77 | if(!hasLock()){//如果当前没有持有锁 78 | //为了解决网络闪断问题,先等待一段时间,再重新竞争锁 79 | Thread.sleep(delayTimeMillis.longValue()); 80 | //如果之前获得锁的线程解除了锁定,则所有等待的线程都重新尝试,这里使得信号量加1 81 | semaphore.release(); 82 | } 83 | return null; 84 | } 85 | }); 86 | } 87 | } 88 | 89 | @Override 90 | public void handleDataCreated(String path, Object data) throws Exception { 91 | } 92 | 93 | @Override 94 | public void handleDataChanged(String path, Object data) throws Exception {} 95 | }; 96 | 97 | this.stateListener = new ZKStateListener() { 98 | @Override 99 | public void handleStateChanged(KeeperState state) throws Exception { 100 | if(state == KeeperState.SyncConnected){//如果重新连接 101 | if( !executorService.isTerminated() ){//如果没有取消获取锁 102 | //异步方式 103 | executorService.submit(new Callable() { 104 | @Override 105 | public Void call() throws Exception { 106 | if(hasLock.get()){//现在持有锁 107 | //重新创建节点 108 | try { 109 | client.create(lockPath+"/lock", lockNodeData, CreateMode.EPHEMERAL); 110 | } catch (ZKNodeExistsException e) { 111 | try { 112 | if (!lockNodeData.equals(client.getData(lockPath+"/lock"))) {//如果节点不是自己创建的,则证明已失去锁 113 | hasLock.set(false); 114 | } 115 | } catch (ZKNoNodeException e2) { 116 | //ignore 117 | } 118 | } 119 | } 120 | return null; 121 | } 122 | }); 123 | } 124 | } 125 | } 126 | 127 | @Override 128 | public void handleSessionError(Throwable error) throws Exception {} 129 | 130 | @Override 131 | public void handleNewSession() throws Exception { 132 | } 133 | }; 134 | } 135 | 136 | /** 137 | * 创建分布式锁实例的工厂方法 138 | * @param client 139 | * @param lockPach 140 | * @return 141 | * @return ZKDistributedLock 142 | */ 143 | public static ZKDistributedDelayLock newInstance(ZKClient client,String lockPach) { 144 | if(!client.exists(lockPach)){ 145 | throw new ZKNoNodeException("The lockPath is not exists!,please create the node.[path:"+lockPach+"]"); 146 | } 147 | ZKDistributedDelayLock zkDistributedDelayLock = new ZKDistributedDelayLock(client, lockPach); 148 | client.listenNodeChanges(lockPach+"/lock", zkDistributedDelayLock.nodeListener); 149 | client.listenStateChanges(zkDistributedDelayLock.stateListener); 150 | try { 151 | client.create(lockPach+"/nodes", null, CreateMode.PERSISTENT); 152 | } catch (ZKNodeExistsException e) { 153 | //已被其他线程创建,这里忽略就可以 154 | } 155 | 156 | return zkDistributedDelayLock; 157 | } 158 | 159 | @Override 160 | public boolean lock(){ 161 | return lock(0); 162 | } 163 | 164 | /** 165 | * 获得锁,默认的延迟时间5000毫秒 166 | * @param timeout 超时时间 167 | * 如果超时间大于0,则会在超时后直接返回false。 168 | * 如果超时时间小于等于0,则会等待直到获取锁为止。 169 | * @return 170 | * @return boolean 成功获得锁返回true,否则返回false 171 | */ 172 | public boolean lock(int timeout) { 173 | return lock(timeout,5000); 174 | } 175 | 176 | /** 177 | * 获得锁路径 178 | * @return 179 | * @return String 180 | */ 181 | public String getLockPath(){ 182 | return lockPath+"/lock"; 183 | } 184 | 185 | /** 186 | * 187 | * @param timeout 188 | * @param delayTimeMillis 189 | * @return 190 | * @return boolean 191 | */ 192 | public boolean lock(int timeout,int delayTimeMillis){ 193 | this.delayTimeMillis.set(delayTimeMillis); 194 | long startTime = System.currentTimeMillis(); 195 | while (true) { 196 | try { 197 | //信号量为0,线程就会一直等待直到数据变成正数 198 | semaphore = new Semaphore(0); 199 | client.create(lockPath+"/lock", lockNodeData, CreateMode.EPHEMERAL); 200 | hasLock.set(true); 201 | return true; 202 | } catch (ZKNodeExistsException e) { 203 | try { 204 | semaphore.acquire(); 205 | } catch (InterruptedException interruptedException) { 206 | return false; 207 | } 208 | } 209 | //超时处理 210 | if (timeout > 0 && (System.currentTimeMillis() - startTime) >= timeout) { 211 | return false; 212 | } 213 | } 214 | } 215 | 216 | 217 | /** 218 | * 设置锁存储的值,一定要唯一,且不允许为null 219 | *默认使用UUID动态生成 220 | * @param lockNodeData 221 | * @return void 222 | */ 223 | public void setLockNodeData(String lockNodeData){ 224 | if(lockNodeData==null){ 225 | throw new ZKException("lockNodeData can not be null!"); 226 | } 227 | this.lockNodeData = lockNodeData; 228 | } 229 | 230 | /** 231 | * 判断是否持有锁 232 | * @return 233 | * @return boolean 234 | */ 235 | public boolean hasLock(){ 236 | return hasLock.get(); 237 | } 238 | 239 | @Override 240 | public boolean unlock() { 241 | if(hasLock()){ 242 | hasLock.set(false); 243 | client.unlistenNodeChanges(lockPath+"/lock", nodeListener); 244 | client.unlistenStateChanges(stateListener); 245 | executorService.shutdownNow(); 246 | boolean flag = client.delete(lockPath+"/lock"); 247 | return flag; 248 | } 249 | throw new ZKException("not locked can not unlock!"); 250 | } 251 | 252 | } 253 | -------------------------------------------------------------------------------- /src/test/java/com/api6/zkclient/ZKLeaderDelaySelectorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient; 17 | import static org.assertj.core.api.Assertions.assertThat; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.concurrent.Callable; 22 | import java.util.concurrent.CountDownLatch; 23 | import java.util.concurrent.TimeUnit; 24 | import java.util.concurrent.atomic.AtomicInteger; 25 | 26 | import org.apache.zookeeper.CreateMode; 27 | import org.junit.After; 28 | import org.junit.Before; 29 | import org.junit.Test; 30 | 31 | import com.api6.zkclient.leader.LeaderSelector; 32 | import com.api6.zkclient.leader.ZKLeaderDelySelector; 33 | import com.api6.zkclient.leader.ZKLeaderSelectorListener; 34 | import com.api6.zkclient.util.TestSystem; 35 | import com.api6.zkclient.util.TestUtil; 36 | import com.api6.zkclient.util.ZKServer; 37 | 38 | public class ZKLeaderDelaySelectorTest { 39 | 40 | private TestSystem testSystem = TestSystem.getInstance(); 41 | private ZKServer zkServer = null; 42 | private ZKClient zkClient = null; 43 | @Before 44 | public void before() { 45 | zkServer = testSystem.getZKserver(); 46 | zkClient = ZKClientBuilder.newZKClient() 47 | .servers("localhost:"+zkServer.getPort()) 48 | .sessionTimeout(1000) 49 | .build(); 50 | } 51 | 52 | @After 53 | public void after(){ 54 | testSystem.cleanup(zkClient); 55 | } 56 | 57 | /** 58 | * 测试分布式锁 59 | * @throws Exception 60 | * @return void 61 | */ 62 | @Test 63 | public void testZKLeaderDelaySeletor() throws Exception{ 64 | final String leaderPath = "/zk/leader"; 65 | final List msgList = new ArrayList(); 66 | final CountDownLatch latch = new CountDownLatch(5); 67 | final CountDownLatch latch1 = new CountDownLatch(5); 68 | zkClient.createRecursive(leaderPath, null, CreateMode.PERSISTENT); 69 | final AtomicInteger index = new AtomicInteger(0); 70 | for(int i=0;i<5;i++){ 71 | final String name = "server:"+index.get(); 72 | 73 | Thread thread1 = new Thread(new Runnable() { 74 | public void run() { 75 | final ZKClient zkClient1 = ZKClientBuilder.newZKClient() 76 | .servers("localhost:"+zkServer.getPort()) 77 | .sessionTimeout(1000) 78 | .build(); 79 | final LeaderSelector selector = new ZKLeaderDelySelector(name, true,3000, zkClient1, leaderPath, new ZKLeaderSelectorListener() { 80 | 81 | @Override 82 | public void takeLeadership(ZKClient client, LeaderSelector selector) { 83 | msgList.add(name+" I am the leader"); 84 | System.out.println(name+": I am the leader-"+selector.getLeader()); 85 | System.out.println(selector.getParticipantNodes()); 86 | try { 87 | Thread.sleep(100); 88 | } catch (InterruptedException e) { 89 | // TODO Auto-generated catch block 90 | e.printStackTrace(); 91 | } 92 | selector.close(); 93 | latch1.countDown(); 94 | } 95 | }); 96 | 97 | try { 98 | latch.await(); 99 | } catch (InterruptedException e1) { 100 | e1.printStackTrace(); 101 | } 102 | selector.start(); 103 | 104 | try { 105 | latch1.await(); 106 | 107 | } catch (InterruptedException e) { 108 | e.printStackTrace(); 109 | } 110 | } 111 | }); 112 | thread1.start(); 113 | latch.countDown(); 114 | index.getAndIncrement(); 115 | } 116 | 117 | 118 | //等待事件到达 119 | int size = TestUtil.waitUntil(5, new Callable() { 120 | @Override 121 | public Integer call() throws Exception { 122 | return msgList.size(); 123 | } 124 | 125 | }, TimeUnit.SECONDS, 100); 126 | 127 | assertThat(size).isEqualTo(5); 128 | } 129 | 130 | 131 | @Test 132 | public void testZKLeaderDelaySeletor1() throws Exception{ 133 | final String leaderPath = "/zk/leader"; 134 | final List msgList = new ArrayList(); 135 | zkClient.createRecursive(leaderPath, null, CreateMode.PERSISTENT); 136 | 137 | final LeaderSelector selector = new ZKLeaderDelySelector("server1", true,3000, zkClient, leaderPath, new ZKLeaderSelectorListener() { 138 | 139 | @Override 140 | public void takeLeadership(ZKClient client, LeaderSelector selector) { 141 | msgList.add("server1 I am the leader"); 142 | 143 | try { 144 | Thread.sleep(1000); 145 | } catch (InterruptedException e) { 146 | e.printStackTrace(); 147 | } 148 | System.out.println("server1: I am the leader-"+selector.getLeader()); 149 | zkClient.reconnect(); 150 | } 151 | }); 152 | 153 | 154 | 155 | Thread thread1 = new Thread(new Runnable() { 156 | public void run() { 157 | final ZKClient zkClient1 = ZKClientBuilder.newZKClient() 158 | .servers("localhost:"+zkServer.getPort()) 159 | .sessionTimeout(1000) 160 | .build(); 161 | final LeaderSelector selector = new ZKLeaderDelySelector("server2", true,3000, zkClient1, leaderPath, new ZKLeaderSelectorListener() { 162 | 163 | @Override 164 | public void takeLeadership(ZKClient client, LeaderSelector selector) { 165 | msgList.add("server2 I am the leader"); 166 | 167 | try { 168 | Thread.sleep(1000); 169 | } catch (InterruptedException e) { 170 | e.printStackTrace(); 171 | } 172 | System.out.println("server2: I am the leader-"+selector.getLeader()); 173 | selector.close(); 174 | } 175 | }); 176 | selector.start(); 177 | } 178 | }); 179 | 180 | selector.start(); 181 | thread1.start(); 182 | 183 | 184 | //等待事件到达 185 | int size = TestUtil.waitUntil(1, new Callable() { 186 | @Override 187 | public Integer call() throws Exception { 188 | return msgList.size(); 189 | } 190 | 191 | }, TimeUnit.SECONDS, 100); 192 | 193 | assertThat(size).isEqualTo(1); 194 | } 195 | 196 | @Test 197 | public void testZKLeaderDelaySeletor2() throws Exception{ 198 | final String leaderPath = "/zk/leader"; 199 | final List msgList = new ArrayList(); 200 | zkClient.createRecursive(leaderPath, null, CreateMode.PERSISTENT); 201 | 202 | final LeaderSelector selector = new ZKLeaderDelySelector("server1", true,1, zkClient, leaderPath, new ZKLeaderSelectorListener() { 203 | 204 | @Override 205 | public void takeLeadership(ZKClient client, LeaderSelector selector) { 206 | try { 207 | Thread.sleep(1000); 208 | } catch (InterruptedException e) { 209 | e.printStackTrace(); 210 | } 211 | msgList.add("server1 I am the leader"); 212 | System.out.println("server1: I am the leader-"+selector.getLeader()); 213 | zkClient.reconnect(); 214 | } 215 | }); 216 | 217 | 218 | 219 | Thread thread1 = new Thread(new Runnable() { 220 | public void run() { 221 | final ZKClient zkClient1 = ZKClientBuilder.newZKClient() 222 | .servers("localhost:"+zkServer.getPort()) 223 | .sessionTimeout(1000) 224 | .build(); 225 | final LeaderSelector selector = new ZKLeaderDelySelector("server2", true,1, zkClient1, leaderPath, new ZKLeaderSelectorListener() { 226 | 227 | @Override 228 | public void takeLeadership(ZKClient client, LeaderSelector selector) { 229 | try { 230 | Thread.sleep(1000); 231 | } catch (InterruptedException e) { 232 | e.printStackTrace(); 233 | } 234 | msgList.add("server2 I am the leader"); 235 | System.out.println("server2: I am the leader-"+selector.getLeader()); 236 | selector.close(); 237 | } 238 | }); 239 | selector.start(); 240 | } 241 | }); 242 | 243 | selector.start(); 244 | thread1.start(); 245 | 246 | 247 | //等待事件到达 248 | int size = TestUtil.waitUntil(3, new Callable() { 249 | @Override 250 | public Integer call() throws Exception { 251 | return msgList.size(); 252 | } 253 | 254 | }, TimeUnit.SECONDS, 100); 255 | 256 | assertThat(size).isEqualTo(3); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /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 | #ZKClient 2 | 3 | 这是一个ZooKeeper客户端,实现了断线重连,会话过期重连,永久监听,子节点数据变化的监听。并且加入了常用功能,例如分布式锁,Leader选举,分布式队列等。 4 | 5 | * * * 6 | #使用说明 7 | 8 | ##一、创建ZKClient对象 9 | 有两种方式可以方便的创建ZKClient对象。 10 | 11 | 1. 使用构造函数创建 12 | 13 | String address = "localhost:2181"; 14 | ZKClient zkClient1 = new ZKClient(address); 15 | ZKClient zkClient2 = new ZKClient(address,500); 16 | ZKClient zkClient3 = new ZKClient(address,500,1000*60); 17 | ZKClient zkClient4 = new ZKClient(address,500,1000*60,new BytesSerializer()); 18 | ZKClient zkClient5 = new ZKClient(address,500,1000*60,new BytesSerializer(),Integer.MAX_VALUE); 19 | ZKClient zkClient6 = new ZKClient(address,500,1000*60,new BytesSerializer(),Integer.MAX_VALUE,2); 20 | 21 | 2. 使用辅助类创建 22 | 23 | String address = "localhost:2181"; 24 | ZKClient zkClient = ZKClientBuilder.newZKClient(address) 25 | .sessionTimeout(1000)//可选 26 | .serializer(new SerializableSerializer())//可选 27 | .eventThreadPoolSize(1)//可选 28 | .retryTimeout(1000*60)//可选 29 | .connectionTimeout(Integer.MAX_VALUE)//可选 30 | .build();创建实例 31 | 32 | * * * 33 | 34 | ##二、节点的新增、更新、删除和获取 35 | ###新增节点 36 | 1. 常规新增节点 37 | 38 | 父节点不存在会抛出异常 39 | 40 | zkClient.create("/test1", "123", CreateMode.EPHEMERAL); 41 | zkClient.create("/test1-1",123,CreateMode.EPHEMERAL_SEQUENTIAL); 42 | zkClient.create("/test1-2",123,CreateMode.PERSISTENT); 43 | zkClient.create("/test1-3",123,CreateMode.PERSISTENT_SEQUENTIAL); 44 | 45 | 2. 递归新增节点(新增节点及其父节点) 46 | 47 | 如果父节点不存在会被一并创建。 48 | 49 | 对于PERSISTENT类型的节点,递归创建,父节点和子节点都创建为PERSISTENT。 50 | 51 | 对于EPHEMERAL类型的节点,递归创建,父节点都是PERSISTENT类型,而最后一级节点才是EPHEMERAL类型。(因为EPHEMERAL不能拥有子节点) 52 | 53 | 注意:第二个参数为节点的值,指的的最后一级节点的值。 54 | 55 | String path = "/test8/1/2/3"; 56 | //递归创建节点及父节点 57 | zkClient.createRecursive(path, "abc", CreateMode.PERSISTENT); 58 | zkClient.createRecursive(path, "123", ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT); 59 | 60 | 3. 特殊的EPHEMERAL类型节点 61 | 62 | 特殊类型的EPHEMERAL节点,该节点在会话失效被删除后,重新连接会被自动创建。 63 | 64 | String path = "/test8/1/2/3"; 65 | //EPHEMERAL类型节点 66 | zkClient.createEphemerale(path, "123", false); 67 | zkClient.createEphemerale(path, "123",ZooDefs.Ids.CREATOR_ALL_ACL, false); 68 | //EPHEMERAL_SEQUENTIAL类型 69 | String retPath = zkClient.createEphemerale(path, "456", true); 70 | 71 | 72 | ###更新节点数据 73 | 74 | String path = "/test"; 75 | zkClient.setData(path, "456"); 76 | //带期望版本号的更新,如果真实的版本号与期望版本号不一致会更新失败,抛出异常 77 | zkClient.setData(path, "123", 2); 78 | 79 | 80 | ###删除节点 81 | 1. 常规删除 82 | 83 | boolean flag = zkClient.delete("/test");//删除任意版本 84 | boolean flag = zkClient.delete("/test",1);//删除指定版本 85 | 86 | 2. 递归删除(删除节点及子节点) 87 | 88 | String path = "/test"; 89 | zkClient.deleteRecursive(path);//如果/test下有多个子节点,会被一并删除 90 | 91 | 92 | ###获取节点数据 93 | 94 | String path = "/test"; 95 | zkClient.getData(path); //如果节点不存在抛出异常 96 | zkClient.getData(path, true); //如果节点不存在返回null 97 | Stat stat = new Stat(); 98 | zkClient.getData(path, stat); //获得数据以及stat信息 99 | 100 | 101 | ###等待节点创建 102 | 103 | String path = "/test"; 104 | //等待直到超时或者节点创建成功。 105 | zkClient.waitUntilExists(path, TimeUnit.MILLISECONDS, 1000*5); 106 | 107 | * * * 108 | 109 | ##三、监听相关 110 | 111 | 注意:对于断开连接时间过长造成的会话过期,由于服务器端在会话过期后会删除客户端设置的监听。 112 | 113 | 即便客户端在会话过期后自动连接成功,但是在会话过期到会话重建这段时间客户端监听的节点仍可能发生了改变, 114 | 115 | 而具体哪些变了或是没变,客户端是无法感知到的。 116 | 117 | 为了避免丢掉任何数据改变的事件,所有的监听器的都有一个回调方法(handleSessionExpired),用来处理会话过期这种特殊情况。 118 | 119 | 对于handleSessionExpired方法可以这样处理,以节点监听为例: 120 | 121 | public void handleSessionExpired(String path) throws Exception { 122 | //在会话过期后,getData方法会阻塞直到会话重建,并且连接成功,获取数据后才会返回。 123 | Object data = zkClient.getData(path); 124 | //这里把返回的data与上次改变后的数据做对比如果改变了,则执行数据改变后的业务逻辑 125 | //do someting 126 | } 127 | 128 | 129 | ###节点监听 130 | 131 | String path = "/test"; 132 | ZKClient zkClient = ZKClientBuilder.newZKClient() 133 | .servers("localhost:2181") 134 | .build(); 135 | //注册监听 136 | zkClient.listenNodeChanges(path, new ZKNodeListener() { 137 | @Override 138 | public void handleSessionExpired(String path) throws Exception { 139 | System.out.println("session expired ["+path+"]"); 140 | } 141 | 142 | @Override 143 | public void handleDataDeleted(String path) throws Exception { 144 | System.out.println("node is deleted ["+path+"]"); 145 | } 146 | 147 | @Override 148 | public void handleDataCreated(String path, Object data) throws Exception { 149 | System.out.println("node is created ["+path+"]"); 150 | } 151 | 152 | @Override 153 | public void handleDataChanged(String path, Object data) throws Exception { 154 | System.out.println("node is changed ["+path+"]"); 155 | } 156 | }); 157 | ###子节点数量监听 158 | 159 | String path = "/parent"; 160 | ZKClient zkClient = ZKClientBuilder.newZKClient("localhost:2181").build(); 161 | //注册监听 162 | zkClient.listenChildCountChanges(path, new ZKChildCountListener() { 163 | 164 | @Override 165 | public void handleSessionExpired(String path, List children) throws Exception {//会话过期 166 | System.out.println("children:"+children); 167 | } 168 | 169 | @Override 170 | public void handleChildCountChanged(String path, List children) throws Exception {//节点数量发生改变 171 | System.out.println("children:"+children); 172 | } 173 | }); 174 | 175 | ###子节点数量和子节点数据变化监听 176 | 177 | String path = "/test"; 178 | ZKClient zkClient = ZKClientBuilder.newZKClient() 179 | .servers("localhost:2181") 180 | .build(); 181 | 182 | //注册监听 183 | zkClient.listenChildDataChanges(path, new ZKChildDataListener() { 184 | @Override 185 | public void handleSessionExpired(String path, Object data) throws Exception {//会话过期 186 | System.out.println("children:"+children); 187 | } 188 | 189 | @Override 190 | public void handleChildDataChanged(String path, Object data) throws Exception {//子节点数据发生改变 191 | System.out.println("the child data is changed:[path:"+path+",data:"+data+"]"); 192 | } 193 | 194 | @Override 195 | public void handleChildCountChanged(String path, List children) throws Exception {//子节点数量发生改变 196 | System.out.println("children:"+children); 197 | } 198 | }); 199 | 200 | ###客户端状态监听 201 | 202 | ZKClient zkClient = ZKClientBuilder.newZKClient() 203 | .servers("localhost:2181") 204 | .build(); 205 | //注册监听 206 | zkClient.listenStateChanges(new ZKStateListener() { 207 | 208 | @Override 209 | public void handleStateChanged(KeeperState state) throws Exception {//客户端状态发生改变 210 | System.out.println("state is "+state); 211 | } 212 | 213 | @Override 214 | public void handleSessionError(Throwable error) throws Exception {//创建session出错 215 | //ignore 216 | } 217 | 218 | @Override 219 | public void handleNewSession() throws Exception {//会话创建 220 | System.out.println("new session"); 221 | } 222 | }); 223 | 224 | * * * 225 | 226 | ##四、扩展功能 227 | 228 | ###分布式锁 229 | 230 | ZKClient zkClient = ZKClientBuilder.newZKClient() 231 | .servers("localhost:2181") 232 | .build(); 233 | final String lockPath = "/zk/lock"; 234 | zkClient.createRecursive(lockPath, null, CreateMode.PERSISTENT); 235 | //创建分布式锁, 非线程安全类,每个线程请创建单独实例。 236 | ZKDistributedLock lock = ZKDistributedLock.newInstance(zkClient,lockPath); 237 | 238 | lock.lock(); //获得锁 239 | 240 | //do someting 241 | 242 | lock.unlock();//释放锁 243 | 244 | ###延迟获取分布式锁 245 | 网络闪断会引起短暂的网络断开,这个时间很短,但是却给分布式锁带来很大的麻烦。 246 | 247 | 例如线程1获得了分布式锁,但是却发生网络的短暂断开,如果这期间ZooKeeper服务器删除临时节点,分布式锁就会释放,其实线程1的工作一直在进行,并没有完成也没有宕机。 248 | 249 | 显然,由于网络短暂的断开引起的锁释放一般情况下不是我们想要的。所以提供了,具有延迟功能的分布式锁。 250 | 251 | 如果线程1获得了锁,并发生网络闪断,在ZK服务器删除临时节点后,那么其他线程并不会立即尝试获取锁,而是会等待一段时间,如果再这段时间内线程1成功连接上,那么线程1将继续持有锁。 252 | 253 | String lockPath = "/zk/delaylock"; 254 | ZKClient zkClient1 = ZKClientBuilder.newZKClient() 255 | .servers("localhost:"+zkServer.getPort()) 256 | .sessionTimeout(1000) 257 | .build(); 258 | ZKDistributedDelayLock lock = ZKDistributedDelayLock.newInstance(zkClient1, lockPach); 259 | lock.lock(); //获得锁 260 | 261 | //do someting 262 | 263 | lock.unlock();//释放锁 264 | 265 | ###Leader选举 266 | Leader选举是异步的,只需要调用selector.start()就会启动并参与Leader选举,如果成为了主服务,则会执行监听器ZKLeaderSelectorListener。 267 | 268 | ZKClient zkClient = ZKClientBuilder.newZKClient() 269 | .servers("localhost:2181") 270 | .build(); 271 | final String lockPath = "/zk/leader"; 272 | final ZKLeaderSelector selector = new ZKLeaderSelector("service1", true, zkClient1, leaderPath, 273 | new ZKLeaderSelectorListener() { 274 | 275 | //成为Leader后的回调函数 276 | @Override 277 | public void takeLeadership(ZKClient client, ZKLeaderSelector selector) { 278 | //在这里可以编写,成为主服务后需要做的事情。 279 | System.out.println("I am the leader-"+selector.getLeader()); 280 | } 281 | }); 282 | //启动并参与Leader选举 283 | selector.start(); 284 | 285 | //获得当前主服务的ID 286 | selector.getLeader(); 287 | 288 | //如果要退出Leader选举 289 | selector.close(); 290 | 291 | 292 | ###延迟Leader选举 293 | 例如线程1被选举为Leader,但是却发生网络的短暂断开,如果zooKeeper服务器删除临时节点,其他线程会认为Leader宕机,会重新选举Leader,其实线程1的工作一直在继续并没有宕机。 294 | 295 | 显然,由于网络短暂的断开引起的这种情况不是我们需要的。 296 | 297 | 延迟Leader选举类是这样解决的,如果线程1成为了Leader,并发生网络闪断,在ZK服务器删除临时节点后,那么其他线程并不会立即竞争Leader,而是会等待一段时间。 298 | 299 | 如果再这段时间内线程1成功连接上,那么线程1保持Leader的角色。 300 | 301 | ZKClient zkClient = ZKClientBuilder.newZKClient() 302 | .servers("localhost:2181") 303 | .build(); 304 | String lockPath = "/zk/delayleader"; 305 | //延迟3秒选举 306 | LeaderSelector selector = new ZKLeaderDelySelector("server1", true,3000, zkClient, leaderPath, new ZKLeaderSelectorListener() { 307 | 308 | @Override 309 | public void takeLeadership(ZKClient client, LeaderSelector selector) { 310 | msgList.add("server1 I am the leader"); 311 | 312 | try { 313 | Thread.sleep(1000); 314 | } catch (InterruptedException e) { 315 | e.printStackTrace(); 316 | } 317 | System.out.println("server1: I am the leader-"+selector.getLeader()); 318 | zkClient.reconnect(); 319 | } 320 | }); 321 | //启动并参与Leader选举 322 | selector.start(); 323 | 324 | //获得当前主服务的ID 325 | selector.getLeader(); 326 | 327 | //如果要退出Leader选举 328 | selector.close(); 329 | 330 | 331 | ###分布式队列 332 | 333 | ZKClient zkClient = ZKClientBuilder.newZKClient() 334 | .servers("localhost:2181") 335 | .build(); 336 | final String rootPath = "/zk/queue"; 337 | zkClient.createRecursive(rootPath, null, CreateMode.PERSISTENT); 338 | 339 | //创建分布式队列对象 340 | ZKDistributedQueue queue = new ZKDistributedQueue(zkClient, rootPath); 341 | 342 | queue.offer("123");//放入元素 343 | 344 | String value = queue.poll();//删除并获取顶部元素 345 | 346 | String value = queue.peek(); //获取顶部元素,不会删除 347 | 348 | ###主从服务锁 349 | 350 | ZKClient zkClient = ZKClientBuilder.newZKClient() 351 | .servers("localhost:2181") 352 | .build(); 353 | final String lockPath = "/zk/halock"; 354 | zkClient.createRecursive(rootPath, null, CreateMode.PERSISTENT); 355 | 356 | //创建锁, 非线程安全类,每个线程请创建单独实例。 357 | ZKHALock lock = ZKHALock.newInstance(zkClient, lockPach); 358 | 359 | lock.lock();//尝试获取锁 360 | 361 | //获取锁成功,当前线程变为主服务。 362 | //直到主服务宕机或与zk服务端断开连接,才会释放锁。 363 | //此时从服务尝试获得锁,选取一个从服务变为主服务 364 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/connection/ZKConnectionImpl.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.connection; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.util.Date; 21 | import java.util.concurrent.Callable; 22 | import java.util.concurrent.TimeUnit; 23 | import java.util.concurrent.locks.Lock; 24 | import java.util.concurrent.locks.ReentrantLock; 25 | 26 | import javax.security.auth.login.Configuration; 27 | 28 | import org.apache.zookeeper.KeeperException; 29 | import org.apache.zookeeper.KeeperException.ConnectionLossException; 30 | import org.apache.zookeeper.KeeperException.SessionExpiredException; 31 | import org.apache.zookeeper.Watcher; 32 | import org.apache.zookeeper.Watcher.Event.KeeperState; 33 | import org.apache.zookeeper.ZooKeeper; 34 | import org.slf4j.Logger; 35 | import org.slf4j.LoggerFactory; 36 | 37 | import com.api6.zkclient.event.ZKEventLock; 38 | import com.api6.zkclient.exception.ZKException; 39 | import com.api6.zkclient.exception.ZKInterruptedException; 40 | import com.api6.zkclient.exception.ZKTimeoutException; 41 | import com.api6.zkclient.util.ExceptionUtil; 42 | 43 | /** 44 | * 负责连接、关闭与服务器的连接,实现了断线重连,会话过期重连等机制 45 | * @author: zhaojie/zh_jie@163.com.com 46 | */ 47 | public class ZKConnectionImpl implements ZKConnection { 48 | private static final Logger LOG = LoggerFactory.getLogger(ZKConnectionImpl.class); 49 | 50 | protected static final String JAVA_LOGIN_CONFIG_PARAM = "java.security.auth.login.config"; 51 | protected static final String ZK_SASL_CLIENT = "zookeeper.sasl.client"; 52 | protected static final String ZK_LOGIN_CONTEXT_NAME_KEY = "zookeeper.sasl.clientconfig"; 53 | 54 | //session超时时间 55 | private static final int DEFAULT_SESSION_TIMEOUT = 30000; 56 | private final String servers;//服务器地址 57 | private final int sessionTimeout;//会话超时时间 58 | 59 | //原生zookeeper客户端 60 | private ZooKeeper zooKeeper = null; 61 | //ReentrantLock 比起synchronized有更多的操作空间, 62 | //类似定时锁等候和可中断锁等候,都可以实现,同时性能更优 63 | private Lock connectionLock = new ReentrantLock(); 64 | 65 | private final ZKEventLock eventLock = new ZKEventLock(); 66 | private KeeperState currentState;//ZooKeeper连接状态 67 | protected final long retryTimeout;//重试超时时间 68 | private boolean isZkSaslEnabled;// 69 | 70 | /** 71 | * 根据给出的服务地址,创建连接 72 | * @param zkServers zookeeper服务地址, 73 | * 格式采用,逗号分隔主机:端口号。 74 | * 例如 "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002" 75 | * 如果改变了根目录例如: "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002/app/a" 76 | */ 77 | public ZKConnectionImpl(String servers) { 78 | this(servers, DEFAULT_SESSION_TIMEOUT,-1); 79 | } 80 | 81 | 82 | public ZKConnectionImpl(String servers,int sessionTimeOut) { 83 | this(servers, sessionTimeOut,-1); 84 | } 85 | 86 | /** 87 | * 根据给出的服务地址,和会话超时时间创建连接 88 | * @param servers 服务器地址 89 | * @param sessionTimeOut 会话超时时间 90 | */ 91 | public ZKConnectionImpl(String servers, int sessionTimeout,int retryTimeout) { 92 | this.servers = servers; 93 | this.sessionTimeout = sessionTimeout; 94 | this.isZkSaslEnabled = isZkSaslEnabled(); 95 | this.retryTimeout = retryTimeout; 96 | } 97 | 98 | @Override 99 | public void connect(Watcher watcher) { 100 | connectionLock.lock(); 101 | try { 102 | if (zooKeeper != null) { 103 | throw new ZKException("connection is already connected to server"); 104 | } 105 | try { 106 | LOG.info("connecting to server [" + servers + "]"); 107 | zooKeeper = new ZooKeeper(servers, sessionTimeout, watcher); 108 | LOG.info("connected to server [" + servers + "]"); 109 | } catch (IOException e) { 110 | throw new ZKException("connecting to server failed [" + servers+ "]", e); 111 | } 112 | } finally { 113 | connectionLock.unlock(); 114 | } 115 | } 116 | 117 | public void reconnect(Watcher watcher) { 118 | try { 119 | acquireEventLock(); 120 | close(); 121 | connect(watcher); 122 | } catch (InterruptedException e) { 123 | throw new ZKInterruptedException(e); 124 | } finally { 125 | releaseEventLock(); 126 | } 127 | } 128 | 129 | 130 | @Override 131 | public T retryUntilConnected(Callable callable) 132 | throws ZKInterruptedException,ZKTimeoutException, ZKException, RuntimeException { 133 | final long operationStartTime = System.currentTimeMillis(); 134 | while (true) { 135 | if(zooKeeper == null){ 136 | throw new ZKException("the connection is closed."); 137 | } 138 | try { 139 | T retVal = callable.call(); 140 | return retVal; 141 | } catch (ConnectionLossException e) { 142 | // 当前线程交出CPU权限,让CPU去执行其他的线程 143 | // 主要是处理事件监听,将currentState置为'Disconnected'状态 144 | Thread.yield(); 145 | LOG.debug("Connection Disconnected waitForRetry..."); 146 | //等待重试 147 | waitForRetry(); 148 | } catch (SessionExpiredException e) { 149 | // 当前线程交出CPU权限,让CPU去执行其他的线程 150 | // 主要是处理事件监听,将currentState置为'Expired'状态 151 | Thread.yield(); 152 | LOG.debug("Session Expired waitForRetry..."); 153 | //等待重试 154 | waitForRetry(); 155 | } catch (KeeperException e) { 156 | throw ZKException.create(e); 157 | } catch (InterruptedException e) { 158 | throw new ZKInterruptedException(e); 159 | } catch (Exception e) { 160 | throw ExceptionUtil.convertToRuntimeException(e); 161 | } 162 | // 检查是否超时,如果超时抛出异常终止尝试连接 163 | if (retryTimeout > -1 && (System.currentTimeMillis() - operationStartTime) >= retryTimeout) { 164 | throw new ZKTimeoutException("retry connect timeout (" + retryTimeout + " milli seconds)"); 165 | } 166 | } 167 | } 168 | 169 | /** 170 | * 等待直到超时或者成功连接 171 | * @return void 172 | */ 173 | private void waitForRetry() { 174 | if (retryTimeout < 0) { 175 | waitUntilConnected(Integer.MAX_VALUE, TimeUnit.MILLISECONDS); 176 | return; 177 | } 178 | waitUntilConnected(retryTimeout, TimeUnit.MILLISECONDS); 179 | } 180 | 181 | 182 | 183 | @Override 184 | public boolean waitUntilConnected(long timeout, TimeUnit timeUnit) throws ZKInterruptedException { 185 | if (isZkSaslEnabled) { 186 | return waitForKeeperState(KeeperState.SaslAuthenticated, timeout, timeUnit); 187 | } else { 188 | return waitForKeeperState(KeeperState.SyncConnected, timeout, timeUnit); 189 | } 190 | } 191 | 192 | 193 | /** 194 | * 等待直到连接处于某个状态才停止,如果超时返回false,当正确处于某个状态返回true 195 | * 这里使用到了EventLock的 stateChangedCondition 条件, 196 | * 如果当前状态不是期待的状态,则此时线程处于等待状态。 197 | * 1.如果事件监听器发现ZooKeeper状态改变,则会标记stateChangedCondition,当前线程被唤醒, 198 | * 当前线程继续判断是否是期待的状态,如果是则返回true,如果不是,则线程继续处于等待状态,直到下次ZooKeeper状态改变,重复上述操作。 199 | * 2.如果等待超时则直接返回false。 200 | * @param keeperState ZooKeeper状态 201 | * @param timeout 超时时间 202 | * @param timeUnit 时间单位 203 | * @return 204 | * @throws ZKInterruptedException 205 | * @return boolean 206 | */ 207 | private boolean waitForKeeperState(KeeperState keeperState, long timeout, TimeUnit timeUnit) throws ZKInterruptedException { 208 | Date timeoutDate = new Date(System.currentTimeMillis() + timeUnit.toMillis(timeout)); 209 | 210 | LOG.info("Waiting for ZooKeeper state " + keeperState); 211 | //使用可中断锁 212 | acquireEventLockInterruptibly(); 213 | try { 214 | boolean stillWaiting = true; 215 | while (currentState != keeperState) { 216 | if (!stillWaiting) { 217 | return false; 218 | } 219 | stillWaiting = getEventLock().getStateChangedCondition().awaitUntil(timeoutDate); 220 | if (currentState == KeeperState.AuthFailed && isZkSaslEnabled) { 221 | throw new ZKException("authorization failed"); 222 | } 223 | } 224 | LOG.info("ZooKeeper State is " + currentState); 225 | return true; 226 | } catch (InterruptedException e) { 227 | throw new ZKInterruptedException(e); 228 | } finally { 229 | releaseEventLock(); 230 | } 231 | } 232 | 233 | @Override 234 | public void close() throws InterruptedException { 235 | connectionLock.lock(); 236 | try { 237 | if (zooKeeper != null) { 238 | LOG.debug("closing the connetion [" + servers+ "]"); 239 | zooKeeper.close(); 240 | zooKeeper = null; 241 | LOG.debug("the connection is closed [" + servers+ "]"); 242 | } 243 | } finally { 244 | connectionLock.unlock(); 245 | } 246 | } 247 | 248 | 249 | private boolean isZkSaslEnabled() { 250 | boolean isSecurityEnabled = false; 251 | boolean zkSaslEnabled = Boolean.parseBoolean(System.getProperty(ZK_SASL_CLIENT, "true")); 252 | String zkLoginContextName = System.getProperty(ZK_LOGIN_CONTEXT_NAME_KEY, "Client"); 253 | 254 | if (!zkSaslEnabled) { 255 | LOG.warn("Client SASL has been explicitly disabled with " + ZK_SASL_CLIENT); 256 | return false; 257 | } 258 | 259 | String loginConfigFile = System.getProperty(JAVA_LOGIN_CONFIG_PARAM); 260 | if (loginConfigFile != null && loginConfigFile.length() > 0) { 261 | LOG.info("JAAS File name: " + loginConfigFile); 262 | File configFile = new File(loginConfigFile); 263 | if (!configFile.canRead()) { 264 | throw new IllegalArgumentException("File " + loginConfigFile + "cannot be read."); 265 | } 266 | 267 | try { 268 | Configuration loginConf = Configuration.getConfiguration(); 269 | isSecurityEnabled = loginConf.getAppConfigurationEntry(zkLoginContextName) != null; 270 | } catch (Exception e) { 271 | throw new ZKException(e); 272 | } 273 | } 274 | return isSecurityEnabled; 275 | } 276 | 277 | @Override 278 | public String getServers() { 279 | return servers; 280 | } 281 | 282 | @Override 283 | public ZooKeeper getZooKeeper() { 284 | return zooKeeper; 285 | } 286 | 287 | @Override 288 | public ZKEventLock getEventLock(){ 289 | return eventLock; 290 | } 291 | 292 | @Override 293 | public void acquireEventLock(){ 294 | getEventLock().lock(); 295 | } 296 | @Override 297 | public void releaseEventLock(){ 298 | getEventLock().unlock(); 299 | } 300 | 301 | /** 302 | * 获得可中断的EventLock,在获取锁的时候被阻塞后,如果当前线程抛出interrupt信号, 303 | * 此线程会被唤醒并处理InterruptedException异常,不会一直阻塞下去 304 | * @return void 305 | * @author: zhaojie/zh_jie@163.com 306 | * @version: 2016年5月24日 上午9:05:55 307 | */ 308 | @Override 309 | public void acquireEventLockInterruptibly() { 310 | try { 311 | getEventLock().lockInterruptibly(); 312 | } catch (InterruptedException e) { 313 | throw new ZKInterruptedException(e); 314 | } 315 | } 316 | 317 | @Override 318 | public KeeperState getCurrentState() { 319 | return currentState; 320 | } 321 | 322 | @Override 323 | public void setCurrentState(KeeperState currentState) { 324 | acquireEventLock(); 325 | try { 326 | this.currentState = currentState; 327 | } finally { 328 | releaseEventLock(); 329 | } 330 | } 331 | 332 | /** 333 | * 添加认证信息,用于访问被ACL保护的节点 334 | * @param scheme 335 | * @param auth 336 | * @return void 337 | */ 338 | @Override 339 | public void addAuthInfo(final String scheme, final byte[] auth) { 340 | retryUntilConnected(new Callable() { 341 | @Override 342 | public Object call() throws Exception { 343 | getZooKeeper().addAuthInfo(scheme, auth); 344 | return null; 345 | } 346 | }); 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /src/main/java/com/api6/zkclient/watcher/ZKWatcherProcess.java: -------------------------------------------------------------------------------- 1 | /** 2 | *Copyright 2016 zhaojie 3 | * 4 | *Licensed under the Apache License, Version 2.0 (the "License"); 5 | *you may not use this file except in compliance with the License. 6 | *You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | *Unless required by applicable law or agreed to in writing, software 11 | *distributed under the License is distributed on an "AS IS" BASIS, 12 | *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | *See the License for the specific language governing permissions and 14 | *limitations under the License. 15 | */ 16 | package com.api6.zkclient.watcher; 17 | 18 | import java.util.List; 19 | import java.util.Map.Entry; 20 | import java.util.Set; 21 | import java.util.concurrent.CopyOnWriteArraySet; 22 | 23 | import org.apache.zookeeper.WatchedEvent; 24 | import org.apache.zookeeper.Watcher.Event.EventType; 25 | import org.apache.zookeeper.Watcher.Event.KeeperState; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | import com.api6.zkclient.ZKClient; 30 | import com.api6.zkclient.event.ZKEvent; 31 | import com.api6.zkclient.event.ZKEventThreadPool; 32 | import com.api6.zkclient.exception.ZKNoNodeException; 33 | import com.api6.zkclient.listener.ZKChildDataListener; 34 | import com.api6.zkclient.listener.ZKListener; 35 | import com.api6.zkclient.listener.ZKNodeListener; 36 | import com.api6.zkclient.listener.ZKStateListener; 37 | 38 | /** 39 | * 事件处理类,接受监听器,并回调 40 | * @author: zhaojie/zh_jie@163.com.com 41 | */ 42 | public class ZKWatcherProcess { 43 | private static Logger LOG = LoggerFactory.getLogger(ZKWatcherProcess.class); 44 | private final ZKEventThreadPool eventThreadPool; 45 | private final ZKClient client; 46 | 47 | public ZKWatcherProcess(ZKClient zkClient) { 48 | this.client = zkClient; 49 | //创建事件处理线程池 50 | eventThreadPool = new ZKEventThreadPool(zkClient.getEventThreadPoolSize()); 51 | } 52 | 53 | /** 54 | * 停止处理 55 | * @return void 56 | */ 57 | public void stop(){ 58 | eventThreadPool.destory(); 59 | } 60 | 61 | public void processStateChanged(WatchedEvent event) { 62 | final KeeperState keeperState = event.getState(); 63 | LOG.info("ZooKeeper state is changed [" + keeperState + "] ."); 64 | 65 | //这里需要更新一下,ZooKeeper客户端的状态 66 | client.setCurrentState(keeperState); 67 | if (client.getShutdownTrigger()) { 68 | return; 69 | } 70 | //获取所有的事件监听器 71 | Set listeners = client.getStateListenerSet(); 72 | 73 | //状态改变事件处理 74 | for (final ZKStateListener stateListener : listeners) { 75 | eventThreadPool.submit(new ZKEvent("State changed to " + keeperState + " sent to " + stateListener) { 76 | @Override 77 | public void run() throws Exception { 78 | stateListener.handleStateChanged(keeperState); 79 | } 80 | }); 81 | } 82 | 83 | //如果会话过期,要重新连接服务器 84 | if (event.getState() == KeeperState.Expired) { 85 | try { 86 | //会话过期,重新连接 87 | client.reconnect(); 88 | //会话过期事件处理 89 | for (final ZKStateListener stateListener : listeners) { 90 | ZKEvent zkEvent = new ZKEvent("New session event sent to " + stateListener) { 91 | @Override 92 | public void run() throws Exception { 93 | stateListener.handleNewSession(); 94 | } 95 | }; 96 | eventThreadPool.submit(zkEvent); 97 | } 98 | } catch (final Exception e) { 99 | LOG.info("Unable to re-establish connection. Notifying consumer of the following exception: ", e); 100 | //会话过期后重连出错,事件处理 101 | for (final ZKStateListener stateListener : listeners) { 102 | eventThreadPool.submit(new ZKEvent("Session establishment error[" + e + "] sent to " + stateListener) { 103 | @Override 104 | public void run() throws Exception { 105 | stateListener.handleSessionError(e); 106 | } 107 | }); 108 | } 109 | } 110 | } 111 | } 112 | 113 | 114 | /** 115 | * 处理子节点变化事件 116 | * @param event 117 | * @return void 118 | */ 119 | public void processChildChanged(final WatchedEvent event){ 120 | final String path = event.getPath(); 121 | final Set listeners = client.getChildListenerMap().get(path); 122 | //提交事件监听进行处理 123 | submitChildEvent(listeners,path,event.getType()); 124 | } 125 | 126 | /** 127 | * 处理数据改变事件 128 | * @param event 129 | * @return void 130 | */ 131 | public void processNodeChanged(final WatchedEvent event){ 132 | final String path = event.getPath(); 133 | final EventType eventType = event.getType(); 134 | final Set listeners = client.getNodeListenerMap().get(path); 135 | if (listeners == null || listeners.isEmpty()) { 136 | return; 137 | } 138 | 139 | //如果listeners中如果有ZKChildDataListener类型的监听器, 140 | //证明是此节点是某个节点的子节点,并监听此节点的数据变化 141 | //这里要单独拿出来,用于触发ZKChildDataListener 142 | final Set childDataChangeListners = new CopyOnWriteArraySet<>(); 143 | final Set nodeListners = new CopyOnWriteArraySet<>(); 144 | 145 | classifyListeners(listeners,nodeListners,childDataChangeListners); 146 | 147 | //提交事件监听进行处理 148 | submitNodeEvent(nodeListners,childDataChangeListners,path,eventType); 149 | 150 | //当前节点作为子节点数据变化 151 | if(eventType == EventType.NodeDataChanged){ 152 | //提交事件监听进行处理 153 | submitChildDataEvent(childDataChangeListners,path,eventType); 154 | } 155 | } 156 | 157 | /** 158 | * 触发所有的监听器,用于在会话失效后调用。 159 | * 会话失效后,服务端会取消watch, 160 | * 如果在会话失效后与重连这段时间内有数据发生变化,监听器是无法监听到的, 161 | * 所以要调用此方法,触发所有监听器,告诉监听器,会话失效,可能存在数据变化(不是一定有变化)。 162 | * @param eventType 163 | * @return void 164 | * @author: zhaojie/zh_jie@163.com 165 | */ 166 | public void processAllNodeAndChildListeners(final WatchedEvent event){ 167 | LOG.debug("processAllNodeAndChildListeners...."); 168 | //对选取的监听器进行处理 169 | for (Entry> entry : client.getNodeListenerMap().entrySet()) { 170 | Set nodeListners = new CopyOnWriteArraySet(); 171 | Set childDataChangeListners = new CopyOnWriteArraySet(); 172 | Set listeners = entry.getValue(); 173 | 174 | classifyListeners(listeners,nodeListners,childDataChangeListners); 175 | //提交事件监听进行处理 176 | submitNodeEvent(nodeListners, childDataChangeListners, entry.getKey(), event.getType()); 177 | } 178 | //获取所有的节点监听器,并进行处理 179 | for (Entry> entry : client.getChildListenerMap().entrySet()) { 180 | //提交事件监听进行处理 181 | submitChildEvent(entry.getValue(),entry.getKey(),event.getType()); 182 | } 183 | } 184 | 185 | /** 186 | * 对listeners进行分类整理,把{@link ZKNodeListener}类型的放入nodeListeners,把{@link ZKChildDataListener} 类型的放入childDataChangeListeners 187 | * @param listeners 188 | * @param nodeListeners 189 | * @param childDataChangeListeners 190 | * @return void 191 | */ 192 | private void classifyListeners(Set listeners,Set nodeListeners,Set childDataChangeListeners){ 193 | for(ZKListener listener : listeners){ 194 | if(listener instanceof ZKChildDataListener){ 195 | if(!childDataChangeListeners.contains(listener)){ 196 | childDataChangeListeners.add(listener); 197 | } 198 | }else{ 199 | if(!nodeListeners.contains(listener)){ 200 | nodeListeners.add(listener); 201 | } 202 | } 203 | } 204 | } 205 | 206 | 207 | /** 208 | * 提交节点改变相关的事件进行处理 209 | * @param listeners 210 | * @param childDataChangeListners 211 | * @param path 212 | * @param eventType 213 | * @return void 214 | */ 215 | private void submitNodeEvent(final Set listeners,final Set childDataChangeListners,final String path,final EventType eventType ){ 216 | if (listeners != null && !listeners.isEmpty()) { 217 | for (final ZKListener listener : listeners) { 218 | ZKEvent zkEvent = new ZKEvent("Node of " + path + " changed sent to " + listener) { 219 | @Override 220 | public void run() throws Exception { 221 | //原生的zookeeper 的监听只生效一次,重新注册监听 222 | LOG.debug("Rewatch the path ["+path+"] by exists method"); 223 | boolean flag = client.exists(path, true); 224 | LOG.debug("Rewatched the path ["+path+"] by exists method"); 225 | try { 226 | LOG.debug("Rewatch and get changed data [path:"+path+" | EventType:"+eventType+"] by getData method"); 227 | Object data = client.getData(path, null); 228 | LOG.debug("Rewatched and return data ["+path+" | "+data+" | EventType:"+eventType+"] by getData method"); 229 | listener.handle(path, eventType, data); 230 | 231 | //响应了删除事件,但是在再次注册监听之前节点又被创建了,这样是无法重新监听到节点创建的 232 | //这里主动触发节点创建事件。 233 | if (eventType == EventType.NodeDeleted && flag) { 234 | listener.handle(path, EventType.NodeCreated, data); 235 | } 236 | } catch (ZKNoNodeException e) { 237 | //如果是节点不存在了,则只移除,ZKChildDataListener监听器 238 | client.unlistenNodeChanges(path, childDataChangeListners); 239 | //如果路径不存在,在调用client.getData(path,null)会抛出异常 240 | listener.handle(path, eventType, null); 241 | 242 | //如果是创建节点事件,并且在创建事件收到后,监听还没来得及重新注册,刚创建的节点已经被删除了。 243 | //对于这种情况,客户端就无法重新监听到节点的删除事件的,这里做特殊处理 244 | //主动触发删除的监听事件 245 | if (eventType == EventType.NodeCreated && !flag) { 246 | listener.handle(path, EventType.NodeDeleted, null); 247 | } 248 | } 249 | } 250 | }; 251 | eventThreadPool.submit(zkEvent); 252 | } 253 | } 254 | 255 | } 256 | 257 | /** 258 | * 提交子节点改变相关的事件进行处理 259 | * @param listeners 260 | * @param path 261 | * @param eventType 262 | * @return void 263 | */ 264 | private void submitChildEvent(final Set listeners,final String path,final EventType eventType){ 265 | if (listeners != null && !listeners.isEmpty()) { 266 | try { 267 | for (final ZKListener listener : listeners) { 268 | //创建事件,并在独立的事件处理线程池中执行 269 | ZKEvent zkEvent = new ZKEvent("Children of " + path + " changed sent to " + listener) { 270 | @Override 271 | public void run() throws Exception { 272 | //原生的zookeeper 的监听只生效一次,所以要重新注册父节点的监听 273 | LOG.debug("Rewatch the path ["+path+"] by exists method"); 274 | client.exists(path); 275 | LOG.debug("Rewatched the path ["+path+"] by exists method"); 276 | try { 277 | 278 | LOG.debug("Rewatch and get chilldren [path:"+path+" | EventType:"+eventType+"] by getChildren method"); 279 | //获取数据并重新监听子节点变化 280 | List children = client.getChildren(path); 281 | LOG.debug("Rewatched and return children [children:"+children+" | EventType:"+eventType+"] by getChildren method"); 282 | //子节点数量改变,如果当前路径path设置了ZKChildDataListener。 283 | //则尝试重新注册子节点数据变化的监听 284 | client.listenNewChildPathWithData(path,children); 285 | listener.handle(path, eventType, children); 286 | } catch (ZKNoNodeException e) { 287 | //如果路径不存在,在调用client.getChildren(path)会抛出异常 288 | listener.handle(path, eventType, null); 289 | } 290 | } 291 | }; 292 | eventThreadPool.submit(zkEvent); 293 | } 294 | } catch (Exception e) { 295 | LOG.error("Failed to fire child changed event. Unable to getChildren. ", e); 296 | } 297 | } 298 | } 299 | 300 | /** 301 | * 提交子节点数据改变的事件进行处理 302 | * @param listeners 303 | * @param path 304 | * @param eventType 305 | * @return void 306 | */ 307 | private void submitChildDataEvent(final Set listeners,final String path,final EventType eventType){ 308 | if (listeners != null && !listeners.isEmpty()) { 309 | for (final ZKListener listener : listeners) { 310 | ZKEvent zkEvent = new ZKEvent("Children of " + path + " changed sent to "+ listener) { 311 | @Override 312 | public void run() throws Exception { 313 | //原生的zookeeper 的监听只生效一次,重新注册监听 314 | LOG.debug("rewatch the path ["+path+"]"); 315 | client.exists(path, true); 316 | LOG.debug("rewatched the path ["+path+"]"); 317 | try { 318 | LOG.debug("Try to get child changed data [path:"+path+" | EventType:"+eventType+"]"); 319 | //在事件触发后到获取数据之间的时间,节点的值是可能改变的, 320 | //所以此处的获取也只是,获取到最新的值,而不一定是事件触发时的值 321 | //重新监听节点变化 322 | Object data = client.getData(path, null); 323 | LOG.debug("Child changed data is [path:"+path+" | data:"+data+" | EventType:"+eventType+"]"); 324 | listener.handle(path, eventType, data); 325 | } catch (ZKNoNodeException e) { 326 | //ignore 327 | } 328 | } 329 | }; 330 | eventThreadPool.submit(zkEvent); 331 | } 332 | } 333 | } 334 | } 335 | --------------------------------------------------------------------------------