03 - 客户端 API 操作、服务器动态上下线监听以及分布式锁案例

目录

1、客户端 API 操作

1.1、IDEA 环境搭建

1)创建一个工程:zookeeper

2)添加pom文件

3)拷贝log4j.properties文件到项目根目录

4)创建包名com.atguigu.zk

5)创建类名称zkClient

1.2、创建 ZooKeeper 客户端

1.3、创建子节点

1.4、获取子节点并监听节点变化

1.5、判断 Znode 是否存在

2、客户端向服务端写数据流程

3、服务器动态上下线监听案

3.1、需求

3.2、需求分析

3.3、具体实现

3.4、测试

4、ZooKeeper 分布式锁案例

4.1、原生 Zookeeper 实现分布式锁案例

4.2、Curator 框架实现分布式锁案例


1、客户端 API 操作

1.1、IDEA 环境搭建

1)创建一个工程:zookeeper

2)添加pom文件

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.8.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.5.7</version>
    </dependency>
</dependencies>

3)拷贝log4j.properties文件到项目根目录

        需要在项目的 src/main/resources 目录下,新建一个文件,命名为“log4j.properties”,在 文件中填入。

log4j.rootLogger=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n log4j.appender.logfile=org.apache.log4j.FileAppender log4j.appender.logfile.File=target/spring.log log4j.appender.logfile.layout=org.apache.log4j.PatternLayout log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

4)创建包名com.atguigu.zk

5)创建类名称zkClient

1.2、创建 ZooKeeper 客户端

	// 注意:逗号前后不能有空格
	private static String connectString =
	"hadoop102:2181,hadoop103:2181,hadoop104:2181";
	private static int sessionTimeout = 2000;
	private ZooKeeper zkClient = null;
	@Before
	public void init() throws Exception {
		zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
			@Override
			public void process(WatchedEvent watchedEvent) {
				// 收到事件通知后的回调函数(用户的业务逻辑)
				System.out.println(watchedEvent.getType() + "--" + watchedEvent.getPath());
				// 再次启动监听
				try {
					List<String> children = zkClient.getChildren("/", true);
					for (String child : children) {
						System.out.println(child);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
		}
	}

1.3、创建子节点

// 创建子节点
@Test
public void create() throws Exception {
	// 参数 1:要创建的节点的路径; 参数 2:节点数据 ; 参数 3:节点权限 ;参数 4:节点的类型
	String nodeCreated = zkClient.create("/atguigu", "shuaige".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
}

测试:在 hadoop102 的 zk 客户端上查看创建节点情况

[zk: localhost:2181(CONNECTED) 16] get -s /atguigu shuaige

 

1.4、获取子节点并监听节点变化

// 获取子节点
@Test
public void getChildren() throws Exception {
    List<String> children = zkClient.getChildren("/", true);
    for (String child : children) {
        System.out.println(child);
    }
    // 延时阻塞
    Thread.sleep(Long.MAX_VALUE);
}

(1)在 IDEA 控制台上看到如下节点:

zookeeper sanguo atguigu

1.5、判断 Znode 是否存在

// 判断 znode 是否存在
@Test
public void exist() throws Exception {
	Stat stat = zkClient.exists("/atguigu", false);
	System.out.println(stat == null ? "not exist" : "exist");
}

2、客户端向服务端写数据流程

3、服务器动态上下线监听案

3.1、需求

        某分布式系统中,主节点可以有多台,可以动态上下线,任意一台客户端都能实时感知 到主节点服务器的上下线。

3.2、需求分析

3.3、具体实现

(1)先在集群上创建/servers 节点

[zk: localhost:2181(CONNECTED) 10] create /servers "servers"

Created /servers

(2)在 Idea 中创建包名:com.atguigu.zkcase1

(3)服务器端向 Zookeeper 注册代码

package com.atguigu.zkcase1;
import java.io.IOException;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooDefs.Ids;

public class DistributeServer {
	
	private static String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
	private static int sessionTimeout = 2000;
	private ZooKeeper zk = null;
	private String parentNode = "/servers";
	
	// 创建到 zk 的客户端连接
	public void getConnect() throws IOException{
		
		zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
			
			@Override
			public void process(WatchedEvent event) {
				
			}
		});
	}
	// 注册服务器
	public void registServer(String hostname) throws Exception{
		
		String create = zk.create(parentNode + "/server", hostname.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
		System.out.println(hostname +" is online "+ create);
		
	}
	// 业务功能
	public void business(String hostname) throws Exception{
		
		System.out.println(hostname + " is working ...");
		Thread.sleep(Long.MAX_VALUE);
		
	}
	public static void main(String[] args) throws Exception {
		// 1 获取 zk 连接
		DistributeServer server = new DistributeServer();
		server.getConnect();
		// 2 利用 zk 连接注册服务器信息
		server.registServer(args[0]);
		// 3 启动业务功能
		server.business(args[0]);
	}
}

(3)客户端代码

package com.atguigu.zkcase1;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;

public class DistributeClient {
	
	private static String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
	
	private static int sessionTimeout = 2000;
	
	private ZooKeeper zk = null;
	
	private String parentNode = "/servers";
	
	// 创建到 zk 的客户端连接
	public void getConnect() throws IOException {
		
		zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
			
			@Override
			public void process(WatchedEvent event) {
				// 再次启动监听
				try {
					getServerList();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			
		});
	}
	
	// 获取服务器列表信息
	public void getServerList() throws Exception {
		 
		 // 1 获取服务器子节点信息,并且对父节点进行监听
		List<String> children = zk.getChildren(parentNode, true);
		 
		 // 2 存储服务器信息列表
		ArrayList<String> servers = new ArrayList<>();
		 
		 // 3 遍历所有节点,获取节点中的主机名称信息
		for (String child : children) {
			
			byte[] data = zk.getData(parentNode + "/" + child, false, null);
			
			servers.add(new String(data));
			
		}
		 // 4 打印服务器列表信息
		System.out.println(servers);
	}
	
	// 业务功能
	public void business() throws Exception{
		System.out.println("client is working ...");
		Thread.sleep(Long.MAX_VALUE);
	}
	
	public static void main(String[] args) throws Exception {
		
		// 1 获取 zk 连接
		DistributeClient client = new DistributeClient();
		
		client.getConnect();
		
		// 2 获取 servers 的子节点信息,从中获取服务器信息列表
		client.getServerList();
		
		// 3 业务进程启动
		client.business();
	}
}

3.4、测试

1)在 Linux 命令行上操作增加减少服务器

4、ZooKeeper 分布式锁案例

        什么叫做分布式锁呢? 比如说"进程 1"在使用该资源的时候,会先去获得锁,"进程 1"获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,"进程 1"用完该资源以后就将锁释放掉,让其 他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的 访问该临界资源。那么我们把这个分布式环境下的这个锁叫作分布式锁。

4.1、原生 Zookeeper 实现分布式锁案例

1)分布式锁实现

package com.atguigu.lock2;
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 {
	
	 // zookeeper server 列表
	 private String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
	 
	 // 超时时间
	 private int sessionTimeout = 2000;
	 
	 private ZooKeeper zk;
	 
	 private String rootNode = "locks";
	 
	 private String subNode = "seq-";
	 
	 // 当前 client 等待的子节点
	 private String waitPath;
	 
	 //ZooKeeper 连接
	 private CountDownLatch connectLatch = new CountDownLatch(1);
	 
	//ZooKeeper 节点等待
	 private CountDownLatch waitLatch = new CountDownLatch(1);
	 
	 // 当前 client 创建的子节点
	 private String currentNode;
	 
	 // 和 zk 服务建立连接,并创建根节点
	 public DistributedLock() throws IOException, InterruptedException, KeeperException {
		 
		 zk = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
			 
			 @Override
			 public void process(WatchedEvent event) {
				 
				 // 连接建立时, 打开 latch, 唤醒 wait 在该 latch 上的线程
				 if (event.getState() == Event.KeeperState.SyncConnected) {
					connectLatch.countDown();
				 }
				 
				 // 发生了 waitPath 的删除事件
				 if (event.getType() == Event.EventType.NodeDeleted && event.getPath().equals(waitPath)) 
				 {
					waitLatch.countDown();
				 }
			 }
		 });
		 
		 // 等待连接建立
		 connectLatch.await();
		 
		 //获取根节点状态
		 Stat stat = zk.exists("/" + rootNode, false);
		 
		 //如果根节点不存在,则创建根节点,根节点类型为永久节点
		 if (stat == null) {
			System.out.println("根节点不存在");
			zk.create("/" + rootNode, new byte[0],ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
		 }
	 }
	 
	 // 加锁方法
	 public void zkLock() {
		 
		 try {
				//在根节点下创建临时顺序节点,返回值为创建的节点路径
				currentNode = zk.create("/" + rootNode + "/" + subNode,null, ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
			
				// wait 一小会, 让结果更清晰一些
				Thread.sleep(10);
			 
				// 注意, 没有必要监听"/locks"的子节点的变化情况
				List<String> childrenNodes = zk.getChildren("/" + rootNode, false);
			 
				// 列表中只有一个子节点, 那肯定就是 currentNode , 说明client 获得锁
			 if (childrenNodes.size() == 1) {
				 
				return;
				
			 } else {
			
				//对根节点下的所有临时顺序节点进行从小到大排序
				Collections.sort(childrenNodes);
			 
				//当前节点名称
				String thisNode = currentNode.substring(("/" + rootNode + "/").length());
				
				//获取当前节点的位置
				int index = childrenNodes.indexOf(thisNode);
			 
			 if (index == -1) {
				System.out.println("数据异常");
				
			 } else if (index == 0) {
				 
				// index == 0, 说明 thisNode 在列表中最小, 当前client 获得锁
				return;
			 
			 } else {
			 
				// 获得排名比 currentNode 前 1 位的节点
				this.waitPath = "/" + rootNode + "/" + childrenNodes.get(index - 1);
			
				// 在 waitPath 上注册监听器, 当 waitPath 被删除时, zookeeper 会回调监听器的 process 方法
				zk.getData(waitPath, true, new Stat());
			 
				//进入等待锁状态
				waitLatch.await();
				return;
			 }
			 }
		 } catch (KeeperException e) {
			e.printStackTrace();
		 } catch (InterruptedException e) {
			e.printStackTrace();
		 }
	 }
	 
	 // 解锁方法
	 public void zkUnlock() {
		 try {
			zk.delete(this.currentNode, -1);
		 } catch (InterruptedException | KeeperException e) {
			e.printStackTrace();
		 }
	 }
}

2)分布式锁测试

(1)创建两个线程

package com.atguigu.lock2;
import org.apache.zookeeper.KeeperException;
import java.io.IOException;
public class DistributedLockTest {
 public static void main(String[] args) throws 
InterruptedException, IOException, KeeperException {
 // 创建分布式锁 1
 final DistributedLock lock1 = new DistributedLock();
 // 创建分布式锁 2
 final DistributedLock lock2 = new DistributedLock();
 new Thread(new Runnable() {
 @Override
 public void run() {
 // 获取锁对象
 try {
 lock1.zkLock();
 System.out.println("线程 1 获取锁");
 Thread.sleep(5 * 1000);
 lock1.zkUnlock();
 System.out.println("线程 1 释放锁");
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }).start();
 new Thread(new Runnable() {
 @Override
 public void run() {
 // 获取锁对象
 try {
 lock2.zkLock();
 System.out.println("线程 2 获取锁");
 Thread.sleep(5 * 1000);
 lock2.zkUnlock();
 System.out.println("线程 2 释放锁");
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }).start();
 }
}

(2)观察控制台变化:

        线程 1 获取锁

        线程 1 释放锁

        线程 2 获取锁

        线程 2 释放锁

4.2、Curator 框架实现分布式锁案例

(2)代码实现

package com.atguigu.lock;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class CuratorLockTest {
	
	 private String rootNode = "/locks";
	
	// zookeeper server 列表
	 private String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
	
	// connection 超时时间
	private int connectionTimeout = 2000;
	
	 // session 超时时间
	 private int sessionTimeout = 2000;
	 
	 public static void main(String[] args) {
		new CuratorLockTest().test();
	 }
	 
	 // 测试
	 private void test() {
		 
		 // 创建分布式锁 1
		 final InterProcessLock lock1 = new InterProcessMutex(getCuratorFramework(), rootNode);
		 
		 // 创建分布式锁 2
		 final InterProcessLock lock2 = new InterProcessMutex(getCuratorFramework(), rootNode);
		 
		 new Thread(new Runnable() {
			 @Override
			 public void run() {

				 // 获取锁对象
				 try {
					lock1.acquire();
					System.out.println("线程 1 获取锁");
				
					// 测试锁重入
					lock1.acquire();
					System.out.println("线程 1 再次获取锁");
				 
					Thread.sleep(5 * 1000);
					lock1.release();
					System.out.println("线程 1 释放锁");
				 
					lock1.release();
					System.out.println("线程 1 再次释放锁");
				 } catch (Exception e) {
					e.printStackTrace();
				 }
			 }
		 }).start();
		 
		 
		 new Thread(new Runnable() {
			 
			 @Override
			 public void run() {
				 
				 // 获取锁对象
				 try {
					lock2.acquire();
					System.out.println("线程 2 获取锁");
				 
					// 测试锁重入
					lock2.acquire();
					System.out.println("线程 2 再次获取锁");
				 
				 Thread.sleep(5 * 1000);
				
					lock2.release();
					System.out.println("线程 2 释放锁");
				
					lock2.release();
					System.out.println("线程 2 再次释放锁");
				 } catch (Exception e) {
					e.printStackTrace();
				 }
			 }
		 }).start();
	 }
	 
	 // 分布式锁初始化
	 public CuratorFramework getCuratorFramework (){
		 
		 //重试策略,初试时间 3 秒,重试 3 次
		 RetryPolicy policy = new ExponentialBackoffRetry(3000, 3);
		 
		 //通过工厂创建 Curator
		 CuratorFramework client = CuratorFrameworkFactory.builder()
				.connectString(connectString)
				.connectionTimeoutMs(connectionTimeout)
				.sessionTimeoutMs(sessionTimeout)
				.retryPolicy(policy).build();
		 
		 //开启连接
		 client.start();
		 
		 System.out.println("zookeeper 初始化完成...");
		 
		 return client;
	 }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值