分布式架构之Zookeeper

zookeeper分布式锁原理:https://my.oschina.net/u/3492343/blog/2992492

 

zookeeper的树形结构 

 

 

 

 

zookeeper节点特性

1.同级节点唯一性

2.临时节点和持久化节点

3.有序节点和无序节点

4.临时节点下不能存在子节点

集群搭建

 

server.id = ip:port:port

在zoo.cfg里面加入以下

server.1=192.168.182.128:2888:3888
server.2=192.168.182.129:2888:3888
server.3=192.168.182.130:2888:3888

192.168.182.128:2888是完成数据同步的节点 192.168.182.128:3888是选举leader的节点

自己练习的话关闭防火墙service iptables stop,生产就开放相关端口,

在data目录下创建myid(id一定要和上面id对应的ip对应)

vim /tmp/zookeeper/myid

里面加入:1或2或3

 

zoo.cfg里面的参数

1.tickTime=2000      心跳时间   

2.initLimit=10  初始化同步数据的时候10个心跳时间

3.syncLimit=5  心跳检测的最大延迟

4.dataDir = /x   同步数据存储的位置

5.clientPort=2181  客户端和服务端连接的端口

[zk: localhost:2181(CONNECTED) 1] get /mic
mic
cZxid = 0x100000002     
ctime = Tue Dec 03 21:08:23 CST 2019
mZxid = 0x100000002     
mtime = Tue Dec 03 21:08:23 CST 2019
pZxid = 0x100000002      //子节点变更以后才会产生pzxid的影响
cversion = 0     //类似乐观锁,当前节点的子节点版本号
dataVersion = 0    //当前数据内容的版本号
aclVersion = 0      //当前节点ACL变更的版本号
ephemeralOwner = 0x0   //绑定当前会话的信息
dataLength = 3      //当前数据长度
numChildren = 0     //子节点数量

通过版本号来控制并发情况下的数据修改

 

ACL (access control list)      权限控制

CREATE/READ/WRITE/DELETE/ADMIN   五种权限

 

 

watch机制

 

 zookeeper的应用场景

分布式协调服务

配置中心(aoolication.properties,数据库配置,常量配置等),这个配置中心统一维护配置

负载均衡(知道机器的状态,选举master)

分布式锁

 zookeeper主要是解决分布式环境下的服务协调问题而产生的,如果我们要去实现一个zookeeper这样的中间件,需要做什么?

1.防止单点故障

如果要防止zookeeper这个中间件的单点故障,那就势 必要做集群。而且这个集群如果要满足高性能要求的话, 还得是一个高性能高可用的集群。高性能意味着这个集 群能够分担客户端的请求流量,高可用意味着集群中的 某一个节点宕机以后,不影响整个集群的数据和继续提 供服务的可能性

结论:这个中间件需要考虑到集群,还需要分摊客户端的请求流量

2.如果要满足这样的一个高 性能集群,我们最直观的想法应该是,每个节点都能接 收到请求,并且每个节点的数据都必须要保持一致。要 实现各个节点的数据一致性,就势必要一个leader节点 负责协调和数据同步操作。这个我想大家都知道,如果 在这样一个集群中没有leader节点,每个节点都可以接收所有请求,那么这个集群的数据同步的复杂度是非常 大

结论:所以这个集群中设计到数据同步以及会存在leader节点

3.如何在这些节点中选举出leader节点,以及leader挂了以后,如何恢复?

结论:zookeeper用了基于paxos理论所衍生出来的ZAB协议

4. leader 节点如何和其他节点保证数据一致性,并且要求 是强一致的。在分布式系统中,每一个机器节点虽然都 能够明确知道自己进行的事务操作过程是成功和失败, 但是却无法直接获取其他分布式节点的操作结果。所以 当一个事务操作涉及到跨节点的时候,就需要用到分布 式事务,分布式事务的数据一致性协议有 2PC 协议和 3PC协议。

关于2PC提交

(Two Phase Commitment Protocol)当一个事务操作需要跨越多个分布式节点的时候,为了保持事务处理的ACID 特性,就需要引入一个“协调者”(TM)来统一调度所有分 布式节点的执行逻辑,这些被调度的分布式节点的执行逻辑,这些被调度的分布式节点被称为AP。 TM负责调度AP的行为,并最终决定这些AP是否要把事 务真正进行提交;因为整个事务是分为两个阶段提交,所 以叫2pc 

 

 

 

 

 

阶段一:提交事务请求(投票)
1. 事务询问 协调者向所有的参与者发送事务内容,询问是否可以执行事 务提交操作,并开始等待各参与者的响应 2. 执行事务 各个参与者节点执行事务操作,并将Undo和Redo信息记 录到事务日志中,尽量把提交过程中所有消耗时间的操作和 准备都提前完成确保后面100%成功提交事务 3. 各个参与者向协调者反馈事务询问的响应 如果各个参与者成功执行了事务操作,那么就反馈给参与者 yes的响应,表示事务可以执行;如果参与者没有成功执行 事务,就反馈给协调者 no 的响应,表示事务不可以执行, 上面这个阶段有点类似协调者组织各个参与者对一次事务 操作的投票表态过程,因此 2pc 协议的第一个阶段称为“投 票阶段”,即各参与者投票表名是否需要继续执行接下去的 事务提交操作。
阶段二:执行事务提交
在这个阶段,协调者会根据各参与者的反馈情况来决定最终是 否可以进行事务提交操作,正常情况下包含两种可能:执行事务、 中断事务

 

 

 zookeeper的集群

在zookeeper中,客户端会随机连接到zookeeper集群中的一个节点,如果是读请求,就直接从当前节点中读取数据,如果是写请求,那么请求会被转发给leader提交事务,然后leader会广播事务,只要有超过半数节点写入成功,那么写请求就会被提交(类2PC事务)

所有的事务请求必须由一个全局唯一的服务器来协调处理,这个服务器就是Leader服务器,其他的服务器就是follower。leader服务器把客户端的事务请求转化成一个事务(Proposal)提议,并把这个Proposal分发给集群中的所有follower服务器。之后leader服务器需要等待所有follower服务器的反馈,一旦超过半数的follower服务器进行了正确的反馈,那么leader就会再次向所有的follower服务器发送Commit消息,要求各个follower节点对前面的一个Proposal进行提交

集群角色

Leader 角色

Leader服务器是整个zookeeper集群的核心,主要的工作 任务有两项 1. 事物请求的唯一调度和处理者,保证集群事物处理的顺 序性 2. 集群内部各服务器的调度者

Follower 角色

Follower角色的主要职责是 1. 处理客户端非事物请求、转发事物请求给leader服务器 2. 参与事物请求 Proposal 的投票(需要半数以上服务器 通过才能通知leader commit数据; Leader发起的提案, 要求Follower投票) 3. 参与Leader选举的投票

Observer 角色

Observer 是 zookeeper3.3 开始引入的一个全新的服务器 角色,从字面来理解,该角色充当了观察者的角色。 观察zookeeper集群中的最新状态变化并将这些状态变化 同步到 observer 服务器上。Observer 的工作原理与 follower 角色基本一致,而它和 follower 角色唯一的不同 在于 observer 不参与任何形式的投票,包括事物请求 Proposal的投票和leader选举的投票。简单来说,observer 服务器只提供非事物请求服务,通常在于不影响集群事物 处理能力的前提下提升集群非事物处理的能力

集群组成

通常zookeeper是由2n+1台server组成,每个server都 知道彼此的存在。对于2n+1台server,只要有n+1台(大 多数)server可用,整个系统保持可用。我们已经了解到, 一个zookeeper集群如果要对外提供可用的服务,那么集 群中必须要有过半的机器正常工作并且彼此之间能够正常 通信,基于这个特性,如果向搭建一个能够允许 F 台机器 down 掉的集群,那么就要部署 2*F+1 台服务器构成的 zookeeper 集群。因此 3 台机器构成的 zookeeper 集群, 能够在挂掉一台机器后依然正常工作。一个 5 台机器集群 的服务,能够对 2 台机器怪调的情况下进行容灾。如果一
台由6台服务构成的集群,同样只能挂掉2台机器。因此, 5 台和 6 台在容灾能力上并没有明显优势,反而增加了网 络通信负担。系统启动时,集群中的server会选举出一台 server为Leader,其它的就作为follower(这里先不考虑 observer角色)。 之所以要满足这样一个等式,是因为一个节点要成为集群 中的 leader,需要有超过及群众过半数的节点支持,这个 涉及到leader选举算法。同时也涉及到事务请求的提交投票。

ZAB协议

ZAB(Zookeeper Atomic Broadcast) 协议是为分布式协 调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子 广播协议。在 ZooKeeper 中,主要依赖 ZAB 协议来实现 分布式数据一致性,基于该协议,ZooKeeper 实现了一种 主备模式的系统架构来保持集群中各个副本之间的数据一 致性。

zab协议介绍

ZAB协议包含两种基本模式,分别是

1.崩溃恢复

2.原子广播

当整个集群在启动时,或者当leader节点出现网络中断、崩溃等情况时,ZAB协议就会进入恢复模式并选举产生新的leader,当leader服务器选举出来后,并集群中有过半的机器和该leader节点完成数据同步后(同步指的是数据同步,用来保证集群中过半的机器能够和leader服务器的数据状态保持一致),ZAB协议就会退出恢复模式

当集群中已经有过半的follower节点完成了和leader状态同步以后,那么整个集群就进入了消息广播模式。这个时候。在leader节点正常工作时,启动 一台新的服务器加入到集群,那这个服务器会直接进入数据恢复模式,和leader节点进行数据同步。同步完成后即可正常对外提供非事务请求的处理

消息广播的实现原理

如果大家了解分布式事务的2pc和3pc协议的话(不了解 也没关系,我们后面会讲),消息广播的过程实际上是一个 简化版本的二阶段提交过程

1. leader 接收到消息请求后,将消息赋予一个全局唯一的 64位自增id,叫:zxid,通过zxid的大小比较既可以实 现因果有序这个特征

2. leader为每个follower准备了一个FIFO队列(通过TCP 协议来实现,以实现了全局有序这一个特点)将带有zxid
的消息作为一个提案(proposal)分发给所有的follower

3. 当follower接收到proposal,先把proposal写到磁盘, 写入成功以后再向leader回复一个ack

4. 当leader接收到合法数量(超过半数节点)的ACK后, leader就会向这些follower发送commit命令,同时会 在本地执行该消息

5. 当 follower 收到消息的 commit命令以后,会提交该消 息

leader 的投票过程,不需要 Observer 的 ack,也就是 Observer不需要参与投票过程,但是Observer必须要同 步 Leader 的数据从而在处理请求的时候保证数据的一致 性 

崩溃恢复(数据恢复) 

ZAB 协议的这个基于原子广播协议的消息广播过程,在正 常情况下是没有任何问题的,但是一旦 Leader 节点崩溃, 或者由于网络问题导致 Leader 服务器失去了过半的
Follower节点的联系(leader失去与过半follower节点联 系,可能是leader节点和follower节点之间产生了网络分 区,那么此时的leader不再是合法的leader了),那么就 会进入到崩溃恢复模式。在ZAB协议中,为了保证程序的 正确运行,整个恢复过程结束后需要选举出一个新的 Leader 为了使 leader 挂了后系统能正常工作,需要解决以下两 个问题

1. 已经被处理的消息不能丢失 当 leader 收到合法数量 follower 的 ACKs 后,就向 各个 follower 广播 COMMIT 命令,同时也会在本地 执行 COMMIT 并向连接的客户端返回「成功」。但是如 果在各个 follower 在收到 COMMIT 命令前 leader 就挂了,导致剩下的服务器并没有执行都这条消息。

 

 

 leader 对事务消息发起 commit 操作,但是该消息在 follower1 上执行了,但是 follower2 还没有收到 commit, 就已经挂了,而实际上客户端已经收到该事务消息处理成功的回执了。所以在zab协议下需要保证所有机器都要执 行这个事务消息 

2. 被丢弃的消息不能再次出现
当 leader 接收到消息请求生成 proposal 后就挂了,其他 follower并没有收到此proposal,因此经过恢复模式重新选了 leader 后,这条消息是被跳过的。 此时,之前挂了的 leader 重新启动并注册成了 follower,他保留了被跳过消息的 proposal 状态,与整个系统的状态是不一致的,需要将其删除。
ZAB 协议需要满足上面两种情况,就必须要设计一个 leader 选举算法:能够确保已经被 leader 提交的事务 Proposal能够提交、同时丢弃已经被跳过的事务Proposal。 针对这个要求

1. 如果 leader 选举算法能够保证新选举出来的 Leader 服 务器拥有集群中所有机器最高编号(ZXID最大)的事务 Proposal,那么就可以保证这个新选举出来的Leader一 定具有已经提交的提案。因为所有提案被 COMMIT 之 前必须有超过半数的 follower ACK,即必须有超过半数 节点的服务器的事务日志上有该提案的 proposal,因此,只要有合法数量的节点正常工作,就必然有一个节点保 存了所有被 COMMIT 消息的 proposal 状态 另外一个,zxid是64位,高32位是epoch编号,每经过 一次 Leader 选举产生一个新的 leader,新的 leader 会将 epoch 号+1,低 32 位是消息计数器,每接收到一条消息 这个值+1,新 leader选举后这个值重置为0.这样设计的好 处在于老的leader挂了以后重启,它不会被选举为leader, 因此此时它的 zxid 肯定小于当前新的 leader。当老的 leader 作为 follower 接入新的 leader 后,新的 leader 会 让它将所有的拥有旧的 epoch 号的未被 COMMIT 的 proposal 清除 

关于 ZXID
zxid,也就是事务id, 为了保证事务的顺序一致性,zookeeper 采用了递增的事 务 id 号(zxid)来标识事务。所有的提议(proposal)都 在被提出的时候加上了 zxid。实现中 zxid 是一个 64 位的 数字,它高32位是epoch(ZAB协议通过epoch编号来 区分 Leader 周期变化的策略)用来标识 leader 关系是否 改变,每次一个 leader 被选出来,它都会有一个新的 epoch=(原来的epoch+1),标识当前属于那个leader的 统治时期。低32位用于递增计数。

epoch :可以理解为当前集群所处的年代或者周期,每个 leader 就像皇帝,都有自己的年号,所以每次改朝换代, leader 变更之后,都会在前一个年代的基础上加 1 。这样 就算旧的 leader 崩 溃 恢 复 之 后 ,也 没 有 人 听 他 的 了 ,因 为 follower 只听从当前年代的 leader 的命令。 *
epoch的变化大家可以做一个简单的实验,

1. 启动一个zookeeper集群。 2. 在/tmp/zookeeper/VERSION-2 路径下会看到一个 currentEpoch文件。文件中显示的是当前的epoch 3. 把 leader 节点停机,这个时候在看 currentEpoch 会有 变化。 随着每次选举新的leader,epoch都会发生变化

leader 选举

Leader选举会分两个过程
启动的时候的leader选举、 leader崩溃的时候的的选举

服务器启动时的 leader 选举

每个节点启动的时候状态都是 LOOKING,处于观望状态, 接下来就开始进行选主流程
进行Leader选举,至少需要两台机器(具体原因前面已经 讲过了) ,我们选取3台机器组成的服务器集群为例。在集 群初始化阶段,当有一台服务器Server1启动时,它本身是 无法进行和完成Leader选举,当第二台服务器Server2启 动时,这个时候两台机器可以相互通信,每台机器都试图 找到Leader,于是进入Leader选举过程。选举过程如下
(1) 每个Server发出一个投票。由于是初始情况,Server1 和 Server2 都会将自己作为 Leader 服务器来进行投 票,每次投票会包含所推举的服务器的myid和ZXID、 epoch,使用(myid, ZXID,epoch)来表示,此时Server1 的投票为(1, 0),Server2的投票为(2, 0),然后各自将 这个投票发给集群中其他机器。
(2) 接受来自各个服务器的投票。集群的每个服务器收到 投票后,首先判断该投票的有效性,如检查是否是本 轮投票 (epoch)、是否来自LOOKING状态的服务器。
(3)处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则如下i.优先检查ZXID。ZXID比较大的服务器优先作为Leader。ii.如果ZXID相同,那么就比较myid。myid较大的服务器作为Leader服务器。 对于 Server1 而言,它的投票是(1,0),接收Server2的投票为(2,0),首先会比较两者的ZXID,均为0,再比较myid,此时Server2的myid最大,于是更新自己的投票为(2,0),然后重新投票,对于Server2而言,它不需要更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。
(4) 统计投票。每次投票后,服务器都会统计投票信息, 判断是否已经有过半机器接受到相同的投票信息,对 于Server1、Server2而言,都统计出集群中已经有两 台机器接受了(2, 0)的投票信息,此时便认为已经选出 了Leader。 (5) 改变服务器状态。一旦确定了Leader,每个服务器就 会更新自己的状态,如果是Follower,那么就变更为 FOLLOWING,如果是Leader,就变更为LEADING。

运行过程中的 leader 选举

当集群中的 leader 服务器出现宕机或者不可用的情况时, 那么整个集群将无法对外提供服务,而是进入新一轮的 Leader 选举,服务器运行期间的 Leader 选举和启动时期 的Leader选举基本过程是一致的。
(1) 变更状态。Leader挂后,余下的非Observer服务器 都会将自己的服务器状态变更为 LOOKING,然后开 始进入Leader选举过程。
(2) 每个Server会发出一个投票。在运行期间,每个服务 器上的ZXID可能不同,此时假定Server1的ZXID为 123,Server3的ZXID为122;在第一轮投票中,Server1 和Server3都会投自己,产生投票(1, 123),(3, 122), 然后各自将投票发送给集群中所有机器。接收来自各 个服务器的投票。与启动时过程相同。
(3) 处理投票。与启动时过程相同,此时,Server1将会成 为Leader。
(4) 统计投票。与启动时过程相同。
(5) 改变服务器的状态。与启动时过程相同

 事件机制

watcher特性:当数据发生变化的时候,zookeeper会产生一个watcher事件,并且会发送到客户端。但是客户端只会收到一次通知。如果后续这个节点再次发生变化,那么之前设置watcher的客户端不会再次收到消息。(watcher是一次性的操作)。可以通过循环监听去达到永久监听效果

如何注册事件机制

通过这三个操作来绑定事件:getData\Exists\getChildren

如何触发事件?凡是事务类型的操作,都会触发监听事件。create /delete /setDat

 

watcher 事件类型

None (-1), 客户端链接状态发生变化的时候,会收到 none 的事件
NodeCreated (1), 创建节点的事件。 比如 zk-persis
NodeDeleted (2), 删除节点的事件
NodeDataChanged (3), 节点数据发生变更
NodeChildrenChanged (4); 子节点被创建、被删除、会发生事件触发

posted @ 2020-01-08 12:50  MonsterZL  阅读(311)  评论(0编辑  收藏  举报