目录
- 前言
- 阅读建议
- 课程内容
- 一、ZK Leader选举流程回顾
- 二、源码流程图
- 三、Leader选举模型图
- 学习总结
前言
为什么要看源码?当然是装逼啊!哈哈
说实在博主之前看Spring源码之前没细想过这个问题,只是听大佬们说【Spring是一个非常优秀的源码】,甚至【Java程序员一定要看的系列】的地步。于是奔着这些噱头,我还是抱着积极的心态去认真学习的(3分为装逼,7分为学习)。
那ZK源码,有必要学习吗?Spring再怎么说也是基石之一,还可以说的过去,ZK呢?它在市面上又并非是无可替代的。所以一开始我是觉得没必要去学习的,而且不少人还评价ZK源码写的很抽象、不规范,可读性差。但是我的培训老师们又比较力推,所以就跟着学习了部分源码。此后,我突然间有点明悟,为什么要学习ZK源码了。原因如下:
- ZK服务端是以Java编写出来的程序。所以,看ZK源码,其实是等于看一个行业大拿的优秀代码
- 学习ZK,在没有依赖其他中间件的情况下,如何实现高可用架构。或许,如果我们想成为一个优秀的JavaCoder,甚至是架构师,这确实是一条必经之路,毕竟这些成熟、优秀的产品架构,肯定能让你有所收获
- 不止是ZK,所有以Java编写的市面上优秀的源码都值得学习。毕竟这种能够在Java生态圈里面占据重要地位的应用,称它们为【Java源码金字塔顶端行列】也不为过
以上是我的一些思考。同样,这个问题,我的老师也给出了他们的答案,在这里分享给大家:
Q1:为什么要学习源码?
答:
- 提升技术功底:学习源码里的优秀设计思想,比如一些疑难问题的解决思路,还有一些优秀的设计模式,整体提升自己的技术功底
- 深度掌握技术框架:源码看多了,对于一个新技术或框架的掌握速度会有大幅提升,看下框架demo大致就能知道底层的实现,技术框架更新再快也不怕
- 快速定位线上问题:遇到线上问题,特别是框架源码里的问题(比如bug),能够快速定位,这就是相比其他没看过源码的人的优势
- 对面试大有裨益:面试一线互联网公司对于框架技术一般都会问到源码级别的实现
- 知其然知其所以然:对技术有追求的人必做之事,使用了一个好的框架,很想知道底层是如何实现的
- 拥抱开源社区:参与到开源项目的研发,结识更多大牛,积累更多优质人脉
阅读建议
看源码方法:
- 先使用:先看官方文档快速掌握框架的基本使用
- 抓主线:找一个demo入手,顺藤摸瓜快速静态看一遍框架的主线源码,画出源码主流程图,切勿一开始就陷入源码的细枝末节,否则会把自己绕晕。实在看不懂的凭经验猜
- 画图做笔记:总结框架的一些核心功能点,从这些功能点入手深入到源码的细节,边看源码边画源码走向图,并对关键源码的理解做笔记,把源码里的闪光点都记录下来,后续借鉴到工作项目中,理解能力强的可以直接看静态源码,也可以边看源码边debug源码执行过程,观察一些关键变量的值
- 整合总结:所有功能点的源码都分析完后,回到主流程图再梳理一遍,争取把自己画的所有图都在脑袋里做一个整合
课程内容
我跟着老师看了一下源码,说实在,思考了很久实在不知道怎么给大家伙记下来分享给大家,有点无从下手的感觉,因为相关性太大了。所以,这里算是自结吧,主要是为了加深个人理解,以及方便后续回头看。
一、ZK Leader选举流程回顾
我在之前的笔记当中有写过选举原理,但是有点囫囵吞枣,后来才知道原来这个挺重要的,算是ZK比较重点的内容之一。现在这里重新讲解一番。
什么是Leader选举
ZooKeeper的Leader选举过程是基于投票和对比规则的,确保集群中选出一个具有最高优先级的服务器作为Leader来处理客户端请求,以及同步数据给集群中的其他节点。
选举规则
选举投票对比规则如下:
- 首先比较epoch,选取具有最大epoch的服务器。epoch用于区分不同的选举轮次,每次重新选举时都会增加epoch。
- 如果epoch相同,则比较zxid(事务ID),选取事务ID最大的服务器。zxid表示最后一次提交的事务ID。
- 如果zxid也相同,则比较myid(服务器ID),选取服务器ID最大的服务器。
epoch
:表示ZooKeeper服务器的逻辑时期(logical epoch),它是一个相对时间的概念,用于区分不同的Leader选举周期。
zxid
:是一个64位的整数,由高32位的epoch和低32位的counter组成。
counter
:是一个在每个时期(epoch)内递增的计数器,用于标识事务的顺序。
选举流程
首先得说明的是,ZK的Leader选举是分两步的,所以又叫:两阶段选举。为什么需要两个阶段?接下来我们看一下流程,这个流程是我们网上能搜索到的,大家都清楚的流程:(流程按照上面的模型图。即:假设集群中有3个节点,并且只启动了2个节点,第3个节点没启动)
- ZK集群刚启动的时候,由于他们仍未经历过选举,所以每一台ZK机器中,上面投票规则提到的
epoch
肯定是一样的,包括zxid
,毕竟还没有接受过客户端的读写,所以唯一的差异就在myId
上了 - 开始第一阶段选举:由于彼此还不知道谁的
epoch
、zxid
、myId
,所以会优先将票投给自己,并且广播出去;当然也能接收到别的服务器【投票自己的广播】。所以,myid=1
的机器按照投票规则,投票vote=(1, 0)
,并且收到myid=2
的投票vote=(2, 0)
;同时myid=2
的机器按照投票规则,投票vote=(2, 0)
,并且收到myid=1
的投票vote=(1, 0)
。显然,目前投票情况是服务1跟服务2各收到一张选票,所以Leader没办法选举出来 - 第二阶段选举:每个节点服务器收到投票广播后,按照投票选举规则,各自更新自己最新的投票。比如:
myid=1
的服务器经过比较之后,发现myid=2
比自己更适合当Leader,于是在第二轮投票的时候投票vote=(2,0)
;而myid=2
的服务也经过比较之后,觉得还是自己适合做Leader,于是在第二轮投票的时候投票vote=(2,0)
。就这样,myid=2
的服务收到了2张选票,2 > (3/2)
,符合【过半机制】,于是成为了Leader
- 接着
myid=3
的节点上线,发现已经有Leader了,那不用投票了,直接把自己置为Follower(也许有人问,为什么不重新选举?因为没必要啊,对于ZK集群来说,尽快对外服务才是重点,重新选举不是浪费时间嘛)
OK,流程回顾就到这里。相信大伙通过这个业务流程去理解代码,将会事半功倍。
二、源码流程图
说明:我估计很多人源码入口都找不到,给大家一个方法,也是很多源码阅读的办法。那就是从启动脚本找,比如ZK,我们知道它的启动脚本为zkServer.sh
,那就在里面找好了。
ZK源码入口类:org.apache.zookeeper.server.quorum.QuorumPeerMain
。
下面是是一个自结的源码流程图,不是很好看。感兴趣的大伙,可以跟着我的流程图看一遍源码
看完选举源码之后的一些总结与思考:
- 【总结】比较清晰的了解,为什么是二阶段选举,因为ZK在设计投票与选举的时候,是使用两个独立的业务领域类(独立的线程)来完成的(投票决策领域类 + 投票发送/接受领域类),也就是说,他们的数据并非是直接耦合在一起的(我描述的比较抽象,咱也不知道该怎么解释了)。
- 【总结】在投票、选举中,ZK新建了好几个业务线程,往往是,一个业务领域类本身就是一个线程子类,然后在
run
方法中实现各自领域的逻辑。每个线程通常各自维护了一条阻塞队列,线程之间交换数据是将消息发送至对应的阻塞队列中。也许,这就是JVM级别,不基于中间件的线程通信的可靠手段之一吧 - 【思考】Leader节点与Follower节点,节点之间投票都分别监听了独立的端口,使用独立的scoket进行通信交互。其实前者Leader节点与Follower节点数据同步采用socket长连接保持通信我是能理解的,但是选举方面,我发现这个需求本来就不是特别大,为什么也要用socket通信呢?为什么不是http?别笑我哈,我一直以为socket比起http是一种比较重度的通信手段。现在看来可能跟我想象的不一样。
sockert
是传输层协议,http
是应用层协议,且两者安全性方面前者更高。下面是来自gpt的回答:
- 【总结】BIO网络连接在简单的应用通信之间,还是很可靠的,人家ZK都这么玩
三、Leader选举模型图
接下来再给大家画一下,选举的相关模型图。
整个zookeeper选举底层可以分为【选举逻辑层】和【选举消息传输层】,【逻辑层】有自己的队列统一接收和发送选票,【传输层】也设计了自己的队列,但是按发送的机器分了队列,避免给每台机器发送消息时相互影响,比如某台机器如果出问题发送不成功则不会影响对正常机器的消息发送。
学习总结
- 学习了ZK底层选举源码,学习像JUC,阻塞队列、网络通信在这种优秀应用中的使用思路
- 学习了ZK源码设计思想。其实我看着很像是DDD的领域设计