ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中数据一致性管理问题。Zookeeper=文件系统+监听通知机制。分布式应用程序基于Zookeeper实现以下功能:
- 统一命名服务
- 分布式注册中心
- 分布式锁
- 数据发布/订阅
- 集群管理
- 分布式应用配置管理
- 分布式队列
为什么要用Zookeeper?
由于应用规模的扩张,高并发的请求导致单体应用无法满足服务要求,因此产生了集群化和分布式应用,为了协调分布式应用服务和解决分布式应用数据一致性管理问题。Zookeeper提供了类似文件系统的数据结构以及监听通知机制,来完成分布式应用程序协调服务。ZooKeeper 的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
Zookeeper保证了哪些一致性特性?
全局数据一致性:集群中每个服务器保存一份相同的数据副本,client无论连接到哪个服务器,展示的数据都是一致的,这是最重要的特征;
顺序一致性:包括全局有序和偏序两种;
- 全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;2. 偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。
原子性:一次数据更新要么成功(半数以上节点成功),要么失败,不存在中间状态;
单一视图
可靠性:如果消息被其中一台服务器接受,那么将被所有的服务器接受;
实时性(最终一致性):Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息;
CP
Zookeeper提供了什么?
- 文件系统
- 监听通知机制
文件系统
Zookeeper维护一个类似文件系统的树形目录数据结构,每个子目录项被称为znode(目录节点),和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。而这些数据都是存储在内存中的, Zookeeper是一个基于内存的小型数据库。
注意:Zookeeper是以节点组织数据的,没有相对路径这么一说,所有的节点一定是以 / 开头;
四种类型的znode
PERSISTENT(持久化目录节点):客户端与zookeeper断开连接后,该节点依旧存在;
PERSISTENT_SEQUENTIAL(持久化顺序编号目录节点)(-s):客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号;
EPHEMERAL(临时目录节点)(-e):客户端与zookeeper断开连接后,该节点被删除;
EPHEMERAL_SEQUENTIAL(临时顺序编号目录节点)(-e -s):客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号;
容器目录节点(-c):容器节点主要用来容纳子节点,如果没有给其创建子节点,容器节点表现和持久化节点一样,如 果给容器节点创建了子节点,后续又把子节点清空,容器节点也会被zookeeper删除。
注意:临时节点不能创建子节点;
znode的结构
- stat:此为状态信息, 描述该znode的版本, 权限等信息;
- cZxid:创建znode的事务ID(Zxid的值)。- mZxid:最后修改znode的事务ID。- pZxid:最后添加或删除子节点的事务ID(子节点列表发生变化才会发生改变)。- ctime:znode创建时间。- mtime:znode最近修改时间。- dataVersion:znode的当前数据版本。- cversion:znode的子节点结果集版本(一个节点的子节点增加、删除都会影响这个 版本)。- aclVersion:表示对此znode的acl版本。- ephemeralOwner:znode是临时znode时,表示znode所有者的 session ID。 如果 znode不是临时znode,则该字段设置为零。- dataLength:znode数据字段的长度。- numChildren:znode的子znode的数量。
- data:与该znode关联的数据
- children:该znode下的子节点
监听通知机制(Watcher)
Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher 通知状态和事件类型做出业务上的改变。客户端注册监听到指定节点上,当该节点状态发生改变时,服务器就会通知监听了该节点的客户端。(单次监听,需要重复注册)
过程:客户端向服务端注册Watcher、服务端事件发生触发Watcher、客户端回调Watcher得到触发事件情况
监听以下7种事件类型变化
- None:连接建立事件
- NodeCreated:节点创建
- NodeDeleted:节点删除
- NodeDataChanged:节点数据内容变化
- NodeChildrenChanged:子节点列表变化
- DataWatchRemoved:节点监听被移除
- ChildWatchRemoved:子节点监听被移除
Watcher机制特点
- 一次性触发:事件发生触发监听,一个watcher event就会被发送到设置监听的客户端,这种效果是一次性的,后续再次发生同样的事件,不会再次触发,如果需要重复监听就需要重复注册(一般在回调函数中注册)。这样的设计有效的减轻了服务端的压力,不然对于更新非常频繁的节点,服务端会不断的向客户端发送事件通知,无论对于网络还是服务端的压力都非常大;
- 事件封装:使用WatchedEvent对象来封装服务端事件并传递。WatchedEvent包含了每一个事件的三个基本属性:通知状态(keeperState),事件类型(EventType)和节点路径(path);
- Watcher event异步发送:watcher的通知事件从服务端发送到客户端是异步的。
- 先注册再触发:Zookeeper中的watcher机制,必须客户端先去服务端注册监听,这样事件发生才会触发监听,通知给客户端。
基于Curator实现事件监听(cache)
- NodeCache(监听注册节点)
- PathChildrenCache(监听注册节点的子节点)
- TreeCache(监听注册节点和其子节点)
ACL(access control list)权限控制
Zookeeper提供了ACL访问控制列表。ACL 权限可以针对节点设置相关读写等权限,保障数据安全性。
ACL特性
- zookeeper的权限控制是基于每个znode节点的,需要对每个节点设置权限
- 每个znode支持多种权限控制方案和多个权限
- 子节点不会继承父节点的权限,客户端无权访问某节点,但可能可以访问它的子节点
权限模式(schema)
授权的策略,zookeeper服务器进行权限验证的方式,采用的某种权限机制,包括 world、auth、digest、ip、super 几种。
权限模式
描述
world
只有一个用户:anyone,代表登录zookeeper的所有人(默认)
ip
对客户端使用ip地址认证
auth
使用已添加的用户认证,需先创建用户
digest
使用"用户名:密码"方式认证,密码需要用加密后的密文
super
超级用户
授权对象(id)
授权的对象,代表允许访问的用户(由授权策略决定)
权限信息(permission)
授予的权限,由 cdrwa 组成,其中每个字母代表支持不同权限,delete是指对子节点的删除权限,其他4种权限是指对自身节点的操作权限。
权限信息
ACL简写
描述
create
c
可以创建子节点
delete
d
可以删除子节点(仅下一级节点)
read
r
可以读取节点数据及显示子节点列表
write
w
可以设置节点数据
admin
a
可以设置节点访问控制列表权限
ACL权限控制命令
通过
acl = [schema:id:permissions]
来构成权限列表。
命令
使用方式
描述
getAcl
getAcl [-s] path
读取ACL权限
setAcl
setAcl [-s] [-v version] [-R] path acl
设置ACL权限
addauth
addauth schema auth
添加认证用户
Zookeeper的数据持久化
Zookeeper的数据是存储在内存中的,数据持久化是将数据保持在磁盘中。持久化两种方式,一种是记录事务日志,一种是快照方式。
记录事务日志
事务日志的存放地址通过zoo.cfg配置文件中的dataDir来指定。记录事务日志磁盘会进行IO操作,事务日志的不断增多会触发磁盘为文件开辟新的磁盘块,所以为了提升磁盘的效率,可以在创建文件的时候就向操作系统申请一块大一点的磁盘块,通过参数zookeeper.preAllocSize配置。
快照方式
Curator(Java API)
Curator是Apache ZooKeeper的Java/JVM客户端框架,Curator帮助我们在其基础上进行封装、实现一些开发细节,包括接连重连、反复注册Watcher和NodeExistsException等。目前已经作为Apache的顶级项目出现,是最流行的Zookeeper客户端之一。从编码风格上来讲,它提供了基于Fluent的编程风格支持。
为什么使用Curator?
zookeeper提供的原生API操作过于烦琐,curator框架是对zookeeper提供的原生API进行了封装,提供了更高级的API接口,使客户端程序员使用zookeeper更加容易及高效。
版本
目前Curator有2.x.x和3.x.x两个系列的版本,支持不同版本的Zookeeper。
- Curator 2.x.x兼容Zookeeper的3.4.x和3.5.x
- Curator 3.x.x和4.x只兼容Zookeeper 3.5.x
统一命名服务(文件系统)
在分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等——这些我们都可以统称它们为名字。ZooKeeper 提供的命名服务功能能够帮助应⽤系统通过⼀个资源引⽤的⽅式来实现对资源的定位与使⽤。
- RPC中的服务列表:使⽤命名服务,客户端应⽤能够根据指定名字来获取资源的实体、服务地址和提供者的信息等。
- 分布式全局唯一ID:在分布式调度系统中可以使用zookeeper实现统一命名服务,以获得类似于UUID的全局唯一名称。使用zookeeper创建顺序节点时,成功创建的每个节点都会返回一个编号,使用该编号以及给定的名称即可生成具有特定含义的统一名称。
UUID的缺点:过长、含义不明确、无序。
分布式锁(文件系统+监听通知机制)
某个应用进程想要获得锁时,需要在zookeeper某个节点下创建临时顺序节点,序号最小的节点先获得锁,其余节点监听比自己小的上一个节点的状态,当获得锁的应用进程执行完业务逻辑后释放锁并删除节点,监听他的节点会收到通知并检查自己是否是当前节点中最小的节点,如果是则获取锁。分布式锁保证同一时刻只有一个应用进程访问共享资源,分布式锁是相对于JVM的。
问题
解决方法
锁无法释放
Zookeeper可以有效的解决锁无法释放的问题,因为在创建锁的时候,客户端会在ZK中创建一个临时节点,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会自动删除掉,其他客户端就可以再次获得锁。如果服务出现错误导致锁无法释放,可设置超时时间。
非阻塞锁
Zookeeper可以实现阻塞的锁,客户端可以通过在ZK中创建顺序节点,并且在节点上绑定监听器,一旦节点有变化,Zookeeper会通知客户端,客户端可以检查自己创建的节点是不是当前所有节点中序号最小的,如果是,那么自己就获取到锁,便可以执行业务逻辑了。非阻塞锁就是没有获得锁的节点之间进行其他业务逻辑,不再等待;
不可重入
Zookeeper也可以有效的解决不可重入的问题,客户端在创建节点的时候,把当前客户端的主机信息和线程信息直接写入到节点中,下次想要获取锁的时候和当前最小的节点中的数据比对一下就可以了。如果和自己的信息一样,那么自己直接获取到锁,如果不一样就再创建一个临时的顺序节点,参与排队。
单点问题
Zookeeper可以有效的解决单点问题,ZK是集群部署的,只要集群中有半数以上的机器存活,就可以对外提供服务。
公平性问题
Zookeeper可以解决公平锁问题,客户端在ZK中创建的临时节点是有序的,每次锁被释放时,ZK可以通知最小节点来获取锁,保证了公平。
一致性问题
Zookeeper是一个保证了弱一致性即最终一致性的分布式组件。
基于Curator实现分布式锁
InterProcessMutex
:分布式可重入排它锁
InterProcessSemaphoreMutex
:分布式排它锁
InterProcessReadWriteLock
:分布式读写锁
InterProcessMultiLock
:将多个锁作为单个实体管理的容器
获取锁:
interProcessMutex.acquire()
释放锁:
interProcessMutex.release()
集群管理(文件系统+监听通知机制)
Zookeeper通过集群管理对其他应用集群进行协调服务,所谓集群管理无在乎两点:监控集群服务器的状态、对集群进行操作和控制。
监控集群服务器的状态
- 快速的统计出当前生产环境下一共有多少台机器
- 快速的获取到机器上下线的情况
- 实时监控集群中每台主机的运行时状态
所有服务器约定在父目录下创建临时目录节点,然后监听父目录节点的子节点变化消息。一旦有机器挂掉,该机器与 zookeeper的连接断开,其所创建的临时目录节点被删除,所有其他机器都收到通知;添加机器类似。
对集群进行操作和控制
- 负载均衡:所有机器在zookeeper中创建自己的目录节点,zookeeper根据负载均衡算法将任务分发到各个机器节点中处理
- 集群master选举:多个客户端请求创建同一个临时节点,那么最终一定只有一个客户端请求能够创建成功,利用这个特性,就能很容易的在分布式环境中进行master选举了。
- 分布式锁:保证同一时刻只有一个应用程序对共享资源的访问,应用程序在zookeeper中创建临时顺序节点,每次只有序号最小的获得锁。
分布式应用配置管理(数据发布/订阅)(文件系统+监听通知机制)
数据发布/订阅(Publish/Subscribe)系统,即所谓的配置中⼼,顾名思义就是发布者将数据发布到ZooKeeper的⼀个或⼀系列节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。
为什么需要配置中心?
在微服务架构中应用程序在启动和运行的时候往往需要读取一些配置信息,同一份程序在不同的环境(开发,测试,生产)经常需要有不同的 配置(如不同的数据中心配置),所以需要有多版本的集群配置管理来进行服务治理。而且对于集群中项目的部分配置是相同的,如果每次更改配置都需要到项目中的配置文件中更改然后再重新部署会比较繁琐,所以用配置中心可以把公共配置进行抽取维护,配置更新后应用不需要重新部署。
zookeeper实现发布/订阅的设计模式
- 推模式(Push):服务端主动将数据更新发送给所有订阅的客户端
- 拉模式(Pull):客户端主动发起请求来获取最新数据,通常客户端都采用定时轮询拉取的方式
Zookeeper采用的是推拉相结合的方式,客户端向服务端注册自己需要关注的节点,一旦该节点的数据发生变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知后,需要主动到服务端获取最新的数据。
Zookeeper集群
Zookeeper集群的目的是为了保证系统的高可用性,高性能,使系统能承载更多的客户端连接所专门提供的机制;组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 ZAB 协议(ZooKeeper Atomic Broadcast)来保持分布式数据的一致性。
集群的作用
- 读写分离:Leader服务器作为主服务器提供写服务,其他的 Follower 服务器通过异步复制的方式获取 Leader 服务器最新的数据提供读服务。
- 提高可用性:部分节点故障不会影响整个服务集群,主从自动切换,只要有半数以上节点存活,Zookeeper集群就能正常服务。
为什么zookeeper适合安装奇数台服务器?
因为zookeeper集群只要有半数以上节点存活就能正常服务的机制,也就是说假如部署3台服务器做集群,最大允许宕机1台服务器,如果部署4台服务器做集群,也是最大允许宕机1台服务器,所以2n和2n-1的容忍度是一样的,都是n-1,因此不必增加一台不必要的服务器。
三种类型角色
- **Leader(领导者): **处理所有的事务请求(写请求),可以处理读请求,集群中只能有一个Leader - 事务请求的唯一调度和处理者,保证集群事务处理的顺序性。Zookeeper中所有事务操作都是由leader服务器进行处理。- 集群内部服务器的调用者。- 接受所有的Follower的提案请求并统一协调发起提案投票,负责与所有Follower进行内部数据交换(同步)。
- Follower(跟随者):只能处理读请求,同时作为 Leader的候选节点,即如果Leader宕机,Follower节点 要参与到新的Leader选举中,有可能成为新的Leader节点。 - 处理客户端的非事务请求,并转发事务请求给Leader服务器。- 参与事务请求的同步提交投票。同时与Leader进行数据交换(同步)。- 参与Leader选举投票。
- Observer(观察者):只能处理读请求。不能参与选举。在不影响写性能的情况下提升集群的读性能。此角色于 ZooKeeper3.3 系列新增的角色。 - 处理客户端的非事务请求,并转发事务请求给Leader服务器。- 不参与选举过程中的投票,也不参与“过半写成功”策略。
集群中服务器状态
- LOOKING :寻找 Leader。
- LEADING :Leader 状态,对应的节点为 Leader。
- FOLLOWING :Follower 状态,对应的节点为 Follower。
- OBSERVING :Observer 状态,对应节点为 Observer,该节点不参与 Leader 选举。
集群阶段
- Leader election(选举阶段):节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。
- Discovery(发现阶段):在这个阶段,followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。
- Synchronization(同步阶段):同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后准 leader 才会成为真正的 leader。
- Broadcast(广播阶段):到了这个阶段,ZooKeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。
Zookeeper集群模式安装
wget https://downloads.apache.org/zookeeper/stable/apache-zookeeper-3.6.3-bin.tar.gz
tar -zxvf apache-zookeeper-3.6.3-bin.tar.gz
cd apache-zookeeper-3.6.3-bin
cp conf/zoo_sample.cfg conf/zoo‐1.cfg
vim conf/zoo‐1.cfg
dataDir=/usr/local/data/zookeeper‐1
clientPort=2181
server.1=127.0.0.1:2001:3001:participant
server.2=127.0.0.1:2002:3002:participant
server.3=127.0.0.1:2003:3003:participant
server.4=127.0.0.1:2004:3004:observer
配置说明
- tickTime:用于配置Zookeeper中最小时间单位的长度,很多运行时的时间间隔都是使用tickTime的倍数来表示的。
- initLimit:该参数用于配置Leader服务器等待Follower启动,并完成数据同步的时间。Follower服务器再启动过程中,会与Leader建立连接并完成数据的同步,从而确定自己对外提供服务的起始状态。Leader服务器允许Follower在initLimit 时间内完成这个工作。
- syncLimit:Leader与Follower心跳检测的最大延时时间
- dataDir:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。
- clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监 听这个端口,接受客户端的访问请求。
- server.A=B:C:D:E - A 是一个数字,表示这个是第几号服务器;- B 是这个服务器的 ip 地址;如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。- C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;- D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。- E 表示如果需要添加不参与集群选举以及事务请求的过半机制的Observer节点,可以添加observer标识。
cp conf/zoo.cfg conf/zoo-1.cfg
cp conf/zoo-1.cfg conf/zoo-2.cfg
cp conf/zoo-1.cfg conf/zoo-3.cfg
cp conf/zoo-1.cfg conf/zoo-4.cfg
#修改以下参数
dataDir=/usr/local/data/zookeeper‐1
clientPort=2181
mkdir -p /usr/local/data/zookeeper‐1
echo 1 >/usr/local/data/zookeeper‐1/myid
mkdir -p /usr/local/data/zookeeper‐2
echo 2 >/usr/local/data/zookeeper‐2/myid
mkdir -p /usr/local/data/zookeeper‐3
echo 3 >/usr/local/data/zookeeper‐3/myid
mkdir -p /usr/local/data/zookeeper‐4
echo 4 >/usr/local/data/zookeeper‐4/myid
- myid:是提供应用的唯一标识,唯一标识要求是自然数。保存位置是:$dataDir/myid。
Leader选举机制
得票数超过半数的服务器就是leader
为什么Leader选举要采用过半机制?
防止集群脑裂。对于一个集群,通常多台机器会部署在不同机房,来提高这个集群的可用性。保证可用性的同时,会发生一种机房间网络线路故障,导致机房间网络不通,而集群被割裂成几个小集群。这时候子集群各自选主导致“脑裂”的情况,脑裂会带来数据一致性问题。
如部署6台机器到2个机房,每个机房3台,如果两个机房中途网络中断,每个机房都认为对方的3台服务器下线了从而自主选举,若没有过半机制,每个机房都能各自选出一个leader,当网络恢复时就会导致两个leader对外服务,如果有过半机制,每个机房最多能得3票,并不能满足过半机制6/2+1=4,所以不会选举出leader。
服务器初始化启动选举机制(部署5台服务器)
- 服务器1启动,发起一次选举。服务器1投自己一票,此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持为LOOKING;
- 服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息,此时服务器1发现服务器2的myid比自己目前投票推举的(服务器1)大,更改选票为推举服务器2。此时服务器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器1,2状态保持LOOKING;
- 服务器3启动,发起一次选举。此时服务器1和2都会更改选票为服务器3。此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3的票数已经超过半数,服务器3当选Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING;
- 服务器4启动,发起一次选举。此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING;
- 服务器5启动,同4一样当FOLLOWING。
- 和启动顺序以及myid有关
服务器运行期间无法与Leader保持连接(半数以上follower不能与leader连接或者leader宕机)
如果当前节点失联,leader仍存在,虽然当前节点会试图去选举Leader,但与其他节点交换信息时会被告知当前服务器的Leader信息,然后重新和Leader机器建立连接,并进行状态同步即可。
在集群运行其间如果有follower或observer节点宕机只要不超过半数并不会影响整个集群服务的正常运行。但如果leader宕机,将暂停对外服务,所有follower将进入LOOKING 状态,进入选举流程。基于ZAB协议的崩溃恢复模式完成。
- EPOCH大的直接胜出
- EPOCH相同,ZXID大的胜出
- ZXID相同,SID大的胜出
- SID:服务器ID。用来唯一标识一台ZooKeeper集群中的机器,每台机器不能重复,和myid一致。
- ZXID:事务ID。ZXID是一个事务ID,用来标识一次服务器状态的变更。在某一时刻,集群中的每台机器的ZXID值不一定完全一致,这和ZooKeeper服务器对于客户端"更新请求"的处理逻辑速度有关。ZXID是一个长度64位的数字,其中低32位是按照数字递增,任何数据的变更都会导致,低32位的数字简单加1。高32位是leader周期编号(EPOCH),每当选举出一个新的leader时,新的leader就从本地事物日志中取出ZXID,然后解析出高32位的周期编号,进行加1生成新的周期编号(新的epoch = 旧的epoch + 1),再将低32位的全部设置为0。这样就保证了每次新选举的leader后,保证了ZXID的唯一性而且是保证递增的。(用于保证消息的有序性)
- EPOCH:每个Leader任期的代号。没有Leader时同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加。
数据同步机制
zookeeper 的数据同步是为了保证各节点中数据的一致性,同步时涉及两个流程,一个是正常的客户端数据提交,另一个是集群某个节点宕机在恢复后的数据同步。Zookeeper使用了ZAB协议来保证各个Server之间的同步。
数据同步过程导致集群数据不一致问题
- 已经被处理的消息不能丢失:当领导者收到半数以上的跟随者的ack(回复)后,就会像集群中的跟随者发送commit命令。但是如果各个跟随者在收到commit命令前,领导者就挂了,导致剩下的服务器并没有执行到这个消息。举个例子:领导者发送commit命令后,跟随者1接收到了并写入自己的服务器,而跟随者2没有收到commit命令的时候领导者就挂了。此时跟随者1将事物成功消息发送给客户端了,那么这条被确认成功的消息就必须要同步到集群中所有的机器上。因为客户端已经收到了事物成功的消息,所以即便其他的跟随者没有收到commit命令,也必须想办法告诉其他跟随者这条消息保存成功。
- 被丢弃的消息不能再次出现:当领导者接收到事物请求后,生成事物提议(Proposal)后就挂掉了,此时集群中的跟随者还没有接收到事物提议。当领导者挂掉以后会进行重新选举,那么新的领导者是不知道该提议的存在。那么此时为了保证集群中数据一致性,该提议必须被丢弃。也就是说该提议的事物操作必须是失败的。
ZAB协议
Zookeeper主要是根据ZAB协议是实现分布式系统数据一致性。采用ZAB协议的最大目标就是建立一个高可用可扩展的分布式数据主备系统。即在任何时刻只要leader发生宕机,都能保证分布式系统数据的可靠性和最终一致性。ZAB协议中主要有两种模式,消息广播模式(同步);崩溃恢复模式(选举)
ZAB协议原理
- 发现:即要求zookeeper集群必须选择出一个leader进程,同时leader会维护一个follower可用列表。将来客户端可以和follower中的节点进行通信。
- 同步:leader要负责将本身的数据与follower完成同步,做到多副本存储。这样也是体现了CAP中高可用和分区容错。follower将队列中未处理完的请求消费完成后,写入本地事物日志中。
- 广播:leader可以接受客户端新的proposal请求,将新的proposal请求广播给所有的follower。
消息广播模式
Zookeeper集群中数据副本的传递策略就是采用消息广播模式。
具体流程
- 客户端发起一个写请求(如果请求的server是follower,该server会把请求转发到leader处理)
- Leader服务器将客户端的request请求转化为事物(proposql提案),同时为每个proposal分配一个全局唯一的ID,即ZXID。
- leader服务器与每个follower之间都有一个队列,leader将消息发送到该队列
- follower机器从队列中取出消息处理完(写入本地事物日志中)毕后,向leader服务器发送ACK确认。
- leader服务器收到半数以上的follower的ACK后,即认为可以发送commit
- leader向所有的follower服务器发送commit消息。
zookeeper采用ZAB协议的核心就是只要有一台服务器提交了proposal,就要确保所有的服务器最终都能正确提交proposal。这也是CAP/BASE最终实现一致性的一个体现。
只需要半数以上的follower发送ACK确认就可以commit,减少阻塞问题;
leader服务器与每个follower之间都有一个单独的队列进行收发消息,使用队列消息可以做到异步解耦。leader和follower之间只要往队列中发送了消息即可。如果使用同步方式容易引起阻塞。性能上要下降很多。
崩溃恢复模式
如果leader服务器发生崩溃,则zab协议要求zookeeper集群进行崩溃恢复和leader服务器选举。新选举出来的leader不能包含未提交的proposal,即新选举的leader必须都是已经提交了proposal的follower服务器节点。同时,新选举的leader节点中含有最高的ZXID。这样做的好处就是可以避免了leader服务器检查proposal的提交和丢弃工作。
崩溃场景及解决
- leader在提出proposal时未提交之前崩溃。
解决:由于ZAB协议保证新选举的leader不能存在未提交的proposal,所以对于旧leader未提交就崩溃了,新选举的leader一定不能是刚才的leader。新的leader之前并没有收到该提议的commit命令,所以新的领导者同步数据的时候只会同步自己已经commit的数据,而提议数据将会被直接丢弃。其他跟随者也会同步新的领导者,故提议数据会被丢掉。所以就实现被丢弃的消息不能再次出现这个问题。
- leader在发送commit消息之后崩溃,即消息已经发送到队列中。
解决:因为ZXID是根据每次操作由epoch和消息计数器组成的,也就是说,ZXID越大越接近领导者的操作,新的领导者的ZXID最后一个记录就是旧领导者最后一个操作。也就是说当旧领导者发送commit命令后,跟随者3执行了commit,而其他的跟随者还没来得及接受到commit命令时,领导者就挂了。那么此时跟随者3的ZXID最大。跟随者3就会变成新的领导者,那么跟随者3最后一条操作就是旧领导者最后一次操作,所以当跟随者3变成领导者后,其数据就是最新数据,他会将自己的数据同步到其他跟随者上。所以就实现已经被处理的消息不能丢失这个问题。
当领导者选举出来后,会通知所有跟随者并进行数据同步,当超过半数的跟随者回复收到领导者确认的消息后,会自动退出奔溃回复模式,然后通过消息广播模式进行消息同步。在zookeeper集群中新的leader选举成功之后,leader会将自身的提交的最大proposal的事物ZXID发送给其他的follower节点。follower节点会根据leader的消息进行回退或者是数据同步操作。最终目的要保证集群中所有节点的数据副本保持一致。
ZAB协议和Paxos算法的联系与区别
相同:
- 两者都存在类似于Leader角色,用来管理所有的Follower的运行。
- 都是等待超过半数的Follower给出正确反馈才进行事物提交。
不同:
- zab协议中事物提议都有一个epoch,而Paxos算法里叫Ballot。
- zab是用来构建高可用的主备系统,而Paxos算法是用来构建分布式一致性状态机系统的
版权归原作者 夜酱ovo 所有, 如有侵权,请联系我们删除。