Zookeeper

观看千锋最新Zookeeper集群教程-全网最全Zookeeper应用及原理分析课程_哔哩哔哩_bilibili进行学习。

部分笔记来源于网络。

本文使用的Zookeeper版本是3.7.1

  • 分布式协调组件

在分布式系统中,需要有zookeeper作为分布式协调组件,协调分布式系统中的状态

  • 分布式锁

zk在实现分布式锁上,可以做到强一致性,关于分布式锁的相关知识,会在之后的ZAB协议中介绍

  • 无状态化的实现

安装和配置启动

下载地址

官网下载:Apache ZooKeeper

阿里云镜像站下载:apache-zookeeper安装包下载_开源镜像站-阿里云 (aliyun.com)

下载后解压即可。

配置文件

配置文件样例conf/zoo_sample.cfg如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# The number of milliseconds of each tick
tickTime=2000
# 允许follower初始化连接到leader最大时长,它表示tickTime时间倍数
# 即initLimit*tickTime
initLimit=10
# 允许follower与leader数据同步最大时长,它表示tickTime时间倍数
syncLimit=5
# zookeeper数据(快照)储存的目录
# 若无dataLogDir则日志也储存在此目录
dataDir=/tmp/zookeeper
# 对客户端提供的端口号
clientPort=2181
# 客户端的最大连接数
#maxClientCnxns=60

# 保存的快照数量
#autopurge.snapRetainCount=3

# 自动触发清除任务时间间隔,单位小时
# 0表示关闭
#autopurge.purgeInterval=1

## Metrics Providers
#
# https://prometheus.io Metrics Exporter
#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider
#metricsProvider.httpPort=7000
#metricsProvider.exportJvmInfo=true


要启动必须要有一个配置文件,我们复制一份样例文件在相同目录并重命名为zoo.cfg,修改相关配置即可。

zookeeper默认寻找conf/zoo.cfg配置文件。

启动Zookeeper

在bin目录下可以看到许多可执行脚本,后缀为cmd为Windows下执行的,sh为Linux下执行的。

Windows下启动

双击zkServer.cmd打开,不闪退则启动成功。

双击zkCli.cmd打开客户端窗口。

Linux下启动

在bin目录下:

  • ./zkServer.sh start启动Zookeeper
  • ./zkServer.sh status查询Zookeeper状态信息
  • ./zkServer.sh stop停止Zookeeper
  • 在后面添加配置文件路径来指定配置文件
    • 如:./zkServer.sh start ../conf/zoo.cfg

连接客户端:

  • ./zkCli.sh -server <host:port>连接Zookeeper,没有-server默认连接localhost:2181

内部数据模型

Zookeeper的数据是以节点(Znode)的形式构成一棵储存。

/为树的根。

Znode的组成

Zookeeper中的Znode包含了四个部分

data:保存数据

acl:权限:

  • c:create 创建权限,允许在该节点下创建子节点

  • w:write 更新权限,允许更新该节点的数据

  • r:read 读取权限,允许读取该节点的内容以及子节点的列表信息

  • d:delete 删除权限,允许删除该节点的子节点信息

  • a:admin 管理者权限,允许对该节点进行acl权限设置

    stat:描述当前znode的元数据

    child:当前节点的子节点

Znode的类型

  1. 持久节点:创建出的节点,在会话结束后依然存在。保存数据

  2. 持久序号节点:创建出的节点,根据先后顺序,会在节点之后带上一个数值,越后执行数值越大,适用于分布式锁的应用场景-单调递增

  3. 临时节点:临时节点是在会话结束后,自动被删除的,通过这个特性,zk可以实现服务注册与发现的效果。

  4. 临时序号节点:跟持久序号节点相同,适用于临时的分布式锁

  5. Container节点(3.5.3版本新增):Container容器节点,当容器中没有任何子节点,该容器节点会被zk定期删除

  6. TTL节点:可以指定节点的到期时间,到期后被zk定时删除。只能通过系统配置zookeeper.extendedTypeEnablee=true开启

数据持久化

zk的数据是运行在内存中,zk提供了两种持久化机制:

  • 事务日志:zk把执行的命令以日志形式保存在dataLogDir指定的路径中的文件中

  • 数据快照:zk会在一定的时间间隔内做一次内存数据快照,把时刻的内存数据保存在快照文件中

zk通过两种形式的持久化,在恢复时先恢复快照文件中的数据到内存中,再用日志文件中的数据做增量恢复,这样恢复的速度更快。

zkCli客户端的使用

多节点类型创建

  • 创建持久节点

    create [data] [acl]

  • 创建持久序号节点

    create -s [data] [acl]

  • 创建临时节点

    create -e [data] [acl]

  • 创建临时序号节点

    create -e -s [data] [acl]

  • 创建容器节点

    create -c [data] [acl]

查询节点

  • 普通查询

    • ls [-s -R]

      -s 详细信息

      -R 当前目录和子目录中的所有信息

  • 查询节点相关信息

    • cZxid:创建节点的事务ID
    • mZxid:修改节点的事务ID
    • pZxid:添加和删除子节点的事务ID
    • ctime:节点创建的时间
    • mtime:节点最近修改的时间
    • cversion:子节点的version
    • dataVersion:节点内数据的版本,每更新一次数据,版本会+1
    • aclVersion:此节点的权限版本
    • ephemeralOwner:如果当前节点是临时节点,该是是当前节点所有者的session id。如果节点不是临时节点,则该值为零
    • dataLength:节点内数据的长度
    • numChildren:该节点的子节点个数
  • 查询节点的内容

    • get [-s]

      -s 详细信息

删除节点

  • delete [-v] 删除空节点

    • -v 版本
  • deleteall [-b batch size] 删除包括非空节点

权限设置

  • 注册当前会话的账号和密码:

    1
    addauth digest <账号>:<密码>
  • 创建节点并设置权限(指定该节点的用户,以及用户所拥有的权限s)

    1
    create /<node> <data> auth:<账号>:<密码>:<权限>

    例:create /test-node abcd auth:xiaowang:123456:cdwra

  • 在另一个会话中必须先使用账号密码,才能拥有操作节点的权限

使用Curator客户端

Curator是Netflix公司开源的一套zookeeper客户端框架,Curator是对Zookeeper支持最好的客户端框架。Curator封装了大部分Zookeeper的功能,比如Leader选举、分布式锁等,减少了技术人员在使用Zookeeper时的底层细节开发工作。

引入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.7.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.4.0</version>
</dependency>

创建CuratorProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
@Component
@ConfigurationProperties(prefix = "curator")
public class CuratorProperties {

private int retryCount;

private int elapsedTimeMs;

private String connectionString;

private int sessionTimeoutMs;

private int connectionTimeoutMs;

}

创建CuratorConfig配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class CuratorConfig {

@Resource
private CuratorProperties curatorProperties;

@Bean
public CuratorFramework curatorFramework() {
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(curatorProperties.getConnectionString())
.connectionTimeoutMs(curatorProperties.getConnectionTimeoutMs())
.sessionTimeoutMs(curatorProperties.getSessionTimeoutMs())
.retryPolicy(new RetryNTimes(curatorProperties.getRetryCount(), curatorProperties.getElapsedTimeMs()))
.build();
client.start();
return client;
}
}

配置application.yml

1
2
3
4
5
6
curator:
connection-string: localhost:2181
retry-count: 5
session-timeout-ms: 60000
connection-timeout-ms: 4000
elapsed-time-ms: 5000

分布式锁

锁的种类:

  • 读锁(读锁共享):大家都可以读。上锁前提:之前的锁没有写锁
  • 写锁(写锁排他):只有得到写锁的才能写。上锁前提:之前没有任何锁

如何上读锁

  • 创建一个临时序号节点,节点的数据是read,表示是读锁

  • 获取当前zk中序号比自己小的所有节点

  • 判断最小节点是否是读锁
    • 如果不是读锁的话,则上锁失败,为最小节点设置监听。阻塞等待,zk的watch机制会当最小节点发生变化时通知当前节点,再执行第二步的流程
    • 如果是读锁的话,则上锁成功。

如何上写锁

  • 创建一个临时序号节点,节点的数据是write,表示写锁
  • 获取zk中所有的子节点
  • 判断自己是否是最小的节点:
    • 如果是,则上写锁成功
    • 如果不是,说明前面还有锁,则上锁失败,监听最小节点,如果最小节点有变化,则再执行第二步。

羊群效应

如果用上述的上锁方式,只要有节点发生变化,就会触发其他节点的监听事件,这样对zk的压力非常大,而羊群效应,可以调整成链式监听。解决这个问题。

Curator实现读写锁

  • 获取读锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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("等待释放锁...");
}
  • 获取写锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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机制

介绍

我们可以把Watch理解成是注册在特定Znode上的触发器。当这个Znode发生改变,也就是调用了createdeletesetData方法的时候,将会触发Znode上注册的对应事件,请求Watch的客户端会收到异步通知。

具体交互过程如下:

  • 客户端调用getData方法,watch参数是true。服务端接到请求,返回节点数据,并且在对应的哈希表里插入被Watch的Znode路径,以及Watcher列表。

  • 当被Watch的Znode已删除,服务端会查找哈希表,找到该Znode对应的所有Watcher,异步通知客户端,并且删除哈希表中对应的key-value。

zkCli客户端使用Watch

1
2
3
4
create /test date
get -w /test 一次性监听节点
ls -w /test 监听目录,创建和删除子节点会收到通知。但是子节点中新增节点不会被监听到
ls -R -w /test 监听子节点中节点的变化,但内容的变化不会收到通知

Curator客户端使用Watch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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集群

集群角色

zookeeper集群中的节点有三种角色

  • Leader:处理集群的所有事务请求,集群中只有一个Leader
  • Follower:只能处理读请求,参与Leader选举
  • Observer:只能处理读请求,提升集群读的性能,但不能参与Leader选举

集群搭建

以搭建4个节点为例,搭建伪集群,其中一个节点为Observer

  • 创建4个节点的myid并设值

    在ZK的数据储存目录/tmp/zookeeper中创建以下四个文件

    1
    2
    3
    4
    /tmp/zookeeper/zk1# echo 1 > myid
    /tmp/zookeeper/zk2# echo 2 > myid
    /tmp/zookeeper/zk3# echo 3 > myid
    /tmp/zookeeper/zk4# echo 4 > myid
  • 编写4个zoo.cfg并修改以下选项

    1
    2
    3
    4
    5
    6
    7
    8
    dataDir=/tmp/zookeeper/zk1
    clientPort=2181

    #2001为集群通信端口,3001为集群选举端口,observer(观察者身份)
    server.1=192.168.245.128:2001:3001
    server.2=192.168.245.128:2002:3002
    server.3=192.168.245.128:2003:3003
    server.4=192.168.245.128:2004:3004:observer
  • 依次启动Zookeeper

    1
    2
    3
    4
    ./zkServer.sh start ../conf/zoo1.cfg
    ./zkServer.sh start ../conf/zoo2.cfg
    ./zkServer.sh start ../conf/zoo3.cfg
    ./zkServer.sh start ../conf/zoo4.cfg
  • 连接集群

    1
    ./zkCli.sh -server 192.168.245.128:2181,192.168.245.128:2182,192.168.245.128:2183

ZAB协议

简介

Zookeeper作为非常重要的分布式协调组件,需要进行集群部署,集群中会以一主多从的形式进行部署。zookeeper为了保证数据的一致性,使用了ZAB(Zookeeper Atomic Broadcast)协议,这个协议解决了Zookeeper的崩溃恢复和主从数据同步的问题。

集群选举Leader的过程

集群节点的状态:

  • Looking:选举状态
  • Following:Follower节点(从节点)所处的状态
  • Leading:Leader节点(主节点)所处状态

leader选举过程

崩溃恢复时的Leader选举

Leader建立完后,Leader周期性地不断向Follower发送心跳(ping命令,没有内容的socket)。当Leader崩溃后,Follower发现socket通道已关闭,于是Follower开始进入到Looking状态,重新回到上一节中的Leader选举状态,此时集群不能对外提供服务。

主从服务器之间的数据同步

主从复制

Zookeeper中的NIO与BIO的应用

  • NIO
    • 用于被客户端连接的2181端口,使用的是NIO模式与客户端建立连接
    • 客户端开启Watch时,也使用NIO,等待Zookeeper服务器的回调
  • BIO
    • 集群在选举时,多个节点之间的投票通信端口,使用BIO进行通信

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理论

核心思想是即使无法做到强一致性(Strong Consistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency) 。

  • 基本可用(Basically Available)

基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。

电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现。

  • 软状态(Soft State)

软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的体现。mysql replication的异步复制也是一种体现。

  • 最终一致性(Eventual Consistency)

最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的—种特殊情况。

Zookeeper追求的一致性

Zookeeper在数据同步时,追求的并不是强一致性,而是顺序一致性(事务id的单调递增)