一、Zookeeper简介
zookeeper起源于雅虎研究院,主要用于解决服务间协同以及单节点问题,所以雅虎研究院就研发了无单节点问题的分布式协同系统-Zookeeper。
1、 在开源项目的使用:
Hadoop:使用zookeeper做NameNode的高可用
HBase:保证集群中只有一个master
kafka:集群成员管理、controller选举
2、 典型的应用场景:
配置管理
分布式锁
组成员管理
3、数据模型:
Zookeeper的数据模型是层次模型(比如文件系统),zk采用层次模型主要考虑以下几点优势:
(1)、便于表达数据间的层次关系
( 2)、便于为不同的应用分配不同的命名空间(namespace)
zk的每个节点称为znode,不同于文件系统每个节点都可以保存数据,每个节点都有一个版本(version),版本从0开始计数
4、 znode分类:
持久性znode(PERSISTENT):即使发生zk集群宕机或者client宕机也不会丢失
临时行znode(EPHEMERMAL):client宕机或者client超过timeout没有给zk集群发送消息,这样的节点就会消失
znode的节点也可以是顺序性的,每个顺序性的znode节点是关联一个单调递增的整数,这个单调递增的整数是znode的节点的后缀
持久顺序性的znode节点(PERSISTENT_SEQUENTIAL):除了具备持久性znode节点的特性外,znode的名字具备顺序性
临时顺序性的znode节点(EPHEMERMAL_SEQUENTIAL):除了具备临时性znode节点的特性外,znode的名字具备顺序性
二、 Leader选举
1、Zookeeper节点有4种状态
LOOKING:寻找Leader状态,处于该状态需要进入选举流程
LEADING:领导者状态,处于该状态的节点说明是角色已经是Leader
FOLLOWING:跟随者状态,表示Leader已经选举出来,当前节点角色是follower
OBSERVER:观察者状态,表明当前节点角色是observer
2、事务ID
ZooKeeper状态的每次变化都接收一个ZXID(ZooKeeper事务id)形式的标记。ZXID是一个64位的数字,由Leader统一分配,全局唯一,不断递增。
ZXID展示了所有的ZooKeeper的变更顺序。每次变更会有一个唯一的zxid,如果zxid1小于zxid2说明zxid1在zxid2之前发生。
3、Zookeeper集群初始化启动时Leader选举(以三台实例为例)
在集群初始化阶段,当有一台服务器ZK1启动时,其单独无法进行和完成Leader选举,当第二台服务器ZK2启动时,此时两台机器可以相互通信,每台机器都试图找到Leader,于是进入Leader选举过程。选举过程开始,过程如下:
(1) 每个Server发出一个投票。由于是初始情况,ZK1和ZK2都会将自己作为Leader服务器来进行投票,每次投票会包含所推举的服务器的myid和ZXID,使用(myid, ZXID)来表示,此时ZK1的投票为(1, 0),ZK2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
(2) 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。
(3) 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行比较,规则如下
· 优先检查ZXID。ZXID比较大的服务器优先作为Leader。
· 如果ZXID相同,那么就比较myid。myid较大的服务器作为Leader服务器。
对于ZK1而言,它的投票是(1, 0),接收ZK2的投票为(2, 0),首先会比较两者的ZXID,均为0,再比较myid,此时ZK2的myid最大,于是ZK2胜。ZK1更新自己的投票为(2, 0),并将投票重新发送给ZK2。
(4) 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于ZK1、ZK2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出ZK2作为Leader。
(5) 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为Leader)
三、 zk的ACL权限控制
1、ACL 权限控制
使用:scheme:id:perm 来标识,主要涵盖 3 个方面:
权限模式(Scheme):授权的策略
授权对象(ID):授权的对象 【比如ip/auth/world】
权限(Permission):授予的权限 [比如cdrwa]
‼️【重要】其特性如下:
ZooKeeper的权限控制是基于每个znode节点的,需要对每个节点设置权限
每个znode支持设置多种权限控制方案和多个权限
子节点不会继承父节点的权限,客户端无权访问某节点,但可能可以访问它的子节点
2、scheme 采用何种方式授权
world:默认方式,相当于全部都能访问
auth:代表已经认证通过的用户(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户)
digest:即用户名:密码这种方式认证,这也是业务系统中最常用的。用 username:password 字符串来产生一个MD5串,然后该串被用来作为ACL ID。认证是通过明文发送username:password 来进行的,当用在ACL时,表达式为username:base64 ,base64是password的SHA1摘要的编码。
ip:使用客户端的主机IP作为ACL ID 。这个ACL表达式的格式为addr/bits ,此时addr中的有效位与客户端addr中的有效位进行比对。
比如在根目录“/”,针对用户mxy授予所有的权限: setAcl / auth:mxy:cdrwa
比如在根目录“/”,针对IP“127.0.0.1”授予所有的权限:setAcl / ip:127.0.0.1:cdrwa
比如在根目录“/”,针对所有的用户授予所有的权限:setAcl / world:cdrwa
3、permission 授予什么权限
CREATE、READ、WRITE、DELETE、ADMIN 也就是 增、删、改、查、管理权限,这5种权限简写为crwda
详细的如下:
CREATE c 可以创建子节点
DELETE d 可以删除子节点(仅下一级节点)
READ r 可以读取节点数据及显示子节点列表
WRITE w 可以设置节点数据
ADMIN a 可以设置节点访问控制列表权限
⚠️【注意】:这5种权限中,delete是指对子节点的删除权限,其它4种权限指对自身节点的操作权限
4、ACL 相关命令
getAcl getAcl
setAcl setAcl
addauth addauth
四、 zk实现master-worker协同
1、 什么是master-worker架构:
master-worker架构,其中有一个master和多个worker,master负责监听woker状态,并为worker分配任务。
1、任何时刻系统中只能有一个master,多个master会导致脑裂现象
2、系统中除了处于active状态的mater还有一个backup master,如果active master宕机,backup master 很快进入active状态
3、master实时监听worker状态,能够实时收到worker成员的变化通知。master在收到worker成员有变化的时候,通常进行任务重新分配
2、zk在其他分布式服务中的应用
1、Hbase:其采用的是master-worker架构。HMBase是系统中的master,HRegiionServer是系统中的worker。HMBase监控HBase cluser中的worker变化,把region分配给HregionServer。系统中只有一个HMaster处于active状态,其他处于备用状态
2:kafka:一个kafka集群有多个broker,这些broker是系统的worker。其broker会竞选一个controller,这个controller就是系统中的master,负责把topic partition分配给其他broker。
五、 zk架构解析
zk可以分为单机模式和集群模式(一般是3台集群,保证高可用,如果是2台虽然可以搭建master-worker但是不能保证高可用,因为当master挂掉,选举的数是1小于2的大多数)。集群模式下,一个leader节点,多个follower节点。leader节点可以处理读写请求,follower在接到写请求时会把请求转发给leader来处理。
session机制:zk客户端和zk集群中的某个节点创建一个session。客户端可以主动关闭session,如果超zk节点超过timeout没有收到客户端的心跳消息的话,zk节点会关闭session。另外zk客户端如果发现连接的zk出错,会主动和其他zk节点建立连接。
zk集群时,配置文件的server列表 例如:server.1=127.0.0.1:3333:3334 ,其中3333用于集群通信,3334用于leader选举。
【重点】:zk为了保证数据一直性采用的是线性化写入的方式,这点类似于mysql的serialize隔离级别,并发非常低。
【注意】zk的API是wait-free机制(一个操作是否执行完不影响另外操作的执行),这个跟传统的锁不一致,zk不直接提供锁机制,但是借助API可以实现加锁的功能(比如创建节点)
【注意】Zookeeper不适合存储大量的数据:
1. 设计方面:ZooKeeper需要把所有的数据(它的data tree)加载到内存中。这就决定了ZooKeeper存储的数据量受内存的限制。这一点和基于内存KV存储Redis比较像。
2. 工程方面:ZooKeeper的设计目标是为协同服务提供数据存储,数据的高可用性和性能是最重要的系统指标,处理大数量不是ZooKeeper的首要目标
六、zookeeper 开发
Apache Curator是Apache Zookeeper的Java客户端。其简化Zookeeper客户端的使用,并为分布式协同服务提供高质量的实现,Curator最初是Netflix研发,后来捐献给Apache。
Curator 技术栈:
Client:封装来zookeeper类,管理和ZK集群的连接,并提供类重建连接机制。
Framework:为所有的zookeeper操作提供类重试机制,对外提供一个Fluent风格的API
Recipes:使用Framework实现类大量的Zookeeper协同服务。
Extensions:扩展模块。
一、zk在服务发现方面的应用:主要应用于微服务架构/分布式架构/面向服务架构的场景。在这些场景下,一个服务通常需要松耦合的多个协同的协同才能完成。服务发现就是让服务发现相关的服务。服务发现需要提供一下功能:
1、服务注册
2、服务实例的获取
3、服务变化的通知机制
curator有一个扩展叫做curator-x-discovery。其基于zk实现服务发现功能。curator-x-discovery设计如下:
使用一个base path作为整个服务发现的根目录。在这个根目录下是各个服务的目录,服务目录下是服务实例,实例的数据是JSON虚拟化的内容。服务实例对应的节点可以像普通的zk节点一样设置持久性、临时性、顺序性。
4、zk-curator-x-discory设计:
5、curator-x-discovery API设计如下(主要就是3个Java类)
zk-curator-x-discovery-接口设计:
ServiceDiscovery: 提供服务注册是对zk节点的更新操作,服务发现是对zk节点的读取操作,最核心的类,包含ServiceProvider和ServiceCache实例的构建。
ServiceProvider: 其封装ProviderStraegy和InstanceProvider,InstanceProvider的数据来自zk在本地的一个缓存。ProviderStraegy提供服务三种发现策略,比如轮训、随机、粘性。
ServiceCache: 将在zk中的服务数据缓存至本地,并监听服务变化,实时更新缓存。
⚠️【注意】这三个类在使用时都需要先调用start(),在使用完之后调用close,释放自己创建的资源,其不会释放上游资源,比如ServiceDiscory的close不会调用CuratorFramework的close方法。
七、Paxos算法
ZK采用Zab算法保证保证分布式服务的CP,Zab算法是Paxos算法的变体,Paxos算法解决的是一个分布式系统如何就某个值(决议)达成一致。一个典型的场景是,在一个分布式数据库系统中,如果各个节点的初始状态一致,每个节点执行相同的操作序列,那么他们最后能够得到一个一致的状态。为了保证每个节点执行相同的命令序列,需要在每一条指令上执行一个“一致性算法”以保证每个节点看到的指令一致。zookeeper使用的zab算法是该算法的一个实现。在Paxos算法中,有三种角色:Proposer (提议者),Acceptor(接受者),Learners(记录员)
Proposer提议者:(发送prepare和accept请求)只要Proposer发的提案Propose被半数以上的Acceptor接受,Proposer就认为该提案例的value被选定了。
Acceptor接受者:(处理prepare和accept请求)只要Acceptor接受了某个提案,Acceptor就认为该提案例的value被选定了
Learner记录员:(获取Paxos算法决定的结果)Acceptor告诉Learner哪个value就是提议者的提案被选定,Learner就认为哪个value被选定。
Paxos算法分为两个阶段,具体如下:
阶段一 (准leader 确定 ):
(a) Proposer 选择一个提案编号 N,然后向半数以上的Acceptor 发送编号为 N 的 Prepare 请求。
(b) 如果一个 Acceptor 收到一个编号为 N 的 Prepare 请求,且 N 大于该 Acceptor 已经响应过的所有 Prepare 请求的编号,那么它就会将它已经接受过的编号最大的提案(如果有的话)作为响 应反馈给 Proposer,同时该Acceptor 承诺不再接受任何编号小于 N 的提案。
阶段二 (leader 确认):
(a) 如果 Proposer 收到半数以上 Acceptor 对其发出的编号为 N 的 Prepare 请求的响应,那么它就会发送一个针对[N,V]提案的 Accept 请求给半数以上的 Acceptor。注意:V 就是收到的响应中编号最大的提案的 value ,如果响应中不包含任何提案,那么V 就由 Proposer 自己决定。
(b) 如果 Acceptor 收到一个针对编号为 N 的提案的 Accept 请求,只要该 Acceptor 没有对编号
大于 N 的 Prepare 请求做出过响应,它就接受该提案。
Paxos算法伪代码
Paxos算法伪代码描述
使用paxos算法实现一致性的有redis哓兵选举master算法(采用的是paxos算法的变体),google的分布式锁Chubby系统,zk选leader机制(Zab算法也是Paxos算法的变体)等
八、协同服务领域,对比Google分布式锁Chubby系统
Chubby架构:
一个Chubby集群叫做一个cell,由多个replica实例组成,其中一个replica是整个cell的master。所有的读写请求只能通过master来处理。客户端和Chubby服务端建立连接后,客户端会维持一个保证数据一致性的cache。Chubby系统采用的是和zk一样的层次数据模型(文件系统模型)
Chubby API:
Open(): 使用唯一路径明访问文件的API,其他API都使用一个文件句柄。
Close():关闭文件句柄。
Poison():取消文件句柄正在进程的操作,主要用于多线程场景。
文件操作API:getContentsAndStat()、SetContent()、Delete()
锁API:Acquire()、TryAcquire()、Release()、GetSequencer()、SetSequencer()、CheckSequencer()
Chubby 和 Zookeeper对比:
相同之处:
1、Chubby的文件相当于Zookeeper的znode,都是用来存储少量的数据,不适合存储较大的数据量。
2、都只提供文件的全部读取或者写入。
3、都提供类似Linux文件系统的API。
4、都提供数据更新的通知机制,Chubby的Event机制,ZK的watcher机制。
5、写操作都是通过Leader/master来进行。
6、都支持永久性和临时性数据。
7、都使用副本机制(复制状态)来做容错。
不同之处:
1、Chubby内置分布式锁的支持(Chubby主要就是做这件事的),zk本身不提供锁,但是可以基于znode实现锁机制。
2、Chubby读写操作都必须通过master来执行(优点是数据一致性高,缺点是并发下降,没有利用多实例的优点),ZK读操作可以通过任意的节点来执行(数据一致性弱一些,可能读到久数据)。
3、Chubby使用Paxos数据一致性协议,ZK采用的是Zab数据一致性协议(Paxos的变体)。
4、Chubby提供保证数据一致性的cache(比如更新操作,Chubby先通知所有客户端更新cache后,才进行更新操作,然后返回操作结果),而ZK不执行cache,但是可以通过watcher机制实现Cache(zk是先更新,再通知client的watcher)。
九、系统服务领域对比Etcd
1、Etcd采用的是Raft算法
Raft 是一个共识算法(consensus algorithm),所谓共识,就是多个节点对某个事情达成一致的看法,即使是在部分节点故障、网络延时、网络分割的情况下。在分布式系统中,共识算法更多用于提高系统的容错性,比如分布式存储中的复制集(replication)。
Raft协议中,一个节点任一时刻处于以下三个状态之一:leader、follower、candidate;所有节点启动时都是follower状态;在一段时间内如果没有收到来自leader的心跳,从follower切换到candidate,发起选举;如果收到majority的造成票(含自己的一票)则切换到leader状态;如果发现其他节点的状态比自己更新,则主动切换到follower。
系统中最多只有一个leader,如果在一段时间里发现没有leader,则大家通过选举-投票选出leader。leader会不停的给follower发心跳消息,表明自己的存活状态。如果leader故障,那么follower会转换成candidate,重新选出leader。
raft将共识问题分解成两个相对独立的问题,leader election,log replication。流程是先选举出leader,然后leader负责复制、提交log(log中包含command);为了在任何异常情况下系统不出错,即满足safety属性,对leader election,log replication两个子问题有诸多约束
(1)、leader election约束:
获得多数赞成票
同一任期内最多只能投一票,先来先得
选举人必须比自己知道的更多(比较term,log index,比如Mongo/Redis故障转移时master选举机制)
(2)、log replication约束:
一个log被复制到大多数节点,就是committed,保证不会回滚【这点分布式事物不同的是,分布式事物是原子性的,全部成功或者全部失败,如果提交后部分成功,会通过coorinator进行回滚】
leader一定包含最新的committed log,因此leader只会追加日志,不会删除覆盖日志
不同节点,某个位置上日志相同,那么这个位置之前的所有日志一定是相同的
2、Etcd服务介绍
etcd是一个高可用的分布式KV系统,可以用来实现各种分布式协同服务。etcd采用一致性算法Raft,基于Go语言实现。目前火热的K8s的服务发现和配置信息管理采用的是Etcd中间件做协同服务。tcd使用的是bbolt存储引擎,会将数据持久化磁盘,而Zookeeper需要将所有的数据都要加载至内存中,所以内存是ZK的瓶颈
etcd的数据模型是KV模型,所有的key构成了一个扁平的命名空间,所有的key通过字典排序。整个etcd的KV存储维护一个递增的64位整数。etcd使用这个整数位为每一次KV更新分配一个revision。每一个key可以有多个revision。每一次更新操作都会生成一个新的revision。删除操作会生成一个tombstone的新的revision。如果进行了compaction,etcd会对compaction revision之前的key-value进行清理。整个KV上最新的一次更新操作的revision叫做整个KV的revision。其中createVersion是创建key的version,modRevision是更新key的revision,version是key的版本号。比如获取key值数据:(kvs:[{
"key": "Zm9v",
"create_revision": "9030",
"mod_revision": "9070",
"version": "41",
"value": "YmFy"
}])。
可以把etcd看作一个状态机。etcd的状态是所有的key-value,revision是状态编号,每一个状态转换是若干个key的更新操作。
etcd使用bbolt进行KV的存储。bbolt使用持久化的B+tree保存key-value。三元组(major、sub、type)是B+tree的key,major是revision,sub用来区别一次更新中的key,type保存可选的特殊值(例如type为t代表这个三元组对应一个tombstone),这样做的目的是为了加速某一个revision上的范围查找。另外etcd还维护一个in-memory的B-tree的索引,这个索引中的key是key-value中的key。
reference:
【Raft算法,很棒的动画展示】:http://thesecretlivesofdata.com/raft/