Zookeeper核心知识笔记整理

        本文内容主要从《从Paxos到Zookeeper  分布式一致性原理与实践》一书中摘录总结成文,可以让我们以最快的速度回顾相关的核心知识点。文章成文以常见的领域模块组织。

集群模块

  1. 集群分为消息广播模式和崩溃恢复模式。处于崩溃恢复模式时,整个集群处于协商选举中,暂时不可用。无法读写数据。
  2. 数据节点使用版本号递增机制,用于实现类似数据库应用中常用的乐观锁机制,防止并发更新时冲突。这个与JAVA中的CAS方式就不同了,CAS是使用旧值比较,不是版本号。
  3. follower同步leader数据是异步拉取吗?很多准备系统比如mysql、redis、kafka等都是副本节点主动异步方式从主节点拉取已经提交的数据的获取最终一致性。但是zk不是这样的,是leader主动将数据发送给follower的,follower接受数据后更新到本地磁盘日志,同时更新到内存中。其他采用类似机制有raft协议系统、es等。与谷歌Chubby相同。

角色

  1. leader:事务请求的唯一调度和处理者,保证集群事务处理的顺序。
  2. follower:处理客户端非事务请求,转发事务请求给leader,参与事务请求proposal的投票,参与leader选举
  3. observer:处理客户端非事务请求,转发事务请求给leader,但是不参与事务投票和选举投票

选举

  1. ZXID是一个64位的值,高32位是代(epoch)的递增值,低32位才是本代的事务ID递增值。
  2. 代(epoch)每次leader更替都会递增变化。用以区分事务ID是否最新用。
  3. 集群选举成功,至少需要2台机器。通常我们至少部署3台是为了防止其中一台宕机,这样剩下的两台就还可以继续进行工作了。为了防止以后出现选举投票持平的情况,一般部署基数台。
  4. leader选举过程:1、每个server会发出一个投票。2、接受来自各个服务器的投票。3、处理投票。4、统计投票。5、改变服务器状态。前面是集群初始化选举过程,对于奔溃恢复模式选举前还多了一步,就是节点状态改变为LOOKING。
  5. 投票原则:首先比较选举轮次(epoch),总是以最新的轮次开始重新投票。然后比较事务日志ZXID选最大的,如果有相同的,则选服务器编号SID较大的。最终得票过半的成为新leader。
  6. 节点运行过程中,有4种状态变化。looking、following、leading、observing

Paxos/Raft/ZAB协议

  1. 是否有leader?paxos系统中,没有raft/zab系统中的leader角色。但是有一个专职生产全局序列号唯一节点。其他节点都可以向集群发起数据更新的提案,但必须先获取到这个全局递增的序列号。相反raft/zab系统中,发起数据更新的事务提案都要转发给leader进行统一操作。
  2. 读操作差别。paxos系统读取一个key的值,需要客户端向全体集群发起请求,等收集到的值中,有超过半数节点的就认定为最终的正确值。而raft/zab系统就不会让客户端发起这么多请求了,被请求的当前节点,直接返回key的值即可。但是这样的话,就有可能读到旧值,所以提供一个fsync方法强行从leader那边同步下最新值再反馈给客户端,以便做到强一致性。
  3. 写操作差别。paxos系统任意节点都可以发起写请求,向集群全体成员发请求写,只要过半节点应答OK即可算写成功。而raft/zab系统只有leader才能发起写请求,其他节点写操作都需要转发给leader进行。
  4. 事务提交方式。目前他们都是采用2PC提交,这样交互较小,减少了网络交互次数。同样的2PC并不能保证事务性质。都依赖过半节点回答OK,就算事务完成。
  5. 数据同步差别。raft/zab系统,对于未能在2PC事务提交阶段更新数据的follower节点,此时follower要想获取最新数据,需要给leader发起一个请求,然后leader将缺失的事务数据按顺序发送到对应的follower队列中,follower顺序接受并处理,写入自己的本地事务日志文件,并更新内存中的数据。而paxos系统因为没有leader,所以似乎并没有这一个同步操作。缺失就是永远缺失,其客户端要获取最新的可靠值,依赖于所有节点返回值中的过半的那个值。
  6. 虽然CHubby号称基于Paxos协议实现的,但是它更类似于Raft/ZAB协议。即有中心控制节点master,负责写操作。区别主要在于中心节点宕机奔溃判断方式不同,Chubby是采用租期续约方式来延迟自己的地位,一旦出现宕机,其他节点检测到中心节点租期到期没有续约,就会发起重新选举master的操作,进入奔溃恢复模式。而zk则是采用普通的心跳机制维持中心节点的地位。Chubby有“锁延迟”机制,防止已获得锁的客户端假死,导致网络断开释放了锁,然后锁被其他客户端抢了,从而造成问题。Chubby为暂时因为网络异常原因需要释放的锁继续保留一段时间。

存储模块

内存

  1. ZKDatabase是ZK中独立的一个存储模块,负责管理整个内存数据的CRUD以及快照到磁盘文件或恢复数据。
  2. DataTree是内存数据库的核心,一个自定义的树形结构。
  3. 整个树形上的所有节点DataNode都是以ConcurrentHashMap方式存储的键值对。

磁盘

  1. 事务日志采用自定义的二进制编码存储,并采用分段存储,这样就会产生多个日志文件,每个日志文件是64M。磁盘文件这种4的倍数大小在很多系统中很常见,原因就是4k大小一般是一个操作系统默认磁盘块的大小,这样写入数据有利于充分利用写入效率,降低额外一次的写入成本。比如数据写磁盘时,不要来一点数据就立马写入,而是等缓冲区中凑齐了4k或4k的倍数数据后写入,这样效率才能更高。
  2. 为了方便索引日志文件,日志文件以当前日志第一条日志的ZXID为后缀命名。这样就可以通过事务ID快速定位到所在的日志文件了。
  3. 为了提高磁盘存储效率,当日志文件剩余空间不足时就会预分配一个新的64M日志文件,默认以‘0’填充。这样可以避免操作系统频繁申请新的磁盘块。
  4. 由于是预分配了磁盘空间,所有追加的日志写入本质上还是seek寻址方式覆盖原来的默认填充的占位符‘0’,而不是纯粹意义上的append方式。
  5. 由于日志写入普通接口都是带有缓冲的。Java进程内或操作系统的PageCache。所以允许用户配置一个强制刷入磁盘的策略,比如定时或缓冲池满时。
  6. 数据快照文件生成采用异步线程进行,当事务更新达到一个量后进行。不采用文件空间预分配方式,以最大的ZXID命名文件的后缀。这个快照文件和事务记录文件生成方式有较大区别。和redis也有较大区别。
  7. 数据快照前需要对DataTree做整体序列化操作变成可以存储的字节流。同时生成一个签名。防止数据文件被破坏或篡改过。

网络模块

  1. 对客户端请求的处理采用经典的责任链模式,依次由各种处理器流水线式处理。
  2. 使用Jute组件作为底层通信序列化。兼容性原因,一直没有替换掉。这个东西比较古老,现在很少应用。现在流行的是谷歌的protobuf、脸书的Thrift等。
  3. 使用自定义的二进制通信协议。更加有效的进行通信(降低通信体积和编码解码工作量)

容错模块

  1. 分布式环境下异常场景:通信异常、网络分区(脑裂)、超时、节点故障。

事务机制

  1. 使用2PC提交事务:第一阶段是预提交,leader将事务信息广播给follower,follower进行ACK确认。leader收集到过半ACK后进入第二阶段,即事务提交。此时leader会先将事务日志应用到自己内存数据的正式更新,然后将此事务放入本地已提交事务的队列中,再广播Commit消息给follower,最后follower将事务也应用到本地内存数据库中,完成事务的最终提交了。
  2. 第二阶段(Commit)不再需要收集过半follower的确认消息。Commit消息广播后follower直接应用,没有应用成功的节点,重新从leader获取已提交的事务日志重新应用。
  3. leader收到事务请求后,先使用本地事务日志文件记录下来(暂时不会应用到内存),第一阶段(预提交)广播给follower,follower收到后也是同样只是记录事务日志,并不会直接应用到内存中更新数据库。
  4. 第二阶段(Commit)leader只需要发送一个Commit命令,只包含事务ID(ZXID)即可。不需要重新打包事务具体更新信息给follower,因为第一阶段的广播命令中已包含此信息,且follower本地事务日志中也有记录,第二阶段只需要应用到内存数据库就行。
  5. observer不参与事务投票过程,只是在第二阶段leader会将事务信息打包推送给它。
  6. 为了保证事务消息广播的顺序性,leader为每个follower都单独建立一个发送队列,队列中的事务消息严格按照ZXID顺序先进先出发送给follower处理,只有前面的一条事务消息被处理完了,才会发送第二条事务消息。这样可以有效保证事务的顺序(时序)一致性特性。
  7. 使用全局统一的事务序列号,保证事务提交的顺序。并由leader统一提交,避免每个节点提交事务的处理复杂性。恰恰Paxos系统相反的。
  8. 2PC/3PC事务。1、使用协调者机制实现,要实现较强一致性,需要稳定的网络环境和系统环境。依然无法解决脏读、重复读、幻读现象。2、相关框架可以自身实现回滚机制,通过记录反向SQL日志方式。有自己的redo和undo日志机制。3、系统处于不一致状态时间较短。相比事务服务和事务消息方案。4、不管你设计几个阶段提交事务,都会存在数据不一致性问题。阶段越多,只能保证出错率降低了一点点,但是同时也导致巨大的事务延时。所以zk只使用2PC提交事务。5、zk并不是使用2pc来保证事务的,只是使用了这一协议方式减少事务提交的出错率。最终还是使用了fsync强刷模式获得数据强一致性能力。

数据一致性

  1. 因为是分布式系统,必然存在各种异常导致follower的事务ZXID可能会大于leader的情况。为了保证数据一致性,此时leader会发送命令要求follower删除事务日志中的任何比leader还大的事务,这叫日志截断。需要注意的是,数据一致性不代表数据绝对不能丢失,而是指系统有能力保持数据之间的合理逻辑关系。允许短时间内的不一致性,但是很快就能发现不一致的数据并采取措施使其达到一致。
  2. 存在的一致性问题。1、已提交事务丢失。在事务2PC提交过程中。事务刚在leader上最终提交,还来不及通知Follower最终提交,leader就挂了。此时ZK进入“崩溃恢复”阶段,新选出的Leader上最新数据也就不会有那个已提交的事务了。最终这个已提交的事务被集群丢失了。但是因为之前已经被leader提交,很可能客户端已经收到提交成功的反馈。导致最终结果和客户端预期不一致,但是集群内部数据仍然处于一致性状态。2、读到旧值。由于事务成功的标志采用的是过半机器响应OK就认为事务完成。所以集群中必然会有一部分节点异常,导致未能及时同步到最新的事务提交的值。此时某客户端请求到此机器就可能读到旧值。不过客户端可以使用fsync方式强制读取最新值(每次读取,follower都会强制从leader处同步最新值过来),获取强一致性的数据。但是很明显,这样就大大降低了可用性,也就是说,zk提供CP和AP模型两种使用方式。3、已提交事务,客户端反馈失败。假设现在leader已经通过2PC将客户端事务已经提交了,下一步就是向客户端反馈成功消息,结果因为网络延迟、异常或自身宕机原因,导致客户端请求超时或请求异常从而最终结果是更新异常。这对客户端来说,本次更新请求失败了,而对于zk集群来说事务已经提交了。从而导致了问题。无法回滚,就算回滚后续步骤也可能导致各种异常而无法进行。不过这对客户端来说重新提交一次也没问题,一般不会导致严重的错误。同样的问题,即便是单机的数据库系统也会存在。此时客户端重新运行一次事务即可。但是要注意幂等性,比如银行转账这种,重做可能导致重复转账的问题。所以重做事务需要事先校验一遍。
  3. 时序一致性理解。事务提交是并发进行的吗?不同key的更新是并发进行的吗?有更新操作都需要转发给leader统一操作,每个更新事务根据先后顺序都赋值唯一的ZXID(全局有序递增的编号),leader为每个follower建立单独的队列,依据FIFO(先进先出)原则提交事务给follower。每个follower响应完一个事务后才能进行响应下一个事务。类似于Redis的单线程处理模型,即便是不同的key的更新操作,也不是并行处理的。

公平锁

  1. 客户端向某个节点创建临时顺序节点。
  2. 客户端调用getChildren方法获取目标节点下所有已经创建的子节点。
  3. 客户端发现自己创建的节点序号最小,那么就可以认为自己获得了锁,反之就找到最小字节点,调用exist()方法,同时注册事件监听。
  4. 一旦最小字节点对应的进程释放了分布式锁,则其注册的临时节点就会被移除,客户端因为注册了对该子节点的事件监听而收到该通知。
  5. 收到通知的其他客户端进程再次调用getChildren方法获取所有字节点,然后重复第三步骤。直到自己获得锁为止。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值