ZooKeeper工作原理及其应用

ZooKeeper是怎么工作的,原理是什么?

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,它是集群的必备元素,可以提供分布式锁、配置管理、分布式通知等服务。

ZooKeeper的工作原理主要基于以下几个方面:

  1. 数据模型:ZooKeeper的数据模型类似于一个分层的文件系统,类似于一个多叉树,也被称为ZNode树。每个节点称为一个znode,每一个ZNode都可以有子节点,每个节点都可以存储数据,。每个znode在ZooKeeper中都可以通过全路径名进行唯一标识。

  2. 会话:客户端和ZooKeeper之间的交互是基于会话的。客户端启动时,会与ZooKeeper建立一个TCP连接,这个连接就是一个会话。会话有超时机制,如果在超时时间内,ZooKeeper没有收到客户端的心跳,那么会话就会被关闭。

  3. Watcher机制:客户端可以在一个znode上注册一个watcher,当这个znode的数据发生变化时,ZooKeeper会通知到客户端。这种机制可以让客户端及时地获取到数据的变化。

  4. 一致性:ZooKeeper保证了在整个集群中,客户端无论连接到哪个ZooKeeper服务器,看到的数据都是一致的。这是通过ZooKeeper的Zab协议来实现的,Zab协议是一个基于主从模式的一致性协议,主要用于在主节点崩溃后,从从节点中选举出新的主节点,并且在主节点崩溃前后,保证所有已经提交的请求都能被正确处理。

  5. 原子性:ZooKeeper的所有操作都是原子性的,要么全部成功,要么全部失败。这保证了即使在面临网络分区、服务器崩溃等故障时,ZooKeeper的数据也能保持一致性。

  6. 顺序性:ZooKeeper保证了来自同一个客户端的请求,会按照请求的顺序来执行。这对于需要依赖请求顺序的应用来说,是非常重要的。

总的来说,ZooKeeper通过数据模型、会话、Watcher机制、一致性、原子性和顺序性等机制,为分布式应用提供了一个高可用、高性能、可靠的协调服务。

Zab协议

Zab协议全称为ZooKeeper Atomic Broadcast协议,是ZooKeeper为了保证分布式数据一致性而设计的一种协议。Zab协议主要用于在所有ZooKeeper服务器之间复制内存中的数据树和事务日志,保证了在整个ZooKeeper集群中,所有的写操作都是线性一致的。

Zab协议主要包括两种模式:崩溃恢复模式和消息广播模式。

崩溃恢复模式

当ZooKeeper集群启动,或者Leader服务器崩溃后,Zab协议会进入崩溃恢复模式。在这个模式下,Zab协议会从Follower服务器中选举出新的Leader服务器,并且在Leader服务器崩溃前后,保证所有已经提交的请求都能被正确处理。

在Zab协议的崩溃恢复模式下,为了选举出新的Leader,同时保证新的Leader拥有最新的数据,Zab协议采用了一个叫做"Leader选举+日志同步"的机制。

  • Leader选举: Zab协议的Leader选举过程是基于投票的("过半机制")。每个ZooKeeper服务器都会发起一轮投票,投票包含自己的服务器ID和最后一条日志的ZXID(ZooKeeper事务ID)。每个服务器都会选择ZXID最大的服务器作为Leader,如果ZXID相同,则选择服务器ID最大的。这样可以保证新的Leader至少拥有和其他服务器一样新的数据。

谁可以监控到leader崩溃了,谁触发了选举动作?

在ZooKeeper集群中,所有的Follower服务器都会定期向Leader服务器发送心跳,以确认Leader服务器是否还在运行。如果在一定时间内,Follower服务器没有收到Leader服务器的心跳回应,那么Follower服务器就会认为Leader服务器已经崩溃。 当Follower服务器检测到Leader服务器崩溃后,它会触发新一轮的Leader选举。在这个选举过程中,所有的服务器都会参与,每个服务器都会发起一轮投票,投票包含自己的服务器ID和最后一条日志的ZXID(ZooKeeper事务ID)。每个服务器都会选择ZXID最大的服务器作为Leader,如果ZXID相同,则选择服务器ID最大的。 这个过程会进行多轮,直到所有的服务器的投票都一致,选举出一个新的Leader。新的Leader在被选举出来后,会向所有的Follower服务器发送新的心跳,以确认自己已经成为新的Leader。 所以,是Follower服务器监控到Leader服务器崩溃,并触发了新一轮的Leader选举。
leader选举过程是谁主导执行的?

在ZooKeeper的Zab协议中,Leader选举过程是由集群中的所有服务器共同参与的,而不是由某一个特定的服务器主导执行的。 当ZooKeeper集群启动,或者当前的Leader服务器崩溃后,所有的服务器都会进入到Leader选举过程。每个服务器都会发起一轮投票,投票包含自己的服务器ID和最后一条日志的ZXID(ZooKeeper事务ID)。每个服务器都会选择ZXID最大的服务器作为Leader,如果ZXID相同,则选择服务器ID最大的。 在投票过程中,每个服务器既是投票者,也是被投票者。每个服务器在收到其他服务器的投票后,会将自己的投票和收到的投票进行比较,如果收到的投票更优(即ZXID更大,或者ZXID相同但服务器ID更大),那么服务器会更新自己的投票。这个过程会进行多轮,直到所有的服务器的投票都一致,选举出一个新的Leader。 所以,ZooKeeper的Leader选举过程是一个分布式的过程,由集群中的所有服务器共同参与,而不是由某一个特定的服务器主导执行的。
过半机制

具体来说,一个ZooKeeper服务器要成为Leader,需要得到超过半数的服务器(包括自己)的投票。这个机制可以保证在整个ZooKeeper集群中,只能选举出一个Leader。 这个"过半机制"是为了保证ZooKeeper集群的数据一致性和可用性。因为只有当超过半数的服务器同意时,一个请求才能被提交,这样可以保证即使有部分服务器崩溃或者网络分区,只要有超过半数的服务器还在运行,ZooKeeper集群就能继续提供服务。同时,这个机制也保证了在整个集群中,只能有一个Leader,避免了数据的不一致。 需要注意的是,这个"过半机制"也决定了ZooKeeper集群的容错能力。一个有N个服务器的ZooKeeper集群,最多可以容忍N/2(向下取整)个服务器崩溃。所以在实际部署时,通常会选择奇数个服务器,例如3个、5个、7个等,以提高集群的容错能力。

  • 日志同步: Leader选举结束后,新的Leader会和其他的Follower进行日志同步。Leader会将自己的日志中比Follower新的部分发送给Follower,Follower在接收到这些日志后,会将这些日志追加到自己的日志中。这样可以保证所有的Follower都拥有和Leader一样新的数据。

通过这个"Leader选举+日志同步"的机制,Zab协议在崩溃恢复模式下,可以保证新的Leader拥有最新的数据,同时也可以保证所有的Follower都能更新到最新的数据。

消息广播模式

当ZooKeeper集群中的Leader服务器被选举出来后,Zab协议会进入消息广播模式。在这个模式下,Leader服务器会将所有的写操作(即状态变更请求)以事务提案的形式发送给所有的Follower服务器。Follower服务器在接收到事务提案后,会将其写入本地日志,并向Leader服务器发送ACK。当Leader服务器收到了超过半数的Follower服务器的ACK后,就会认为这个事务提案已经被接受,然后向所有的Follower服务器发送COMMIT命令,通知它们提交这个事务。

在Zab协议中,Leader服务器在收到超过半数的Follower服务器的ACK后,会认为事务提案已经被接受,然后向所有的Follower服务器发送COMMIT命令。

对于没有发送ACK的Follower服务器,也就是说它们还没有接收到事务提案,或者还没有来得及处理事务提案,它们在接收到COMMIT命令后,会直接忽略这个命令,因为它们没有对应的事务提案可以提交。

然后,这些Follower服务器会继续从Leader服务器接收新的事务提案。如果它们接收到的新的事务提案的ZXID(ZooKeeper事务ID)比它们期望的ZXID大,那么它们就知道它们错过了一些事务提案。这时,它们会向Leader服务器发送一个SNAPSHOT请求,请求Leader服务器发送一个数据快照,以及从这个数据快照之后的所有事务提案。这样,这些Follower服务器就可以通过这个数据快照和事务提案,来更新它们的状态,使之与Leader服务器保持一致。

所以,即使有一部分Follower服务器没有发送ACK,也没有影响,因为它们最终还是会通过SNAPSHOT请求,来获取到错过的事务提案,并更新它们的状态。这就保证了所有的Follower服务器最终都能提交成功。

Zab协议通过这两种模式,保证了ZooKeeper集群中的数据一致性和高可用性。

ZooKeeper应用

分布式锁

ZooKeeper可以用于实现分布式锁,主要是利用了ZooKeeper的数据模型和一致性保证。具体来说,当一个客户端想要获取锁时,它会在ZooKeeper的一个指定节点(例如/lock)下创建一个临时顺序节点,然后查看自己创建的节点是否是当前所有子节点中序号最小的,如果是,则获取锁成功;如果不是,则监听比自己序号小的那个节点,当那个节点被删除时,再次尝试获取锁。这样就保证了同一时刻只有一个客户端能获取到锁。

在ZooKeeper中,每个节点被称为一个znode,znode有几种类型,包括持久节点、临时节点、持久顺序节点和临时顺序节点。

  1. 持久节点:创建后在客户端断开连接后依然存在。
  2. 临时节点:创建后如果客户端断开连接,那么这个节点就会被自动删除。
  3. 持久顺序节点:创建后会在节点后面自动添加一个递增的数字,即使客户端断开连接,节点依然存在。
  4. 临时顺序节点:创建后会在节点后面自动添加一个递增的数字,如果客户端断开连接,那么这个节点就会被自动删除。

在实现分布式锁时,客户端会在一个指定的节点(例如/lock)下创建一个临时顺序节点。例如,第一个客户端创建的节点可能是/lock/0000000001,第二个客户端创建的节点可能是/lock/0000000002,以此类推。这个节点是临时的,也就是说,如果客户端断开连接,那么这个节点就会被自动删除。同时,这个节点是顺序的,也就是说,每个客户端创建的节点的序号都是递增的,这样就可以通过序号来确定哪个客户端应该获得锁。

在ZooKeeper中,客户端与服务器之间的连接可能因为以下几种情况断开:

  1. 客户端主动关闭连接:客户端在完成所有操作后,可以主动调用API来关闭与ZooKeeper服务器的连接。

  2. 网络问题:如果客户端与ZooKeeper服务器之间的网络出现问题,例如网络断开、延迟过高等,那么连接可能会被断开。

  3. ZooKeeper服务器故障:如果ZooKeeper服务器出现故障,例如服务器崩溃、重启等,那么与客户端的连接也会被断开。

  4. Session超时:ZooKeeper客户端在与服务器建立连接时,会设置一个Session超时时间。如果在这个时间内,客户端没有发送任何请求到服务器,那么服务器就会认为这个Session已经超时,然后关闭与客户端的连接。

在使用ZooKeeper实现分布式锁时,如果客户端在获取到锁,完成操作后,可以主动关闭与ZooKeeper的连接,这样对应的临时节点就会被删除,其他等待获取锁的客户端就可以获取到锁。如果客户端在还没有完成操作时,就与ZooKeeper服务器的连接被断开(例如因为网络问题或者服务器故障),那么对应的临时节点也会被删除,这样可以避免因为客户端故障导致的锁无法被释放的问题。

通过监听(Watcher)机制来实现重试: 具体来说,当一个客户端尝试获取锁(即在ZooKeeper的指定节点下创建临时顺序节点)失败时(因为已经有其他客户端获取到了锁),它会找到比自己创建的节点序号小的那个节点,然后对那个节点注册一个Watcher。 当持有锁的客户端完成操作,释放锁(即关闭与ZooKeeper的连接,删除对应的临时节点)时,ZooKeeper会通知到注册了Watcher的客户端,这个客户端就知道可以再次尝试获取锁了。 这个监听机制是ZooKeeper的一个重要特性,它可以避免客户端通过轮询来检查锁状态,从而减少了网络流量和服务器的负载。
InterProcessMutexacquire方法有两个版本,一个是可以设置超时的,一个是无限等待直到获取到锁。

  1. acquire(long time, TimeUnit unit):这个版本的acquire方法会尝试获取锁,如果在指定的时间内没有获取到锁,那么就会返回false。这个方法是可以设置超时的,也就是说,它会在尝试获取锁时阻塞,但是最多阻塞指定的时间。
  2. acquire():这个版本的acquire方法会尝试获取锁,如果没有获取到锁,那么就会一直等待,直到获取到锁。这个方法是无限等待的,也就是说,它会在尝试获取锁时阻塞,直到获取到锁。

所以,如果你不希望acquire方法阻塞,你可以使用可以设置超时的版本,然后设置一个合适的超时时间。例如:

java 复制代码
InterProcessMutex lock = new InterProcessMutex(client, "/lock_path");
if (!lock.acquire(10, TimeUnit.SECONDS)) {
    // 在10秒内没有获取到锁,返回失败或者执行其他操作
    return;
}

在这个示例中,如果在10秒内没有获取到锁,acquire方法就会返回false,然后你可以选择返回失败,或者执行其他的操作。

配置中心

ZooKeeper可以用于实现分布式配置中心,主要是利用了ZooKeeper的数据模型和Watcher机制。具体来说,可以将配置信息存储在ZooKeeper的节点中,当需要修改配置时,只需要更新对应的节点。客户端在启动时,会读取这些配置信息,并注册Watcher,当配置信息发生变化时,ZooKeeper会通知到客户端,客户端再重新读取配置信息。这样就可以实现配置的动态更新。

Kafka中的Controller选举

Kafka中的Controller选举也是利用了ZooKeeper的数据模型和一致性保证。具体来说,当Kafka集群启动时,所有的Broker都会尝试去创建一个临时节点(例如/controller),由于ZooKeeper保证了创建节点的原子性,所以只有一个Broker能创建成功,创建成功的Broker就成为Controller。当Controller崩溃时,对应的临时节点会被删除,其他的Broker检测到节点被删除后,会再次尝试创建节点,从而选举出新的Controller。

zk的分布式锁和redis的分布式锁有什么区别?

ZooKeeper和Redis都可以用来实现分布式锁,但是它们的实现方式和特性有一些不同:

  1. 实现方式:

    • ZooKeeper的分布式锁是基于其临时顺序节点和Watcher机制实现的。客户端在尝试获取锁时,会在ZooKeeper的指定节点下创建一个临时顺序节点,然后检查自己创建的节点是否是所有子节点中序号最小的,如果是,那么就获取到了锁;如果不是,那么就找到比自己小的那个节点,对其注册Watcher,然后等待锁被释放。
    • Redis的分布式锁是基于其SETNX(Set if Not eXists)命令和过期时间实现的。客户端在尝试获取锁时,会使用SETNX命令尝试设置一个键,如果设置成功,那么就获取到了锁;如果设置失败(因为键已经存在),那么就等待一段时间后再次尝试。
  2. 容错性:

    • ZooKeeper的分布式锁具有较好的容错性。因为锁是基于ZooKeeper的临时节点实现的,所以当持有锁的客户端发生故障时,其创建的临时节点会被自动删除,锁就会被释放,其他等待获取锁的客户端就有机会获取到锁。
    • Redis的分布式锁的容错性较差。如果持有锁的客户端发生故障,没有来得及删除键,那么其他客户端需要等待到键过期后才能获取到锁。如果设置的过期时间过长,那么就可能导致锁被长时间占用,影响系统的性能。
  3. 性能:

    • ZooKeeper的性能较低,因为它需要在磁盘上持久化所有的数据,而且其所有的写操作(包括创建、删除节点等)都需要在集群中的所有服务器上达成一致,这会消耗较多的网络和磁盘IO。
    • Redis的性能较高,因为它是基于内存的,其所有的操作都是在内存中完成的,所以速度非常快。但是,如果Redis服务器发生故障,那么可能会导致锁的信息丢失。
  4. 适用场景:

    • ZooKeeper更适合用于需要严格的顺序一致性和容错性的场景,例如Leader选举、配置管理等。
    • Redis更适合用于需要高性能和简单的场景,例如缓存、消息队列等。
相关推荐
Rverdoser41 分钟前
RabbitMQ的基本概念和入门
开发语言·后端·ruby
Tech Synapse1 小时前
Java根据前端返回的字段名进行查询数据的方法
java·开发语言·后端
.生产的驴1 小时前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
微信-since811922 小时前
[ruby on rails] 安装docker
后端·docker·ruby on rails
运维&陈同学3 小时前
【zookeeper03】消息队列与微服务之zookeeper集群部署
linux·微服务·zookeeper·云原生·消息队列·云计算·java-zookeeper
代码吐槽菌4 小时前
基于SSM的毕业论文管理系统【附源码】
java·开发语言·数据库·后端·ssm
豌豆花下猫4 小时前
Python 潮流周刊#78:async/await 是糟糕的设计(摘要)
后端·python·ai
YMWM_4 小时前
第一章 Go语言简介
开发语言·后端·golang
码蜂窝编程官方4 小时前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的虎鲸旅游攻略网的设计与实现
java·vue.js·spring boot·后端·spring·旅游
hummhumm4 小时前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang