zookeeper的概述
ZooKeeper集群工作的核心
1. 维护与各Follower及Observer之间的心跳。
2. 完成读写的操作,完成后将写操作广播给其他服务器,只要有半数的节点(不包括Observer)写入成功,这个写的请求就会被提交。
Follower:
一个集群中可以同时存在多个Follower。
1. 响应leader的心跳。
2. 处理客户端的读请求。
3. 将写的请求提交给Leader。
4. 选举领导者时进行投票。
Observer:
角色跟Follower类似,作用是扩展系统,提高读取速度。
1. 处理客户端的读请求。
2. 将写的请求提交给Leader。
3. 不参与投票。
为了支持更多的客户端,需要增加更多的server,但是Server越多,投票阶段延迟越大,会影响性能,引入观察者,观察者不参与投票,多加入Observer节点,提高伸缩性,同时不影响吞吐率。
总结:集群中由一个Leader节点和N个Follower节点组成,所有的Server之间能彼此通信,并且在各自内存中维护了状态图,与之一致的事务日志和快照位于持久化存储中。这一点类似Redis的Sentinel模式,它们都具有高度的自治能力,一旦Leader节点不可达,则自动完成新Leader的选举并恢复服务。
图中的每个节点称为一个Znode。每个Znode由三部分组成:
1. stat:此为状态信息,描述该Znode的版本,权限等信息。
2. data:与该Znode关联的数据。
3. children:该Znode下的子节点。
节点类型
Znode有两种,分别为临时节点和永久节点。节点的类型在创建时即被确定,并且不能改变。
临时节点
该节点的生命周期依赖于创建它们的会话。一旦会话结束,临时节点将被自动删除,当然也可以手动删除。临时节点不允许拥有子节点。
永久节点
该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
序列化特性
Znode还有一个序列化的特性,如果创建的时候指定的话,该Znode的名字后面会自动追加一个不断增加的序列号。序列号对于此节点的父节点来说是唯一的,这样便会记录每个子节点创建的先后顺序。他的格式为 “%10d”(10位数字,没有数值的数位用0补充,例如:0000000001)
这样便会存在四种类型的Znode节点,分别对应:
PERSISTENT:永久节点
EPHEMERAL:临时节点
PERSISTENT_SEQUENTIAL:永久节点、序列化
EPHEMERAL_SEQUENTIAL:临时节点、序列化
节点属性
每个Znode都包含了一系列的属性,通过命令get,可以获得节点的属性。
dataVersion:数据版本号,每次对节点进行set操作,dataVersion的值都会增加1(即使设置的是相同的数据),可有效避免了数据更新时出现的先后顺序问题。
cversion:子节点的版本号。当Znode的子节点有变化时,cversion的值就会增加1。
aclVersion:ACL的版本号。
cZxid:Znode创建的事务id。
mZxid:Znode被修改的事务id,即每次对Znode的修改都会更新mZxid。
对于zk来说,每次的变化都会产生一个唯一的事务id,zxid(Zookeeper Transaction Id)。通过zxid,可以确定更新操作的先后顺序。例如,如果zxid1小于zxid2,说明zxid1操作先于zxid2发生,zxid对于整个zk都是唯一的,即使操作的是不同的Znode。
ctime:节点创建时的时间戳。
mtime:节点最新一次更新发生时的时间戳。
ephemeralOwner:如果该节点为临时节点,ephemeralOwner值表示与该节点绑定的session id,如果不是,ephemeralOwner值为0。
在client和server通信之前,首先需要建立连接,该连接称为session。连接建立后,如果发生连接超时、授权失败,或者显式关闭连接,连接便处于CLOSED状态,此时session结束。
zookeeper的监控
Zookeeper支持监控,即客户端可以设置在某个znode上的监控;当一个节点发生变化时,就会触发和删除一个监控。当一个监控触发时,客户端会收到一个说明节点变化的包。
当客户端和Zookeeper Server断开时,客户端将收到一个本地通知。
本质就是:监听器(观察者模式)
watcher
watcher的工作流程
客户端在向zookeeper服务器注册watcher的同时,会将watcher对象存储在客户端的watcherManager中,当zookeeper服务器触发watcher事件后,会向客户端发送通知,客户端线程从watchermanager中取出对应的watcher对象执行回调逻辑。
watcher监听机制
Watcher 监听机制是 Zookeeper 中非常重要的特性,我们基于 zookeeper 上创建的节点,可以对这些节点绑定监听事件。比如可以监听节点数据变更、节点删除、子节点状态变更等事件,通过这个事件机制,可以基于 zookeeper 实现分布式锁、集群管理等功能。
Watcher特性
Watcher具有一次性,无论是服务端还是客户端,一旦一个Watcher被触发,ZooKeeper都会将其从相应的存储中移除。因此Watcher需要反复注册。
客户端串行执行:最终Watcher会被放入一个队列中串行执行。
zookeeper的选举机制
zookeeper默认的算法是FastLeaderElection,采用投票数大于半数则胜出的逻辑。
概念
服务器ID
比如有三台服务器,编号分别为1,2,3。编号越大在选择算法中的权重越大。
选举状态
LOOKING:竞选状态。
FOLLOWING:随从状态,同步leader状态,参与投票。
OBSERVING:观察状态,同步leader状态,不参与投票。
LEADING:领导者状态。
数据ID
服务器中存放的最新数据version。值越大说明数据越新,在选举算法中数据越新权重越大。
逻辑时钟
也叫投票的次数,同一轮投票过程中的逻辑时钟值是相同的,每投完一次票这个数据就会增加,然后与接收到的其他服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断。
全新集群选举
假设目前有5台服务器,每台服务器均没有数据,他们的编号分别是1,2,3,4,5 按编号依次启动,他们的选举过程如下:
1. 服务器1启动,给自己投票,然后发投票信息,由于其他机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking。
2. 服务器2启动,给自己投票,同时与之前启动得服务器1交换结果,由于服务器2得编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器得状态依然是Looking。
3. 服务器3启动,给自己投票,同时与之前启动得服务器1,2交换信息,由于服务器3得编号最大,所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为领导者,服务器1,2成为小弟。
4. 服务器4启动,给自己投票,同时与之前启动得服务器1,2,3交换信息,尽管服务器4得编号大,但之前服务器3已经胜出,所以服务器4只能成为小弟。
5. 服务器5启动,后面的逻辑同服务器4成为小弟。
非全新集群选举
对于运行正常的zookeeper集群,中途有机器down掉,需要重新选举时,选举过程就需要加入数据ID,服务器ID和逻辑时钟。
数据ID:数据新的 version就大,数据每次更新都会更新version。
服务器ID:就是我们配置的myid中的值,每个机器一个。
逻辑时钟:这个值从0开始递增,每次选举对应一个值。如果在同一次选举中,这个值是一致的。
这样选举的标准就变成:
1. 逻辑时钟小的选举结果被忽略,重新投票
2. 统一逻辑时钟后,数据id大的胜出
3. 数据id相同的情况下,服务器id大的胜出,根据这个规则选出leader。
zookeeper的数据发布与订阅(配置中心)
发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。
应用在启动的时候会自动来获取一次配置,同时,在节点上注册一个Watcher,这样一来,以后每次配置有更新的时候,都会实时通知到订阅的客户端,从而达到获取最新配置信息的目的。
比如:分布式搜索服务中,索引的元信息和服务器集群机器的节点状态存放在Zk的一些指定节点,供各个客户端订阅使用。
注意:适合数据量很小的场景,这样数据更新可能会比较快。
zookeeper的命名服务(Naming Service)
在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务地址,远程对象等等--这些我们都可以统称他们为名字(Name)。其中较为常见的就是一些分布式服务框架中的服务地址列表。通过调用ZK提供的创建节点的API,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。
阿里巴巴集团开源的分布式服务框架Dubbo中使用Zookeeper来作为其命名服务,维护全局的服务地址列表。
zookeeper的分布式锁
分布式锁,这个主要得益于zookeeper保证了数据的强一致性。锁服务可以分为两类,一个是保持独占,另一个是控制时序。
所谓保持独占,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁,通常的做法是把zk上的一个znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建/distribute_lock节点,最终成功创建的那个客户端也即拥有了这把锁。