前言:当初学习的时候看的这篇文章,借鉴别人去学习的
能否用通俗的讲法解释一下什么是Zookeeper?www.zhihu.com![1403b7c2c86477b01a8b79dd624d174b.png](https://i-blog.csdnimg.cn/blog_migrate/af4bb79444e4031471df9d926de9dcc0.jpeg)
本文大纲
- 什么是ZK
- ZK能够干什么
- zookeeper的数据结构
- ZK的选举
什么是zookeeper?
1:Zookeeper是Apache Hadoop的子组件之一
2:ZooKeeper主要服务于分布式系统。统一配置管理、统一命名服务、分布式锁、集群管理
zookeeper能够干什么
ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
zookeeper的数据结构
可以看做是一颗树,每个节点叫做ZNode。每一个节点可以通过路径来标识,结构图如下:
![fb994b507bb639951b6a1d0ffc01bb5a.png](https://i-blog.csdnimg.cn/blog_migrate/14a64e8afa1ffb6f70869ffecc3f1f4c.png)
ZNode分为两种类型
短暂/临时(Ephemeral):当客户端和服务端断开连接后,所创建的Znode(节点)会自动删除
持久(Persistent):当客户端和服务端断开连接后,所创建的Znode(节点)不会删除
zookeeper如何做到统一配置管理、统一命名服务、分布式锁、集群管理
1:统一配置管理
假设我们现在有三个系统A、B、C,他们有三份配置,分别是ASystem.yml、BSystem.yml、CSystem.yml,这三份配置非常类似,很多的配置项几乎都一样。此时,如果我们要改变其中一份配置项的信息,很可能其他两份都要改。并且,改变了配置项的信息很可能就要重启系统。于是,我们希望把ASystem.yml、BSystem.yml、CSystem.yml相同的配置项抽取出来成一份公用的配置common.yml,并且即便common.yml改了,也不需要系统A、B、C重启。
![37768c2981cca1fe887cc72a89062a7e.png](https://i-blog.csdnimg.cn/blog_migrate/c00d9beb8006f263871514cc4cd40aeb.png)
做法:我们可以将common.yml这份配置放在ZooKeeper的Znode节点中,系统A、B、C监听着这个Znode节点有无变更,如果变更了,及时响应
2:统一命名服务
比如说,现在我有一个域名http://www.xxx.com,但我这个域名下有多台机器:
192.168.1.1
192.168.1.2
192.168.1.3
192.168.1.4
别人访问http://www.xxx.com即可访问到我的机器,而不是通过IP去访问
![52918d4ef322e4ff124752c08323c449.png](https://i-blog.csdnimg.cn/blog_migrate/30d8f9473ea69a9cd6d0b6fbb63fcfee.jpeg)
3:分布式锁
系统A、B、C都去访问/locks节点
![98c4da32c195a0c576ba36aded1bb86e.png](https://i-blog.csdnimg.cn/blog_migrate/0dc7a3d904cf5aec3b2edd3a702133aa.png)
访问的时候会创建带顺序号的临时/短暂(EPHEMERAL_SEQUENTIAL)节点,比如,系统A创建了id_000000节点,系统B创建了id_000002节点,系统C创建了id_000001节点。
![a32d4723a1176db3d4a5d9478afc04eb.png](https://i-blog.csdnimg.cn/blog_migrate/753e4bfbbc044f5b56c2f30bf3c26795.jpeg)
接着,拿到/locks节点下的所有子节点(id_000000,id_000001,id_000002),判断自己创建的是不是最小的那个节点
如果是,则拿到锁。
释放锁:执行完操作后,把创建的节点给删掉
如果不是,则监听比自己要小1的节点变化
原理:
系统A拿到/locks节点下的所有子节点,经过比较,发现自己(id_000000),是所有子节点最小的。所以得到锁
系统B拿到/locks节点下的所有子节点,经过比较,发现自己(id_000002),不是所有子节点最小的。所以监听比自己小1的节点id_000001的状态
系统C拿到/locks节点下的所有子节点,经过比较,发现自己(id_000001),不是所有子节点最小的。所以监听比自己小1的节点id_000000的状态…...等到系统A执行完操作以后,将自己创建的节点删除(id_000000)。通过监听,系统C发现id_000000节点已经删除了,发现自己已经是最小的节点了,于是顺利拿到锁….
系统B如上
4:集群管理
以我们三个系统A、B、C为例,在ZooKeeper中创建临时节点即可
![10b1fe38bbed1afd54690464b6870637.png](https://i-blog.csdnimg.cn/blog_migrate/47de8ed473138343084a13b5590a4fa5.jpeg)
只要系统A挂了,那/groupMember/A这个节点就会删除,通过监听groupMember下的子节点,系统B和C就能够感知到系统A已经挂了。(新增也是同理)
除了能够感知节点的上下线变化,ZooKeeper还可以实现动态选举Master的功能。(如果集群是主从架构模式下)
原理也很简单,如果想要实现动态选举Master的功能,Znode节点的类型是带顺序号的临时节点(EPHEMERAL_SEQUENTIAL)就好了。
Zookeeper会每次选举最小编号的作为Master,如果Master挂了,自然对应的Znode节点就会删除。然后让新的最小编号作为Master,这样就可以实现动态选举的功能了。
ZK选举
1:集群至少由三个节点组成
虽然说两个也可以,如果其中一个节点挂了,zookeeper就不能提供对外服务,失去了集群的意义。
2:节点并非越多越好
节点越多,使用的资源越多。
节点越多,ZooKeeper节点间花费的通讯成本越高,节点间互连的Socket也越多。影响ZooKeeper集群事务处理。
节点越多,造成脑裂的可能性越大。
3:集群规模为奇数
3台和4台都是只可以挂一台,因为过半性
解释3的一半为1.5以上最小整数为2,所以只能挂一台;4的一半为2以上最小整数为3,也是只能挂一台。
![e94ec9f018f28a76442a4803bfd86d7e.png](https://i-blog.csdnimg.cn/blog_migrate/a97ce30862283c4c34e20fd5cff565bb.png)
4:集群角色
Leader
在一个zookeeper集群中只能存在一个leader,集群中事务请求的唯一调度者和处理者,leader根据事务ID保证事务处理的顺序性
如果一个集群中存在多个leader,这个现象称为脑裂,大集群裂变为小集群,会导致数据不同步,数据混乱。
Follower
只能处理非事务请求,如果收到事务请求转发给leader;参与leader选举;参与事务处理投票
observer
与follower相似,可以处理非事务请求;将事务请求转发给Leader服务器。但是它不会参与leader的选举,不会参与投票。
如何成为观察者:在zoo.cfg中添加如下属性:peerType=observer
作用:用于不影响集群事务处理能力的前提下提升集群的非事务处理能力。
5:leader选举机制
1、选举投票必须在同一轮次中进行
如果Follower服务选举轮次不同,不会采纳投票。
2、数据最新的节点优先成为Leader
数据的新旧使用事务ID判定,事务ID越大认为节点数据约接近Leader的数据,自然应该成为Leader。
3、比较myid,id值大的优先成为Leader
如果每个参与竞选节点事务ID一样,再使用myid做比较。myid是节点在集群中唯一的id,myid文件中配置。
6:过半原则
Leader选举投票;事务提议投票;这些投票依赖过半原则。
- 事务提议投票
- 假设有3个节点组成ZooKeeper集群,客户端请求添加一个节点。Leader接到该事务请求后给所有Follower发起「创建节点」的提议投票。如果Leader收到了超过集群一半数量的反馈,继续给所有Follower发起commit。此时Leader认为集群过半了,就算自己挂了集群也是安全可靠的。
- Leader选举投票
- 假设有3个节点组成ZooKeeper集群,这时Leader挂了,需要投票选举Leader。当相同投票结果过半后Leader选出。
- 集群可用节点
- ZooKeeper集群中每个节点有自己的角色,对于集群可用性来说必须满足过半原则。这个过半是指Leader角色 + Follower角色可用数大于集群中Leader角色 + Follower角色总数。
- 假设有5个节点组成ZooKeeper集群,一个Leader、两个Follower、两个Observer。当挂掉两个Follower或挂掉一个Leader和一个Follower时集群将不可用。因为Observer角色不参与任何形式的投票。
7:场景实战
1:leader挂了选举过程
假设有3节点组成的集群,分别是server.1(Follower)、server.2(Leader)、server.3(Follower)。此时server.2不可用了。集群会产生以下变化:
状态变更
所有Follower节点变更自身状态为LOOKING。投票内容就是自己节点的事务ID和myid。我们以(事务ID,myid)表示。
假设server.1的事务id是10,变更的自身投票就是(10, 1);server.3的事务id是8,变更的自身投票就是(8, 3)。
投票机制
将变更的投票发给集群中所有的Follower节点。
server.1将(10, 1)发给集群中所有Follower,包括它自己。server.3也一样,将(8, 3)发给所有Follower。
所以server.1将收到(10, 1)和(8, 3)两个投票,server.3将收到(8, 3)和(10, 1)两个投票。
投票PK
比较规则:先比较事务id(Zxid),如果事务id相同则比较myid;超过一半节点才能成为leader。
对于server.1来说收到(10, 1)和(8, 3)两个投票,与自己变更的投票比较后没有一个比自身投票(10, 1)要大的,所以server.1维持自身投票不变。
对于server.3来说收到(10, 1)和(8, 3)两个投票,与自身变更的投票比较后认为server.1发来的投票要比自身的投票大,所以server.3会变更自身投票并将变更后的投票发给集群中所有Follower。
统计投票
server.1接收到来自server.1的(10, 1)投票和来自server.3的(8, 3)投票。
server.3同样接收到来自server.1的(10, 1)投票和来自server.3的(8, 3)投票。
此时server.1和server.3接收桶里的数据是这样的:
![48ceb800c8dbaeb1510ab5a1bf3ad0a8.png](https://i-blog.csdnimg.cn/blog_migrate/84f517491cab10235f98cd69ec846045.jpeg)
server.3经过PK后认为server.1的选票比自己要大,所以变更了自己的投票并重新发起投票。
server.1收到了来自server.3的(10, 1)投票;server.3收到了来自sever.3的(10, 1)投票。
此时server.1和server.3接收桶里的数据变成了这样:
![a5a7059994606fd5a28e52003849c5df.png](https://i-blog.csdnimg.cn/blog_migrate/04771866469ecd566c8095e5d1de3832.jpeg)
基于ZooKeeper过半原则:桶内投票选举server.1作为Leader出现2次,满足了过半 2 > 3/2 即 2>1。
最后sever.1节点晋升为Leader,server.3变更为Follower。
接收桶
节点接收的投票存储在一个接收桶里,每个Follower的投票结果在桶内只记录一次。ZooKeeper源码中接收桶用Map实现。
下面代码片段是ZooKeeper定义的接收桶,以及向桶内写入数据。Map.Key是Long类型,用来存储投票来源节点的myid,Vote则是对应节点的投票信息。节点收到投票后会更新这个接收桶,也就是说桶里存储了所有Follower节点的投票并且仅存最后一次的投票结果。
HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();
recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
2:集群扩容选举
假设目前有3个节点组成集群,分别是server.1(Follower)、server.2(Leader)、server.3(Follower),假设集群中节点事务ID相同。集群中新增server.4和server.5两个节点,首先修改server.4和server.5的zoo.cfg配置并启动。
选举机制
![0b581f3c1d9e05b88814519160ce0a9e.png](https://i-blog.csdnimg.cn/blog_migrate/21971c01616cf826a0e53e0dccbda09c.png)
脑裂现象的出现
修改server.2zoo.cfg配置文件,增加server.4和server.5的配置并停止server.2服务。停止server.2后,Leader不存在了,集群中所有Follower会发起投票。当server.1和server.3发起投票时并不会将投票发给server.4和server.5,因为在server.1和server.3的集群配置中不包含server.4和server.5节点。相反,server.4和server.5会把选票发给集群中所有节点。也就是说对于server.1和server.3他们认为集群中只有3个节点。对于server.4和server.5他们认为集群中有5个节点。
根据过半原则,server.1和server.3很快会选出一个新Leader,我们这里假设server.3晋级成为了新Leader。但是我们没有启动server.2的情况下,因为投票不满足过半原则,server.4和server.5会一直做投票选举Leader的动作。截止到现在集群中节点状态是这样的:
![ce32b3c0e4ac04681a5c2ef4aaa71310.png](https://i-blog.csdnimg.cn/blog_migrate/e1ebc7b7708c6807cac18e47a44ff2ea.png)
在启动leader后意想不到的事情发生了,出现两个Leader:
![75b91b2862d8dfab76a52511aec9ff6b.png](https://i-blog.csdnimg.cn/blog_migrate/0aeab9eba10623ba3b4c652f1c5bdc9a.png)
如何避免
ZooKeeper集群扩容时,如果Leader节点最后启动就可以避免这类问题发生。
因为在Leader节点重启前,所有的Follower节点zoo.cfg配置已经是相同的,他们基于同一个集群配置两两互联,做投票选举。
对大数据整体学习有兴趣的欢迎看我的文章
大忽悠:大数据学习之路(小白进来)zhuanlan.zhihu.com![b3cff36045bea5f64e21d42cabff7120.png](https://i-blog.csdnimg.cn/blog_migrate/d73c72b05565782abac96fef80838bba.jpeg)