Zookeeper_安装部署原理及应用

安装

  1. 下载
wget https://downloads.apache.org/zookeeper/zookeeper-3.6.0/apache-zookeeper-3.6.0-bin.tar.gz
  1. 解压
tar -zxvf apache-zookeeper-3.6.0-bin.tar.gz
  1. 配置文件重命名
[root@localhost conf]# mv zoo_sample.cfg zoo.cfg
  1. 在配置文件中修改dataDir路径
    在这里插入图片描述
  2. 创建上面修改的目录
[root@localhost apache-zookeeper-3.6.0-bin]# mkdir zkData
  1. 启动服务端
[root@localhost bin]# ./zkServer.sh start
  1. 查看是否启动
jps
  1. 查看状态
[root@localhost bin]# ./zkServer.sh status
  1. 启动客户端
[root@localhost bin]# ./zkCli.sh
  1. 退出客户端
quit
  1. 停止服务
[root@localhost bin]# ./zkServer.sh stop

配置参数解读

  1. 通信心跳数,Zookeeper服务器与客户端心跳时间,单位毫秒
    Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒。
    它用于心跳机制,并且设置最小的session超时时间为两倍心跳时间。(session的最小超时时间是2*tickTime)
tickTime =2000
  1. LF初始通信时限
    集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限。
initLimit =10
  1. LF同步通信时限
    集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。
syncLimit =5
  1. 数据文件目录+数据持久化路径,主要用于保存Zookeeper中的数据。
dataDir=/usr/src/apache-zookeeper-3.6.0-bin/zkData
  1. 客户端连接端口,监听客户端连接的端口。
clientPort =2181

分布式安装部署

集群规划

在192.168.2.2,192.168.2.4,192.168.2.5部署三台Zookeeper服务

配置服务器编号

  1. 在zkData目录下创建一个myid文件
touch myid
  1. 编辑myid文件,在其中添加id即可
vim myid
2

配置zoo.cfg

#######################cluster##########################
server.2=192.168.2.2:2888:3888
server.4=192.168.2.4:2888:3888
server.5=192.168.2.5:2888:3888

2888是服务器与集群中的Leader服务器交换信息的端口。
3888是集群中的Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口。

启动服务,查看状态

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

客户端命令行操作

ZooKeeper -server host:port cmd args
# optional mode is one of [PERSISTENT, PERSISTENT_RECURSIVE] - default is PERSISTENT_RECURSIVE
addWatch [-m mode] path 
addauth scheme auth
close 
config [-c] [-w] [-s]
connect host:port
#创建节点
#普通节点:create /sanguo "liubei"	create /sanguo/shu "liubei"
#-e:创建短暂节点:create -e /sanguo/wu "zhouyu"
#-s: 创建带序号的节点 create -s /sanguo/wei "cc"
create [-s] [-e] [-c] [-t ttl] path [data] [acl]
# 删除节点
delete [-v version] path
deleteall path [-b batch size]
delquota [-n|-b] path
#获取节点的值
#get /sanguo
# -w 节点的值变化监听,只监听一次变化
get [-s] [-w] path
getAcl [-s] path
getAllChildrenNumber path
getEphemerals path
history 
listquota path
# 查看当前znode中所包含的内容
# -s:查看当前节点详细数据
# -w:节点的子节点变化监听(路径变化)
# -R:显示目录
ls [-s] [-w] [-R] path
printwatches on|off
quit 
reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,...]*]] | [-add serverId=host:port1:port2;port3[,...]]* [-remove serverId[,...]*]
redo cmdno
removewatches path [-c|-d|-a] [-l]
#修改节点的值	set /sanguo/weiguo "simayi"
set [-s] [-v version] path data
setAcl [-s] [-v version] [-R] path acl
setquota -n|-b val path
#查看节点状态
stat [-w] path
sync path
version 

内部原理

选举机制

  1. 半数机制:集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器。
  2. Zookeeper虽然在配置文件中并没有指定Master和Slave。但是,Zookeeper工作时,是有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的。
  3. 选举过程:
    1. 假设有五台服务器组成的Zookeeper集群,它们的id从1-5,同时它们都是最新启动的,也就是没有历史数据,在存放数据量这一点上,都是一样的。假设这些服务器依序启动。
      在这里插入图片描述
    2. 服务器1启动,此时只有它一台服务器启动了,它发出去的报文没有任何响应,所以它的选举状态一直是LOOKING状态。
    3. 服务器2启动,它与最开始启动的服务器1进行通信,互相交换自己的选举结果,由于两者都没有历史数据,所以id值较大的服务器2胜出,但是由于没有达到超过半数以上的服务器都同意选举它(这个例子中的半数以上是3),所以服务器1、2还是继续保持LOOKING状态。
    4. 服务器3启动,根据前面的理论分析,服务器3成为服务器1、2、3中的老大,而与上面不同的是,此时有三台服务器选举了它,所以它成为了这次选举的Leader。
    5. 服务器4启动,根据前面的分析,理论上服务器4应该是服务器1、2、3、4中最大的,但是由于前面已经有半数以上的服务器选举了服务器3,所以它只能接收当小弟的命了。
    6. 服务器5启动,同4一样当小弟。

节点类型

  • 持久(Persistent):客户端和服务端断开连接之后,创建的节点不删除
    • 持久化目录节点(Persistent)
      客户端与Zookeeper断开连接后,该节点依旧存在
    • 持久化顺序编号目录节点(Persistent_sequential)
      客户端与Zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号。创建znode时设置顺序标识,znode名称后会有附加一个值,顺序号是一个单调递增计数器,由父节点维护。在分布式系统中,顺序号可以被用于为所有事件进行全局维护,客户端可以通过顺序号推断事件的顺序。
  • 短暂(Ephemeral):客户端和服务端断开连接之后,创建的节点自己删除
    • 临时目录节点(Ephemeral)
    • 临时顺序编号目录节点(Ephemeral_sequential)

Stat结构体

[zk: localhost:2181(CONNECTED) 21] stat /sanguo
#创建节点的事务zxid
#每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。
#事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid
#如果zxid1小于zxid2,那么zxid1在zxid2之前发生。
cZxid = 0x300000002
# 被创建的毫秒数(从1970年开始)
ctime = Wed Mar 25 09:20:14 CST 2020
#最后更新的事务zxid
mZxid = 0x300000006
#最后修改的毫秒数(从1970年开始)
mtime = Wed Mar 25 09:36:38 CST 2020
#最后更新的子节点zxid
pZxid = 0x300000003
#子节点变化号
cversion = 1
#数据变化号
dataVersion = 2
#访问控制列表的变化号
aclVersion = 0
#如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0
ephemeralOwner = 0x0
#数据长度
dataLength = 8
#子节点数量
numChildren = 1

监听器原理

  1. 首先要有一个main线程
  2. 在main线程中创建Zookeeper客户端,这时就会创建两个线程,一个负责网络连接通信(connect),一个负责监听(listener)
  3. 通过connect将注册的监听事件发送给zookeeper
  4. 在Zookeeper的注册监听列表中将注册的监听事件添加到列表中
  5. Zookeeper监听到有数据或者路径变化,就会将这个消息发送给listener线程
  6. listener线程内部调用了process方法
    在这里插入图片描述

写数据流程

在这里插入图片描述

API应用

环境搭建

  1. 创建一个Maven工程
  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>
	 <!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
	 <dependency>
	  <groupId>org.apache.zookeeper</groupId>
	  <artifactId>zookeeper</artifactId>
	  <version>3.6.0</version>
	 </dependency>
</dependencies>
  1. 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  

创建Zookeeper客户端

private String connectString = "192.168.2.2:2181,192.168.2.4:2181,192.168.2.5:2181";
private int sessionTimeout = 3000000;
private ZooKeeper zkClient;

 @Before
public void init() throws IOException {

     zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {

         @Override
         public void process(WatchedEvent event) {

//				System.out.println("---------start----------");
//				List<String> children;
//				try {
//					children = zkClient.getChildren("/", true);
//					
//					for (String child : children) {
//						System.out.println(child);
//					}
//					System.out.println("---------end----------");
//				} catch (KeeperException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				} catch (InterruptedException e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				}
         }
     });
 }

创建节点

 @Test
public void createNode() throws KeeperException, InterruptedException {

    String path = zkClient.create("/test", "dahaigezuishuai".getBytes(), Ids.OPEN_ACL_UNSAFE,
            CreateMode.PERSISTENT);
    System.out.println(path);
}

如果出现异常org.apache.zookeeper.KeeperException$ConnectionLossException: KeeperErrorCod,就把sessionTimeout 的值调大一点,直到可以运行为止。

获取子节点 并监控节点的变化

保持该方法处于运行状态

@Test
public void getDataAndWatch() throws KeeperException, InterruptedException {
    Thread.sleep(Long.MAX_VALUE);
}

在process方法中得到节点变化数据

@Before
public void init() throws IOException {

     zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {

         @Override
         public void process(WatchedEvent event) {

				System.out.println("---------start----------");
				List<String> children;
				try {
					children = zkClient.getChildren("/", true);
					
					for (String child : children) {
						System.out.println(child);
					}
					System.out.println("---------end----------");
				} catch (KeeperException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
         }
     });
 }

判断节点是否存在

 @Test
public void exist() throws KeeperException, InterruptedException {

     Stat stat = zkClient.exists("/test", false);
     System.out.println(stat == null ? "not exist" : "exist");
 }

监听服务器节点动态上下线案例

需求

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

服务端代码

public class DistributeServer {

	private String connectString = "192.168.2.2:2181,192.168.2.4:2181,192.168.2.5:2181";
	private int sessionTimeout = 3000000;
	private ZooKeeper zkClient;

	public static void main(String[] args) throws Exception {

		DistributeServer server = new DistributeServer();

		// 1 连接zookeeper集群
		server.getConnect();
		// 2 注册节点
		server.regist(args[0]);
		// 3 业务逻辑处理
		server.business();
	}

	private void business() throws InterruptedException {
		Thread.sleep(Long.MAX_VALUE);
	}

	private void regist(String hostname) throws KeeperException, InterruptedException {
		
		String path = zkClient.create("/servers/server", hostname.getBytes(), Ids.OPEN_ACL_UNSAFE,
				CreateMode.EPHEMERAL_SEQUENTIAL);
		System.out.println(hostname +"is online ");
	}


	private void getConnect() throws IOException {
		
		zkClient = new ZooKeeper(connectString , sessionTimeout , new Watcher() {
			
			@Override
			public void process(WatchedEvent event) {

			}
		});
	}
}

客户端代码

public class DistributeClient {

	private String connectString = "192.168.2.2:2181,192.168.2.4:2181,192.168.2.5:2181";
	private int sessionTimeout = 3000000;
	private ZooKeeper zkClient;

	public static void main(String[] args) throws IOException, KeeperException, InterruptedException {

		DistributeClient client = new DistributeClient();

		// 1 获取zookeeper集群连接
		client.getConnect();
		// 2 注册监听
		client.getChlidren();
		// 3 业务逻辑处理
		client.business();

	}

	private void business() throws InterruptedException {
		Thread.sleep(Long.MAX_VALUE);
	}

	private void getChlidren() throws KeeperException, InterruptedException {

		List<String> children = zkClient.getChildren("/servers", true);

		// 存储服务器节点主机名称集合
		ArrayList<String> hosts = new ArrayList<>();

		for (String child : children) {
			byte[] data = zkClient.getData("/servers/"+child, false, null);
			hosts.add(new String(data));
		}
		// 将所有在线主机名称打印到控制台
		System.out.println(hosts);

	}


	private void getConnect() throws IOException {
	
		zkClient = new ZooKeeper(connectString , sessionTimeout , new Watcher() {
			
			@Override
			public void process(WatchedEvent event) {
				
				try {
					getChlidren();
				} catch (KeeperException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		
	}
}

发布了417 篇原创文章 · 获赞 45 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/Chill_Lyn/article/details/105073371