Fork me on GitHub

ZooKeeper

ZooKeeper

1.简介

  • Zookeeper是一个分布式的,开源的分布式应用程序协调服务。是Hadoop和Hbase重要组件,它是一个为分布式应用提供一致性服务。提供功能包括:配置维护,域名服务,分布式同步,组服务等。

  • ZooKeeper是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户

  • Zookeeper提供的服务功能有:

    1)数据注册功能;(存储)
    2)数据查询功能;(读取数据)
    3)数据监听功能;(监听)
    
  • Zookeeper能用来做什么:

    a)分布式系统中主从协调
    b)分布式系统中配置信息同步
    c)分布式系统中分布式共享锁(Master选举)
    d)分布式系统中负载均衡
    e)分布式系统中明名称服务
    
  • 特点

    ![img](file:///C:\Users\XUJK~1\AppData\Local\Temp\ksohtml8332\wps1.jpg)

  • 主要功能:

    1.记录数据(功能性数据)
    2.监控数据的变化 并 反馈
    

2.应用场景

  • 服务统一命名

    在分布式环境下,经常需要对应用、服务进行统一命名便于识别
    eg:IP不容易记住,而域名容易记住
    
  • 统一配置

    分布式环境下配置文件同步
    	1.一般要求一个急群众,所有节点配置信息一致,如Kafka集群。
    	2.对配置文件修改后,希望能够快速同步各个节点上。
    配置管理可交给Zookeeper实现
    	1.可配置信息写入Zookeeper上的一个Znode
    	2.各个客户端服务器监听这个Znode
    	3.一旦Znode中数据被修改,Zookeeper将通知各个客户端服务器
    
  • 统一集群管理

    分布式环境中,实时掌握每个节点状态
    	1.可根据节点实时状态做出一些调整
    ZooKeeper可以实现实时监控节点状态变化
    	1.可将节点信息写入ZooKeeper上的一个ZNode.
    	2.监听这个ZNode可获取它的实时状态变化
    
  • 服务器动态上下线

    客户端能实时洞察到服务器上下线变化
    
  • 负载均衡

    在Zookeeper中记录每台服务器的访问数,让访问数最少的服务器去处理最新客户端请求
    

3.ZooKeeper集群

假如有三台机器上配置ZooKeeper,有1个leader(主节点)和2个follow(从节点),在这三台存储数据是一模一样的。
	当客户端请求到一个follow,向follow写数据,而follow不会进行写数据操作,它会将请求汇报给leader,而leader会验证follow是否能执行写入,能执行写入操作,leader会发送指令让follow执行写操作。这样follow和leader都执行写操作,如果有一半以上机器运行正常,是不耽误写入操作。leader宕机会重新选举一个leader

4.Zookeeper集群搭建

  • 准备奇数台机器

  • 下载:官网

  • 解压

    tar -zxvf zookeeper-3.4.6.tar.gz
    
  • 到zookeeper内conf文件夹内,拷贝zoo-sample.cfg 为zoo.cfg,以zoo.cfg为配置文件信息

    cp zoo_sample.cfg zoo.cfg
    
  • 配置zoo.cfg

    $ vi zoo.cfg
    tickTime = 2000
    dataDir = /opt/zookeeper-3.4.6/zkData
    clientPort = 2181
    initLimit = 5
    syncLimit = 2
    # linux01 02 03 为进去主机名
    server.1=linux01:2888:3888
    server.2=linux02:2888:3888
    server.3=linux03:2888:3888
    
  • 再zkData写入一个当前zookeeper id

    [root@linux01 zookeeper-3.4.6]# echo 1 > zkData/myid
    
  • 当前配置信息写入另外2台机器上

    scp -r zookeeper-3.4.6/ linux02:$PWD
    scp -r zookeeper-3.4.6/ linux03:$PWD
    
  • 在linux02,linux03重新设置zookeeper id

    #linux02
    [root@linux02 zookeeper-3.4.6]# echo 2 > zkData/myid
    #linux03
    [root@linux02 zookeeper-3.4.6]# echo 3 > zkData/myid
    

5.数据存储方式

  • key-value形式,其实是一个树结构,类似于linux的文件系统。它的key都是带路径斜杠

6.启动服务

  • 启动目录在ZooKeeper的bin目录下

    [root@linux01 bin]# ./zkServer.sh start ../conf/zoo.cfg
    

    另外2台机器启动方式一样

  • jps检验:

    # 表示启动成功
    [root@linux01 bin]# jps
    9619 QuorumPeerMain
    9637 Jps
    
  • 停止服务

    [root@linux01 bin]# ./zkServer.sh stop
    
  • 状态查看

    [root@linux01 bin]# ./zkServer.sh status
    
  • 环境变量配置

    
    

7.选举机制

  • 我们之前设置myid文件时候就用于选举机制:
(1)id=1的服务器启动了,它向局域网发送组播寻找leader(端口2888)。发现没有leader,这台服务器就进入选举状态(3888端口),并向局域网组播投票(投自己);
(2)id=2的服务器启动了,它向局域网寻找leader,发现没有,进入投票状态(3888端口),收到服务器1所投的票;然后发现1<自己2,投票(投2);此时,服务1也会收到2的投票,发现2>自己,重新投(投2);此时,服务器2会收到两票;然后通过配置文件获知集群总共有3台机器,从而知道自己已经得多数票(当选);服务器2就切换到leader状态(2888);
(3)id=3的服务器启动了,它向局域网寻找leader,发现服务器2是leader,自己主动进入follower状态;

8.客户端启动

[root@linux01 bin]# ./zkCli.sh

9.使用命令

  • 在根目录下创建k-v
[zk: localhost:2181(CONNECTED) 0] create /user xiaoming
  • 查看根目录下子节点数据
[zk: localhost:2181(CONNECTED) 1] ls /
[zookeeper, user]
  • 获取user的值
[zk: localhost:2181(CONNECTED) 2] get /user 
xiaoming
cZxid = 0x100000002
ctime = Fri Jan 01 10:34:56 CST 2021
mZxid = 0x100000002
mtime = Fri Jan 01 10:34:56 CST 2021
pZxid = 0x100000002
cversion = 0 # 版本
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8 #长度
numChildren = 0 # 子节点个数
  • 连接其他机器客户端
[root@linux02 bin]#./zkCli.sh -server linux01:2181
  • 更新数据
[zk: linux01:2181(CONNECTED) 0] set /user xiaohong
  • 查看历史命令
history
  • 删除数据
[zk: linux01:2181(CONNECTED) 5] rmr /user
  • 创建一个带序号节点 -s,它会默认在key后面加一个序号
[zk: linux01:2181(CONNECTED) 12] create -s /user xiaofei
Created /user0000000002
  • 生成临时节点 -e .当所有客户端断开,它就会消失
create -e group ming

​ 生成临时有序节点 -es

10 stat结构体

1)czxid-创建节点的事务zxid
每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。
事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。
2)ctime - znode被创建的毫秒数(从1970年开始)
3)mzxid - znode最后更新的事务zxid
4)mtime - znode最后修改的毫秒数(从1970年开始)
5)pZxid-znode最后更新的子节点zxid
6)cversion - znode子节点变化号,znode子节点修改次数
7)dataversion - znode数据变化号
8)aclVersion - znode访问控制列表的变化号
9)ephemeralOwner- 如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0。
10)dataLength- znode的数据长度
11)numChildren - znode子节点数量

11.Java客户端

  • 连接Zookpeer
package xjk.zookpeer.client;
// 导入jar包
import java.io.IOException;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;

public class ZkClient {
	public static void main(String[] args) throws Exception, InterruptedException {
		// 获取客户端对象
		ZooKeeper zk = new ZooKeeper("10.0.0.134:2181,10.0.0.131:2181,10.0.0.132:2181", 2000, null);
		// 获取指定节点数据
		byte[] data = zk.getData("/user", false, null);
		System.out.println(new String(data));
		// :创建一个节点
		// 参数1:节点名
		// 参数2:数据
		// 参数3 权限   OPEN_ACL_UNSAFE 表示开放全部权限
		// 参数4 节点类型   PERSISTENT_SEQUENTIAL 表示永久有序节点
		zk.create("/servers", "linux".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
		
		// 关闭连接
		zk.close();
	} 
	// 获取一个客户端对象
	public ZooKeeper getZkClient(String connectString, int sessionTime, Watcher w) throws Exception {
		return new ZooKeeper(connectString, sessionTime, w);
	}
	// 获取指定节点数据
	public String getData(String path) throws Exception {
		ZooKeeper zk = getZkClient("10.0.0.134:2181,10.0.0.131:2181,10.0.0.132:2181", 2000, null);
		byte[] data = zk.getData(path, false, null);
		return new String(data);
	}
}


  • eclipse创建测试环境
1.右键点击项目
2.Build Path --> Configure Build Path
3.选择Libraries ---> AddLibrary ---> JUnit ---> Finish
  • 测试环境使用客户端
package xjk.zookpeer.client;

import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.List;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import org.junit.After;

public class TestZkClient {
	ZooKeeper zk = null;
	String connectString = "10.0.0.134:2181,10.0.0.131:2181,10.0.0.132:2181";
	int sessionTime = 2000;
	@Before
	public void init() throws Exception {
		zk = new ZooKeeper(connectString, sessionTime, null);
	}
	/*
	 * 删除节点,该删除的节点如果有子节点无法删除
	 * */
	@Test
	public void delete() throws Exception {
		// 参数1:节点   参数2: 版本(-1代表最新版本)
		zk.delete("/user0000000002", -1);
	}
	
	
	/*
	 * 获取根目录下所有子节点
	 * */
	@Test
	public void getChildren() throws Exception {
		// 参数1:节点   参数2: 版本(-1代表最新版本)
		List<String> children = zk.getChildren("/user", false);
		for (String str : children) {
			System.out.println(str);
		}
	}
	/*
	 * 节点是否存在
	 * */
	@Test
	public void ifexists() throws Exception {
		Stat stat = zk.exists("/user", false);
		System.out.println(stat ==null?"不存在": "存在");
		
	}
	/*
	 * 更新数据
	 * */
	@Test
	public void set() throws Exception {
		zk.setData("/user0000000003", "123".getBytes(),-1);	
	}
	
	@After
	public void close() throws InterruptedException {
		zk.close();
	}
}

  • 递归删除
package xjk.zookpeer.client;

import java.io.IOException;
import java.util.List;

import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

/*
 * 递归删除
 * 
 * */

public class TestDelete { 
	static ZooKeeper zk = null;
	static {
			// 1.获取客户端对象
			try {
				zk = new ZooKeeper("10.0.0.134:2181,10.0.0.131:2181,10.0.0.132:2181", 2000, null);
			} catch (Exception e) {
				e.printStackTrace();
			}
	}
	public static void main(String[] args) throws Exception {
		delete("/user");
	}
	public static void delete(String path) throws Exception {
		List<String> list = zk.getChildren(path, false);
		for (String string : list) {
			System.out.println(path + "/"+ string);
			Stat stat = zk.exists(path+"/"+string, false);
			System.out.println(stat);
			// 如果存在节点
			if (stat != null) {
				// 执行删除
				delete(path+"/" + string);
			}
		}
		zk.delete(path, -1);
	}
}

12.shell演示监听器

12.1示例1

  • 执行命令后面加watch进行监听:客户端1执行:
[zk: localhost:2181(CONNECTED) 14] get /servers0000000004 watch
  • 返回该节点信息

  • 当客户端2执行set方法,对/servers0000000004操作

[zk: localhost:2181(CONNECTED) 0] set /servers0000000004 myserver
  • 客户端2更新数据

  • 客户端1,显示如下:

意思是/servers0000000004节点发生变化

12.2示例2:

  • 当客户端1执行watch
[zk: localhost:2181(CONNECTED) 1] ls /servers0000000004 watch
[]

  • 客户端端2执行create
[zk: localhost:2181(CONNECTED) 15] create /servers0000000004/list 11111
Created /servers0000000004/list

  • 此时客户端1会如下:

表示/servers0000000004 下节点发生变化
  • shell监控只进行一次监控。

13.Java客户端监听

package xjk.zookpeer.client;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class TestWatch {
	ZooKeeper zk = null;
	String connectString = "10.0.0.134:2181,10.0.0.131:2181,10.0.0.132:2181";
	int sessionTime = 2000;
	@Before
	public void getConnection() throws Exception {
		zk = new ZooKeeper(connectString, sessionTime, new Watcher() {
			// 匿名内部类:连接成功后执行此函数
			public void process(WatchedEvent event) {
				System.out.println("连接成功....");
			}
		});
		
	}
	/*
	 * 监控节点数据发生变化
	 * */
	@Test
	public void getData() throws Exception {
		// 获取数据,并绑定监听事件
		byte[] data = zk.getData("/user", new Watcher() {
			/*
			 * 监听节点数据变化,当节点/user的数据发生变化,会执行此方法
			 * */
			@Override
			public void process(WatchedEvent event) {
				System.out.println("事件类型:"+ event.getType() + "  监听的节点:"+ event.getPath());
				// 循环进行监控
				try {
					zk.getData("/user", this, null);
				} catch (KeeperException | InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}, null);
		// 让程序阻塞停滞。。。
		Thread.sleep(Integer.MAX_VALUE);
	}
	/*
	 * 监控子节点变化
	 * */
	@Test
	public void getChildren( ) throws Exception {
		zk.getChildren("/user", new Watcher() {
			@Override
			public void process(WatchedEvent event) {
				// TODO Auto-generated method stub
				System.out.println("事件类型:"+ event.getType() + "  监听的节点:"+ event.getPath());
				try {
					zk.getChildren("/user", this);
				} catch (KeeperException | InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
		// 让程序阻塞停滞。。。
		Thread.sleep(Integer.MAX_VALUE);
	}
	@After
	public void close() throws InterruptedException {
		zk.close();
	}
}

14.python客户端

  • 下载kazoo包
pip install kazoo
  • 客户端连接、断开
from kazoo.client import KazooClient
# read_only= True 表示只读
zk = KazooClient(hosts='10.0.0.134:2181,10.0.0.131:2181,10.0.0.132:2181', timeout=10)
zk.start()
print(node)
zk.stop()
  • 连接状态监听
    • 用于监听zk对象连接状态。
def my_listener(state):
    if state == KazooState.LOST:
        print('注销...')
    elif state == KazooState.SUSPENDED:
        print('暂停状态...')
    else:
        print("正在连接状态...")

zk.add_listener(my_listener)
  • 获取根目录下子节点
node = zk.get_children('/')
  • 创建节点
from kazoo.client import KazooClient
from kazoo.exceptions import NodeExistsError

zk = KazooClient(hosts='10.0.0.134:2181,10.0.0.131:2181,10.0.0.132:2181', timeout=10)
zk.start()
# ensure_path将递归地创建该节点以及该路径中必要的路径中的所有节点
zk.ensure_path("/account2/xjk/home")
try:
    ret = zk.create('/account2/xjk/home/rst',value=b'hello xjk')
    print('rst:::', ret)
except NodeExistsError as e:
    print('error:::',e)
zk.stop()


# create其他参数:
makepath=True #递归创建
ephemeral #默认False 是否创建临时节点
sequence # 布尔值,指示路径是否以唯一索引作为后缀

e.g.:
    ret = zk.create('/account2/xjk2',value=b'hello xjk',makepath=True,sequence=True,ephemeral=True)
    print('retret:::', ret)# retret::: /account2/xjk20000000001
  • 更改节点
# version=-1最新版本
ret = zk.set('/account2/xjk/home/rst',value=b'new xjk',version=-1)
print(ret)
#ZnodeStat(czxid=12884901963, mzxid=12884901972, ctime=1609853601853, mtime=1609855503142, version=1, cversion=0, aversion=0, ephemeralOwner=0, dataLength=7, numChildren=0, pzxid=12884901963)

​ 注意!set这种增加节点内容的方式是覆盖式增加,并不是在原有基础上增添。而且添加中文的话可能在ZooInspecter里出现的是乱码

  • 查看节点
res = zk.get('/account2/xjk/home/rst')
print(res)
# (b'new xjk', ZnodeStat(czxid=12884901963, mzxid=12884901972, ctime=1609853601853, mtime=1609855503142, version=1, cversion=0, aversion=0, ephemeralOwner=0, dataLength=7, numChildren=0, pzxid=12884901963))
  • 是否存在
res = zk.exists('/account')
print(res)
# 如果为None表示不存在
  • 删除节点
# recursive=True代表递归删除,就是它下面的子节点无论是空,都删除,如果不加上,会提示子节点非空,无法删除
zk.delete('/account2/xjk/home/rst',recursive=True)
  • 观察者模式设置监听
    • Kazoo可以在节点上设置监视功能,该监视功能可以在节点更改或节点的子级更改时触发。对节点或子节点的此更改也可以是要删除的节点或其子节点。
def my_func(event):
    print(event)# WatchedEvent(type='CHILD', state='CONNECTED', path='/user')
    
children = zk.get_children("/user", watch=my_func)
time.sleep(10)
# 当shell客户端执行create /user/xjk xxx 会打印event事件。只监听一次。当/user 创建子节点或删除子节点会触发事件
  • 装饰器观察者模式
@zk.ChildrenWatch("/user")
def watch_children(children):
    print("当前子节点: %s" % children)

@zk.DataWatch("/user")
def watch_node(data, stat):
    print("Version为: %s, 数据: %s" % (stat.version, data.decode("utf-8")))
time.sleep(100)
# shell执行:[zk: localhost:2181(CONNECTED) 121] create /user/xjk1 1111
# watch_children 会打印: 当前子节点: ['xjk1']
# shell执行:[zk: localhost:2181(CONNECTED) 124] set /user newxjk
# watch_node 会打印: Version为: 7, 数据: newxjk
# 如果shell执行: rmr /user 此时监听会报错。
  • 事务:
    • 提交多个命令,要么全部成功要么全部失败
transaction = zk.transaction()
# 检查版本是否存在
transaction.check('/account', version=0)
# 创建数据
transaction.create('/account/b', b"a value")
results = transaction.commit()
print(results)
# 如果为 [True, '/account/b'] 表示事务提交成功
# 如果为[RolledBackError(), NoNodeError()] 表示事务提交失败,并进行回滚
  • 异步连接
from kazoo.client import KazooClient
from kazoo.handlers.gevent import SequentialGeventHandler
zk = KazooClient(hosts='10.0.0.134:2181,10.0.0.131:2181,10.0.0.132:2181', handler=SequentialGeventHandler())
# 返回事件
event = zk.start_async()

# 等待30秒钟,看看是否已连接
event.wait(timeout=30)

if not zk.connected:
    zk.stop()
    print('连接失败...')
else:
    print('连接成功...')
  • 异步回调
    • 所有kazoo _async方法都 返回一个 IAsyncResult实例。这些实例使您可以查看结果准备就绪的时间,或将一个或多个回调函数准备就绪时将被调用的结果。
from kazoo.client import KazooClient
import sys
import time
from kazoo.exceptions import ConnectionLossException
from kazoo.exceptions import NoAuthException
zk = KazooClient(hosts='10.0.0.134:2181,10.0.0.131:2181,10.0.0.132:2181', timeout=10)

zk.start()

def my_callback(async_obj):
    try:
        print('async_obj', async_obj)
        children = async_obj.get()
        print(children)
    except (ConnectionLossException, NoAuthException):
        sys.exit(1)
async_obj = zk.get_children_async("/")
async_obj.rawlink(my_callback)

time.sleep(10)
zk.stop()

  • 异步CURD
    • ensure_path()具有目前没有异步副本也不能delete_async()方法来执行递归删除
# 创建
create_async()
# 是否存在
exist_async()
# 获取
get_async()
# 获取子节点
get_children_async()
# 更新节点
set_async()
posted @ 2021-01-03 20:32  是阿凯啊  阅读(148)  评论(0编辑  收藏  举报