本文作者:HelloGitHub- 老荀
Hi,这里是 HelloGitHub 推出的 HelloZooKeeper 系列,收费开源、乏味、入门级的 ZooKeeper 教程,面向有编程根底的老手。
本系列教程是 从零开始 解说 ZooKeeper,内容从 最根底的装置应用到背地原理和源码的解说 ,整个系列心愿通过乏味文字、滑稽的氛围中让 ZK 的常识“钻”进你聪慧的大脑。本教程是开放式:开源、合作,所以不论你是老手还是老司机,咱们都心愿你能够 退出到本教程的奉献中,一起让这个教程变得更好:
- 老手:参加批改文中的错字、病句、拼写、排版等问题
- 使用者:参加到内容的探讨和问题解答、帮忙其他人的事件
- 老司机:参加到文章的编写中,让你的名字呈现在作者一栏
我的项目地址:https://github.com/HelloGitHu…
明天让咱们持续深刻聊一聊 ZK 的内存模型吧~
一、内存模型
ZKr~老规矩,明天让咱们看看动物村又产生了什么事件吧?
之前的故事咱们提到办事处的所有数据都是记录在小红本和小黄本中的,毕竟 马果果 是办事处的负责人,如果弄丢了,乌纱帽怕是要保不住了,所以 马果果 当初睡觉都要抱着两个账本睡觉呢。
之前 马果果 把小 F 招进来的时候,刚来的 小 F 就问 马果果 :“马老师,这个账本要怎么记录啊,我怕我做不好”, 马果果 微微一笑:“别怕,既然你曾经是我的入室弟子了,传内不传外,我会教你的”,“谢谢马老师!快教教我,怎么做的吧”,于是 马果果 就给 小 F 上起课来 …
我先拿一张之前的图:
过后为了不便大家了解树形构造和 chroot,所以顺便这样的画了下面这个图,实际上小红本长这样:
然而图中我还是把数据给省略了(因为没地画了),大家须要晓得的是这样记有两个点须要留神:
- 每一个门路的父级门路必须存在,顶级的门路(例:“/ 鸡太美”)的父门路就是“/”根门路
- 门路和数据一一对应,在图中能够了解就是记在左边
然而光记录门路和数据你就太小看 马果果 了,毕竟是姜还是老的辣,马果果 可是很高瞻远瞩的呢,咱们挑一条记录放大来看看具体的细节吧
除了数据,节点上还记录着权限,统计,子节点列表。怎么样?马果果 是不是很厉害。ZKr~
上面把这个内存模型用猿话翻译一下:
整个内存对象在 ZK 中对应的对象其实就是 DataTree
- 其实整个 ZK 的数据最终是存在一个哈希表(
ConcurrentHashMap
)中,key 是门路,而 value 则是对应的节点 - 节点蕴含了之前图中的:数据、子节点列表、权限、统计
- 门路、数据比较简单就不讲了
- 权限相干之后另外开篇讲,这里晓得 -1 代表不进行权限校验就行
- 子节点列表,每一个节点都会保护一个子节点的列表,只记录儿子节点。孙子节点及以下都不记录
- 统计数据是给客户端查问的,统计中的数据版本会被用在删除以及更新时作为乐观锁的版本号应用
因为应用的是哈希表,所以 ZK 查问速度是很快的。而根本的那些增删改查操作,其实就是操作的这个哈希表,具体到每一个操作的流程我这里就不赘述了,因为是很简略的,只是要留神的是:
- 父门路必须存在,不存在就报错
- 当创立新门路的时候,门路和已存在的反复就报错
二、回调告诉
下面的内容其实只说了小红本是怎么存的,然而 马果果 还有另一本外围账本:小黄本。
咱们接下来一起看看小黄本,马果果 是怎么玩的吧?
2.1 订阅(办事处视角)
办事处当初热火朝天的气氛,也离不开 马果果 过后定下的订阅性能,这样有需要的村民就不必一次次跑来办事处询问了。咱们这次以村民们订阅大明星 鸡太美 的更新为例子解说吧。
首先假如头等粉丝 坤坤 先跑去办事处,提出须要订阅 鸡太美 更新视频的申请,小 F 首先查看小红本必须确保 / 鸡太美 / 更新视频
存在,而后掏出了小黄本,记下了:
一次订阅须要记两条记录,别离是:
- 具体事务门路对应村民
- 村民对应具体事务门路
如果有多个村民以及多个事务的话就会变成这样:
而后 坤坤 就能够安心回家等告诉了。
直到 鸡太美 去办事处上传了最新的唱跳视频,小 F 在小红本中记录了:
而后 小 F 就会去小黄本中查看有没有 / 鸡太美 / 更新视频
的订阅,发现有三个村民: 坤坤 、 马小云 、 东东 订阅了此次事件,记住后就会把他们订阅的记录和对应的事务给删除:
而后会一一打电话给他们,并通知他们:
- 你们订阅的
/ 鸡太美 / 更新视频
有事件产生了 - 这次的事件是「数据更新」
到这里办事处负责的局部就完结了。
2.2 订阅(村民视角)
接下来让咱们把视角切到订阅的村民,咱们就拿 坤坤 举例,坤坤 之前到办事处订阅完回家后,其实也没闲着,也拿进去一个小本本记了下来:
之后接到了办事处的电话告诉,坤坤 就会再次拿出这个小本本,依据以后办事处告诉的事件 / 鸡太美 / 更新视频
找到小本本中须要做的事件:拉上窗帘、关上电脑、带上耳机、筹备纸巾。记住了之后,就将他们从小本本上删除了:
而后就严格依照过后记录的内容一件件去执行。
直到这里,整个从订阅到告诉的流程算是完结了。因为两边都删除了记录,所以之后如果 坤坤 想要再次收到 鸡太美 更新视频的告诉,须要再一次返回办事处注销一次。
然而你认为到这里就完了吗?
咱们的 马果果 是一个活到老学到老的武术巨匠,他自从学了计算机当前,智商突飞猛进,真的是文武双全的人才啊!立马发现刚刚的流程里存在的几个问题:
- 每次有村民过去注销订阅事务的时候,小 F 须要一个一个的记录,如果同一时间有多个村民注销的话,处于靠后地位的村民须要排队,等后面的村民注销完能力被 小 F 注销在案
- 在触发告诉的时候,小 F 在小黄本中找到指标事件的订阅的之后,是一个个把要告诉的村民从小黄本上删除的,并且整个删除的操作也和上一条注销的操作是抵触的,都须要排队
- 在小黄本中记录村民注销数据的时候,一次订阅须要记两条记录,十分的占中央,能不能找个节约点的方法
通过周密的思考后,马果果 找到了优化的方法,并且筹备传授给 小 F ,让咱们和 小 F 一起跟着 马果果 学习下到底是什么方法吧~
2.3 小黄本的改良之路
前排揭示:以下解说属于进阶内容,有那么点硬核!请酌情食用
先假如 坤坤 、 马小云 、 东东 三个人按程序过去订阅 鸡太美 的事件 / 鸡太美
,小黄本是这样记录的:
每一个村民过去之后都会调配惟一的一个递增的编号从 0 开始,编号和村民做好相互映射关系,之后在记录下订阅门路和编号之间的关系就行。那么这么记有什么益处呢?咱们先从之前 马果果 提到的第 3 点讲:
- 订阅的门路作为字符串自身的占用比拟大,而且移除了原先的 村民对应具体事务门路映射关系
- 数字自身占用比拟小,而且采纳了 马果果 新学的
BitSet
存储形式(这个呆会说),每一个村民只占用 1 个 bit 存储,实践上同一个门路订阅的村民少于 64 个的话,只须要 8 个字节就能存完
这两点都变相解决了占用内存的问题,存储问题讲完了,咱们再讲讲剩下的两个问题:
- 1、当初如果村民前来注销订阅事务的话,先让 小 F 查看该村民对应的编号是否存在,不存在的话须要递增以后编号并如图中一样新增编号和村民的映射关系,这个操作须要让其余村民临时等下。然而如果该村民是曾经注销过的话,间接拿着编号去门路和编号映射关系中加上这个编号即可,大抵流程如下:
只有调配的新的编号的时候会进行上锁,其余时候如果同时来多个订阅申请的话,应用的是读锁是能够并发的。
- 2、而触发告诉的时候,只须要间接取出待告诉门路对应的所有编号即可,再拿着编号去查到对应的村民一个个告诉即可
村委会对这次的改良很称心,马果果 也非常高兴!此次改良只和办事处无关,村民的解决办法还是和之前是一样的。
故事讲(chui)完了,当初用猿话翻译一下。
2.4 改良前
改良前的版本中服务端应用了两个哈希表别离记录了门路和客户端的映射以及客户端和门路的映射,两个哈希表都是一对多的关系。
订阅
而客户端尝试订阅某一个门路的时候,只会在申请中通知服务端,以后这个门路须要订阅,其实就是申请中的一个布尔值。服务端获取这个申请后,得悉这个门路须要订阅就会把这个客户端和门路别离存在下面提到的两个哈希表中。
而后客户端在服务端胜利返回后,也会在本地做一个记录,把门路和具体要执行的回调给映射起来,所以真正的回调是放在客户端执行的,千万不要认为是服务端来近程调用客户端的代码噢!
触发
服务端在解决完一些事务办法后,比方:setData
、create
、delete
等,都会去查看下是否有回调告诉须要触发,有的话取出须要告诉的所有客户端,并一一对他们发动告诉。
客户端在收到告诉后,也会从本人本地的记录中,通过门路取出具体的回调对象,而后触发该对象的回调办法。
2.5 改良后
改良只牵涉到服务端,所以客户端的逻辑不再赘述。
而且次要的改良并不牵涉到逻辑,只牵涉到了底层的数据结构,所以咱们重点来讲下这个新存储形式的数据结构 BitSet
后方真高能,酌情劳动下,再持续浏览
一个 BitSet
底层理论是一个 long
类型的数组,默认初始化长度是 1,咱们放大这个索引 0,看看是什么样子的吧。
这个数据类型最重要的就是 set
和 get
办法,而 ZK 服务端保障了每次往 BitSet
中增加的数字是递增的从 0 开始,所以咱们从 0 开始一一往其中增加数字。
0 的话就是把从左边数第一位翻成 1
而后是 1
依此类推 2 到 5
所以到这里大家能晓得,BitSet
用一个 64 位长度的整型数字,别离用每一位的 0 或 1 来记录数据,这样长度只有 1 的数组也能记录 64 个数字,精确的说是 0 ~ 63,这 64 个数字。那么你肯定会问,如果要记录 64 怎么办呢?在记录 64 的时候,数组会先扩容(通常是 2 倍)
而后把索引 1 的那个数字的最左边的那一位翻成 1
看到这里你应该能晓得了为什么 BitSet
能存下这么多数据了吧,然而毛病也很显著,只能存大于等于 0 的数字,所以 ZK 须要应用另外两个哈希表去映射数字和客户端之间的关系。
当初再回头看这个图大家就很分明了:
对于
BitSet
这里我留一个作业给大家,ZK 为什么要保护一个从 0 开始自增的数字,如果跳着数字存,比方间接存 100、200 等等,会怎么样呢?
介绍了半天,还没讲这个改良的版本怎么用呢。很遗憾的是,ZK 默认采纳的依然是改良前的解决办法,如果要批改服务端为改良后的办法,须要在服务端的环境变量中设置 export zookeeper.watchManagerName=org.apache.zookeeper.server.watch.WatchManagerOptimized
讲到这里你认为本章要完结了吗?
2.6 一次订阅终生受害
马果果 是一个谋求极致用户体验的负责人!因为不少村民都向他埋怨过,告诉后要从新过去订阅也太麻烦了,他们个别关怀的事件就那些,心愿能不能不用重复过去订阅,马果果 听取了人民大众的意见,在办事处解决订阅事务时新增了一个流程,如果村民想要一次订阅一生受害,请提前告知他们,小黄本上须要多记录一些货色。假如还是 坤坤 、 马小云 过去订阅 / 鸡太美 / 更新视频
,/ 鸡太美 / 跳舞
的话:
和之前不一样的是这个额定的记录是应用村民加上门路作为 key 的,而后在触发告诉的时候,让 小 F 额定查看下这个长久订阅的记录,如果以后村民和门路存在的话,就不删除原来的记录!这样这个订阅记录就会始终存在了,村民就不必每次告诉完从新过去订阅了!村民们听后纷纷拍手叫好!马果果 心里美滋滋,真是办了一件实事啊!
不过很快啊,鸡太美 的头等粉丝 坤坤 又来找 马果果 了,示意本人感觉曾经爱上了 鸡太美 了,想订阅和 鸡太美 有关系的所有事务,然而 鸡太美 当初是大明星了,各种事务很多很杂,如果他要去订阅 鸡太美 的每一个事务的话基本不可能,而且他也无奈得悉具体一共有哪些事务,所以想问 马果果 能不能再办件坏事,只须要订阅 鸡太美 的顶层门路就能承受到他上面所有其余门路的告诉。马果果 不愧是见过世面的人,很快就想到了一个方法,之前新增的长久订阅的记录中,做一下辨别不就行了,当初的记录变成了这样:
而后在告诉的时候,查看到以后门路有长久递归订阅的话,就把所有以后门路的所有父级门路都查看遍是否有订阅,有的话就告诉一遍,那这样,坤坤 能够承受到所有的 / 鸡太美
上面的所有事件告诉, 坤坤 称心如意的回去了。马果果 摇了点头:唉,年轻人追星要理智啊!ZKr~
好了,言归正传,说说最初新加的长久订阅和长久递归订阅:
- 这两个订阅模式名字我是间接通过源码中的枚举值直译过去的:
PERSISTENT
和PERSISTENT_RECURSIVE
- 这两个订阅模式是 ZK 3.6.0 以上版本才反对的新个性
- 客户端必须通过新的接口
addWatch
能力增加这两类的订阅 - 删除长久的订阅也须要另外调用接口
removeWatches
,感兴趣的同学本人花工夫钻研下吧 - 同一个客户端,同一个门路下只能有一种类型(共三种:一次性、长久、长久递归)的订阅,后注册的类别会笼罩之前注册的类别
- 令人遗憾的是这两个新的订阅模式和之前 2.5 提到的
export zookeeper.watchManagerName=org.apache.zookeeper.server.watch.WatchManagerOptimized
配置抵触,无奈一起应用
三、总结
本章介绍了 ZK 的内存模型以及 Watcher 告诉回调机制及其原理,Watcher 回调能够说是 ZK 最罕用的性能了,大家在平时的业务开发中肯定会常常用到,搞清楚原理也很有必要的。
下一章开始就要深刻到 ZK 最外围最有特色的知识点:集群!期待一下吧!
和上一期一样,如果你有任何对文章中的疑难也能够是倡议或者是对 ZK 原理局部的疑难,能够来我创立的话题中来探讨,不便记录和答疑:
地址:https://www.yuque.com/kaixin1…
最初点个赞再走吧!ZKr~