Zookeeper
zookeeper是一个开源的分布式的,为分布式框架提供协调服务的Apache项目
工作机制
zookeeper从设计模式角度来理解是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生改变,zookeeper就将福则通知已经在zookeeper上注册的那些观察者做出相应的反应
特点
- zookeeper有一个领导者,多个跟随者组成的集群
- 集群中只要有半数以上节点存活,zookeeper集群就能正常服务,所以zookeeper适合安装奇数台服务器
- 全局数据一致,每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的
- 更新请求顺序执行,来自同一个client的更新请求,按其发送顺序依次执行。
- 数据更新原子性,一次数据更新要么成功,要么失败。
- 实时性,在一定时间范围内,client能读到最新数据
数据结构
zookeeper数据模型的结构与Unix文件系统很类似,整体上可以看作是一棵树,每个节点称作一个ZNode。每一个ZNode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识
应用场景
- 统一命名服务
在分布式环境下经常需要对应用/服务进行统一命名,便于识别
- 统一配置管理
1)分布式环境下,配置文件同步非常常见
(1)一般要求一个集群中,所有节点的配置信息是一致的,比如kafka集群
(2)对配置文件修改后,希望能够快速同步到各个节点上。
2)配置管理可交由zookeeper实现
(1)可将配置信息写入zookeeper上一个znode
(2)各个客户端服务器监听这个znode
(3)一旦znode中的数据被修改,zookeeper将通知各个客户端服务器
- 统一集群管理
1)分布式环境中,实时掌握每个节点的状态是必要的
2)zookeeper可以实现实时监控节点状态变化
(1)可将节点信息写入zookeeper上的一个znode
(2)监听这个znode可获取他的实时状态变化
- 服务器节点动态上下线
客户端能实时洞察到服务器上下线的变化
- 软负载均衡
记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求
本地安装
安装jdk
拷贝安装包
解压到指定目录
tar -zxvf zookeeper.*.bin.tar.gz -C /opt/module/
修改配置文件
将dataDir = /opt/module/zookeeper-*/zkData
启动zookeeper
./zkServer.sh start
启动客户端
./zkCli.sh
退出客户端
quit
停止zookeeper
./zkServer.sh stop
配置参数解读
tickTime = 2000
通信心跳时间,zookeeper服务器与客户端心跳时间,单位毫秒
initLimit = 10
LF初始通信时限,Leader和Follower初始连接时能容忍的最多心跳数
syncLimit = 5
LF同步通信时限,Leader和Follower之间通信时间如果超过该心跳数,leader认为follwer死掉,从服务器列表删除follwer。
dataDir
保存zookeeper中的数据,默认/tmp目录,容易被系统定期删除
clientPort = 2181
客户端连接端口,通常不做修改
集群安装
配置服务器编号
在zkData目录下创建一个myid文件
在文件中添加server对应的编号(上下不要有空行,左右不要有空格),添加myid文件,一定要在linux里面创建
增加配置
#######cluster#######
server.2 = 192.168.1.1:2888:3888
server.3 = 192.168.1.2:2888:3888
server.4 = 192.168.1.3:2888:3888
server.A = B:C:D
A是一个数字,表示这个是第几号服务器(对应myid中的数字)
B是服务器地址
C是这个服务器Follower和Leader服务器交换信息的端口
D是万一集群中的leader挂了,需要一个端口来重新进行选举,选出一个新的leader,而这个端口就是用来执行选举时服务器相互通信的端口
选举机制
第一次启动
- 服务器1启动,发起一次选举,服务器1投自己一票。此时服务器1票数一票,不够半数以上,选举无法完成,服务器1状态保持为looking
- 服务器2启动,再发起一次选举,服务器1和2分别投自己一票并交换选票信息:此时服务器1翻新啊服务器2的myid比自己目前投票推举的(服务器1)大,更改选票为推举服务器2.此时服务器10票,服务器2票数为2票,没有半数以上的结果,选举无法完成,服务器1,2状态保持looking
- 服务器3启动,发起一次选举,此时服务器1和2都会更改选票为服务器3,此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票,此时服务器3的票数已经超过半数,服务器3成为leader,服务器12更改状态为following,服务器3更改状态为leading
- 服务器4启动,发起选举。此时服务器123已经不是looking状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为1票.此时服务器4服从多数,更改选票信息为服务器3,并更改状态为following
- 服务器5重复服务器4的操作
SID:服务器id,用来唯一标识一台zookeeper集群中的及其,每台机器不能重复和myid一致
ZXID:事务id。zxid是一个事务id,用来标识一次服务器状态的变更。在某一时刻,集群中的每台机器的zxid值不一定完全一致,这和zookeeper服务器对于客户端更新请求的处理逻辑有关
epoch:每个leader任期的代号,没有leader时同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加
非第一次启动
当zookeeper集群中的一台服务器出现以下两种情况之一,就会开始选举
- 服务器初始化启动
- 服务器运行期间无法和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的投票情况
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P56732SE-1658132713709)(D:\TyporaDocument\img\微信截图_20220713214251.png)]
选举规则:
- epoch大的直接胜出
- epoch相同zxid大的胜出
- zxid相同sid大的胜出
集群启动停止脚本
#!/bin.bash
case $1 in
"start"){
for i in 192.168.1.1 192.168.1.2 192.168.1.3
do
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh start"
done
}
;;
"stop"){
for i in 192.168.1.1 192.168.1.2 192.168.1.3
do
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh stop"
done
}
;;
"status"){
for i in 192.168.1.1 192.168.1.2 192.168.1.3
do
ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh status"
done
}
;;
esac
znode节点类型
持久
客户端和服务器端断开连接后,创建的节点不删除
创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护。
在分布式系统中,顺序号被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序
持久化目录节点
客户端与zookeeper断开连接后,该节点依旧存在
create 创建节点,不带序号
持久化顺序编号目录节点
客户端与zookeeper断开连接后,该节点依旧存在,只是与zookeeper给该节点名称进行顺序编号
create -s 创建带序号的节点
短暂
客户端和服务器端断开连接后,创建的节点自己删除
临时目录节点
客户端与zookeeper断开连接后,该节点被删除
create -e 创建不带序号临时节点
临时顺序编号目录节点
客户端与zookeeper断开连接后,该节点被删除,只是zookeeper给该节点名称进行顺序编号
create -e -s 创建带序号的临时节点
获取值 get 设置值 set
监听器原理
- 首先有一个main()线程
- 在main线程中创建zookeeper客户端,这时就会创建两个线程,一个负责网络连接通信,一个负责监听
- 通过connect线程将注册的监听事件发送给zookeeper
- 在zookeeper的注册监听器列表中将注册的监听事件添加到列表中
- zookeeper监听到有数据或路径变化,就会将这个消息发送给listener线程
- listener线程内部调用了process()方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kh3O3PeU-1658132713710)(D:\TyporaDocument\img\微信截图_20220715200856.png)]
常见的监听
监听节点数据的变化
get -w
多次修改节点值,客户端不会再收到监听,因为注册一次只能监听一次,想再次监听需要再次注册
监听子节点增减的变化
同上
客户端向服务端写数据流程
请求直接发送给leader
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XZeZYLui-1658132713711)(D:\TyporaDocument\img\微信图片_20220715203733.png)]
半数以上完成写操作,就直接回复完成,
请求发送给follower
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wDoYUiUg-1658132713711)(D:\TyporaDocument\img\微信截图_20220715204003.png)]
服务器动态上下线监听案例
某分布式系统中,主节点可以有多台,可以动态上下线,任意一台客户端都能实时感知到主节点服务器的上下线
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zjhICTZ9-1658132713712)(D:\TyporaDocument\img\微信截图_20220715204452.png)]
分布式锁
进程1再使用该资源的时候会先去获得锁,进程1获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,进程1用完该资源以后就将锁释放掉,让其他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的访问该临界资源。那么我们把这个分布式环境下的这个锁叫做分布式锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-urFcndzv-1658132713712)(D:\TyporaDocument\img\微信截图_20220715205820.png)]
curator框架实现分布式锁案例
原生api会话连接是异步的,需要自己去处理等待
watch需要重复注册,不然就不能生效
开发的复杂性比较高
不支持多节点删除和创建,需要自己递归
面试重点
生产集群安装多少zk合适?
安装奇数台
生产经验
10台服务器:3台zk
20台服务器:5台zk
100台服务器:11台zk
200台服务器:11台zk
zk服务器台数多:好处:提高可靠性,坏处:提高通信延时
算法相关
数据一致性
拜占庭将军问题
拜占庭帝国军队的将军们必须全体一致决定是否攻击某一只的敌军。问题是在地理上是分隔开来的,并且将军中存在叛徒。叛徒可以任意行动以达到以下目标:欺骗某些将军采取进攻行动:促成一个不是所有将军都同意的决定,如当将军们不希望进攻时促成进攻行动;或者迷惑某些将军使他们无法做出决定。如果叛徒达到了这些目的之一,则攻击行动的结果都是注定要失败的,只有完全一致的牡蛎才能获得胜利。
paxos算法
一种基于消息传递且具有高度容错特性的一致性算法
如何快速正确的在一个分布式系统中对某个数据值达成一致,并且保证不论发生任何异常,都不会破坏整个系统的一致性
在一个paxos系统中,首先将所有节点划分为proposer(提议者),acceptor(接收者)和learner(学习者)。每个节点可以身兼数职
算法流程:
1、prepare准备阶段
- 提议者向多个接收者发出propose请求promise承诺
- 接收者收到propose请求进行promise承诺
2、accept接收阶段
- proposer收到多数acceptor承诺的promise后,向acceptor发出propose请求
- acceptor针对收到的propose请求进行accept处理
3、learn学习阶段
proposer将形成的决议发送给所有learners
(1)prepare:提案者生成全局唯一且递增的proposalID,向所有接收者发送propose请求,这里无需携带填内容,只携带proposalID即可
(2)promise:接收者收到propose请求后做出两个承诺,一个应答
- 不再接收proposalID小于等于当前请求的propose请求
- 不再接收proposalID小于当前请求的accept请求
- 不违背以前做出的承诺下,回复已经accept过的提案中proposalID最大的那个提案value和proposalID,没有则返回空值
(3)propose:proposer收到多数acceptor的promise应答后,从应答中选择proposalID最大的value作为本次要发起的提案.如果所有应答的提案value均为空值,则可以自己随意决定value。然后携带当前proposalID,向所有acceptor发送propose请求。
(4)accept:接收者收到propose请求后,在不违背自己之前做出的承诺下,接收并持久化当前proposal ID和提案value
(5)learn:proposer收到多数接收者的accept后,将形成的决议发送给所有learner
算法缺陷:在网络复杂的情况下,一个分布式系统可能很久无法收敛,甚至陷入活锁的情况
造成这种情况的原因是系统中有一个以上的proposer,多个proposers相互争夺acceptor,造成迟迟无法达成一致的情况。针对这种情况,一种改进的paxos算法被提出:从系统中选出一个节点作为leader,只有leader可以发起提案。这样一次paxos流程中只有一个proposer,不会出现活锁的情况,此时只会出现例子中第一种情况
ZAB协议
借鉴了paxos算法,是特别为zookeeper设计的支持崩溃恢复的原子广播协议。基于该协议,zookeeper设计为只有一台客户端负责处理外部的写事务请求,然后leader客户端将数据同步到其他follower节点。既zookeeper只有一个leader可以发起提案
协议内容
zab协议包括两种基本的模式:消息广播、崩溃恢复
消息广播
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fdBz33dC-1658132713713)(D:\TyporaDocument\img\微信截图_20220716171701.png)]
崩溃恢复
要求满足
- 确保已经被leader提交的提案proposal,必须最终被所有的follow服务器提交
- 确保丢弃已经被leader提出的但是没有被提交的proposal
leader选举
- 新选举的出来的leader不能包含未提交的proposal。既新leader必须都是已经提交了proposal的follower服务器节点
- 新选举的leader节点中含有最大的zxid。这样做的好处是可以避免leader服务器检查proposal的提交和丢弃工作
数据恢复
- 完成leader选举后,在正式开始工作之前(接收事务请求,然后提出新的proposal),leader服务器会首先确认事务日志中的所有的proposal是否已经被集群中过半的服务器commit
- leader服务器需要确保所有的followe服务器能够接收到每一条事务的proposal,并且能将所有已经提交的事务proposal应用到内存数据中。等到follower将所有尚未同步的事务proposal都从leader服务器上同步过,并且应用到内存数据中以后,leader才会把该follower加入到真正可用的follower列表中
CAP理论
一个分布式系统不可能同时满足以下三种
- 一致性
数据在多个副本之间是否能够爆出数据一致的特性。在一致性的需求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态
- 可用性
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果
- 分区容错性
分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障
zookeeper满足的是cp
- zookeeper不能保证每次服务请求的可用性。
- 进行leader选举时集群都是不可用的
源码
数据持久化
zkData中保存者数据快照和编辑日志
服务端初始化
QuorunPeerMain.java
main()
1、服务端启动入口
new QuorunPeerMain()
2、初始化
(1)解析参数
解析zoo.cfg,setupQuorumPeerConfig,解析myid
(3)过期快照删除
new DatadirCleanup Manager
getSnapRetainCount()=3最少保留3个快照
getPurgeInterval()=0关闭清除功能
new PurgeTask清理过期数据
(3)通信初始化
默认NIO通信,初始化NIO服务端socket,绑定2181端口
(4)启动zk
loadDataBase();
1>恢复快照
将系统中存储的快照文件从根节点开始一个节点一个节点的恢复数据
2>恢复编辑日志