赞
踩
Zookeeper 是一种分布式协调服务,用于管理大型主机。在分布式环境中协调和管理服务是一个复杂的过程,ZooKeeper通过其简单的架构和API解决了这个问题。ZooKeeper 能让开发人员专注于核心应用程序逻辑,而不必担心应用程序的分布式特性。
在分布式系统中,需要有zookeeper作为分布式协调组件,协调分布式系统中的状态
zk在实现分布式锁上,可以做到强一致性,关于分布式锁的相关知识,会在之后的ZAB协议中介绍
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=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 connect
clientPort=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
重命名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
zk中的数据是保存在节点上的,节点就是znode,多个znode之间构成一棵树的目录结构。
Zookeeper的数据模型是什么样子呢?类似于数据结构中的树,同时也很像文件系统的目录[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
树是由节点所组成,Zookeeper的数据存储也同样是基于节点,这种节点叫做Znode,但是不同于树的节点,Znode的引用方式是路劲引用,类似于文件路径:
/动物/猫
/汽车/宝马
这样的层级结构,让每一个Znode的节点拥有唯一的路径,就像命名空间一样对不同信息做出清晰的隔离。
zk中的znode包含了四个部分
data:保存数据
acl:权限:
c:create 创建权限,允许在该节点下创建子节点
w:write 更新权限,允许更新该节点的数据
r:read 读取权限,允许读取该节点的内容以及子节点的列表信息
d:delete 删除权限,允许删除该节点的子节点信息
a:admin 管理者权限,允许对该节点进行acl权限设置
stat:描述当前znode的元数据
child:当前节点的子节点
1、持久节点:创建出的节点,在会话结束后依然存在。保存数据
2、持久序号节点:创建出的节点,根据先后顺序,会在节点之后带上一个数值,越后执行数值越大,适用于分布式锁的应用场景-单调递增
3、临时节点:临时节点是在会话结束后,自动被删除的,通过这个特性,zk可以实现服务注册与发现的效果。
临时序号节点:跟持久序号节点相同,适用于临时的分布式锁
Container节点(3.5.3版本新增):Container容器节点,当容器中没有任何子节点,该容器节点会被zk定期删除
TTL节点:可以指定节点的到期时间,到期后被zk定时删除。只能通过系统配置zookeeper.extendedTypeEnablee=true开启
zk的数据是运行在内存中,zk提供了两种持久化机制:
事务日志
zk把执行的命令以日志形式保存在dataLogDir指定的路径中的文件中(如果没有指定dataLogDir,则按照 dataDir指定的路径)。
数据快照 snapshot
zk会在一定的时间间隔内做一次内存数据快照,把时刻的内存数据保存在快照文件中。
zk通过两种形式的持久化,在恢复时先恢复快照文件中的数据到内存中,再用日志文件中的数据做增量恢复,这样恢复的速度更快。
创建持久节点
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]
普通查询
ls [-s -R] path
-s 详细信息
-R 当前目录和子目录中的所有信息
查询节点相关信息
查询节点的内容
get [-s] path
-s 详细信息
普通删除
乐观锁删除
delete [-v] path
-v 版本
deleteall path [-b batch size]
注册当前会话的账号和密码:
addauth digest xiaowang:123456
创建节点并设置权限(指定该节点的用户,以及用户所拥有的权限s)
create /test-node abcd auth:xiaowang:123456:cdwra
在另一个会话中必须先使用账号密码,才能拥有操作节点的权限
Curator是Netflix公司开源的一套zookeeper客户端框架,Curator是对Zookeeper支持最好的客户端框架。Curator封装了大部分Zookeeper的功能,比如Leader选举、分布式锁等,减少了技术人员在使用Zookeeper时的底层细节开发工作。
<!--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
@Data
@Component
@ConfigurationProperties(prefix = "curator")
public class WrapperZK {
private int retryCount;
private int elapsedTimeMs;
private String connectionString;
private int sessionTimeoutMs;
private int connectionTimeoutMs;
}
//引用配置类
@Configuration
public class CuratorConfig {
@Autowired
private WrapperZK wrapperZK;
@Bean(initMethod = "start")
public CuratorFramework curatorFramework(){
return CuratorFrameworkFactory.newClient(
wrapperZK.getConnectionString(),
wrapperZK.getSessionTimeoutMs(),
wrapperZK.getConnectionTimeoutMs(),
new RetryNTimes(wrapperZK.getRetryCount(), wrapperZK.getElapsedTimeMs())
);
}
}
@Autowired
private CuratorFramework curatorFramework;
@Test
//添加节点
void createNode() throws Exception{
//添加默认(持久)节点
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
//获取节点值
void getDate() throws Exception {
byte[] bttes = curatorFramework.getData().forPath("/curator-node");
System.out.println("bttes = " + bttes);
}
@Test
//设置节点值
void setDate() throws Exception {
curatorFramework.setData().forPath("/curator-node", "newMessage".getBytes());
byte[] bytes = curatorFramework.getData().forPath("/curator-node");
System.out.println("bytes = " + bytes);
}
@Test
//创建多级节点
void createWithParent() throws Exception {
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
//删除节点
void delete() throws Exception {
String path = "/node-parent";
//删除节点的同时一并删除子节点
curatorFramework.delete().guaranteed().deletingChildrenIfNeeded().forPath(path);
}
创建一个临时序号节点,节点的数据是read,表示是读锁
获取当前zk中序号比自己小的所有节点
判断最小节点是否是读锁
如果用上述的上锁方式,只要有节点发生变化,就会触发其他节点的监听事件,这样对zk的压力非常大,而羊群效应,可以调整成链式监听。解决这个问题。
@Test
void testGetReadLock()throws Exception{
//读写锁
InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(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("等待释放锁...");
}
@Test
void testGetWriteLock()throws Exception{
//读写锁
InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(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("等待释放锁...");
}
我们可以把Watch理解成是注册在特定Znode上的触发器。当这个Znode发生改变,也就是调用了create,delete,setData方法的时候,将会触发Znode上注册的对应事件,请求Watch的客户端会收到异步通知。
具体交互过程如下:
create /test date
get -w /test 一次性监听节点
ls -w /test 监听目录,创建和删除子节点会收到通知。但是子节点中新增节点不会被监听到
ls -R -w /test 监听子节点中节点的变化,但内容的变化不会收到通知
@Test
public void addNodeListener() throws Exception{
NodeCache nodeCache = new NodeCache(curatorFramework,"/curator-node");
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception{
log.info("{} path nodeChanged: ", "/curator-node");
printNodeData();
}
)};
nodeCache.start();
//System.in.read();
}
public void printNodeData() throws Exception{
byte[] bytes = curatorFramework.getData().forPath("/curator-node");
log.info("data: {}", new String(bytes));
}
zookeeper集群中的节点有三种角色
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u2CVFidl-1660632007227)(C:\Users\chenkangwei\AppData\Roaming\Typora\typora-user-images\image-20220808125935899.png)]
搭建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 tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes. 修改对应的zk1 zk2 zk3 zk4
dataDir=/usr/local/zookeeper/zkdata/zk1
# the port at which the clients will connect
clientPort=2181
#2001为集群通信端口,3001为集群选举端口,observer(观察者身份)
server.1=192.168.200.128:2001:3001
server.2=192.169.200.128:2002:3002
server.3=192.168.200.128:2003:3003
server.4=192.168.200.128:2004:3004:observer
./bin/zkCli.sh -server 192.168.200.128:2181,192.168.200.128:2182,192.168.200.128:2183
zookeeper作为非常重要的分布式协调组件,需要进行集群部署,集群中会以一主多从的形式进行部署。zookeeper为了保证数据的一致性,使用了ZAB(Zookeeper Atomic Broadcast)协议,这个协议解决了Zookeeper的崩溃恢复和主从数据同步的问题。
Leader建立完后,Leader周期性地不断向Follower发送心跳(ping命令,没有内容的socket)。当Leader崩溃后,Follower发现socket通道已关闭,于是Follower开始进入到Looking状态,重新回到上一节中的Leader选举状态,此时集群不能对外提供服务。
2000年7月,加州大学伯克利分校的 Eric Brewer教授在ACM PODC会议上提出CAP猜想。2年后,麻省理工学院的Seth Gilbert和 Nancy Lynch 从理论上证明了CAP。之后,CAP理论正式成为分布式计算领域的公认定理。
CAP理论为:一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和区分容错性(Partition tolerance)这三项中的两项。
一致性指"all nodespsee the same data at the same time",即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致。
可用性指"Reads and writes always succeed",即服务一直可用,而且是正常响应时间。
分区容错性指"the system continues to operate despite arbitrary message loss or failure of part of the system",即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务。——避免单点故障,就要进行冗余部署,冗余部署相当于是服务的分区,这样的分区就具备了容错性。
eBay的架构师Dan Pritchett源于对大规模分布式系统的实践总结,在ACM上发表文章提出BASE理论,BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性《Strong Consistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency) 。
基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。
电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现。
软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的体现。mysql replication的异步复制也是一种体现。
最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的—种特殊情况。
Zookeeper在数据同步时,追求的并不是强一致性,而是顺序一致性(事务id的单调递增)
案例一
zk数据丢失
#!/bin/bash
CUR_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 -u
function logger(){
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"
exit 1
fi
eval ${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"
fi
sleep 5;
./zkServer.sh status;
eval ${CHECK_CMD}
logger "check done."
else
mode=$(echo ruok | nc 127.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"
fi
fi
fi
#!/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=15
ls -t $dataDir/snapshot.* | tail -n +$[$save_count+1] | xargs rm -f
ls -t $dataLogDir/log.* | tail -n +$[$save_count+1] | xargs rm -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数据的校验
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。