zookeeper 动物园管理员,可以用来管理 hadoop(大象)、hive(蜜蜂)、pig(小 猪)、tomcat(猫)等等。
Apache Hbase 和 Apache Solr 的分布式集群都用到了 zookeeper,Dubbo中也要用到zookeeper。
Zookeeper是一个高性能、高可用的分布式管理与协调框架,主要功能包括:配置管理、命名服务、分布式锁、集群管理、发布/订阅、队列管理。
ZooKeeper 的作用(应用场景)
1、配置管理
项目中有各种配置,比如数据库连接配置,springboot、springcloud的yml配置,集群各节点的配置,等等。
当我们只使用少数几台服务器、配置文件很少的时候,配置文件可以直接放在这些服务器上。
但做分布式项目的时候,往往有很多集群,集群节点很多,各种配置文件一大堆,这时候就需要使用zk来集中管理配置。
常见的有,使用zk来管理HBase集群的配置信息,管理Kafka的broker信息,Dubbo中使用zk来管理各服务节点的信息、实现服务治理(相当于SpringCloud中的Eureka)。
2、命名服务
访问服务时,直接使用服务所在机器的ip地址很麻烦,全是数字、机器又多,那么多ip,不好记。
可以把集群各节点提供的服务的名称、机器ip注册到ZK上,在项目中直接使用服务名,向ZK请求某个服务时,ZK会选择合适的节点(ip)来提供服务。
3、分布式锁
除了redis可以实现分布式锁,zk也可以。
4、 集群管理
在分布式的集群中,由于各种原因,比如硬件故障、网络故障、并发量剧增等,经常要增减节点数量。
添加新的节点、或者旧的节点下线,集群中的各节点需要感知集群节点的变化。zk server可以存储集群各节点(client)的信息,集群各节点再从zk server上获取其它集群的信息。
Dubbo的服务治理就是用ZK实现的。
5、发布/订阅
利用zk的观察/通知机制。zk client订阅某个znode,这个znode变化时,zk server会通知对应的zk client获取znode的最新数据。
6、实现队列
利用zk执行写请求的顺序一致性来实现(paxos算法)。
Zookeeper的数据模型
树结构,/是根节点,节点叫做znode,一个znode对应一个文件目录。
一个znode都代表1个zk client,用来存储、管理一个zk client的数据。
每个znode都可以有子节点,即znode可以嵌套。
有四种类型的znode
-
PERSISTENT 持久化。一个节点断开与zk server的连接后,zk server会保留该节点对应的znode
-
PERSISTENT_SEQUENTIAL 持久化顺序编号。客户端与zookeeper断开连接后,zk server会保留该节点对应的znode
-
EPHEMERAL 临时。一个节点断开与zk server的连接后,zk server会删除该节点对应的znode
-
EPHEMERAL_SEQUENTIAL 临时顺序编号。一个节点断开与zk server的连接后,zk server会删除该节点对应的znode
SEQUENTIAL 顺序编号指的是,创建一个znode时会自动在节点名上加一个整数作为后缀。
这个整数由父节点维护,比如最新的一个SEQUENTIAL znode到5了,那下一个创建SEQUENTIAL znode就是6。
临时节点常用于实现分布式锁
服务节点先检测是否有某个临时节点,比如/ticket_lock,没有此节点,接设置此节点,持有锁;如过此节点已存在,就说明锁已经被某个节点获取、持有。
如果获取到锁的服务节点故障,断开连接后会自动删除此临时节点,释放锁。
zkServer的集群模式
zk server一般都要集群,保证zk server的高可用。zk server的集群一般要读写分离,搞成分布式读写的形式。
zkServer有3种角色
- leader 负责处理zk client的写请求(更新znode),一个zk集群只有一个leader
- follower 同步leader的数据,负责处理zk client的读请求(获取znode中的数据),并参与leader的选举
- observer 处理zk client的读请求,但不能参与leader的选举,相当于临时工,没有编制
有follower就够了,为什么还要使用observer?
- 减少选举的时间花销。
假设follower5台、observer6台,选举新的leader时只需要等待3台机器同意;如果10台全是follower,需要等待6台机器同意,大大增加了选举的时间花销。
- 提高可用性。observer只是临时工,宕掉一半集群就不可用,指的是follower宕掉一半,observer不参与数量统计,就算observer全部宕掉,也没关系。
假设follower5台、observer8台,宕掉所有的observer、2台follower总共10台机器,集群仍然可用;如果13台全是follower,宕掉7台机器集群就不可用;显然使用observer可用性更高。
observer可以看做无选举、投票权的follower,主要是为了协助 follower 处理更多的读请求。如果zkCli非常多、zkServer集群的读请求负载很高,可以设置一些 Observer,提高读请求的吞吐量。
zkServer集群至少需要3台机器,为何官方推荐集群的zkServer数量为单数?
比如zk server数量为5,挂掉3个,集群就不可用;数量为6,还是挂掉3个,集群就不可用。
5台、6台的容灾能力是一样的,少用1台机器还可以减少点成本。
zkServer如何处理zkCli的请求?
读请求
zk集群的所有节点都可以处理读请求,自己收到读请求直接就处理了。
写请求
由leader处理写请求,follower、observer收到写请求时转交给leader处理。leader使用paxos算法来处理写请求:
leader将写请求都放到一个队列中,并给写请求分配一个唯一的编号,按照先进先出的顺序处理。执行某个写请求时,leader先向follower发起投票,是否要执行这个写请求,如果超过半数同意,leader就会执行这个写请求。同一时刻,leader只会执行一个写请求,来保证数据的一致性。
如果某个follower、observer同步数据时,比如一个follower同步编号为99的写操作时,发现编号为100的写操作之前已经同步了,意识到自己的数据不一致,马上停止对外服务,并从leader同步全量数据。
准确来说不算是投票。因为follower、observer要从leader同步数据,leader先执行一个写请求,follower、observer再同步这个写操作引起的数据更新,这2步作为一个事务来处理。
如果有一半以上的节点同步成功,就认为这个写操作执行成功,提交事务。至于其他尚未同步成功的,如果机器故障下线了,那也不用去管它是否同步了;如果是网络延迟等原因,过会儿会自动从leader同步,也不用去管。
如果同步成功的节点数量没达到一半,就认为同步失败,在已执行这个写操作的机器上进行回滚。
其实leader处理读、写请求都是放在一个队列中的,只不过读请求不发起投票。
Zookeeper的特点
- 顺序一致性:zkServer使用paxos算法来处理请求,将请求放在队列中,先进先出,请求的执行顺序与发送顺序一致。写代码时可以利用这个特性来实现更高层次的同步。
- 数据更新原子性:一次写操作即一个事务,要么成功(都应用、同步到所有节点),要么失败(所有节点都不使用这次数据更新)。
- 单一视图: zk client无论连接到哪个zk server,读到的数据都是一样的
- 可靠性(高可用性):zk server往往要集群,只要半数以上的节点可以正常工作,zk server集群就可以对外提供服务。(observer不参与数量统计)
- 实时性: zkCli读取到的是zkServer上最新的数据
- 高性能:zk server将全量数量存储在内存中,性能极高,尤其是在读的时候。写(更新znode)的时候,要把更新从内存同步到文件,性能稍低。
Zookeeper的工作原理
zk的核心机制是原子广播,这个机制保证了zk server之间的数据同步。
实现原子广播的协议是Zab协议,Zab协议有3种模式
- 恢复模式。leader故障后自动进入恢复模式,将集群恢复到正常工作状态。先从follower中选出一个新的leader,其它follower从新的leader处同步数据,大多数follower同步完后恢复模式就结束了。
- 同步模式。follower从leader处同步数据。同步模式也是恢复模式的一部分。
- 广播模式。leader处理写请求时,广播通知follower发起投票,半数以上的follower同意后,leader执行写请求(修改自身的znode),执行后将修改、更新广播给follower、observer,完成同步。
leader选举
zkServer在选举leader过程中的状态
- LOOKING:leader宕掉了,正在选举新的leader
- LEADING:当前zkServer成为leader
- FOLLOWING:当前zkServer是follower,同步leader的数据
- OBSERVING:当前zkServer是observer,同步leader的数据
myid
唯一标识集群中的一个zkServer
zxid
zxid其实是一个ReentranReadWriteLock,为保证数据一致性,一个节点同一时刻只执行一个读写操作,自然要加锁。follower、observer只是对外不处理写请求,同步leader数据时依然要同步写操作。
zxid的前32位表示epoch(纪元、时代),一个leader对应一个epoch(时代),换了新的leader,epoch会自动使用新值。
zxid的后32位表示xid,每一个写操作都是一个事务,xid即事务id。
myid标识的是zkServer节点,zxid标识的是数据版本。
Logicalclock 逻辑时钟
逻辑时钟是选举时的一个概念,其值就是zxid中epoch的值。选举出新的leader,开启了新leader的统治时代,新纪元|时代就此诞生。
leader的选举时机
- 集群启动还没有leader时
- leader宕机时
leader的选举机制|算法
集群没有leader时,所有节点都是LOOKING状态。
所有follower都参与投票,投的都是自己,写上自己的myid、zxid,广播出去。
比较zxid,先选epoch大的,再从里面选xid大的。epoch越大、xid越大,说明数据越新。如果zxid相同,选择myid大的。
选出新的leader后,leader变为LEADING状态,follower变为FOLLOWING状态,observer变为OBSERVING状态。
Zookeeper的观察/通知机制
一个zkCli可以观察(watch)某个znode,一旦指定的znode发生改变,zkServer会通知观察此znode的zkCli。
不使用zkCli轮询zkServer的方式,一者开销大,二者轮询大多数时候都是无效的,znode发生变化时由zkServer通知对应的zkCli。