文章目录
一、Zookeeper介绍
1、什么是Zookeeper
Zookeeper 是一种分布式协调服务,用于管理大型主机。在分布式环境中协调和管理服务是一个复杂的过程,ZooKeeper通过其简单的架构和API解决了这个问题。ZooKeeper 能让开发人员专注于核心应用程序逻辑,而不必担心应用程序的分布式特性。
2、Zookeeper的应用场景
- 分布式协调组件
在分布式系统中,需要有zookeeper作为分布式协调组件,协调分布式系统中的状态
- 分布式锁
zk在实现分布式锁上,可以做到强一致性,关于分布式锁的相关知识,会在之后的ZAB协议中介绍
- 无状态化的实现
二、搭建ZooKeeper服务器
1、zoo.conf配置文件说明
# The number of milliseconds of each ticktickTime=2000# The number of ticks that the initial # synchronization phase can takeinitLimit=10# The number of ticks that can pass between # sending a request and getting an acknowledgementsyncLimit=5# the directory where the snapshot is stored.# do not use /tmp for storage, /tmp here is just # example sakes.dataDir=/tmp/zookeeper
# the port at which the clients will connectclientPort=2181# the maximum number of client connections.# increase this if you need to handle more clients#maxClientCnxns=60## Be sure to read the maintenance section of the # administrator guide before turning on autopurge.## https://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance## The number of snapshots to retain in dataDir#autopurge.snapRetainCount=3# Purge task interval in hours# Set to "0" to disable auto purge feature#autopurge.purgeInterval=1## Metrics Providers## https://prometheus.io Metrics Exporter#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider#metricsProvider.httpHost=0.0.0.0#metricsProvider.httpPort=7000#metricsProvider.exportJvmInfo=true
2、Zookeeper服务器的操作命令
重命名conf中的文件zoo_sample.cfg->zoo.cfg
重启zk服务器:
./bin/zkServer.sh start ../conf/zoo.cfg
查看zk服务器的状态:
./bin/zkServer.sh status ../conf/zoo.cfg
停止服务器:
./bin/zkServer.sh stop ../conf/zoo.cfgs
三、Zookeeper内部的数据模型
1、zk是如何保存数据的
zk中的数据是保存在节点上的,节点就是znode,多个znode之间构成一棵树的目录结构。
Zookeeper的数据模型是什么样子呢?类似于数据结构中的树,同时也很像文件系统的目录外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传![(img-WBn7X2Qn-1660632007219)(C:\Users\v_honeylili\AppData\Roaming\Typora\typora-user-images\image-20220816115114223.png)]
树是由节点所组成,Zookeeper的数据存储也同样是基于节点,这种节点叫做Znode,但是不同于树的节点,Znode的引用方式是路劲引用,类似于文件路径:
/动物/猫
/汽车/宝马
这样的层级结构,让每一个Znode的节点拥有唯一的路径,就像命名空间一样对不同信息做出清晰的隔离。
2、zk中的znode是什么样的数据结构
zk中的znode包含了四个部分
data:保存数据
acl:权限:
c:create 创建权限,允许在该节点下创建子节点
w:write 更新权限,允许更新该节点的数据
r:read 读取权限,允许读取该节点的内容以及子节点的列表信息
d:delete 删除权限,允许删除该节点的子节点信息
a:admin 管理者权限,允许对该节点进行acl权限设置
stat:描述当前znode的元数据
child:当前节点的子节点
3、zk中节点znode的类型
1、持久节点:创建出的节点,在会话结束后依然存在。保存数据
2、持久序号节点:创建出的节点,根据先后顺序,会在节点之后带上一个数值,越后执行数值越大,适用于分布式锁的应用场景-单调递增
3、临时节点:临时节点是在会话结束后,自动被删除的,通过这个特性,zk可以实现服务注册与发现的效果。
临时序号节点:跟持久序号节点相同,适用于临时的分布式锁
Container节点(3.5.3版本新增):Container容器节点,当容器中没有任何子节点,该容器节点会被zk定期删除
TTL节点:可以指定节点的到期时间,到期后被zk定时删除。只能通过系统配置zookeeper.extendedTypeEnablee=true开启
4、zk的数据持久化
zk的数据是运行在内存中,zk提供了两种持久化机制:
事务日志
zk把执行的命令以日志形式保存在dataLogDir指定的路径中的文件中(如果没有指定dataLogDir,则按照 dataDir指定的路径)。
数据快照 snapshot
zk会在一定的时间间隔内做一次内存数据快照,把时刻的内存数据保存在快照文件中。
zk通过两种形式的持久化,在恢复时先恢复快照文件中的数据到内存中,再用日志文件中的数据做增量恢复,这样恢复的速度更快。
四、Zookeeper客户端(zkCli)的使用
1、多节点类型创建
- 创建持久节点create path [data] [acl]
- 创建持久序号节点create -s path [data] [acl]
- 创建临时节点create -e path [data] [acl]
- 创建临时序号节点create -e -s path [data] [acl]
- 创建容器节点create -c path [data] [acl]
2、查询节点
- 普通查询- ls [-s -R] path-s 详细信息-R 当前目录和子目录中的所有信息
- 查询节点相关信息- cZxid:创建节点的事务ID- mZxid:修改节点的事务ID- pZxid:添加和删除子节点的事务ID- ctime:节点创建的时间- mtime:节点最近修改的时间- dataVersion:节点内数据的版本,每更新一次数据,版本会+1- aclVersion:此节点的权限版本- ephemeralOwner:如果当前节点是临时节点,该是是当前节点所有者的session id。如果节点不是临时节点,则该值为零- dataLength:节点内数据的长度- numChildren:该节点的子节点个数
- 查询节点的内容- get [-s] path-s 详细信息
3、删除节点
- 普通删除
- 乐观锁删除- delete [-v] path-v 版本- deleteall path [-b batch size]
3、权限设置
- 注册当前会话的账号和密码:
addauth digest xiaowang:123456
- 创建节点并设置权限(指定该节点的用户,以及用户所拥有的权限s)
create /test-node abcd auth:xiaowang:123456:cdwra
- 在另一个会话中必须先使用账号密码,才能拥有操作节点的权限
五、Curator客户端的使用
1、Curator介绍
Curator是Netflix公司开源的一套zookeeper客户端框架,Curator是对Zookeeper支持最好的客户端框架。Curator封装了大部分Zookeeper的功能,比如Leader选举、分布式锁等,减少了技术人员在使用Zookeeper时的底层细节开发工作。
2、引入依赖
<!--Curator--><dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>2.12.0</version></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>2.12.0</version></dependency><!--Zookeeper--><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.7.14</version></dependency>
配置curator基本连接信息
curator.retryCount=5
curator.elapsedTimeMs=5000
curator.connectionString=192.168.200.128:2181
curator.sessionTimeoutMs=60000
curator.connectionTimeoutMs=4000
3、编写配置curator配置类
@Data@Component@ConfigurationProperties(prefix ="curator")publicclassWrapperZK{privateint retryCount;privateint elapsedTimeMs;privateString connectionString;privateint sessionTimeoutMs;privateint connectionTimeoutMs;}//引用配置类@ConfigurationpublicclassCuratorConfig{@AutowiredprivateWrapperZK wrapperZK;@Bean(initMethod ="start")publicCuratorFrameworkcuratorFramework(){returnCuratorFrameworkFactory.newClient(
wrapperZK.getConnectionString(),
wrapperZK.getSessionTimeoutMs(),
wrapperZK.getConnectionTimeoutMs(),newRetryNTimes(wrapperZK.getRetryCount(), wrapperZK.getElapsedTimeMs()));}}
4、测试
@AutowiredprivateCuratorFramework curatorFramework;@Test//添加节点voidcreateNode()throwsException{//添加默认(持久)节点String path = curatorFramework.create().forPath("/curator-node");//添加临时序号节点//String path2 = curatorFramework.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/curator-nodes", "messageDate".getBytes());System.out.println(String.format("curator create node :%s successfully!", path));// System.in.read();}@Test//获取节点值voidgetDate()throwsException{byte[] bttes = curatorFramework.getData().forPath("/curator-node");System.out.println("bttes = "+ bttes);}@Test//设置节点值voidsetDate()throwsException{
curatorFramework.setData().forPath("/curator-node","newMessage".getBytes());byte[] bytes = curatorFramework.getData().forPath("/curator-node");System.out.println("bytes = "+ bytes);}@Test//创建多级节点voidcreateWithParent()throwsException{String pathWithParent ="/node-parent/sub-node-1";String path = curatorFramework.create().creatingParentContainersIfNeeded().forPath(pathWithParent);System.out.println(String.format("curator create node :%s success!", path));}@Test//删除节点voiddelete()throwsException{String path ="/node-parent";//删除节点的同时一并删除子节点
curatorFramework.delete().guaranteed().deletingChildrenIfNeeded().forPath(path);}
六、zk实现分布式锁
1、zk中锁的种类:
- 读锁(读锁共享):大家都可以读。上锁前提:之前的锁没有写锁
- 写锁(写锁排他):只有得到写锁的才能写。上锁前提:之前没有任何锁
2、zk如何上读锁
- 创建一个临时序号节点,节点的数据是read,表示是读锁
- 获取当前zk中序号比自己小的所有节点
- 判断最小节点是否是读锁- 如果不是读锁的话,则上锁失败,为最小节点设置监听。阻塞等待,zk的watch机制会当最小节点发生变化时通知当前节点,再执行第二步的流程- 如果是读锁的话,则上锁成功。
3、zk如何上写锁
- 创建一个临时序号节点,节点的数据是write,表示写锁
- 获取zk中所有的子节点
- 判断自己是否是最小的节点: - 如果是,则上写锁成功- 如果不是,说明前面还有锁,则上锁失败,监听最小节点,如果最小节点有变化,则再执行第二步。
4、羊群效应
如果用上述的上锁方式,只要有节点发生变化,就会触发其他节点的监听事件,这样对zk的压力非常大,而羊群效应,可以调整成链式监听。解决这个问题。
5、Curator实现读写锁
- 获取读锁
@TestvoidtestGetReadLock()throwsException{//读写锁InterProcessReadWriteLock interProcessReadWriteLock =newInterProcessReadWriteLock(client,"/lock1");//获取读锁对象InterProcessLock interProcessLock = interProcessReadWriteLock.readLock();System.out.println("等待获取读锁对象中...");//获取锁
interProcessLock.acquire();for(int i =1; i <=100; i ++){Thread.sleep(3000);System.out.println(i);}//释放锁
interProcessLock.release();System.out.println("等待释放锁...");}
- 获取写锁
@TestvoidtestGetWriteLock()throwsException{//读写锁InterProcessReadWriteLock interProcessReadWriteLock =newInterProcessReadWriteLock(client,"/lock1");//获取写锁对象InterProcessLock interProcessLock = interProcessReadWriteLock.writeLock();System.out.println("等待获取写锁对象中...");//获取锁
interProcessLock.acquire();for(int i =1; i <=100; i ++){Thread.sleep(3000);System.out.println(i);}//释放锁
interProcessLock.release();System.out.println("等待释放锁...");}
七、zk的watch机制
1、Watch机制介绍
我们可以把Watch理解成是注册在特定Znode上的触发器。当这个Znode发生改变,也就是调用了create,delete,setData方法的时候,将会触发Znode上注册的对应事件,请求Watch的客户端会收到异步通知。
具体交互过程如下:
- 客户端调用getData方法,watch参数是true。服务端接到请求,返回节点数据,并且在对应的哈希表里插入被Watch的Znode路径,以及Watcher列表。
- 当被Watch的Znode已删除,服务端会查找哈希表,找到该Znode对应的所有Watcher,异步通知客户端,并且删除哈希表中对应的key-value。
2、zkCli客户端使用Watch
create /test date
get -w /test 一次性监听节点
ls -w /test 监听目录,创建和删除子节点会收到通知。但是子节点中新增节点不会被监听到
ls -R -w /test 监听子节点中节点的变化,但内容的变化不会收到通知
3、Curator客户端使用Watch
@TestpublicvoidaddNodeListener()throwsException{NodeCache nodeCache =newNodeCache(curatorFramework,"/curator-node");
nodeCache.getListenable().addListener(newNodeCacheListener(){@OverridepublicvoidnodeChanged()throwsException{
log.info("{} path nodeChanged: ","/curator-node");printNodeData();})};
nodeCache.start();//System.in.read();}publicvoidprintNodeData()throwsException{byte[] bytes = curatorFramework.getData().forPath("/curator-node");
log.info("data: {}",newString(bytes));}
八、Zookeeper集群实战
1、Zookeeper集群角色
zookeeper集群中的节点有三种角色
- Leader:处理集群的所有事务请求,集群中只有一个Leader
- Follwoer:只能处理读请求,参与Leader选举
- Observer:只能处理读请求,提升集群读的性能,但不能参与Leader选举
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u2CVFidl-1660632007227)(C:\Users\chenkangwei\AppData\Roaming\Typora\typora-user-images\image-20220808125935899.png)]
2、集群搭建
搭建4个节点,其中一个节点为Observer
- 创建4个节点的myid并设值在usr/local/zookeeper中创建一下四个文件
/usr/local/zookeeper/zkdata/zk1# echo 1 > myid/usr/local/zookeeper/zkdata/zk2# echo 2 > myid/usr/local/zookeeper/zkdata/zk3# echo 3 > myid/usr/local/zookeeper/zkdata/zk4# echo 4 > myid
- 编写4个zoo.cfg
# The number of milliseconds of each ticktickTime=2000# The number of ticks that the initial # synchronization phase can takeinitLimit=10# The number of ticks that can pass between # sending a request and getting an acknowledgementsyncLimit=5# the directory where the snapshot is stored.# do not use /tmp for storage, /tmp here is just # example sakes. 修改对应的zk1 zk2 zk3 zk4dataDir=/usr/local/zookeeper/zkdata/zk1# the port at which the clients will connectclientPort=2181#2001为集群通信端口,3001为集群选举端口,observer(观察者身份)server.1=192.168.200.128:2001:3001server.2=192.169.200.128:2002:3002server.3=192.168.200.128:2003:3003server.4=192.168.200.128:2004:3004:observer
### 3、连接Zookeeper集群./bin/zkCli.sh -server 192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183
九、ZAB协议
1、什么是ZAB协议
zookeeper作为非常重要的分布式协调组件,需要进行集群部署,集群中会以一主多从的形式进行部署。zookeeper为了保证数据的一致性,使用了ZAB(Zookeeper Atomic Broadcast)协议,这个协议解决了Zookeeper的崩溃恢复和主从数据同步的问题。
2、ZAB协议定义的四种节点状态
- Looking:选举状态
- Following:Following节点(从节点)所处的状态
- Leading:Leader节点(主节点)所处状态
3、集群上线Leader选举过程
4、崩溃恢复时的Leader选举
Leader建立完后,Leader周期性地不断向Follower发送心跳(ping命令,没有内容的socket)。当Leader崩溃后,Follower发现socket通道已关闭,于是Follower开始进入到Looking状态,重新回到上一节中的Leader选举状态,此时集群不能对外提供服务。
5、主从服务器之间的数据同步
6、Zookeeper中的NIO与BIO的应用
- NIO - 用于被客户端连接的2181端口,使用的是NIO模式与客户端建立连接- 客户端开启Watch时,也使用NIO,等待Zookeeper服务器的回调
- BIO - 集群在选举时,多个节点之间的投票通信端口,使用BIO进行通信
十、CAP理论
2000年7月,加州大学伯克利分校的 Eric Brewer教授在ACM PODC会议上提出CAP猜想。2年后,麻省理工学院的Seth Gilbert和 Nancy Lynch 从理论上证明了CAP。之后,CAP理论正式成为分布式计算领域的公认定理。
CAP理论
CAP理论为:一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和区分容错性(Partition tolerance)这三项中的两项。
- —致性(Consistency)
一致性指"all nodespsee the same data at the same time",即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致。
- 可用性(Availability)
可用性指"Reads and writes always succeed",即服务一直可用,而且是正常响应时间。
- 分区容错性(Partition tolerance)
分区容错性指"the system continues to operate despite arbitrary message loss or failure of part of the system",即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务。——避免单点故障,就要进行冗余部署,冗余部署相当于是服务的分区,这样的分区就具备了容错性。
BASE理论
eBay的架构师Dan Pritchett源于对大规模分布式系统的实践总结,在ACM上发表文章提出BASE理论,BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性《Strong Consistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency) 。
- 基本可用(Basically Available)
基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。
电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现。
- 软状态(Soft State)
软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的体现。mysql replication的异步复制也是一种体现。
- 最终一致性(Eventual Consistency)
最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的—种特殊情况。
Zookeeper追求的一致性
Zookeeper在数据同步时,追求的并不是强一致性,而是顺序一致性(事务id的单调递增)
案例一
zk数据丢失
#!/bin/bashCUR_DIR=`pwd`ZK_DIR="/data/zookeeper-3.4.14"LOGFILE="$HOME/restart.log"CHECK_CMD="ps -ef |grep java| grep /data/zookeeper-3.4.14 | grep -v grep"zk_port="2181"source /etc/profile
#set -ufunctionlogger(){time=`date +%Y%m%d-%H:%M:%S`echo"[$time] $*"|tee -a $LOGFILE}#---- main---------
logger "check_start"test -d ${ZK_DIR}&&cd${ZK_DIR}/bin
if[$? -ne 0];then
logger "zk dir non-exists"exit1fieval${CHECK_CMD}if[$? -ne 0];then
logger "ps -ef no zk,start it now."rm -rf ${ZK_DIR}/data/zookeeper_server.pid
#su - tdsql -c "cd ${ZK_DIR}/bin && ./zkServer.sh start"cd${ZK_DIR}/bin
./zkServer.sh start
if[$? -ne 0];then
logger "start zk failed"fisleep5;
./zkServer.sh status;eval${CHECK_CMD}
logger "check done."elsemode=$(echo ruok |nc127.0.0.1 $zk_port |grep imok |wc -l)if[${mode} -eq "0"];then
logger "service exception, restart it now"cd${ZK_DIR}/bin
./zkServer.sh stop && ./zkServer.sh start
if[$? -ne 0];then
logger "restart zk failed"fififi
#!/bin/bash# Snapshot file dir.dataDir=/data/zookeeper-3.4.14/data/version-2
# Transaction logs dir.dataLogDir=/data/zookeeper-3.4.14/log/version-2
# Reserved 5 files.save_count=15ls -t $dataDir/snapshot.* |tail -n +$[$save_count+1]|xargsrm -f
ls -t $dataLogDir/log.* |tail -n +$[$save_count+1]|xargsrm -f
1、zk存活探测脚本 bash 127.0.0.1 port 未通,造成zk不断重启,产生大量事务日志
2、zk事务清理脚本清除后15个事务日志 事务日志 12(被误删的事务日志) 3…16(不断重启的的事务日志) ---- snap 3.4.5.6…
则造成snap最新的数据3与最老事务日志12衔接不上了,造成中间数据丢失
strings
当zookeeper启动时,会从快照文件和事务日志里面恢复数据,加载到内存中,形成DataTree,即ZooKeeper树形数据结构
当ZooKeeper处理读请求时,会直接根据path从内存中获取数据,不生成事务日志
当ZooKeeper处理写请求时,会生成对应的事务日志,并操作对应的DataTree
ZooKeeper会按照一定请求次数来生成新的事务日志文件和生成新的快照文件
ZK 的开发者给 ZK 设计了两种磁盘文件,对应的路径分别是 zoo.cfg 配置中的 dataDir 和 dataLogDir 这两项目录的配置。log 就是小S(Sync)工作中的归档,snapshot 就是的是小S(Sync)工作中的快照。log 是负责顺序记录每一个写请求到文件,snapshot 则是直接将整个内存对象持久化至文件中。假设我现在 zoo.cfg 的配置是这样:
dataDir=/tmp/zookeeper/snapshot
dataLogDir=/tmp/zookeeper/log
3.snapshot 文件
snapshot 文件名的格式是这样 snapshot.{zxid} zxid 对应当是创建该文件时的最大 zxid,假设现在创建是最大 zxid 是 0,那目录结构会是这样:
每进行一次事务日志记录之后,ZooKeeper都会检测当前是否需要进行数据快照。理论上进行snapCount次事务操作后就会开始数据快照,但是考虑到数据快照对于ZooKeeper所在机器的整体性能影响,需要尽量避免ZooKeeper集群中所有机器在同一时刻进行数据快照。因此ZooKeeper在具体的实现中,并不是严格按照这个策略执行,而是采取“过半随机”策略,即符合如下条件就进行数据快照:
首先有两个配置:
zookeeper.snapCount (默认 100000)
zookeeper.snapSizeLimitInKb(默认 4194304 单位是KB,相当于 4 GB)
在启动后会基于这两个配置分别生成两个随机数,假设上述的配置是按照默认的设置,这两个随机数的范围就是:
randRoll = [0, 50000]
randSize = [0, 4194304 * 1024 / 2]
可以简单的认为就是上述两个配置的一半之内的随机数,至于 randSize 为什么要乘以 1024 因为最终文件计算大小是以 byte 作为单位的。
而是否快照就是取决于上面两个随机数,有两个条件:
1.当前写请求的数量达到了 zookeeper.snapCount 的一半并加上 randRoll 的数量
2.当前 log 文件的大小达到了 zookeeper.snapSizeLimitInKb 的一半并加上 randSize 的大小
上述条件满足任意一个条件后就会重置上面的两个随机数,并开始生成快照,生成快照这个过程是启动一个子线程去创建的。
-way
使用zkCleanup.sh ${条数}
自带脚本会有一个对snap数据的校验
版权归原作者 Labatt 所有, 如有侵权,请联系我们删除。