zookeeper原理与实战-对话篇|Java 开发实战

617 阅读13分钟

本文正在参加「Java主题月 - Java 开发实战」,详情查看 活动链接

大家好,今天呢,杰哥(哈哈,生活中总有人这样叫,感觉很亲切,就收下了)带你正式进入微服务架构中常用的注册中心 zookeeper 篇章的学习

打算写zookeeper,想到刚刚进入这个公司有一次被大神导师“惨虐”的场景

全局

刚来公司,第一周,跟导师一起吃饭,导师说他最近在研究 zookeeper(可能急于找一个人跟他探讨),于是就开始了跟我的如下对话(对,就是吃饭的时候被虐的...)

为什么叫 zookeeper

导师:听你说,你用过一年时间 zookeeper 了,那么你先来说说 zooKeeper 是什么?

我:(我怎么知道之前说的话,居然为自己埋下了一个坑。没办法,得硬着头皮说呀):zookeeper 就是我们平常一直用的 ,它是微服务架构中的重要组成部分,我了解的就是它会经常被用作 Dubbo 的注册中心。

导师:完了吗?

我(心想:这样说的确实有点草率,但是,但是,我就是想不到其他的了):嗯,稍等啊,嗯,完了,那你说说?

导师:这种回答,比较广泛,说实话,有点答非所问,并没有说我的心里去,给人的感觉是你只了解过而已,在项目中并没有使用过。

zooKeeper呢,之所以叫 zookeepr,为什么不跟其他的技术一样也叫一个动物的名字呢?比如说pig 啊,cat 啊,这不是一直以来的传统吗?这个呢,是当初它的作者们对它的定位呢,是分布式环境的协调者,因此叫 zookeeper,不就是动物管理员了吗?

我:哈哈,原来是这样来的啊,那这个名字确实很形象。

导师:对的,它总得来说,实际上是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 zooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。

我:哇,好专业,不过有一点点抽象。。。

zookeeper 的特性

导师:是吧,是会有一点,但是其实对它有一定的了解,包括架构、特性各个方面,你就不会觉得抽象了。对了,知道 zookeeper 的特性是什么吗?

我(这个问题,总能蒙上一点):实现服务的发布订阅,做好服务的协调者。性能比较高,同时需要具备分区容错性、尽可能的一致性和高效性。

导师:嗯,很好,这个问题回答的虽然有点简洁,但是都能看出来你有使用过,而且重点也都有提到。我给你讲讲吧。它的特性有四点:

  • 简单高效

ZooKeeper允许分布式进程通过共享的分层名称空间相互协调,这个命名空间的组织方式,类似于标准文件系统。与典型的用于存储的文件系统不同,ZooKeeper数据保存在内存中,这意味着ZooKeeper可以实现高吞吐量和低延迟。

  • 集群节点同步

就像它协调的分布式进程一样,Zookeeper 自身也在集群的各主机之间进行复制同步。

image.png

组成 zookeeper 服务(Service)的每个服务器(server)之间都必须相互了解对方。他们维护一个内存状态图,以及一个持久存储的事务日志和快照。只要这些服务器(servers)中大多数是可用的,整个ZooKeeper服务就是可用的。

客户端(client)只需要连接到任意一台ZooKeeper服务器,它维护一个 TCP 连接,通过它发送请求、获取响应、获取监视事件以及发送心跳。

如果到服务器的 TCP 连接中断,客户端将连接到其他不同的服务器。

3)有序

zookeeper 使用反映所有 zookeeper 事务顺序的数字来标记每个更新。后续操作可以使用该次序来实现更高级别的抽象,例如同步原语。

4) 快速

在应对以“读”为主的负载时,尤其比较快速。zookeeper 应用程序在数千台机器上运行,并且在读取比写入更为普遍的情况下,性能表现最佳,比例约为 10:1

总得来说,zookeeper 的实现着重于高性能、高可用性和严格的顺序访问。zookeeper 的性能方面意味着它可以用于大型分布式系统。可靠性方面使它不会造成单点故障。严格的排序意味着可以在客户端实现复杂的同步原语。

我(心想:原来如此):噢噢,这样啊,可能平时没有去着重关注这些东西。

zookeeper 的数据模型

导师:对,那么你应该也知道 zookeeper 的数据模型是怎么样的吧,也就是说它是以什么结构去存储的?

我(哈哈,这个我知道啊~):嗯嗯,知道!我会经常通过客户端的命令去查看我的微服务实例有没有注册在 zookeeper 上面,一般就是用斜杠分开的一些,类似于文件系统的那种目录。

比如说我想要找一个实例,那就要先进入它的组,再到 instance,再到具体的注册的实例名,可以通过 ls 命令查看实例有没有注册:

如果这个命令执行结果为[],则表示当前实例并没有成功注册;

image.png

但如果显示的是一个实例号,则表示注册成功。

导师:嗯,这个是常用的,你说的也没有问题。上面也说过了,zookeeper 提供的名称空间与标准文件系统的名称空间非常相似。各个节点名称是由斜杠(/)分隔的一系列路径元素。zookeeper 命名空间中的每个 znode 节点都由路径标识。

image.png

就像拥有一个文件系统一样,该文件系统也允许文件成为目录。

需要注意的是:zookeeper 旨在存储协调数据:状态信息,配置,位置信息等,因此每个节点上存储的数据通常很小,在字节到千字节范围内。

我(不就把我的话复述了一遍吗?):嗯嗯,对。

znode 节点

导师:好了,那么针对模型的组成部分 znode节点,你有哪些了解呢?

我(这个也能说上一点~):嗯,这个,我知道它分为临时节点和永久节点,如果有一个实例注册在上面了,那么就会为这个实例专门创建一个节点,这个实例名就是一个临时节点,当这个实例挂掉之后,这个节点就会消失。

然后上面至少会存储本身节点的数据,通过 get 命令就可以获取到这个数据,其他的可能暂时还没有了解到。嘻嘻~

导师:对,关于临时节点和永久节点,你说对了,当然它还会在被创建时指定一个序列号来保持有序。针对 znode 节点包含哪些内容,可能只是使用的话,会很容易被忽略,但实际上很重要。

znodes 维护一个统计数据结构,其中包括用于数据更改,ACL 更改和时间戳的版本号,以允许进行缓存验证和协调更新。

当然像你所说的,znode 首先是包含了数据信息 data

除了 data 之外,znode 结构实际上是这种的

image.png

还包含 ACLstat 以及子节点引用

  • ACL(Access Control List): 记录Znode的访问权限列表,也就是说存储了哪些人可以访问本节点。

  • stat:包含 znode 的各种元数据,比如事务ID、版本号、时间戳、大小等等。

  • child:当前节点的子节点引用,类似于二叉树的孩子节点,当然不止我画的这么两个孩子节点。

我:嗯嗯,这一点,确实没有了解过。

导师:好,那它的 watch 特性,你了解多少呢?

我(多少扯一点吧):我自己有学习过 Eureka,相对来说的话,zookeeper 的这种 watch 机制能够使得客户端能够实时感知到zk上它所需要调用的那个服务实例的新增、删除以及更新等操作,从而进行相应的服务熔断、回退后续等处理。这样的机制,对于实际生产来说有很大的帮助。

导师:没有问题,这个其实可以理解成是注册在特定 znode 上的触发器。当客户端调用了任何一种获取数据(包含getData(),getChildren()和exist())的操作,并将监听事件参数 watch 设置为 true,则当这个 znode 发生改变,zookeeper 服务端就会发送变化通知到这个请求监听的的客户端。

还有呢,现在在 3.6.0 中还新增了一个功能:客户端还可以在 znode 上设置永久性的递归监视,这些监视在触发时不会删除,并且会以递归方式触发注册 znode 以及所有子 znode 的更改。

导师:那关于集群架构,你应该也有了解吧 ?

我(哈哈,终于到了我昨天看的集群架构啦~这个我会呀!):嗯嗯,对。我知道它是主从结构的,具体来说是一主多从结构,就是有一个 leader,多个 follower,以及只负责读操作、不参与选举的observer,这个是我当时学习的时候画的图

image.png

  • 首先图上是有 clientserver 两大角色。client 通过 TCP 与其中一个 server 建立连接。其中 server 分为 leaderfollower,以及 observer 三个角色

leader:负责投票的发起和决议

follower:同步leader的状态,参与 leader 选举。将写操作转发给 leader,并参与“过半写成功”策略

observer :同步 leader 的状态,将写操作转发给 leader。不参加投票选举过程,也不参加写操作的“过半写成功”策略

  • leader 因为网络或者其他原因挂掉之后,会重新在 follower 里面选择一个新的leader

  • observer 机器可以在不影响写性能的情况下提升集群的读性能。

导师:可以可以,这个图画的比较清楚,学习确实需要整理成这种流程图或者框架图来展示,并有机会为别人讲出来你的理解,效果是最好的。很棒啊!

我(终于被夸奖啦!但要保持低调):嘻嘻,没有没有,因为对这个架构比较感兴趣,所以就花时间了解了一下。

ZAB 协议

导师:那你应该也知道,为了保证主从节点的数据一致性,zookeeper 采用了 ZAB 协议了吧?

我(导师,您继续秀~):这个,有听说,但是具体的话 就不是很清楚了,可能我还没有了解到这块呢。

导师:没关系,我来给你讲讲。

这个 zab 协议有两个基本的模式:崩溃恢复和广播模式

首先先说下崩溃恢复模式。你前边也说到了,集群架构中是有一个 leader 的,但是这个 leader 万一因为网络故障挂掉了怎么办呢?

我:需要进行重新选举,在 follower 里面投票选择一个

导师:对,这个选举就是通过使用 zab 协议进行的,zab 协议会进入恢复模式进行 leader 的选举过程。选举结束以后,zab 协议就会退出恢复模式。

而当超过一半的 follower 都完成了数据同步之后,zab 协议就由崩溃模式,进入到广播模式了。

这个呢,是我画的图。

image.png

1)leader 接收到写入数据请求(客户端发出写入数据请求给任意 FollowerFollower 将写入数据请求转发给Leader)

2)进入广播提议的发起

3)发起广播提议给所有 Follower

4)Follower接到 Propose 消息,写入日志成功

5)返回 ACK 消息给 Leader

6)Leader 接到半数以上 ACK 消息,返回成功给客户端,并且广播 Commit 请求给Follower

7)Follower 接收到消息,则提交事务

我(心想:大神导师果然厉害啊!):哇塞,这个图,让人对 zookeeper 集群的请求处理过程一目了然。这样也能让我理解了为什么说 zookeeper 可以保证原子特性了。

然而,事情还没有收尾的意思,导师又站在他的角度,搬出了性能

8、导师:关于可靠性,应该也没有了解过吧?

我(完全没有!):嘿嘿,没有什么概念。

好,那看看这个官网的图,你就会有概念了。

image.png

1)follower 的宕机和恢复

2)不同 follower 宕机和恢复

3)leader 的宕机

4)两个 follower 的宕机和恢复

5)另一位 leader 宕机

通过该图,我们可以有一些重要的观察结果。

1)首先,如果 follower 失败并迅速恢复,则 zooKeeper 能够在失败的情况下维持高吞吐量;

2)当然更重要的是,leader 选举算法允许系统恢复得足够快,以防止吞吐量大幅下降。根据我们的观察,zooKeeper 只需不到200毫秒即可选出新的领导者:

3)随着 follower 的恢复,zooKeeper 能够在开始处理请求后再次提高吞吐量。

我:沉默了。。。

导师:怎么了?没关系,你现在的学习意识可能还没有建立起来。但我发现你的学习能力还是可以的,你呢,以后就多注意去思考,要是让你看个技术,那我需要的结果,不仅是要会用,还需要你通过流程图的方式给别人讲清楚这个技术具体的原理,以及在实际应用中适合的业务场景。

这样的话,不仅你会深入理解这个技术,让听的人也快速对这个技术有了一定的概念,并且还能够根据不同的业务场景选择不同的技术。比如注册中心里面,我们是要选择 eureka还是 zookeeper,还是说其他注册中心,这些都是需要根据我们的具体业务场景来选择的。

我:对,谢谢导师,看来我还有很长的路要走啊!

总结

就这样,被导师“虐”了之后,就痛定思痛,从那以后,便老老实实真正学习技术了。

估计有很多人跟我之前的想法很类似,想着技术不就是会用就行了吗?面试也真是麻烦,程序员的工作不就是增删改查吗?有必要说的那么理论化吗?直到最后面试多次以失败而告终,看了很多大佬的面试经验与心得,才开始反思这个问题。

到后来呢,也因为踩过很多坑,就渐渐明白了,程序员也是分好多级别的,如果只是会用一门技术,不去思考它的原理,那么不仅不会升级,就连平时的工作可能都会因为考虑不周全而举步维艰。因此,本人也渐渐养成了持续学习的习惯。

所以,希望大家跟着我一起,持续探究架构中各个组件的底层原理。你可以每个月深入学习一个技术,这个技术可能是你经常在用的,也可能是比较生疏的,多去思考,多去分析,也可以多与同伴们交流,相信你不仅会越来越喜欢技术,还会成为你目前正在崇拜着的大牛的!