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更适合用于需要高性能和简单的场景,例如缓存、消息队列等。
相关推荐
Easonmax2 小时前
用 Rust 打造可复现的 ASCII 艺术渲染器:从像素到字符的完整工程实践
开发语言·后端·rust
百锦再2 小时前
选择Rust的理由:从内存管理到抛弃抽象
android·java·开发语言·后端·python·rust·go
小羊失眠啦.2 小时前
深入解析Rust的所有权系统:告别空指针和数据竞争
开发语言·后端·rust
q***71853 小时前
Spring Boot 集成 MyBatis 全面讲解
spring boot·后端·mybatis
大象席地抽烟3 小时前
使用 Ollama 本地模型与 Spring AI Alibaba
后端
程序员小假3 小时前
SQL 语句左连接右连接内连接如何使用,区别是什么?
java·后端
小坏讲微服务3 小时前
Spring Cloud Alibaba Gateway 集成 Redis 限流的完整配置
数据库·redis·分布式·后端·spring cloud·架构·gateway
方圆想当图灵3 小时前
Nacos 源码深度畅游:Nacos 配置同步详解(下)
分布式·后端·github
方圆想当图灵4 小时前
Nacos 源码深度畅游:Nacos 配置同步详解(上)
分布式·后端·github
小羊失眠啦.4 小时前
用 Rust 实现高性能并发下载器:从原理到实战
开发语言·后端·rust