当前位置 博文首页 > detectiveHLH:深入了解Zookeeper核心原理

    detectiveHLH:深入了解Zookeeper核心原理

    作者:detectiveHLH 时间:2021-04-28 12:18

    之前的文章Zookeeper基础原理&应用场景详解中将Zookeeper的基本原理及其应用场景做了一个详细的介绍,虽然介绍了其底层的存储原理、如何使用Zookeeper来实现分布式锁。但是我认为这样也仅仅只是了解了Zookeeper的一点皮毛而已。所以这篇文章就给大家详细聊聊Zookeeper的核心底层原理。不太熟悉Zookeeper的可以回过头去看看。

    ZNode

    这个应该算是Zookeeper中的基础,数据存储的最小单元。在Zookeeper中,类似文件系统的存储结构,被Zookeeper抽象成了树,树中的每一个节点(Node)被叫做ZNode。ZNode中维护了一个数据结构,用于记录ZNode中数据更改的版本号以及ACL(Access Control List)的变更。

    有了这些数据的版本号以及其更新的Timestamp,Zookeeper就可以验证客户端请求的缓存是否合法,并协调更新。

    而且,当Zookeeper的客户端执行更新或者删除操作时,都必须要带上要修改的对应数据的版本号。如果Zookeeper检测到对应的版本号不存在,则不会执行这次更新。如果合法,在ZNode中数据更新之后,其对应的版本号也会一起更新

    这套版本号的逻辑,其实很多框架都在用,例如RocketMQ中,Broker向NameServer注册的时候,也会带上这样一个版本号,叫DateVersion

    接下来我们来详细看一下这个维护版本号相关数据的数据结构,它叫Stat Structure,其字段有:

    字段 释义
    czxid 创建该节点的zxid
    mzxid 最后一次修改该节点的zxid
    pzxid 最后一次修改该节点的子节点的zxid
    ctime 从当前epoch开始到该节点被创建,所间隔的毫秒
    mtime 从当前epoch开始到该节点最后一次被编辑,所间隔的毫秒
    version 当前节点的改动次数(也就是版本号)
    cversion 当前节点的子节点的改动次数
    aversion 当前节点的ACL改动次数
    ephemeralOwner 当前临时节点owner的SessionID(如果不是临时节点则为空)
    dataLength 当前节点的数据的长度
    numChildren 当前节点的子节点数量

    举个例子,通过stat命令,我们可以查看某个ZNode中Stat Structure具体的值。

    关于这里的epoch、zxid是Zookeeper集群相关的东西,后面会详细的对其进行介绍。

    ACL

    ACL(Access Control List)用于控制ZNode的相关权限,其权限控制和Linux中的类似。Linux中权限种类分为了三种,分别是执行,分别对应的字母是r、w、x。其权限粒度也分为三种,分别是拥有者权限群组权限其他组权限,举个例子:

    drwxr-xr-x  3 USERNAME  GROUP  1.0K  3 15 18:19 dir_name

    什么叫粒度?粒度是对权限所作用的对象的分类,把上面三种粒度换个说法描述就是**对用户(Owner)、用户所属的组(Group)、其他组(Other)**的权限划分,这应该算是一种权限控制的标准了,典型的三段式。

    Zookeeper中虽然也是三段式,但是两者对粒度的划分存在区别。Zookeeper中的三段式为Scheme、ID、Permissions,含义分别为权限机制、允许访问的用户和具体的权限。

    Scheme代表了一种权限模式,有以下5种类型:

    • world 在此中Scheme下,ID只能是anyone,代表所有人都可以访问
    • auth 代表已经通过了认证的用户
    • digest 使用用户名+密码来做校验。
    • ip 只允许某些特定的IP访问ZNode
    • X509 通过客户端的证书进行认证

    同时权限种类也有五种:

    • CREATE 创建节点
    • READ 获取节点或列出其子节点
    • WRITE 能设置节点的数据
    • DELETE 能够删除子节点
    • ADMIN 能够设置权限

    同Linux中一样,这个权限也有缩写,举个例子:

    getAcl方法用户查看对应的ZNode的权限,如图,我们可以输出的结果呈三段式。分别是:

    • scheme 使用了world
    • id 值为anyone,代表所有用户都有权限
    • permissions 其具体的权限为cdrwa,分别是CREATE、DELETE、READ、WRITE和ADMIN的缩写

    Session机制

    了解了Zookeeper的Version机制,我们可以继续探索Zookeeper的Session机制了。

    我们知道,Zookeeper中有4种类型的节点,分别是持久节点、持久顺序节点、临时节点和临时顺序节点。

    在之前的文章我们聊到过,客户端如果创建了临时节点,并在之后断开了连接,那么所有的临时节点就都会被删除。实际上断开连接的说话不是很精确,应该是说客户端建立连接时的Session过期之后,其创建的所有临时节点就会被全部删除。

    那么Zookeeper是怎么知道哪些临时节点是由当前客户端创建的呢?

    答案是Stat Structure中的**ephemeralOwner(临时节点的Owner)**字段

    上面说过,如果当前是临时顺序节点,那么ephemeralOwner则存储了创建该节点的Owner的SessionID,有了SessionID,自然就能和对应的客户端匹配上,当Session失效之后,才能将该客户端创建的所有临时节点全部删除

    对应的服务在创建连接的时候,必须要提供一个带有所有服务器、端口的字符串,单个之间逗号相隔,举个例子。

    127.0.0.1:3000:2181,127.0.0.1:2888,127.0.0.1:3888

    Zookeeper的客户端收到这个字符串之后,会从中随机选一个服务、端口来建立连接。如果连接在之后断开,客户端会从字符串中选择下一个服务器,继续尝试连接,直到连接成功。

    除了这种最基本的IP+端口,在Zookeeper的3.2.0之后的版本中还支持连接串中带上路径,举个例子。

    127.0.0.1:3000:2181,127.0.0.1:2888,127.0.0.1:3888/app/a

    这样一来,/app/a就会被当成当前服务的根目录,在其下创建的所有的节点路经都会带上前缀/app/a。举个例子,我创建了一个节点/node_name,那其完整的路径就会为/app/a/node_name。这个特性特别适用于多租户的环境,对于每个租户来说,都认为自己是最顶层的根目录/

    当Zookeeper的客户端和服务器都建立了连接之后,客户端会拿到一个64位的SessionID和密码。这个密码是干什么用的呢?我们知道Zookeeper可以部署多个实例,如果客户端断开了连接又和另外的Zookeeper服务器建立了连接,那么在建立连接使就会带上这个密码。该密码是Zookeeper的一种安全措施,所有的Zookeeper节点都可以对其进行验证。这样一来,即使连接到了其他Zookeeper节点,Session同样有效。

    Session过期有两种情况,分别是:

    • 过了指定的失效时间
    • 指定时间内客户端没有发送心跳

    对于第一种情况,过期时间会在Zookeeper客户端建立连接的时候传给服务器,这个过期时间的范围目前只能在2倍tickTime和20倍tickTime之间。

    ticktime是Zookeeper服务器的配置项,用于指定客户端向服务器发送心跳的间隔,其默认值为tickTime=2000,单位为毫秒

    而这套Session的过期逻辑由Zookeeper的服务器维护,一旦Session过期,服务器会立即删除由Client创建的所有临时节点,然后通知所有正在监听这些节点的客户端相关变更。

    对于第二种情况,Zookeeper中的心跳是通过PING请求来实现的,每隔一段时间,客户端都会发送PING请求到服务器,这就是心跳的本质。心跳使服务器感知到客户端还活着,同样的让客户端也感知到和服务器的连接仍然是有效的,这个间隔就是**tickTime**,默认为2秒。

    Watch机制

    了解完ZNode和Session,我们终于可以来继续下一个关键功能Watch了,在上面的内容中也不止一次的提到**监听(Watch)**这个词。首先用一句话来概括其作用