概述
Zookeeper是一种分布式协调服务。
作用:方便分布式系统开发,提供选举主节点、管理组内成员关系、管理元数据等功能。通过Zookeeper构建分布式系统。
可以把系统中的数据分为应用数据和协同数据,Zookeeper用于管理应用协作的关键数据,不适用于存储海量应用数据。
对分布式系统来说,使用一个独立的协调组件有几个重要的好处:首先,我们可以独立地设计和实现该组件,这样独立的组件可以跨多个应用共享。其次,可以简化应用系统架构师在协作方面的工作。最后,系统可以独立地运行和协作这些组件,独立这样的组件,也简化了生产环境中解决实际问题的任务。这也正是Zookeeper被独立领出来的原因。
层级树状结构
很多用于协作的原语常常在很多应用之间共享,因此,设计一个用于协作需求的服务的方法往往是提供原语列表,暴露出每个原语的实例化调用方法,并直接控制这些实例。比如,我们可以说分布式锁机制组成了一个重要的原语,同时暴露出创建、获取和释放三个调用方法。
不过,这种方式使得应用丧失了灵活性。
因此,Zookeeper另辟蹊径。Zookeeper并不直接暴露原语,取而代之,它暴露了由一小部分调用方法组成的类似文件系统的API,以便允许应用实现自己的原语。
Zookeeper操作和维护了一些小型的数据节点,这些节点被称为znode,采用类似于文件系统的层级树状结构进行管理。
(注意:这里的数据节点与分布式系统节点是不同的,系统节点指的是单独的进程,这里的数据节点是抽象概念类似文件或目录)
如图,根节点包含4个子节点。
./workers节点,其下每个znode子节点保存了系统中的一个可用从节点信息(Zookeeper集群中的一个节点)。
./tasks节点,其下每个znode子节点保存了所有已经创建并等待从节点执行的任务的信息。
./assign节点,其下每个znode子节点保存了分配到某个从节点的一个任务信息。
#Znode的类型
znode一共有4种类型:持久的、临时的、持久有序的、临时有序的。
临时节点:一个临时节点,在以下两种情况下将会被删除:
1.当创建该znode的客户端的会话因超时或主动关闭而中止时。
2.当某个客户端(不一定是创建者)主动删除该节点时。
注意:临时节点不能拥有子节点。
持久节点:持久节点只能通过delete来进行删除。
有序节点:在节点创建时,会在路径之后追加一个单调递增的整数,如/tasks/task-1。有序znode提供了创建具有唯一名称的znode的简单方式,同时也能通过这种方式看到znode的创建顺序。
#Znode版本号
每个znode都有一个版本号,它随着每次数据变化而自增。setData和delete操作以版本号作为传入参数,只有当传入的版本号与服务器上的版本号一致时调用才会成功。
监视和通知
客户端可以向Zookeeper注册监听指定的znode,即设置监视点(watch)。监视点是一个单次触发操作。为了接收多个通知,客户端必须在每次通知后设置一个新的监视点。当节点变化是,客户端就能获得通知。
注意:在设置新监视点之前,节点状态可能已经发送变化。这里要求在设置新的监视点前,要查看节点状态。Zookeeper的所有读操作API(getData、getChildren、exists等)均可以选择在读取的znode节点上设置监视点。
Zookeeper可以设置多种监视点,如监视znode的数据变化、监视znode子节点的变化、监视znode的创建或删除。
客户端设置的监视点与会话关联,如果会话过期,等待中的监视点将被删除。不过监视点可以跨越不同服务端的连接而保持。
监视点有两种类型:数据监视点和子节点监视点。创建、删除或设置一个znode节点的数据都会触发数据监视点,exists和getData操作可以设置数据监视点,只有getChildren操作可以设置子节点监视点,这种监视点只有在znode子节点创建或删除时才被触发。
Zookeeper架构
Zookeeper集群可以由一个(standalone独立模式)或多个节点(quorum仲裁模式)组成,它们之间会进行状态复制,并同时服务于客户端的请求。
#Zookeeper仲裁(法定人数)
在仲裁模式下,Zookeeper复制集群中的所有服务器的数据树。但如果让客户端等待每个服务器完成数据保存后再继续,将会有很大的延迟,这是无法接受的。因此Zookeeper有法定人数的概念,就行在立法领域有投票所需立法者最小数量,在Zookeeper中指能使Zookeeper有效运行的最少服务器数量。比如,我们一共有5个Zookeeper服务器,法定人数为3个,这样,只要任何3个服务器保存了数据,客户端就可以继续,而其他两个服务器最终也将捕获到数据,并保存数据。
##脑裂(split-brain)与法定人数的设置
假设我们共有5个集群节点,设置的法定人数为2。现在由于网络问题,其中两个(A组)与另外三个节点(B组)产生了分区隔离。此时,客户端向A组中的一个节点提交数据修改,A组内部成功完成了数据复制,由于设置的法定人数是2,所以服务返回客户端指出修改已经完成。这时,另外三个节点并不知道发生了数据修改,从而造成了数据的不一致。这就是脑裂问题。
为了避免脑裂问题,需要把法定人数设置为占集群数的多数。并最好使集群节点数保持奇数。
#会话
在对Zookeeper集合执行任何请求前,一个客户端必须先与服务建立会话。当一个会话因某种原因而中止时,在这个会话期间创建的临时节点将会消失。客户端初始连接到集合中某一个服务器,当会话无法与当前连接的服务器继续通信时,会话就可能转移到另一个服务器上。Zookeeper客户端库透明地转移一个会话到不同的服务器。
会话提供了顺序保障,同一个会话中的请求会以FIFO顺序执行。
#集群角色
群首(leader):群首作为中心点处理所有对Zookeeper系统变更的请求,就像一个定序器,建立了所有对Zookeeper状态的更新的顺序。
追随者(follower):追随者接收群首所发出更新操作请求,并对这些请求进行处理。
观察者(observer):观察者不会参与决策哪些请求可被接受的过程,观察者的设计只是为了系统的可扩展性。
##观察者
群首和追随者会被记入法定人数,参与决定选举和节点修改。而观察者,仅仅是决定的执行者,不参与决定本身。引入观察者,是为了在不牺牲写操作的吞吐率的前提下服务更多的读操作。
Zookeeper内部原理
读取操作会在接收服务器上直接处理,写操作会被转发给群首。
#Zookeeper事务
Zookeeper保证每次变更都以原子方式执行。在Zookeeper中每次写操作都会被转发给群主,群主根据请求改变相应节点的数据信息,并增加该节点的版本号。针对每次修改,群首都会产生一个事物,这个事物中包含(节点数据和节点版本号),每个事务会有一个标识符,称之为zxid。当处理事务时,服务端会用事务中的数据信息来替换节点中原来的数据信息,并会用事务中的版本号更新该节点,而不是增加版本号的值。每个节点中都会有特定数量的线程来执行事务。
zxid为一个64位long型整数,分两部分:时间戳(32位)和计数器(32位)。通过zxid标识事务,就可以按照群首所指定的顺序在各个服务器中顺序执行每个修改。通过zxid也能看到各个节点执行了多少事务。
总结起来是这样的,在接收到一个写请求操作后,追随者会将请求转发给群首,群首将探索性地执行该请求,并将执行结果以事务的方式对状态更新进行广播。一个事务中包括服务器需要执行变更的确切操作,当事务提交时,服务器就会将这些变更反馈到数据树上。
服务器如何确认一个事务是否已经提交呢?其实是通过Zookeeper原子广播协议,过程是这样的:
1.群首会向所有追随者发送一个PROPOSAL消息p。
2.当一个追随者接收到消息p后,会响应群首一个ACK消息,通知群首其已接收提案(proposal)。
3.当收到仲裁数量的服务器发送的确认消息后(该仲裁数包括群首自己),群首就会发送消息通知追随者进行事务提交(COMMIT)操作。
#群首选举
群首为集群中的服务器选出来的一个服务器,设置群首的目的是为了对客户端所发起的Zookeeper状态变更请求进行排序。群首将每一个请求转换为一个事务。将这些事务发送给追随者,确保集群按群首确定的顺序接受并处理这些事务。
一个服务器必须被仲裁的法定数量的服务器认可,才能成为群首。
每个服务器启动后进入LOOKING状态,开始选举一个新的群首或查找已经存在的群首,如果群首已经存在,其他服务器会通知这个新启动的服务器,告知哪个服务器是群首,与此同时,新的服务器会与群首建立连接,以确保自己的状态与群首一致。
如果集群中所有的服务器均处于LOOKING状态,这些服务器就会进行通信来选举一个群首。选举胜出的服务器将进入LEADING状态,而集群中其他服务器将会进入FOLLOWING状
访问控制
Zookeeper使用访问访问控制表(ACL)来控制访问权限。
具体可以参考https://blog.csdn.net/cdu09/article/details/51637451
Zookeeper的应用
#使用Zookeeper实现锁
假设有多个进程尝试获取一个锁,每个进程都尝试创建名为/lock,如果其中一个进程成功创建了该节点,就表示它获得了锁。其他进程因为节点已经存在而创建/lock失败。当进程监听到/lock的变化,并检测到/lock被删除时,可以再次尝试创建/lock节点来获得锁。为了避免出现死锁问题,我们需要设置/lock节点为临时节点,这样一旦获得锁的进程过掉,锁也会自动释放。
#使用Zookeeper实现选主
应用客户端可以通过创建/master节点来推选自己为主节点,如果节点已存在,应用客户端确认自己不是主要主节点并返回。我们还需要在/master上设置监视点,来监听主节点崩溃。
Zookeeper常用命令
zkCli.sh -server 10.77.20.23:2181 (连接到zookeeper服务)
ls /
create /path data
delete /path
rmr /path 【删除整个节点,包括子节点】
exists /path
setData /path data
getData /path
getChildren /path