Zookeeper是一个开源的分布式协调服务,由雅虎公司创建,由于最初雅虎公司的内部研究小组的项目大多以动物的名字命名,所以后来就以Zookeeper(动物管理员)来命名了,
而就是由Zookeeper来负责这些分布式组件环境的协调工作。
他的目标是可以提供高性能、高可用和顺序访问控制的能力,同时也是为了解决分布式环境下数据一致性的问题。
集群
首先,Zookeeper集群中有几个关键的概念,Leader、Follower和Observer,Zookeeper中通常只有Leader节点可以写入,Follower和Observer都只是负责读,
但是Follower会参与节点的选举和过半写成功,Observer则不会,他只是单纯的提供读取数据的功能。
通常这样设置的话,是为了避免太多的从节点参与过半写的过程,导致影响性能,这样Zookeeper只要使用一个几台机器的小集群就可以实现高性能了,如果要横向扩展的话,只需要增加Observer节点即可。
Zookeeper建议集群节点个数为奇数,只要超过一半的机器能够正常提供服务,那么整个集群都是可用的状态。
数据节点Znode
Zookeeper中数据存储于内存之中,这个数据节点就叫做Znode,他是一个树形结构,比如/a/b/c类似。
而Znode又分为持久节点、临时节点、顺序节点三大类。
持久节点是指只要被创建,除非主动移除,否则都应该一直保存在Zookeeper中。
临时节点不同的是,他的生命周期和客户端Session会话一样,会话失效,那么临时节点就会被移除。
还有就是临时顺序节点和持久顺序节点,除了基本的特性之外,子节点的名称还具有有序性。
会话Session
会话自然就是指Zookeeper客户端和服务端之间的通信,他们使用TCP长连接的方式保持通信,通常,肯定会有心跳检测的机制,同时他可以接受来自服务器的Watch事件通知。
事件监听器Wather
用户可以在指定的节点上注册Wather,这样在事件触发的时候,客户端就会收到来自服务端的通知。
权限控制ACL
Zookeeper使用ACL来进行权限的控制,包含以下5种:
所以,Zookeeper通过集群的方式来做到高可用,通过内存数据节点Znode来达到高性能,但是存储的数据量不能太大,通常适用于读多写少的场景。
Zookeeper可以提供分布式数据的发布/订阅功能,依赖的就是Wather监听机制。
客户端可以向服务端注册Wather监听,服务端的指定事件触发之后,就会向客户端发送一个事件通知。
他有几个特性:
主要流程如下:
Zookeeper通过ZAB原子广播协议来实现数据的最终顺序一致性,他是一个类似2PC两阶段提交的过程。
由于Zookeeper只有Leader节点可以写入数据,如果是其他节点收到写入数据的请求,则会将之转发给Leader节点。
主要流程如下:
ZAB包含两种基本模式,崩溃恢复和消息广播。
整个集群服务在启动、网络中断或者重启等异常情况的时候,首先会进入到崩溃恢复状态,
此时会通过选举产生Leader节点,当集群过半的节点都和Leader状态同步之后,ZAB就会退出恢复模式。之后,就会进入消息广播的模式。
Leader的选举可以分为两个方面,同时选举主要包含事务zxid和myid,节点主要包含LEADING\FOLLOWING\LOOKING3个状态。
服务启动期间的选举
服务运行期间的选举
如果开始选举出来的leader节点宕机了,那么运行期间就会重新进行leader的选举。
那实际上Zookeeper在选举之后,Follower和Observer(统称为Learner)就会去向Leader注册,然后就会开始数据同步的过程。
数据同步包含3个主要值和4种形式。
PeerLastZxid:Learner服务器最后处理的ZXID
minCommittedLog:Leader提议缓存队列中最小ZXID
maxCommittedLog:Leader提议缓存队列中最大ZXID
直接差异化同步 DIFF同步
如果PeerLastZxid在minCommittedLog和maxCommittedLog之间,那么则说明Learner服务器还没有完全同步最新的数据。
先回滚再差异化同步 TRUNC+DIFF同步
这个设置针对的是一个异常的场景。
如果Leader刚生成一个proposal,还没有来得及发送出去,此时Leader宕机,重新选举之后作为Follower,但是新的Leader没有这个proposal数据。
举个栗子:
假设现在的Leader是A,minCommittedLog=1,maxCommittedLog=3,刚好生成的一个proposal的ZXID=4,然后挂了。
重新选举出来的Leader是B,B之后又处理了2个提议,然后minCommittedLog=1,maxCommittedLog=5。
这时候A的PeerLastZxid=4,在(1,5)之间。
那么这一条只存在于A的提议怎么处理?
A要进行事务回滚,相当于抛弃这条数据,并且回滚到最接近于PeerLastZxid的事务,对于A来说,也就是PeerLastZxid=3。
流程和DIFF一致,只是会先发送一个TRUNC命令,然后再执行差异化DIFF同步。
仅回滚同步 TRUNC同步
针对PeerLastZxid大于maxCommittedLog的场景,流程和上述一致,事务将会被回滚到maxCommittedLog的记录。
这个其实就更简单了,也就是你可以认为TRUNC+DIFF中的例子,新的Leader B没有处理提议,所以B中minCommittedLog=1,maxCommittedLog=3。
所以A的PeerLastZxid=4就会大于maxCommittedLog了,也就是A只需要回滚就行了,不需要执行差异化同步DIFF了。
全量同步 SNAP同步
适用于两个场景:
这两种场景下,Leader将会发送SNAP命令,把全量的数据都发送给Learner进行同步。
还是会存在的,我们可以分成3个场景来描述这个问题。
查询不一致
因为Zookeeper是过半成功即代表成功,假设我们有5个节点,如果123节点写入成功,如果这时候请求访问到4或者5节点,那么有可能读取不到数据,因为可能数据还没有同步到4、5节点中,也可以认为这算是数据不一致的问题。
解决方案可以在读取前使用sync命令。
leader未发送proposal宕机
这也就是数据同步说过的问题。
leader刚生成一个proposal,还没有来得及发送出去,此时leader宕机,重新选举之后作为follower,但是新的leader没有这个proposal。
这种场景下的日志将会被丢弃。
leader发送proposal成功,发送commit前宕机
如果发送proposal成功了,但是在将要发送commit命令前宕机了,如果重新进行选举,还是会选择zxid最大的节点作为leader,因此,这个日志并不会被丢弃,会在选举出leader之后重新同步到其他节点当中。
Nacos | Eureka | Consul | Zookeeper | |
---|---|---|---|---|
一致性协议 | CP+AP | AP | CP | CP |
健康检查 | TCP/HTTP/MYSQL/Client Beat | Client Beat | TCP/HTTP/gRPC/Cmd | Keep Alive |
负载均衡策略 | 权重/ metadata/Selector | Ribbon | Fabio | — |
雪崩保护 | 有 | 有 | 无 | 无 |
自动注销实例 | 支持 | 支持 | 不支持 | 支持 |
访问协议 | HTTP/DNS | HTTP | HTTP/DNS | TCP |
监听支持 | 支持 | 支持 | 支持 | 支持 |
多数据中心 | 支持 | 支持 | 支持 | 不支持 |
跨注册中心同步 | 支持 | 不支持 | 支持 | 不支持 |
SpringCloud集成 | 支持 | 支持 | 支持 | 不支持 |
Dubbo集成 | 支持 | 不支持 | 不支持 | 支持 |
K8S集成 | 支持 | 不支持 | 支持 | 不支持 |
CAP是一个分布式系统设计的定理,他包含3个部分,并且最多只能同时满足其中两个。
为什么只能同时满足CAP中的两个呢?
以A\B两个节点同步数据举例,由于P的存在,那么可能AB同步数据出现问题。
如果选择AP,由于A的数据未能正确同步到B,所以AB数据不一致,无法满足C。
如果选择CP,那么B就不能提供服务,就无法满足A。
参考:
https://my.oschina.net/yunqi/blog/3040280