![](https://img-blog.csdnimg.cn/direct/f5b81f9ce9ed4652a95f14d5ae745837.png)
5.1原生Zookeeper实现分布式锁案例
在 ZooKeeper 中,获取某个节点的子节点列表和在该节点下创建子节点是可以并发同时发生的,但是需要考虑以下几点:
-
ZooKeeper操作的串行化: ZooKeeper保证了对于同一个节点的写操作是串行化的,即同一时刻只能有一个客户端对某个节点进行写操作(创建、删除等)。这意味着如果有一个客户端正在对某个节点进行写操作(比如创建子节点),其他客户端的写操作会被阻塞,直到第一个操作完成。
-
读操作与写操作并发: 节点的读操作(如获取子节点列表)与写操作是可以并发进行的。即使有其他客户端在对节点进行写操作,不影响其他客户端对该节点的读操作。因此,在一个节点上同时进行读和写操作是可以的,但要注意写操作可能会影响到读取的结果,特别是当节点的数据在写操作执行过程中发生变化时。
-
并发操作的注意事项: 虽然获取子节点和创建子节点可以并发进行,但是在实际应用中,需要考虑并发操作可能带来的数据一致性和竞争条件。如果多个客户端同时创建相同名称的子节点,可能会导致其中一些操作失败或者产生竞争冲突。因此,应用程序需要具备一定的并发控制策略,如使用分布式锁或者版本控制来确保操作的正确性和一致性。
-
Watch机制的影响: 在获取子节点列表或创建子节点时,可以注册Watcher来监听节点的变化。当节点的子节点发生变化时,注册的Watcher会被触发,客户端可以得到通知并进行相应处理。Watcher机制可以帮助客户端实时获取节点状态的变化,但在使用过程中也需要注意它的异步性和触发机制。
总的来说,ZooKeeper支持并发地进行读取和写入操作,但在实际开发中需要考虑并发带来的潜在竞争和一致性问题,采取适当的并发控制策略以确保数据的正确性和系统的稳定性。
(1)分布式锁实现
package com.cookie.lock2;
import com.cookie.zkcase1.DistributeServer;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class DistributedLock {
private String connectString = "192.168.1.100:2181,192.168.1.101:2181,192.168.1.102:2181";
private int sessionTimeout = 2000;
private ZooKeeper zkClient;
private CountDownLatch connectLatch = new CountDownLatch(1);
private CountDownLatch waitLatch = new CountDownLatch(1);
private String preNodePath;
private String thisNode;
//获取连接,并且创建根节点
public DistributedLock() throws IOException, InterruptedException, KeeperException {
zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
public void process(WatchedEvent watchedEvent) {
//说明连接上了
if(watchedEvent.getState() == Event.KeeperState.SyncConnected){
connectLatch.countDown();
}
//如果监听到删除节点事件,并且删除的节点路径等于当前节点的前一个节点的路径,则释放当前节点的await
if(watchedEvent.getType() == Event.EventType.NodeDeleted && watchedEvent.getPath().equals(preNodePath)){
System.out.println("cutThread:"+Thread.currentThread().getName()+"醒了");
waitLatch.countDown();
}
}
});
//此处会一直阻塞,直到connectLatch计数器的数量减少为0才会往后执行
connectLatch.await();
Stat stat = zkClient.exists("/locks", false);
if(stat == null){
System.out.println("根节点不存在");
zkClient.create("/locks", "locks".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
//加锁方法
public void zkLock() throws InterruptedException, KeeperException {
thisNode = zkClient.create("/locks/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
List<String> children = zkClient.getChildren("/locks", false);
if(children.size() == 1){
//获取到锁
return;
} else {
Collections.sort(children);
//获取seq-000000
String subNode = thisNode.substring("/locks/".length());
int index = children.indexOf(subNode);
if(index == -1){
System.out.println("数据异常");
}else if(index == 0){
//获取倒锁
return;
} else {
//监听他的前一个节点
preNodePath = "/locks/"+ children.get(index-1);
//这里设置的true,所以监听代码在上面的process方法中
System.out.println("curThread: "+Thread.currentThread().getName()+",curPath: "+thisNode+",preNodePath : "+preNodePath);
zkClient.getData(preNodePath, true, null);
//此处如果监听没有结束,需要阻塞这里
waitLatch.await();
}
}
}
//解锁方法(删除当前节点)
public void unZklock() throws InterruptedException, KeeperException {
zkClient.delete(thisNode,-1);
}
}
(2)分布式锁测试
package com.cookie.lock2;
import org.apache.zookeeper.KeeperException;
import java.io.IOException;
public class zkLockTest {
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
for(int i=0;i<10;i++){
new Thread(new Runnable() {
public void run() {
try {
DistributedLock lock1 = new DistributedLock();
lock1.zkLock();
System.out.println("线程"+Thread.currentThread().getName()+" 启动,获取到锁" + System.currentTimeMillis());
Thread.sleep(2000);
lock1.unZklock();
//System.out.println("线程"+Thread.currentThread().getName()+"释放锁"+ System.currentTimeMillis());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (KeeperException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
}
}
![](https://img-blog.csdnimg.cn/direct/e2f5ecc0b0f14ee8a908312e697b08b3.png)
![](https://img-blog.csdnimg.cn/direct/e93faf44554445beaace28939a0a661a.png)
![](https://img-blog.csdnimg.cn/direct/b21ce1ce46454b72ac7c0567b96d84a3.png)
![](https://img-blog.csdnimg.cn/direct/2fcc7138bfb5459196746edaac6ac1aa.png)
因为zk在创建、删除节点是不支持并发的,所以当其中一个线程创建好了/locks节点后,其余线程重复创建就会报这个错,所以这里我的解决方案是在启动测试代码之前,手动在zk客户端命令行添加/locks根节点,这样就可以解决。
手动创建号/locks根节点后,然后启动测试代码,控制台打印如下:
5.2Curator框架实现分布式锁案例
1)原生的 Java API 开发存在的问题
2)Curator 是一个专门解决分布式锁的框架,解决了原生 JavaAPI 开发分布式遇到的问题。
3)Curator 案例实操
(1)添加依赖
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>4.3.0</version>
</dependency>
package com.cookie.lock3;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.checkerframework.checker.units.qual.C;
public class CuratorLockTest {
private static final String rootNode = "/locks";
private String connectString = "192.168.1.100:2181,192.168.1.101:2181,192.168.1.102:2181";
// connection 超时时间
private int connectionTimeout = 2000;
// session 超时时间
private int sessionTimeout = 2000;
public static void main(String[] args) {
for(int i =0;i<10;i++){
new Thread(new Runnable() {
public void run() {
CuratorLockTest curatorLockTest = new CuratorLockTest();
try {
curatorLockTest.test();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}).start();
}
}
public void test() throws Exception {
InterProcessMutex lock1 = new InterProcessMutex(getCuratorFramework(), rootNode);
lock1.acquire();
System.out.println("线程"+Thread.currentThread().getName()+"获取到了锁");
Thread.sleep(1000);
lock1.release();
System.out.println("线程"+Thread.currentThread().getName()+"释放了锁");
}
private CuratorFramework getCuratorFramework() {
ExponentialBackoffRetry policy = new ExponentialBackoffRetry(3000, 3);
CuratorFramework client = CuratorFrameworkFactory.builder().connectString(connectString)
.connectionTimeoutMs(connectionTimeout)
.sessionTimeoutMs(sessionTimeout)
.retryPolicy(policy)
.build();
client.start();
return client;
}
}
在Apache Curator中,CuratorFramework
是用于与Apache ZooKeeper交互的高级客户端。在创建CuratorFramework
实例时,可以通过CuratorFrameworkFactory.builder()
方法来构建客户端,并设置连接参数。
在你提供的代码片段中,.connectionTimeoutMs(connectionTimeout)
和.sessionTimeoutMs(sessionTimeout)
分别设置了连接超时和会话超时的时间。
-
connectionTimeoutMs(connectionTimeout)
:这个方法设置了连接超时时间,即客户端与ZooKeeper服务器建立连接的最大等待时间。如果在这个时间内无法建立连接,将抛出连接超时异常。 -
sessionTimeoutMs(sessionTimeout)
:这个方法设置了会话超时时间,即客户端与ZooKeeper服务器之间的会话保持时间。如果客户端在会话超时时间内没有发送心跳或者维持连接,ZooKeeper服务器将认为客户端已经断开,然后触发会话过期事件。
一般来说,连接超时时间应该设置得足够长以确保在网络状况不佳或者服务器负载高时仍能成功连接,会话超时时间则应根据应用程序的需求来设置,以确保会话不会因为长时间没有心跳而被关闭。
![](https://img-blog.csdnimg.cn/direct/d90d85f50d4644c7bce6374b09c77e80.png)
![](https://img-blog.csdnimg.cn/direct/eb3e34c8cf124e289123d8f3b8e2b71b.png)
6.1选举机制
6.2生产集群安装多少zk合适?
6.3常用命令
ls、get、create、delete