ZooKeeper笔记(2):ZooKeeper系统模型

1.数据模型

(1)树形结构

ZooKeeper的视图结构和标准的Unix文件系统非常类似,但没有引入传统文件系统中的目录和文件等概念,而是使用了数据节点ZNode。ZNode是ZooKeeper中数据的最小单元,每个ZNoe上都可以保在数据,同时还可挂载子节点,构成了一个层次化的命名空间,称之为树。ZNode的节点路径标识方式和Unix文件系统路径非常相似,都是由一系列使用斜杠(/)进行分割的路径表示,开发人员可以向这个节点中写入数据,也可以在节点下面创建子节点。

img

(2)事务ID

在ZooKeeper中,事务是指能够改变ZooKeeper服务器状态的操作,也称之为事务操作或更新操作,一般包括数据节点创建与删除、数据节点内容更新和客户端会话创建与失效等操作。对于每一个事务请求,ZooKeeper都会为其分配一个全局唯一的事务ID,用ZXID来表示,通常是一个64位的数字。每一个ZXID对应一次更新操作,从这些ZXID中可以间接地识别出ZooKeeper处理这些更新操作请求的全局顺序。

2.节点特性

(1)节点类型

在ZooKeeper中,每个数据节点都有生命周期,其生命周期的长短取决于数据节点的节点类型。在ZooKeeper中,节点类型可以分为持久节点(PERSISTENT)、临时节点(EPHEMERAL)和顺序节点(SEQUENTIAL)三大类,具体在节点创建过程中,通过组合使用,可以生成以下四种组合型节点类型:

持久节点:

ZooKeeper中最常见的一种节点类型,所谓持久节点,是指该数据节点被创建后,就会一直存在于ZooKeeper服务器上,直到有删除操作来主动清除这个节点。

持久顺序节点:

持久顺序节点的基本特性和持久节点是一致的,额外的特性表现在顺序性上。在ZooKeeper中,每个父节点都会为它的第一级子节点维护一份顺序,用于记录下每个子节点创建的先后顺序。基于这个顺序特性,在创建子节点的时候,可以设置这个标记,那么在创建节点过程中,ZooKeeper会自动为给定节点名加上一个数宇后缀,作为一个新的、完整的节点名。另外需要注意的是,这个数字后缀的上限是整型的最大值。

临时节点:

和持久节点不同的是,临时节点的生命周期和客户端的会话绑定在一起,如果客户端会话失效,那么这个节点就会被自动清理掉。注意,这里提到的是客户端会话失效,而非TCP连接断开,TCP连接断开,如果客户端在规定时间内与另外集群中的另外一台服务器建立连接,客户端会话不会失效。ZooKeeper规定了不能基于临时节点来创建子节点,即临时节点只能作为叶子节点。

临时顺序节点:

临时顺序节点的基本特性和临时节点也是一致的,同样是在临时节点的基础上,添加了顺序的特性。

(2)节点状态信息

可以针对ZooKeeper上的数据节点进行数据的写入和子节点的创建,毎个数据节点除了存储了数据内容之外,还存储了数据节点本身的一些状态信息。状态属性说明如下:

  • czxid:CreatedZXID,表示该数据节点被创建时的事务ID
  • mzxid:ModifiedZⅪID,表示该节点最后一次被更新时的事务ID
  • ctime:reatedTime,表示节点被创建的时间
  • mtime:ModifiedTime,表示该节点最后一次被更新的时间
  • version:数据节点的版本号
  • cversion:子节点的版本号
  • aversion:节点的ACL版本号
  • ephemeralOwner:创建该临时节点的会话的sessionID。如果该节点是持久节点,那么这个属性值为0。
  • dataLength:数据内容的长度
  • numChildren:当前节点的子节点个数
  • pzxid:表示该节点的子节点列表最后一次被修改时的事务ID。注意,只有子节点列表变更了才会变更pzxid,子节点内容变更不会影响pzxid。

3.版本

ZooKeeper中为数据节点引入了版本的概念,每个数据节点都具有三种类型的版本信息,对数据节点的任何更新操作都会引起版本号的变化,三类版本信息说明如下:

  • version:当前数据节点数据内容的版本号
  • cversion:当前数据节点子节点的版本号
  • aversion:当前数据节点ACL变更版本号

ZooKeeper中的版本概念和传统意义上的软件版本有很大的区别,它表示的是对数据节点的数据内容、子节点列表,或是节点ACL信息的修改次数。当一个数据节点被创建完毕之后,节点的version值是0,表示的含义是“当前节点自从创建之后,被更新过0次”。如果对该节点的数据内容进行更新操作,那么version的值就会变成1。version是对数据节点数据内容的变更次数,强调的是变更次数,因此,即使前后两次变更并没有使得数据内容的值发生变化,version的值依然会变更。

在并发场景中,version的作用类似于JVM并发场景的CAS,只针对指定版本才能修改。如果version为“-1”,在发生修改的时候,可以忽略版本比对;如果version不是“-1”,那么就比version和current_version,如果两个版本不匹配,那么将会抛出BadVersionException异常。

4.Watcher机制

一个典型的发布订阅模型系统定义了一种一对多的订阅关系,能够让多个订阅者同时监听某个主题对象,当这个主题对象自身状态变化时,会通知所有订阅者,使它们能够做出相应的处理。ZooKeeper提供了分布式数据的发布订阅功能,引入了Watcher机制来实现这种分布式的通知功能。ZooKeeper允许客户端向服务端注册一个Watcher监听,当服务端的一些指定事件触发了这个Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能。

img

ZooKeeper的Watcher机制主要包括客户端Client、客户端WatchManager和ZooKeeper Server三部分。具体工作流程是,客户端向ZooKeeper服务器注册Watcher,同时会将Watcher对象存储在客户端的WatchManager中。当ZooKeeper服务器端触发Watcher事件后,会向客户端发送通知,客户端线程从WatchManager中取出对应的Watcher对象来执行回调逻辑。

(1)Watcher接口

在ZooKeeper中,接口类Watcher表示一个标准的事件处理器,其定义了事件通知相关的逻辑,包括KeeperState和EventType两个枚举类,分别代表了通知状态和事件类型,同时定义了事件的回调方法process(Watched event event)。

事件类型EventType(znode节点相关的):

EventType.NodeCreated:监听的对应数据节点被创建;

EventType.NodeDataChanged:监听的对应数据节点的数据内容发生变更或版本发生改变;

EventType.NodeChildrentChanged:监听的对应数据节点的子节点列表发生变更;

EventType.NodeDeleted:监听的对应数据节点被删除,子节点内容发生变化,不会被监听;

通知状态KeeperState(客户端与服务端之间的状态):

KeeperState.Disconneced:连接失败;

KeeperState.SyncConnected:连接成功;

KeeperState.AuthFailed:认证失败;

KeeperState.Expired:会话过期;

回调方法 process():

process()方法是Watcher接口中的一个回调方法,当ZooKeeper向客户端发送一个Watcher事件通知时,客户端就会对相应的ρrocess()方法进行回调,从而实现对事件的处理。定义如下:

abstract public void process(WatchedEvent event);

WatchedEvent包含了事件的三个基本属性:通知状态(KeeperState)、事件类型(EventType)和节点路径(path),WatchedEvent也只有这三个属性,ZooKeeper使用WatchedEvent对象封装服务端事件。服务端生成WatchedEvent事件后,调用getWrapper()方法将自己包装成一个可序列化的WatcherEvent对象,以便通过网络传输到客户端。客户端在接收到服务端的WatcherEvent对象后,会将WatcherEvent事件还原成一个WatchedEvent事件,然后传递给ρrocess()方法处理。

WatchedEvent与WatcherEvent的联系与区别:

两者表示的是同一个事物,都是对一个服务端事件的封装。不同的是,WatchedEvent是一个逻辑事件,用于服务端和客户端程序执行过程中所需的逻辑对象;而WatcherEvent实现了序列化接口,可以用于网络传输,是用于网络传输的对象。

(2)Watcher机制的实现

ZooKeeper的Watcher机制,可以分为以下三个过程:客户端注册Watcher、服务端处理Watcher和客户端回调Watcher。

1)客户端注册

在创建一个ZooKeeper客户端对象实例时,可以向构造方法中传入一个默认的Watcher。这个Watcher将作为整个ZooKeeper会话期间的默认Watcher,会一直被保存在客户端ZKWatchManager的defaultWatcher中。

public ZooKeeper(String connectString, int sessionTimeout,Watcher watcher);

另外,ZooKeeper客户端也可以通过getData()、getChildren()和exist()三个接口来向ZooKeeper服务器注册Watcher,无论使用哪种方式,注册Watcher的工作原理都是一致的。

在注册过程中,客户端会将Watcher对象转交给ZKWatchManager.dataWatches中去,它是一个Map<String,Set>类型的数据结构,用于将数据节点的路径和Watcher对象进行一一映射后管理起来。客户端向服务端注册Watcher的时候,并不会把客户端真实的Watcher对象传递到服务端,否则可能造成服务端资源紧张,只是在客户端请求中使用boolean类型属性进行了标记。

2)服务端处理Watcher

ZooKeeper服务端处理Watcher的序列图如下:

img

服务端收到来自客户端的请求之后,在FinalRequestProcessor.processRequest()中会判断当前请求是否需要注册Watcher。

WatchManager是ZooKeeper服务端Watcher的管理者,其内部管理的watchTable和watch2Paths两个存储结构,分别从两个维度对Watcher进行存储。

  • watchTable是从数据节点路径的粒度来托管Watcher。
  • watch2Paths是从Watcher的粒度来控制事件触发需要触发的数据节点。

WatchManager还负责Watcher事件的触发,并移除那些已经被触发的Watcher。WatchManager只是一个统称,在服务端,DataTree中会托管两个WatchManager,分别是dataWatches和childWatches,分别对应数据变更Watcher和子节点变更Watcher。

Watcher的触发:

在对指定节点进行数据更新后,通过调用WatchManager的triggerWatch()方法来触发相关的事件,无论是dataWatches还是childWatches管理器,Watcher的触发逻辑都是一致的,基本步骤如下:

①封装WatchedEvent

首先将通知状态(KeeperState)、事件类型(EventType)以及节点路径(Path)封装成一个WatchedEvent对象。

②查询Watcher

根据数据节点的节点路径从watchTable中取出对应的Watcher。如果没找到,说明没有任何客户端在该数据节点上注册过Watcher,直接退出。如果找到了该Watcher,会将其提取出来,同时会直接从watchTable和watch2Paths中将其删除。从这里我们也可以看出,Watcher在服务端是一次性的,即触发一次就失效了。

③调用process方法来触发Watcher

逐个依次地调用从步骤2中找出的所有Watcher的process()方法,process()方法中,服务端会通过TCP连接向客户端发送一个WatcherEvent事件。

3)客户端回调Watcher

客户端收到服务端发送的WatcherEvenert事件响应后,通过SendThread.readResponse()方法处理事件,步骤如下:

①反序列化:将服务端响应的字节流封装为WatcherEvenert事件。

②处理chrootPath:如果服务端设置了chrootPath属性,需要对服务端传回的path路径进行处理,转为客户端的一个相对路径。

③还原WatchedEvent:将WatcherEvenert事件转换为WatchedEvent事件。

④回调Watcher:将WatchedEvent事件交给EventThread处理,EventThread是客户端中专门处理服务端通知事件的线程。

(3)Watcher的特性

1)一次性

无论是服务端还是客户端,一旦一个Watcher被触发,ZooKeeper都会将其从相应的存储中移除,ZooKeeper的监控都是一次性的,因此,开发人员需要反复注册Watcher。这样的设计有效地减轻了服务端的压力。如果注册一个Watcher之后一直有效,那么,针对那些更新非常频繁的节点,服务端会不断地向客户端发送事件通知,这无论对于网络还是服务端性能的影响都非常大。

2)串行执行客户端

客户端Watcher回调的过程是一个串行同步的过程,保证了顺序执行,同时,需要开发人员注意的一点是,千万不要因为一个Watcher的处理逻辑影响了整个客户端的Watcher回调。

3)轻量

WatchedEvent是ZooKeeper整个Watcher通知机制的最小通知单元,这个数据结构中只包含三部分内容:通知状态(KeeperState)、事件类型(EventType)以及节点路径(Path)。Watcher通知只会告诉客户端发生了事件,而不会说明事件的具体内容。例如,针对NodeDataChanged事件,ZooKeeper的Watcher只会通知客户端指定数据节点的数据内容发生了变更,而对于原始数据以及变更后的新数据,无法从这个事件中直接获取到,需要客户端主动重新去获取数据。

另外,客户端向服务端注册Watcher的时候,并不会把客户端真实的Watcher对象传递到服务端,仅仅只是在客户端请求中使用boolean类型属性进行了标记,同时服务端也仅仅只是保存了当前连接的ServerCnxn对象。如此轻量的Watcher机制设计,在网络开销和服务端内存开销上都是非常廉价的。

5.ACL权限控制

ZooKeeper作为一个分布式协调框架,其内部存储的都是一些关乎分布式系统运行时状态的元数据,尤其是一些涉及分布式锁、Master选举和分布式协调等应用场景的数据,会直接影响分布式系统的运行状态。因此,如何有效地保障ZooKeeper中数据的安全,避免因误操作而带来的数据随意变更导致的分布式系统异常就显得格外重要。ZooKeeper提供一套完善的ACL(Access Control list)权限控制机制来保障数据的安全。

(1)UGO权限控制

在Unix/Linux文件系统中使用的,也是目前应用最广泛的权限控制方式UGO(User、Group和Others)权限控制机制。简单地讲,UGO就是针对一个文件或目录,对创建者User、创建者所在组Group和其他用户Other分别配置不同的权限。UGO是一种粗粒度的文件系统权限控制模式,利用UGO只能对三类用户进行权限控制,UGO无法解决下面这个场景:用户U1创建了文件F1,希望U1所在的用户组G1拥有对F1读写和执行的权限,另一个用户组G2拥有读权限,而另外一个用户U3则没有任何权限。

(2)ACL权限控制

ACL即访问控制列表,是一种相对来说比较新颖且更细粒度的权限管理方式,可以针对任意用户和组进行细粒度的权限控制。目前绝大部分Unix系统都已经支持了ACL方式的权限控制,Linux也从2.6版本的内核开始支持这个特性。

ZooKeeper的ACL权限控制与Unix/Linux的ACL权限控制有一定的区别,可以从三个方面来理解ACL机制,分别是:权限模式(Scheme)、授权对象(ID)和权限(Permission),通常使用"scheme🆔permission"来标识一个有效的ACL信息。

权限模式:Scheme

权限模式用来确定权限验证过程中使用的检验策略,在ZooKeeper中使用最多的是以下四种权限模式。

IP: 通过IP地址粒度来进行权限控制,例如配置了"ip:192.168.0.110",表示权限控制都是针对这个IP地址。同时,IP模式也支持按照网段的方式进行配置,例如"ip:192.l68.0.1/24"表示针对192.168.0.*这个IP段进行权限控制。

Digest: Digest是最常用的权限控制模式,也更符合我们对于权限控制的认识,其以类似于"username:password"形式的权限标识来进行权限配置,便于区分不同应用来进行权限控制。当我们通过"username:password"形式配置了权限标识后,ZooKeeper会对其进行SHA-1算法加密和BASE64编码,最终会被混淆成一个无法辨识的字符串。

World: World是一种最开放的权限控制模式,所有用户都可以在不进行任何权限校验的情况下操作ZooKeeper上的数据。另外,World模式也可以看作是一种特殊的Digest 模式,它只有一个权限标识,即"world:anyone"。

Super: Super模式也是一种特殊的Digest模式。在Super模式下,超级用户可以对任意ZooKeeper上的数据节点进行任何操作。

授权对象:ID

授权对象指的是权限赋予的用户或一个指定实体,例如IP地址、机器等。在不同的权限模式下,授权对象是不同的,如下表。

img

权限:Permission

权限指那些通过权限检查后可以被允许执行的操作。在ZooKeeper中,对数据的操作权限分为以下五大类:

  • CREATE(C):子节点创建权限,允许授权对象在该数据节点下创建子节点。
  • DELETE(D):子节点删除权限,允许授权对象删除该数据节点的子节点。
  • READ(R):数据节点读取权限,允许授权对象读取其数据内容、子节点列表等。
  • WRITE(W):数据节点更新权限,允许授权对象对该数据节点进行更新操作。
  • ADMIN(A):数据节点管理权限,允许授权对象对该数据节点进行ACL相关的设置操作。

权限扩展体系

ZooKeeper默认提供的IP、Digest、World、Super这四种权限模式,在绝大部分的场景下,这四种权限模式已经能够很好地实现权限控制的目的。同时,ZooKeeper提供了特殊的权限控制插件体系,允许开发人员通过指定方式对ZooKeeper的权限进行扩展。这些扩展的权限控制方式就像插件一样插入到ZooKeeper的权限体系中去,机制为"Pluggable ZooKeeper Authentication"。

(3)ACL管理

设置ACL

通过zkCli脚本登录ZooKeeper服务器后,可以通过两种方式进行ACL的设置。一种是在数据节点创建的同时进行ACL权限的设置,命令格式如下:

create [-s] [-e] path data acl

另一种方式是使用setACL命令,对已经存在的数据节点进行ACL设置,命令如下:

setACL path acl

Super模式的使用

根据ACL权限控制的原理,一旦对一个数据节点设置了ACL权限控制,那么其他没有被授权的客户端将无法访问该数据节点,这的确很好地保证了ZooKeeper的数据安全。同时,ACL权限控制也给ZooKeeper的运维人员带来一个困扰,如果一个持久数据节点包含了ACL权限控制,而其创建者客户端已经退出或已不再使用,那么这些数据节点该如何清理呢?这个时候,就需要在ACL的Super模式下,使用超级管理员权限来进行处理了。要使用超级管理员权限,首先需要在ZooKeeper服务器上开启Super模式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值