系统模型
- 数据模型
- ZooKeeper视图机构和标准Unix 文件系统非常相似,使用了数据节点(ZNode)
- ZNode是ZooKeeper数据的最小单元
- ZNode可以挂在子节点,保存数据信息
- ZNode层次化,树结构存储
- 在ZooKeeper中,事务操作指的是能改变ZooKeeper服务器状态的操作
- 包括数据节点的创建、删除、数据内容修改、客户端会话的创建和失效
- 每一个事务请求,ZooKeeper为其分配全局唯一id:zxid
节点特性
- 在ZooKeeper中,数据节点可分为:持久节点(PERSISTENT)、临时节点(EPHEMERAL)、顺序节点(SEQUENTIAL)三大类,四种组合
- PERSISTENT
- PERSISTENT_SEQUENTIAL(顺序节点被创建时,节点名一个数字后缀,数字后缀最大值是最大整数)
- EPHEMERAL(临时节点不能创建子节点)
- EPHEMERAL_SEQUENTIAL
- 状态信息
版本--保证分布式数据原子性操作
- ZooKeeper为数据节点引入版本信息的概念
- ZooKeeper版本的概念是指:对数据节点内容、子节点列表、节点ACL信息的修改次数
- ZooKeeper的 version属性正是用来实现乐观锁的 “写入校验”
- setDataRequest 方法中检查版本号
- 如果version ==-1 代表忽略检查
悲观锁(悲观并发控制):
- 一种非常严格的并发控制策略
- 具有强烈的独占性和排他性,能够有效地避免不同事务对同一数据并发更新而造成造成的数据一致性问题
- 悲观锁适合解决那些对于数据更新竞争非常激烈的场景
- 简单粗暴的解决并发控制问题
- 从头到尾加锁,假定数据并发操作一定会相互干扰
乐观锁(乐观并发控制):
- 常见并发控制策略
- 比较宽松友好
- 假定很多事务不会相互干扰,但是也存在相互影响的可能,
- 在提交前进行检查,如果已经被修改,全部回滚
- 数据读取,写入校验,数据写入三个阶段
- jdk CAS是最典型的乐观锁,全称为Compare-And-Swap
Watcher----数据变更的通知
- Watcher 接口
- 表示一个标准的事件处理器,定义了事件通知相关的逻辑
- 包含 KeeperState(通知状态)、EventType(事件类型) 俩枚举类
- 事件回调方法 process(WatchedEvent event);
扫描二维码关注公众号,回复:
4241989 查看本文章
Watcher 事件
- 相同事件类型,在不同通知状态代表含义不同
- NodeDataChanged 事件
- 无论数据内容、数据版本号变化,都会触发
- 因为只要客户调用数据更新接口,就会改变版本号,哪怕内容没变
- NodeChildrenChanged 事件
- 子节点列表变化:子节点个数和组成情况的变更
- 内容变化不会触发
- AuthFailed 事件
- 以下面俩程序为例:
- 第一个会抛出NoAuthException
- 第二个抛出AuthFailedException,同时受到对应的事件通知(AuthFailed,None)
- 以下面俩程序为例:
- 回调函数 process()
- 当ZooKeeper向客户端发送Watcher事件通知时,客户端就会对相应的process方法进行回调
- 和
- WatchedEvent 和 WatcherEvent
- 二者描述的都是服务端事件
- WatchedEvent 是一个逻辑封装,服务端和客户端执行过程中所需的逻辑对象
- WatcherEvent 实现了序列化接口,进行传输的封装
- 服务端生成WatchedEvent 之后,会对其进行封装成WatcherEvent 传输到客户端
- 客户端拿到WatcherEvent传递给process方法,还原成WatchedEvent
- 事件封装都及其简单,比如:ZNode 数据内容变更事件
- 客户端只能收到简单信息:
- 客户端无法获取到更新后的新数据,需要客户端再次主动请求获取,这是Watcher机制非常重要的特性
- 工作机制:
- 包括三个部分:客户端注册、服务端处理、客户端回调
客户端注册Watcher:
- getData、getChildren、exist三个方法可向ZooKeeper注册Watcher
- getData为例:
- 注册后,客户端首先会对请求request 进行标记(“使用Watcher监听”),同时会封装一个Watcher的注册信息WatcherRegistration
- WatcherRegistration用于暂时保存数据节点的路径和Watcher的对应关系
- 注册后,客户端首先会对请求request 进行标记(“使用Watcher监听”),同时会封装一个Watcher的注册信息WatcherRegistration
- ZooKeeper中最小的通信协议单元是 Packet
- 客户端和服务端的任何网络传输都需要封装成Packet对象
- 在客户端中WatcherRegistration会被封装到packet对象,
- 然后放入发送队列,等待客户端发送
- 然后,客户端回想服务端发送请求,同时等待回复
- 完成请求发送后,会有客户端SendThread线程的 readResponse 负责接收服务端响应
- finishPacket会从Packet中取出Watcher 注册到 ZKWatchManager
- register方法中会将暂时保存在WatcherRegistration 的Watcher 对象转交给 ZKWatchManager 的 dataWatchers
- dataWatchers是 Map 类型,用于将数据节点的路径和Watcher对象一一对应
- dataWatchers是 Map 类型,用于将数据节点的路径和Watcher对象一一对应
- 客户端每调用一次getData,就会注册一个Watcher,这些Watcher实体都会被传输到服务端吗?
- 不是,都传的话服务端内存肯定吃不消
- WatchRegistration 封装如Packet 进行传输,并不是对对象进行完全序列化
- 如下源码,只将requestHeader、request两个属性进行序列化
- WatchRegistration 并没有序列化到底层字节数组中,不会进行网络传输
服务端处理Watcher
- 客户端不会将Watcher 真正传递给服务端,服务端是如何完成注册呢?
- FinalRequestProcessor 里的processRequest 判断是否需要注册
- getData中的 getWatch方法 判断是否需要注册
- 传入 ServerCnxn 接口实例(代表客户端和服务端连接接口)
- 默认是 NIOServerCnxn,也可引入Netty,NettyServerCnxn
- 数据节点路径和ServerCnxn被存入WatchManager 的 watchTables、watch2Paths
- WatchManager 还负责事件触发,移除已经触发的事件
- DataTree 会托管两个 WatchManager:dataWatches、childWatches
Watcher触发
- NodeDataChanged事件触发条件是:watcher 监听的数据节点数据内容变更
- process 实际调用的是:
- 服务端 watcher本质保存的是 ServerCnxn
- process方法逻辑非常简单,不是客户端的真正逻辑,仅仅是向客户端发送一个事件通知
- process 实际调用的是:
客户端回调watcher
- SendThread 接收事件通知
- XID 是 -1 代表是一个通知类型的响应
- 四个步骤:
- 反序列化,从response 中拿到WatcherEvent
- 处理 chrootPath,生成相关中间路径
- 还原 WatchedEvent,将WatcherEvent 转换成WatchedEvent
- 回调 Watcher ,最后将WatchedEvent 交给 eventThread(在下一轮中进行Watcher 回调)
- EventThread 处理事件通知:
- 专门用来处理服务端事件通知的线程
- queueEvent方法
- 从 ClientWatchManager 取出所有相关watchers
- 获取到相关watcher,放入waitingEvents 队列
- 待处理watcher 队列
- EventThread 的run() 方法会不断处理该队列
ACL---保障数据的安全
- ACL(Access control list):访问控制列表
- 细粒度
- 如何保障ZooKeeper 误操作带来的数据随意变更
- UGO(user group others)权限控制机制
- 粗粒度权限控制机制
- 包含内容:
- 权限模式(Scheme):
- ip模式:
- ip:168.192.10.3
- 表示权限控制针对该ip
- Digest模式
- 类似:用户名:密码
- world 模式
- 最开放,所有用户不需要权限校验都可使用
- super
- 超级用户,可以对ZooKeeper任何节点进行任何操作
- 运维人员清理垃圾数据
- ip模式:
- 授权对象(id):
- 权限(Permission):
- 被允许的操作
- 权限模式(Scheme):
权限扩展体系
注册权限控制器
- 两种方式:
- 系统属性
- 文件配置