zookeeper

整合网上资料,有不足之处请指正。


Zookeeper

zookeeper是一个分布式协调服务,可以用于元数据管理、分布式锁、分布式协调、发布订阅、服务命名等。

**简短介绍zk:**它是个数据库,文件存储系统,并有监听通知机制(观察者模式)。

分布式数据一致性

附上链接:https://zhuanlan.zhihu.com/p/35616810

分布式事务

保证在多台物理机的数据库上的操作具有原子性,要么都提交,要么都回滚。

两阶段提交(2PC)

既是算法,也是协议。参与者将操作结果通知协调者,再由协调者根据所有参与者的结果决定是否提交操作或者终止操作。

  • 第一阶段:投票阶段------事务协调者给每个参与者发送prepare消息,每个参与者需要返回结果,失败则直接返回,成功则需在本地执行事务,写redo和undo日志,但不提交,响应后,等待协调者的通知。
  • 第二阶段:提交阶段------协调者收到参与者的失败消息或者超时,直接给每个参与者发送回滚消息,否则返回提交消息。参与者根据协调者指令执行对应操作,释放所有事务处理过程中使用的锁资源,并反馈事务执行结果。

缺点:

  • 同步阻塞

    所有参与节点都是事务阻塞型。

  • 单点故障

    协调者挂掉,参与者会一致阻塞下去,即使选举出新的协调者,也无法解决因为宕机导致的参与者处于阻塞的问题。

  • 数据不一致

    由于网络或者协调者自身的问题,导致在提交阶段,只有部分参与者收到了commit消息,导致整体数据不一致。

  • 存在无法解决的问题

    协调者发出commit消息后宕机,恰巧参与者此时也宕机了,即使有新的协调者被选举出,也无法知晓这条事务是否已经提交。

三阶段提交(3PC)

阶段分为:

  • Can-Commit:询问参与者是否具备执行能力。
  • Pre-Commit:如果参与者都具备执行能力,则协调者发送该请求,参与者写入redo和undo,但不提交,并响应,等待协调者后续操作。如果有一个参与者不具备执行能力或者超时,则执行事务中断。
  • Do-Commit:与2PC差不多。

在2PC的基础上有两个改动点:

  • 引入超时机制。同时在协调者和参与者都引入超时机制。
  • 将2PC的第一阶段拆分为两个阶段(canCommit,preCommit),保证在最后提交阶段之前各参与节点状态的一致。

为什么要拆分第一阶段?

​ 如果所有参与者中有一个参与者不能执行提交,而2PC在第一阶段时其余参与者已经写了undo和redo,协调者最后因为这一个参与者执行回滚,消耗很大。所以执行pre的前提是所有参与者都具备可执行条件,减少资源消耗。

2PC和3PC的区别:

​ 与2PC相比,3PC主要解决了单点故障,并减少阻塞。参与者一旦接收不到协调者的信息,会默认执行commit,而不会一直持有事务。

但是要是协调者发出的是中断请求,但由于网络原因,导致部分参与者没有接受到,那么它们会默认执行commit,所以还是会有数据不一致的情况出现,接下来就引入了Paxos算法。

Paxos算法

待研究…

zookeeper使用哪种呢?

zookeeper使用2PC来保证节点之间的数据一致性,但是由于leader需要和所有的follower交互,这样一来通信的开销会变得较大,zookeeper的性能就会下降。所以为了提升性能,采取ACK过半即可。

顺序一致性

zookeeper集群是读写分离的,当leader节点接收到消息之后,会按照请求的严格顺序一一进行处理,它会保证消息的顺序一致性。

如果消息A比消息B先到达,那么在所有的zookeeper节点中,消息A都会先于消息B到达,zookeeper保证了消息的全局顺序。

zookeeper如何保证消息的顺序?

那就是zxid

节点之间会通过发送proposal(提案)来进行通信、数据同步,proposal中就会带上zxid和具体的数据(Message)。

而zxid由两部分组成:

  • epoch(高32位):leader的迭代版本,每个leader的epoch都不一样
  • counter(低32位):计数器,来一条消息就会自增

这是唯一zxid生成算法的底层实现,由于每个leader所使用的epoch都是唯一的,而不同的消息在相同的epoch中,counter值是不同的,所以所有的提案在zookeeper集群中都有唯一的zxid。

ZAB协议

参考链接https://www.jianshu.com/p/b82aaed5389c

即原子广播协议,支持崩溃恢复的原子广播协议,主要用于实现数据一致性。

zookeeper可以在恢复广播模式之间切换,当leader正常时,进入广播模式,不正常时,进入恢复模式。

三种角色

  • follower
  • leader
  • Observer

本质上follower和observer的功能是一样的,都为zookeeper提供了横向扩展的能力,使其能够抗住更多的并发,但区别在于observer不参与投票选举。而且只有leader处理写请求,follower和observer不会处理,当写请求到达follower或者observer时,会将写请求转发给leader

observer到底是干什么的?有什么作用呢?

observer的设计是希望能动态扩展zookeeper集群又不会降低写性能。随着投票的成员增多,写入性能会有所下降,因为投票需要集群中至少一半的节点投票达成一致,因此投票成本会增加。而observer只会听取投票结果,不参与投票。由于这一点,我们可以增加任意数量的observer,同时不会影响我们集群的性能。也就说他不会影响集群的可用性,对用户来说,相比follower,可以用observer更能通过不可靠的网络连接。

启动流程

  • 统一由QuorumPeerMain作为启动类,加载解析zoo.cfg配置文件
  • 初始化核心类:ServerCnxnFactory(IO操作)、FileTxnSnapLog(事务日志及快照文件操作)、QuorumPeer实例(代表zk集群中的一台机器)、ZKDatabase(内存数据库)等
  • 加载本地快照文件及事务日志,恢复内存数据
  • 完成leader选举,节点间通过一系列投票,选举产生最合适的机器成为leader,同时其余机器成为follower或是observer。关于选举算法,就是集群中哪个机器处理的数据越新(通过ZXID来比较,ZXID越大,数据越新),其越有可能被选中
  • 完成leader与learner间的数据同步:集群中节点角色确定后,leader会重新加载本地快照及日志文件,以此作为基准数据,再结合各个learner的本地提交数据,leader再确定需要给具体learner回滚哪些数据及同步哪些数据
  • 当leader收到过半的learner(不太准确,确切地说是follower,observer只会同步leader的数据)完成数据同步的ACK,集群开始正常工作,可以接收并处理客户端请求,在此之前集群不可用

工作原理

  • 客户端首先向zookeeper集群的任意节点发起写请求(事务)
  • 如果节点是follower/observer类型,就将请求转发给leader节点。
  • leader节点接收消息之后对消息进行处理
    • 生成递增的全局唯一的zxid(日志)
    • 将带有zxid的消息包装成一个提案,并转发给所有follower节点
  • follower将这个提案用本地事务进行持久化后,给leader反馈ack
  • leader统计ack数量
    • 如果有过半的ack,则视为成功,即广播commit消息。(在源码中仅仅是循环遍历follower节点,然后通过消息队列发送)
    • 否则,返回失败,即follower抛弃leader服务器。注意分布式事务才有回滚
  • 最后响应客户端

崩溃恢复

触发恢复的两种情况:

  • leader挂掉
  • 集群过半的follower节点挂掉

选主原则:

  • zxid越大越好(主要)
  • myid越大越好(次要)

当其中有超过一半的选票选同一台服务器,即任命其为leader,即在最终一致的前提下,尽量保证强一致性。

一致性原则:

  • 已经被处理的消息不能丢弃(commit后)

    因为每次提交的事务都有一个zxid(全局唯一,递增),因此我们只需要找出所有机器内zxid最大的事务(既该事务是最后一个被提交的事务)并且把存放该zxid的机器选举为leader即可。

  • 已经被丢弃的消息不能再次出现(commit前)

    之前宕机的leader节点重新启动之后若再次被选为Leader,要把之前没有commit的事务重新commit,而当前的epoch大于该事务的epoch所以事务会被丢弃而不会被重新加载。也就是只有当事务zxid的epoch和当前的epoch相同时,事务才会被提交。

新选举的leader后,epoch会自增,并且将counter重置为0。

leader选举完成后,服务依然是不可用的,因为还没有做数据同步。

此后,leader会等待其余的follower来连接,然后通过proposal向所有的follower发送其缺失的数据。

至于缺失哪些数据?

proposal本身是要记录日志,通过proposal中的zxid的低32位的counter值,就可以做一个diff

这里还有一个优化,如果缺失的数据太多,一条一条地发送proposal效率太低。所以leader发现缺失的数据太多时会将当前的数据打个快照,直接打包发送给follower。

leader会发送一个NEW_LEADER的proposal给follower,当且仅当该proposal被过半的follower返回ACK后,leader才会commit这个提案(磁盘到内存),之后集群才会正常进行工作。

为什么集群通常是奇数的?

根据过半机制来说,只要有超过一半的节点能够成功投票,这个集群就是可用的。

假设有两个集群分别有3个和4个节点,此时各挂掉一个节点,两个集群仍然可用,效果是一样的(注意不要钻牛角尖,过半针对的整个集群的,不是当前存活的,节点是根据zoo.conf来读取整个集群的节点的)。所以为了节省集群的资源使用,采用奇数个。

内部运行机制

ZNode

数据存储的最小单元,ZNode中维护了一个数据结构,用于记录ZNode中数据更改的版本号以及ACL(Access Control List)的变更。

有了这些数据的版本号以及更新的时间戳,zookeeper就可以验证客户端请求的缓存是否合法,并协调更新。

当客户端执行更新或删除操作时,都要带上对应的版本号。如果版本号对不上,则不执行。

机制

zookeeper的文件系统中每个文件都是节点,因层级关系,所以会形成一个文件数。

zookeeper的znode也有几种类型:

  • 持久节点

    create /node_name

    除非主动删除,否则一直存在。

  • 持久顺序节点

    create -s /node_name

    除了持久节点的特性,还会保证子节点的先后顺序,并会自动地为节点加上10位自增序列号作为节点名,以此来保证节点名的唯一性。

  • 临时节点

    create -e /node_name

    生命周期和client连接相关,一旦连接断开(session过期也算),节点就会被删除。并且无法创建子节点。

  • 临时顺序节点

    create -e -s /node_name

    除了临时节点的特性之外,还会在后缀加上自增数字。

ACL

ZooKeeper提供了一套完善的ACL权限控制机制保障数据安全性。

  • 对于身份认证,提供了以下几种方式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qaFsgBiL-1631860207873)(C:\Users\11985\AppData\Roaming\Typora\typora-user-images\image-20210713174647027.png)]

  • 对于znode权限,提供了以下5种操作权限。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xkqVglsC-1631860207875)(C:\Users\11985\AppData\Roaming\Typora\typora-user-images\image-20210713174611282.png)]

用途

元数据管理

Kafka在运行时会依赖一个zookeeper集群,并通过zookeeper来管理集群相关的元数据以及leader的选举。

Zookeeper分布式锁

本质: 通过创建临时顺序节点来实现分布式锁。

**原理:**当客户端加锁成功后,实际上是在zookeeper上创建了临时顺序节点。当客户端成功创建节点之后,还会获取同级的所有节点。此时客户端会根据自增号判断自己当前节点是否是最小的节点,如果是最小的,则获取到了分布式锁。如果不是最小的,则会等待锁的释放,即客户端会对锁对象注册一个监听器,对该节点的任意更新都会触发对应操作。当被监听的节点被删除时,就会唤醒客户端并再次判断自己的节点是否最小。

但是这样会因为同时监听一个节点,导致,每次变动都会通知所有服务器,而只有一个能够成功加锁,所以会加重带宽和资源消耗。

优化就是将监听最小的节点,改为监听前一个节点,当前一个节点删除时,即释放监听,再去到下一个循环,判断自己是否是序号最小的节点,如果是,则加锁成功,否则继续监听自己的新的前一个节点。

分布式协调

之前提到的数据一致性,是commit还是rollback。

发布订阅

利用监听器(Watch)功能,一旦发布消息,可以立即动态更新。

命名服务

  • 利用zookeeper的文件系统特性,存储结构化文件。
  • 利用文件特性和顺序节点特性,来生成全局的唯一标识。

前者可以用于在系统之间共享某种业务上的特定资源,后者则可以用于实现分布式锁。

Session

当zookeeper的客户端和服务器建立连接(长连接)之后,客户端会拿到一个64位的SessionID和密码。

SessionID可以理解,那为什么会有密码呢?

zookeeper可以部署多个实例,如果客户端断开连接又和另外的zookeeper服务器建立连接,那么在建立连接时会带上这个密码。该密码是zookeeper的一种安全措施,所有zookeeper节点都可以对其进行验证,所以即使连上了别的zookeeper节点,session一样有效。

Session过期

session过期有两种情况:

  • 过了指定的失效时间

    过期时间的范围只能在2倍的tickTime到20倍的tickTime之间

  • 指定时间内客户端没有发送心跳

    zookeeper中的心跳是通过ping请求来实现的,每隔一段时间,客户端都会发送ping请求到服务器,这就是心跳的本质。心跳不仅可以让服务端感知客户端还存在,同样让客户端感知与服务端的连接还是有效的。

tickTime是zookeeper的配置项,用于指定客户端向服务器发送心跳的时间。默认是2秒

Watch

基础

给某个节点注册监听器,该节点一旦发生变更(更新或删除),监听者就会收到一个Watch Event。

watch的类型:

  • 一次性watch

    被触发后,watch就会移除。

  • 永久性watch

    被触发后,仍然保留,可以继续监听ZNode上的变更,是zookeeper3.6.0版本新增的功能

一次性watch的问题

在watch触发事件到达客户端、再到客户端设立新的Watch时,是有一个时间间隔的,并且这个时段,客户端无法感知。

缺点

  • 非高可用:极端情况下zk会丢弃一些请求,机房之间连接出现故障。
  • Zookeeper master只能照顾一个机房,其他机房运行的业务模块由于没有master都只能停掉,对网络抖动非常敏感。
  • 选举过程很慢而且此时无法对外提供服务。
  • zk的性能有限,承受不住巨大的流量,必须使用缓存来缓解。
  • zk本身的权限控制非常薄弱
  • 羊群效应:所有客户端尝试对一个临时节点加锁,当该节点变更时,会通知所有客户端,并进行新一轮的加锁,这样会极大的消耗网络带宽。详情见Zookeeper分布式锁
  • zk的读取操作,读取到的可能是过期的旧数据。它只能保证最终一致性,尽量去靠拢强一致性。

脑裂

由于假死会导致新的一轮选举,此时旧的leader网络通了,导致出现了两个leader,有的客户端连接到老的leader,而有的客户端连接到新的leader。

旧leader想要向其他follower发送写请求是会被拒绝的,因为epoch已经自增,当然也有别的follower不知道新leader的产生,但是仍然占少数,因为多数的话,新leader不会产生。并且因为过半机制,得不到多数的支持,写无效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值