Zookeeper是一个分布式协调框架,近年来已经成为一个热门的话题,不懂用法及原理几乎会被鄙视。下面将分享下,很久之前的笔记,同时也喜欢可以达到为故而知新的目的。
目录
1.介绍
ZooKeeper是一个开源、高性能、数据一致性、分布式协调服务,它是开源的Hadoop项目中的一个子项目,是Hadoop和Hbase的重要组件。并且根据google发表的<The Chubby lock service for loosely-coupled distributed systems>论文来实现的。
ZooKeeper是google的Chubby一个开源的实现,它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务、配置维护和命名服务等。
2.应用场景
1.数据发布/订阅
也就是配置中心的概念,分为发布者和订阅者
2.负载均衡
通过ZK管理集群中服务提供方的域名及ip及端口,dubbo服务框架就是基于ZK实现服务路由和负载
3.命名服务
名称的服务,通过ZK可以实现类似于J2EE中JNDI的效果,在分布式环境下,更多是资源的定位,并不是真正实体资源;
例如数据库表ID,一种是自增ID,一种是UUID而ZK就可以很好的解决;
4.分布式协调/通知
- 通过watcher和通知机制实现(心跳检测,通过临时节点实现);
- 分布式锁:排他锁和共享锁;
- 分布式事务;
3.基本概念
3.1.Paxos算法
它是一个基于消息传递的一致性算法,在1990年被提出,比如google的chubby,aapache的zookeeper都是基于它的理论。
包含的角色有:
Proposer(申请者)、Acceptor(接收者)、Learner(领导者);
执行过程:
- 提案的选定:一个Proposer向一个或多个Acceptor发提案,由半数以上Acceptor批准的提案会被选定。
- Proposer生成提案:在确定提案后,Proposer会将该提案再次发送给某个Acceptor集合,并期望获得它们的批准。
- Acceptor批准提案:Aceeptor接到Proposer的Prepare或Accept请求后,做出相应的响应。
- 提案的获取:Learner获取提案。
优点:引入“过半”的概念,即少数服从多数的原则,并且支持节点角色之间的轮换,极大地避免了分布式的出现,也解决了无限等待和“脑裂”等问题。
3.2. ZAB协议
为分布式协调服务Zookeeper专门设计的一种支持崩溃恢复的原子广播协议。它规定任何时候都需要保证只有一个主进程负责进行消息广播,如果主进程崩溃了就要选举一个新的主进程,选举机制和消息广播机制是紧密相关。ZAB协议包括两种基本模式:崩溃恢复和消息广播。
- 在消息广播中,Leader服务器会为每一个Follower服务器各自分配一个单独的队列,将需要广播的事务依次放入这些队列中,并根据FIFO的策略进行消息发送。
- 崩溃恢复:当Leader服务器出现崩溃或者由于网络原因导致Leader服务器失去与过半Follower的联系,就进入崩溃恢复模式。 进程正常工作时,处于UP状态;进程崩溃时,称为处于DOWN状态。
为了保证事务的顺序一致性,ZooKeeper采用了递增事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid,实现中zxid是一个64位的数字,它前32位是用epoch来标识leader关系是否改变,每次leader被选出来,它都是一个新的epoch,标识当前属于哪个leader的统治时期。后32位用于递增计数。
每个server在工作过程中有三种状态:
- LOOKING:当前sever不知道leader是谁,正在搜寻;
- LEADING:当前server即被选举出来的leader;
- FOLLOWING:leader已经被选出,当前server与之同步;
ZAB与Paxos的联系与区别:
- 相同点:都存在类似于Leader进程的角色,负责协调多个Follower进程的运行;Leader进程都会等待超过半数的Follower做出正确的反馈后,才会将一个提案进行提交。
- 区别:设计目标不一样。ZAB用于构建一个高可用的分布式数据主备系统,Paxos用于构建分布式一致性状态机系统。
3.3. 选举
半数通过,奇数台部署可以满足挂一台继续有效选举。
- 变更状态。Leader挂后,余下的非Observer服务器都会讲自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。
- 每个Server会发出一个投票。在运行期间,每个服务器上的ZXID可能不同,此时假定Server1的ZXID为123,Server3的ZXID为122;在第一轮投票中,Server1和Server3都会投自己,产生投票(1, 123),(3, 122),然后各自将投票发送给集群中所有机器。
- 接收来自各个服务器的投票。与启动时过程相同。
- 处理投票。与启动时过程相同,此时,Server1将会成为Leader。
- 统计投票。与启动时过程相同。
- 改变服务器的状态。与启动时过程相同。
要确保Zookeeper能够稳定运行,那么就需要确保投票能够正常进行,最好不要挂一个节点整个就不work了,所以我们一般要求最少5个节点部署。
3.4. 集群角色
- Leader:是集群工作机制的核心,为客户端提供读、写服务,负责进行投票的发球和决议;
- Follower:是集群中的跟随者的角色,提供读服务,参与选举;
- Observer:是集群中观察者的角色,提供读服务,将写请求转发给leader,不参与选举,为了增加ZK集群读取请求并发;
注:Observer也完全满足多机房部署的需求
3.5. 会话
会话是指客户端和ZK服务器的连接,ZK中的会话叫Session
- 客户端与服务器建立一个TCP的长连接来维持;
- 通过心跳监测与服务器保持会话的存活;
- 接受来自服务端的watch事件通知;
- 可以设置超时时间;
3.6. 数据模型
zookeeper是一个类似hdfs的树形文件结构
- ZK的数据模型就是一棵树,树的节点就是Znode,Znode中更可以保存信息;
- 层次化的目录结构,命名符合常规文件系统规范;
3.7. 数据节点
ZK中的节点不是集群的一台机器,而是数据模型中的数据单元Znode。
- 持久节点(PERSISTENT):一旦创建,一直有效;
- 临时节点(EPHEMERAL ):与客户端会话绑定,会话失效临时节点被删除;
- 包含SEQUENTIAL(自动)节点,PERSISTENT_SEQUENTIAL(顺序自动编号持久化节点,这种节点会根据当前已存在的节点数自动加 1),EPHEMERAL_SEQUENTIAL(临时自动编号节点);
- 每个节点都有一个唯一的路径标识;
- 节点znode可以包含数据和子节点,但是EPHEMERAL类型节点不能有子节点;
- Znode中的数据可以有多个版本,比如某一个路径下存在多个数据版本,那么查询这个路径下的数据就需要带上版本;
- 客户端应用可以在节点上设置监视器;
3.8. 版本
悲观锁与乐观锁
悲观锁又叫悲观并发锁,是数据库中一种非常严格的锁策略,具有强烈的排他性。乐观锁认为不同事物访问相同的资源是很少出现干扰的情况,事务处理上不需要进行并发控制,通过对每张表添加一个version字段来实现
版本类型 | 说明 |
version | 当前数据节点数据内容的版本号 |
cversion | 当前数据节点子节点的版本号 |
aversion | 当前数据节点ACL变更版本号 |
3.9. Wacher
Watcher在zookeeper是一个核心功能,watcher可以监控节点的数据变化以及子目录的变化,一旦这些状态发生了变化,服务器就会通知所有设置这个目录节点上的watcher,从而每个客户端很快就知道他所关注内容的变化点并做出相应的反应。
这里强调一次:watch事件(不是数据)!watch事件(不是数据)!watch事件(不是数据)!
3.10. ACL
ACL是Aceess Control Lists的简写,ZK采用ACL策略进行权限控制,有以下权限:
- CREATE:创建子节点的权限
- READE:获取节点数据和子节点列表的权限
- WRITE:更新节点数据的权限
- DELETE:删除子节点的权限
- ADMIN:设置节点ACL的权限
4.架构
4.1.工作原理
- 每个server的数据都会放在内存里,形成一棵树的数据结构,同时定时的dump snapshot到磁盘;
- ZooKeeper启动时,将从实例中选举一个leader(ZAB协议);
- Leader负责处理数据的更新等操作;
- 一个写操作需要半数以上的节点ack,所以集群节点数越多,整个集群可以抗挂点的节点数越多(越可靠),但是吞吐量越差
4.2.读写机制
- 由多个server组成的集群;
- 一个leader,多个follower;
- 每个server保存一份数据副本;
- 全局数据一致;
- 分布式读写;
- 更新请求转发,由leader实施;
Follower主要的四个功能
Follower主要的四个功能(1~6):
- 向leader发送请求(PING消息、REQUEST消息、ACK消息、REVALIDATE消息);
- 接收leader消息并进行处理;
- 接收client的请求,如果为写请求,发送给leader进行投票;
- 返回client结果;
Follower的消息循环处理有如下几种来自leader的消息:
- PING:心跳消息;
- PROPOSAL:leader发起的提案,要求follower投票;
- COMMIT:服务端最新一次提案的消息;
- UPTODATE:同步完成;
- REVALIDATE:根据leader的REVALIDATE结果,关闭待revalidate的session还是允许接受消息;
- SYNC:返回SYNC结果到客户端,消息最初是有客户端发起,用来强制得到最新的消息
5.安装
集群与伪集群的搭建
1) data目录创建myid问题件,里面写一个数字,比如server1就写1,server2就写2
2) zoo_sample.cfg修改为zoo.cfg,修改里面内容
dataDir=xxxx/zookeeper/server1/data
dataLogDir=xxx/zookeeper/server1/dataLog
clientPort=2181
server.1=127.0.0.1:2888:3888 //分别为follower和leader通信端口;选举投票端口
server.2=127.0.0.1:2889:3889
server.3=127.0.0.1:2890:3890
3) 启动2台以上
zkServer.sh start
zkCli.sh –server 127.0.0.1:2182
注:server.X 这个数字就是对应 data/myid中的数字
6.命令行
命令行 | 描述 |
help h | 帮助信息 |
ls / ls /node_1 | 查看当前节点数据 |
Ls2 / | 查看当前节点数据并能看到更新次数等数据 |
stat /node_1 | 查看节点状态信息 字段构成: czxid:创建节点的事务的zxid mzxid:对znode最近修改的zxid ctime:以距离时间原点(epoch)的毫秒数表示的znode创建时间 mtime:以距离时间原点(epoch)的毫秒数表示的znode最近修改时间 version:znode数据的修改次数 cversion:znode子节点修改次数 aversion:znode的ACL修改次数 ephemeralOwner:如果znode是临时节点,则指示节点所有者的会话ID;如果不是临时节点,则为零。 dataLength:znode数据长度 numChildren:znode子节点个数 |
get /node_1 | 获取节点数据 |
create /node_1 123 create -e /node_2 456 create -s /node_3 111 | 格式:create [-s] [-e] path data acl -s 顺序节点(自增系列) -e临时节点 |
set /node_2 22 set /node_2 22 2 | set path data [version] 修改节点 |
delete /node_2 | delete path [version] 删除节点(有子节点报错) |
rmr /node_5 | 删除带子节点的节点 |
setquota –n 2 /node_1 | 配额setquota -n|-b val path -n子节点的个数 -b数据的长度 注:配额超出只会有警告日志不报错(zookeeper.out) |
listquota /node_1 | 查看配额 |
| 删除配额delquota [-n|-b] path |
history | 命令历史 |
redo 27 | 重复执行历史的命令 |
| setAcl path acl c:创建,r:读取d:删除,w:写入a:管理 Scheme的类型:(world,auth,digest,super) setAcl /node1 world:anyone:crdwa getAcl /node1 |
7.Java客户端
7.1.默认端口
- 2181:对client端提供服务
- 2888:集群内机器通讯使用(Leader监听此端口)
- 3888:选举leader使用
7.2.Java原生
zookeeper自带的客户端是官方提供的,比较底层、使用起来写代码麻烦、不够直接。
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
7.3.Apache Curator
Curator是Netflix公司开源的一套ZK客户端框架,Curator解决了很多ZK客户端非常底层的细节开发工作,包含连接重连、反复注册Watcher等,实现了Fluent风格的API接口,目前已经成为Apache的顶级项目,是全世界范围内使用最广泛的ZK客户端之一。
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.9.0</version>
</dependency>
7.4.zkclient
ZkClient是Github上一个开源的ZK客户端。ZkClient在ZK原生API接口之上进行了包装,是一个更加易用的客户端。同时,zkClient在内部实现了诸如Session超时重连、watcher反复注册等功能。
<dependency>
<groupId>com.github.adyliu</groupId>
<artifactId>zkclient</artifactId>
<version>2.1.1</version>
</dependency>
8.HDFS的分布式锁
相关源码:
9.目前不足
1.性能是有限的
典型的zookeeper的tps大概是一万多,无法覆盖系统内部每天动辄几十亿次的调用。因此每次请求都去zookeeper获取业务系统master信息是不可能的。 因此zookeeper的client必须自己缓存业务系统的master地址。 因此zookeeper提供的‘强一致性’实际上是不可用的。如果我们需要强一致性,还需要其他机制来进行保障:比如用自动化脚本把业务系统的old master给kill掉,但是那会有很多陷阱(这里先不展开这个议题,读者可以自己想想会有哪些陷阱)。
2.选举过程速度很慢
这是一个很难从理论分析上看到的弱点,但是你一旦遇到就会痛不欲生。 前面我们已经说过,网络实际上常常是会出现隔离等不完整状态的,而zookeeper对那种情况非常敏感。一旦出现网络隔离,zookeeper就要发起选举流程。 zookeeper的选举流程通常耗时30到120秒,期间zookeeper由于没有master,都是不可用的。 对于网络里面偶尔出现的,比如半秒一秒的网络隔离,zookeeper会由于选举过程,而把不可用时间放大几十倍。
3.权限控制非常薄弱
在大型的复杂系统里面,使用zookeeper必须自己再额外的开发一套权限控制系统,通过那套权限控制系统再访问zookeeper 额外的权限控制系统不但增加了系统复杂性和维护成本,而且降低了系统的总体性能
当然了,后续也会陆续分享下它作为注册中心时,被大家疯狂吐槽的相关话题。