概览
https://zookeeper.apache.org/
前面用redis实现分布式锁的时候,会显得很复杂;用zookeeper实现会简单很多
当然,zookeeper不只用来实现分布式锁,还有很多其他的功能.
ZooKeeper是用于分布式应用程序的分布式,开放源代码协调服务。
它公开了一组简单的原语,分布式应用程序可以基于这些原语来实现用于同步,配置维护以及组和命名的更高级别的服务。
使用了按照文件系统熟悉的目录树结构样式设置的数据模型。
zk也是二进制安全的,按byte[]存储.
- ZooKeeper很简单。
ZooKeeper允许分布式进程通过共享的分层名称空间相互协调,该命名空间的组织方式类似于标准文件系统。名称空间由数据寄存器(在ZooKeeper中称为znodes)组成,它们类似于文件和目录。
与设计用于存储的典型文件系统不同,ZooKeeper数据保留在内存中,这意味着ZooKeeper可以实现高吞吐量和低延迟数。 - 以主从复制的形式实现高可用集群,一个leader,多个follower,读写分离,leader读写,follower读.
如果leader挂了,进入无主状态,它可以快速的重新选一个leader(官方压测不到200ms) - 每个client连接到zk时,都会产生一个session作为身份标识,每个session对应一个临时节点,当client挂了或者断开连接时,session就消失了
之前我们用redis实现分布式锁时,要设置过期时间,还要再起个线程监听它挂没挂;
如果用zk,客户端连接到zk,建立了session会话,在zk里创建了一把锁,客户端挂掉后锁就释放了,也可以手动释放锁;依靠session这把锁可以很简单的实现. - 目录树结构,类似与文件系统,每个节点都可以存1M内的数据(二进制安全);
节点分为持久节点和临时节点,临时节点依托于session
只要创建znode的会话处于活动状态,这些znode就会存在。会话结束时,将删除znode.
ZK旨在分布式协调,不要把它当数据库用,即不要频繁的写,因为ZK的优势在于读
曾经的Spark消费Kafka里的数据,会把消费的offset记录到zk里, 以做到分布式协调;但这其实是把ZK当数据库用了,频繁的修改数据,不能发挥ZK的优势. - 节点支持序列化,
- 特点:
顺序一致性,客户端的更新将按发送顺序应用(写操作转交给leader)
原子性,写操作会写到每一个节点上(最终一致性)
单系统镜像,无论客户端连接到哪个服务器,客户端都将看到相同的服务视图(包括session,也统一)。
可靠性(持久性)-应用更新后,此更新将一直持续到客户端覆盖更新为止。
及时性-确保系统的客户视图在特定时间范围内是最新的(最终一致性)。
性能测试图:
this is a throughput graph of ZooKeeper release 3.2 running on servers with dual 2Ghz Xeon and two SATA 15K RPM drives.
安装
需要安装JDK>=1.7
官网下载解压zookeeper后,进入conf文件夹,
复制出一份配置文件cp zoo_sample.cfg zoo.cfg,
然后编辑 zoo.cfg,常用配置说明:
- tickTime=2000 心跳间隔,单位时毫秒
- initLimit=10 节点间日常同步时,超过多少个心跳后就认为该节点有问题
- syncLimit=5 写操作时,follower响应,超过多少个心跳后就认为该follower有问题
- dataDir=/data/data/zookeeper 快照的存储路径
- clientPort=2181 客户端连接时的端口号
- #maxClientCnxns=60 最大客户端连接数
当leader挂掉后,需要投票选出新leader,“过半通过”;在redis的哨兵主从模式下,我们不需要配置所有节点,需要手动配置一个"票数",不一定非得过半;
在zookeeper中,需要配置所有节点,"票数"不能配置,而是通过n/2 + 1 计算得到;
这样配置节点:
(3888是选leader时投票用的端口,follower之间通信,第一次启动没有leader或者leader挂了时用)
(2888是leader开启的端口,后续leader和follower之间用这个端口通信,当有写请求时交给leader)
(server.后面的数字是id,选leader时 给id大的投票;其实还会根据事务id判断)
server.1=node1:2888:3888
server.2=node2:2888:3888
server.3=node3:2888:3888
然后在dataDir=/data/data/zookeeper 目录下,创建一个myid文件,每台机器写自己的id号
然后依次启动就完事了
可以查看端口:
netstat -natp | egrep ‘(2888|3888)’
zkCli shell 使用
随便进入一台机器,zkCli.sh 连接本机的ZK
连接后会打印sessionid:
Session establishment complete on server localhost/127.0.0.1:2181, session id = 0x200004489b40000, negotiated timeout = 30000
输入help查看帮助
ZooKeeper -server host:port -client-configuration properties-file cmd args
addWatch [-m mode] path # optional mode is one of [PERSISTENT, PERSISTENT_RECURSIVE] - default is PERSISTENT_RECURSIVE
addauth scheme auth
close
config [-c] [-w] [-s]
connect host:port
create [-s] [-e] [-c] [-t ttl] path [data] [acl]
delete [-v version] path
deleteall path [-b batch size]
delquota [-n|-b] path
get [-s] [-w] path
getAcl [-s] path
getAllChildrenNumber path
getEphemerals path
history
listquota path
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 [-s] [-v version] path data
setAcl [-s] [-v version] [-R] path acl
setquota -n|-b val path
stat [-w] path
sync path
version
create
创建临时节点/永久节点
create -e 创建一个临时节点
不指定参数,默认创建永久节点
create /hi 创建永久节点,新版本ZK可以不指定值,老版本必须指定,不能为空
create -e /hi/tmp 创建个临时节点;
当客户端断开连接(quit)时,它创建的临时节点就自动消失.
session占用全局事务id
假设客户端a使用API连接了ZK集群中的 F1,客户端会把sessionID保存一份, 客户端创建一些临时节点
这时如果F1挂了,客户端可能会连接到F2,那么客户端创建的临时节点会丢失吗?
不会,因为客户端保存了sessionID,并且ZK服务端会统一视图,每个节点都有该sessionID
序列 create -s
假设两个客户端同时想要创建一个路径的节点,如果不指定序列(-s),就会覆盖;
而指定-s后,会规避这个覆盖的问题,创建节点使会在path后面加上序列号
这个序列号类似于数据库的"自增id",是个全局性id
相当于zk帮我们重命名了,会返回真正的path.
第一个客户端:
[zk: localhost:2181(CONNECTED) 14] create -s /qwe
Created /qwe0000000006
第二个客户端:
[zk: localhost:2181(CONNECTED) 7] create -s /qwe
Created /qwe0000000007
get -s /path
get时 加上 -s 参数,会打印额外信息:
这些ID都分为两部分,高32位是leader的纪元号;低32位是事务id;
客户端的连接和断开也会全局的事务id+1
world 获取到的节点值
cZxid = 0x100000007 该节点创建时的事务id
ctime = Fri Jun 19 08:27:31 CST 2020
mZxid = 0x100000009 该节点最后的修改的事务id
mtime = Fri Jun 19 08:34:52 CST 2020
pZxid = 0x100000008 ?整个ZK中,最后一次创建的事务id
cversion = 1
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0 临时持有者,0x0表示永久节点;如果是临时节点,这里就是创建它的客户端的sessionid
dataLength = 5
numChildren = 1
本节总结
上面那些ZK的功能,可以做哪些事呢:
- 统一配置管理 - 节点中可以存储不到1M的数据
- 分组管理 - path结构
- 统一命名 - sequence (create -s)
- 同步,分布式锁 - 临时节点
千万不要把ZK当数据库用;
更多的用于分布式协调.
实现分布式锁
- 临时节点(-e)
客户端借此可实现简单的分布式锁;可用于高可用选主 - 依托一个父节点,创建临时子节点时 带上序列( -s); 这样相当于一个父节点下可以有多把锁
客户端借此可实现 队列式的,或者事务的锁