zookeeper-笔记
zookeeper是一个开源的分布式的,为分布式框架提供协调服务的apache项目
工作机制
zookeeper从设计角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,zookeeper就将负责通知已经在zookeeper上注册的那些观察者做出相应的反应
特点
- zookeeper是由一个领导者(leader)、多个跟随者(follower)组成的集群
- 集群中只要有半数以上节点存活,zookeeper集群就能正常服务。所以zookeeper适合安装奇数台服务器
- 全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的
- 更新请求顺序执行,来自同一个client的更新请求按其发送顺序依次执行
- 数据更新原子性,一次数据更新要么成功,要么失败
- 实时性,在一定时间范围内,client能读到最新数据
数据结构
zookeeper数据模型的结构与unix文件系统很类似,整体上可以看作是一棵树,每个节点称做一个znode。每一个znode默认能够存储1mb的数据,每个znode都可以通过其路径唯一标识
应用场景
提供的服务包括:
- 统一命名服务:在分布式环境下,经常需要对应用/服务进行统一命名,便于识别。例如ip不容易记住,而域名容易记住
- 统一配置管理:
- 分布式环境下,配置文件同步非常常见
- 一般要求一个集群中,所有节点的配置信息是一致的,比如kafka集群
- 对配置文件修改后,希望能够快速同步到各个节点上
- 配置管理可交由zookeeper实现
- 可将配置信息写入zookeeper上的一个znode
- 各个客户端服务器监听这个znode
- 一旦znode中的数据被修改,zookeeper将通知各个客户端服务器
- 分布式环境下,配置文件同步非常常见
- 统一集群管理:
- 在分布式环境中,实时掌握每个节点的状态是必要的
- 可根据节点实时状态做出一些调整
- zookeeper可以实现实时监控状态变化
- 可将节点信息写入zookeeper上的一个znode
- 监听这个znode可获取它的实时状态变化
- 在分布式环境中,实时掌握每个节点的状态是必要的
- 服务器节点动态上下线:客户端能实时洞察到服务器上下线的变化
- 软负载均衡:在zookeeper中纪录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求
安装
安装jdk
解压zookeeper安装包后,在conf目录下,找到zoo_sample.cfg文件,更名为zoo.cfg
修改zoo.cfg配置文件中的dataDir
通过bin/zkServer.sh start命令开启zk,通过bin/zkServer.sh status查看运行状态,通过bin/zkServer.sh stop关闭zk服务
通过执行bin/zkCli.sh启动客户端,通过quit推出客户端
配置文件
zoo.cfg
- tickTime:通信心跳时间,zookeeper服务器与客户端心跳时间,单位毫秒
- initLimit:lf通信时限,leader和follower初始连接时能容忍的最多心跳数(tickTime的数量)
- syncLimit:lf同步通信时限,leader和follower直接通信时间如果超过syncLimit * tickTime,leader认为follwer死掉,从服务器列表中删除follower
- dataDir:保存zookeeper中的数据,默认的tmp目录,容易被linux定期删除,所以一般不用
- clientPort:客户端连接端口,默认2181
集群安装
在单机安装的基础上,安装多个服务
每个服务的dataDir文件夹中创建一个名为myid的文件,内容为集群中的唯一标识
在zoo.cfg中添加配置server.A=B:C:D
- A:是一个数字,表示这个是几号服务器(myid的值)
- B:是这个服务器的地址
- C:是这个给服务器follower与集群中的leader服务器交换信息的端口号
- D:是万一集群中的leader服务器挂了,需要一个端口来重新进行选举
集群可以通过bin/zkCli.sh -server 地址:交换信息端口号`连接集群其它服务器
选举机制
半数机制,超过半数的投票通过,即通过
假设一共有5个服务
第一次启动
- 服务器1启动,发起一次选举。服务器1投自己一票。此时服务器1票数1票,不够半数以上,选举无法完成,服务器1状态保存为looking
- 服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交互选票信息:此时服务器1发现服务器2的myid比自己目前投票选举的(服务器1)大,更改选票为推举服务器2.此时服务器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器1,2保持looking
- 服务器3启动,发起一次选举。此时服务器1和2都会更改选票为服务器3.此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3票数过半,服务器3成为leader。服务器1、2为following
- 服务器4启动,发起一次选举。此时服务器1、2、3已经不是looking状态,不会更改选票信息。交互选票结果:服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为following
- 服务器5启动,同服务器4相同
名词解释:
sid:服务器id。用来唯一标识一台zookeeper集群中的机器,每台机器不能重复,和myid一致
zxid:事务id。zxid是一个事务id,用来标识一次服务器状态的变更。在某一时刻,集群中的每台机器的zxid值不一定完全一致,这和zookeeper服务器对于客户端“更新请求”的处理逻辑有关
epoch:每个leader任期的id。没有leader时同一轮投票过程中的逻辑时钟值是相同的。每完成一次投票这个数据就会增加
非第一次启动
-
当zookeeper集群中的一台服务器出现以下两种情况之一时,就会开始进入leader选举
- 服务器初始化
- 服务器运行期间无法和leader保持连接
-
而当一台机器进入leader选举流程时,当前集群也可能会处于以下两种状态
-
集群中本来就已经存在一个leader
这种情况下机器试图去选举leader时,会被告知当前服务器的leader信息,对于机器来说,仅仅需要和leader机器建立连接,并进行状态同步即可
-
集群中确实不存在leader
假设zookeeper由5台服务器组成,sid分别为1、2、3、4、5,zxid分别为8、8、8、7、7,并且此时sid为3的服务器是leader。某一时刻,3和5服务器出现故障,因此开始进行leader选举
sid为1、2、4的投票情况:
sid(服务器id) epoch(任期id) zxid(事务id) 1 1 8 2 1 8 4 1 7 选举leader规则:
- epoch大的直接胜出
- epoch相同,事务id大的胜出
- 事务id相同,服务器id大的胜出
-
常用命令
命令 | 作用 |
---|---|
create | 创建节点 |
ls | 查看父节点下子节点 |
get | 查看节点信息 |
set | 修改节点信息 |
delete | 删除节点 |
deleteall | 递归删除节点 |
stat | 查看节点状态 |
节点信息
ls -s /
命令查看节点信息
参数 | 含义 |
---|---|
czxid | 创建节点事务id |
ctime | znode被创建的毫秒数(1970年开始) |
mzxid | znode最后更新的事务id |
mtime | znode最后修改的毫秒数(1970年开始) |
pZxid | znode最后更新的子节点事务id |
cversion | znode子节点变化号,znode子节点修改次数 |
dataversion | znode数据变化号 |
aclVersion | znode访问控制列表的变化号 |
ephemeralOwner | 如果是临时节点,这个是znode拥有者的sersionid,如果不是临时节点则是0 |
dataLength | znode的数据长度 |
numChildren | znode子节点数量 |
节点类型
-
持久:客户端和服务器端断开连接后,创建的节点不删除
-
持久化目录节点
-
持久化顺序编号目录节点:zk给该节点进行顺序编号,创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护
在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序
-
-
短暂:客户端和服务器端断开连接后,创建的节点自己删除
- 临时目录节点
- 临时顺序编号目录节点:zk给该节点进行顺序编号
语法:
- 持久无序号:create 节点名 节点值
- 持久有序号:create -s 节点名 节点值
- 临时无序号:create -e 节点名 节点值
- 临时有序号:create -e -s 节点名 节点值
监听器
客户端注册监听关心的目录节点,当目录节点发生变化时,zk会通知客户端。监听机制保证zk保存的任何数据的任何改变都能快速的响应到监听了该节点的应用程序
原理
- 首先要有一个main线程
- 在main线程中创建zk客户端,这时就会创建两个线程,一个负责网络连接通信(connet),一个负责监听(listener)
- 通过connect线程将注册的监听事件发送给zookeeper
- 在zk的注册监听器列表中将注册的监听事件条件到列表中
- zk监听到数据或路径变化,就会将这个消息发送给listener线程
- listener线程内部调用了process方法
常见监听
- 监听节点数据的变化:get -w path
- 监听子节点增减的变化:ls -w path
注册一次监听只能收到一次结果
客户端api
创建客户端
//参数
// 地址(可以设置多个地址用逗号分割,注意逗号作用不能有空格)
//会话超时时间
//监听器回调
ZooKeeper zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent event) {
}
});
创建节点
//参数
//路径
//值
//权限
//模式
String s = zkClient.create("/dqn", "v1".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
监听节点变化
//参数:
//监听路径
//为true后会走创建连接时的回调方法
//返回值:监听节点下的节点列表
List<String> children = zkClient.getChildren("/", true);
修改监听回调方法
zkClient = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
@SneakyThrows
@Override
public void process(WatchedEvent event) {
List<String> children = zkClient.getChildren("/", true);
System.out.println(children);
}
});
节点是否存在
//Stat为null表示不存在
Stat stat = zkClient.exists("/aaa1", false);
写入数据流程
假设有三台机器
请求发给了leader节点
- 请求发给leader,leader进行写数据
- leader发送消息给follower,follower进行写数据,写完告知leader
- 当已经有半数机器完成写操作,leader告知客户端完成写操作
- leader发送消息给剩余follower,进行写数据,写完告知leader
请求发给了follower节点
- 请求发送给follower,follower将请求转发给leader
- leader进行写数据,写完发送给另一台follower进行写数据,写完告知leader
- 当有半数以上完成写操作,leader告知最初受到请求的follower
- 最后由follower告知客户端
分布式锁
- 收到请求后,在节点下创建一个临时顺序节点
- 判断自己是不是当前节点下最小的节点:
- 是:获取到锁
- 不是:对前一个节点进行监听
- 获取到锁,处理完业务后,delete节点释放锁,然后下面的节点将收到通知,重复第二步判断
可以使用curator框架实现
paxos算法
一种基于消息传递且具有高容错特性的一致性算法
解决的问题
就是如何快速正确的在一个分布式系统中对某个数据达成一致,并且保证不论发生任何异常都不会破坏整个系统的一致性
描述
- 在一个paxos系统中,首先将所有节点划分为proposer(提议者)、acceptor(接受者)和leamer(学习者)。注意:每个节点可以身兼数职
- 一个完整paxos算法流程分为三个阶段:
- prepare准备阶段
- proposer向多个acceptor发出propose请求promise(承诺)
- acceptor针对收到的propose请求进行promise(承诺)
- accept接受阶段
- proposer收到多数acceptor承诺的promise后,向acceptor发出propose请求
- acceptor针对收到的propose请求进行accept处理
- learn学习阶段:proposer将形成的决议发送给所有learners
- prepare准备阶段
zab算法
zab借鉴了paxos算法,特别是为zookeeper设计的支持崩溃恢复的原子广播协议。基于该协议,zookeeper设计为只有一台客户端(leader)负责处理外部的写事务请求,然后leader客户端将数据同步到其他follower节点。即zookeeper只有一个leader可以发起提案
基本模式:消息广播、崩溃恢复
消息广播
- 客户端发起一个写操作请求
- leader服务器将客户端的请求转化为事务proposal提案,同时为每个proposal分配一个全局的id,即zxid
- leader服务器为每个follower服务器分配一个单独的队列,然后将需要广播的proposal依次放到队列中去,并根据fifo策略进行消息发送
- follower接收到proposal后,会首先将其以事务日志的方式写入本地磁盘中,写入成功后向leader反馈一个ack响应消息
- leader接收到超过半数以上follower的ack响应消息后,即认为消息发送成功,可以发送commit消息
- leader向所有follower广播commit消息,同时自身也会完成事务提交。follower接收到commit消息后,会将上一条事务提交
zookeeper采用zab协议的核心,就是只有有一台服务器提交了proposal,就要确保所有的服务器最终都能正确提交proposal
zab协议针对事务请求的处理过程类似于一个两阶段提交过程
- 广播事务阶段
- 广播提交操作
崩溃恢复
服务器异常状况
- 假设一个事务在leader提出之后,leader挂了
- 一个事务在leader上提交了,并且过半的follower都响应ack了,但是leader在commit消息发送之前挂了
崩溃恢复要满足的条件
- 确保已经被leader提交的提案proposal,必须被所有的follower服务器提交(已经产生的提案follower必须执行)
- 确保丢弃已经被leader提出的,但是没有被提交的proposal(丢弃胎死腹中的提案)
leader选举:根据上述要求,zab协议要保证选举出来的leader需要满足以下条件
- 新选举出来的leader不能包含未提交的proposal。即leader必须都是已经提交了proposal的follower服务器节点
- 新选举的leader节点中含有最大的zxid,这样做的好处是可以避免leader服务器检查proposal的提交和丢弃工作
数据同步:
- 完成leader选举后,在正式开始工作之前(接收事务请求,然后提出新的proposal),leader服务器会首先确认事务日志中所有的proposal是否已经被集群中过半数的服务器commit
- leader服务器需要确保所有的follower服务器能够接收到每一条事务的proposal,并且能够将所有已经提交的事务proposal应用到内存数据中。等到follower将所有尚未同步的事务proposal都从leader服务器上同步过,并且应用到内存数据中以后,leader才会把该follower加入到真正可用的follower列表中
cap理论
cap理论告诉我们,一个分布式系统不可能同时满足以下三种:
- 一致性(consistency)
- 可用性(available)
- 分区容错性(partition tolerance)
最多只能同时满足其中两项,因为p是必须的,因此往往选择就在cp或者ap中
一致性
在分布式环境中,一致性是指数据在多个副本之间是否能够保持数据一致性。在一致性的需求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态
可用性
指系统提供的服务必须一直处于可用的状态,对应用户的每一个操作请求总是能够在有限的时间内返回结果
分区容错性
分布式系统在遇到任何网络分区故障的时候,仍然能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障
zookeeper保证的是cp
zk不能保证每次服务请求的可用性(在极端环境下,zk可能会丢弃一些请求,消费者程序需要重新请求才能获得结果)。所以说,zk不能保证可用性
leader选举时集群都是不可用