ZooKeeper能解决什么问题?不能解决什么问题?

本文深入探讨ZooKeeper的设计理念,解析其如何简化分布式应用开发,提供高效、可靠的分布式协调服务。ZooKeeper通过屏蔽底层细节,如消息延迟、处理器性能和时钟偏移等问题,确保应用程序专注于业务逻辑。此外,文章还介绍了ZooKeeper的API,以及其在统一命名、分布式锁、进程崩溃检测、Leader选举和配置管理等方面的应用。

来源 | https://urlify.cn/67BZnm

ZooKeeper 很流行,有个基本的疑问:

  • ZooKeeper 是用来做什么的?

  • 之前没有ZK,为什么会诞生 ZK?

OK,解答一下上面的疑问:(下面是凭直觉说的)

  • ZooKeeper 是用于简化分布式应用开发的,对开发者屏蔽一些分布式应用开发过程中的底层细节

  • ZooKeeper 对外暴露简单的 API,用于支持分布式应用开发

  • ZooKeeper 在提供上述功能的同时,其还是一个 高性能、高可用、高可靠的分布式集群

上面说这么多,总结一下,ZK 能解决分布式应用开发的问题,ZK 能很好的解决问题。到这一步,疑问就更多了:

  1. 分布式应用开发,有哪些常见问题?ZK 是如何屏蔽这些底层细节的?

  2. ZooKeeper 对外暴露了那些 API?这些 API 如何支持分布式应用开发的?这些 API 还能简化吗?API 的语义性怎么样?

  3. ZooKeeper 自身是一个高性能、高可用、高可靠的分布式集群,那有个简单的问题:

  • 高性能是指什么?ZooKeeper 为了达到高性能,做了哪些工作?

  • 高可用同上

  • 高可靠同上

Note:本篇 wiki 就是为了解决上述第一个疑问的。(其他疑问会在其他 blog 中逐步解答)

为什么有 ZooKeeper

一个应用程序,涉及多个进程协作时,业务逻辑代码中混杂有大量复杂的进程协作逻辑。

上述多进程协作逻辑,有 2 个特点:

  • 处理复杂

  • 处理逻辑可重用

因此,考虑将多进程协作的共性问题拎出,作为基础设施,让 RD 更加专注业务逻辑开发,即:

ZooKeeper 就是上述多进程协作基础服务的一种。

ZooKeeper 的特点

ZooKeeper 有几个简单特点:

  • ZooKeeper 的 API:从 文件系统 API 得到的启发,提供简单的 API

  • ZooKeeper 运行在专用服务器上,跟业务逻辑分离,保证了高容错性可扩展性

ZooKeeper 是存储设施,但特别注意

  • ZK上存储的数据聚焦为:协作数据元数据),而不是应用数据,应用数据有自己的存储方案,例如 HDFS 等

  • ZK 本质上,可以看作一种特殊的 FS

特别说明:

应用数据和元数据,由于使用场景不同,对一致性和持久性的要求有差异, 因此,架构设计、数据治理过程中,应将 2 类数据独立看待、独立存储。

ZooKeeper 的使命

ZK 要解决的核心问题:

ZK 目标:简化分布式应用开发中,多进程协作问题。为分布式应用,提供高效可靠的分布式协调服务(基础服务),例如:

  • 统一的命名服务

  • 分布式锁

  • 进程崩溃检测

  • Leader 选举

  • 配置管理:配置变更时,及时下发到各个 Client。

一个简单的问题:多进程的协作是什么?尼玛呀,有完没完,啥问题你都有,面对这个掉咋天的脑壳,还是回答一下。

多进程协作,整体分为 2 类:

  1. 协作:多进程需要一同处理某些事情,一些进程采取行动是的其他进程能够正常工作,例如:主从结构,M 向 S 分配任务,S 才会执行,否则 S 就保持空闲状态

  2. 竞争:两个进程不能同时工作,一个进程必须等待另个进程执行完毕,例如:主从结构,M 节点失效后,很多 S 都想成为 M,这时,就需要互斥锁,只有第一个获得锁的 S 成为 M

特别说明:

  1. 不跨网络协作:多进程,可以在同一台物理主机上,同步原语很方便(比如?管道、共享内存、消息队列、信号量)

  2. 跨网络协作:多进程,分布在不同的物理主机上,ZK 关注这一类

跨网络多进程协作,进程通信,基本思路有 2 个:

  1. 消息机制:通过网络,直接信息交换,多消息传递算法,实现同步原语

  2. 共享存储:利用外部共享存储,实现多进程协作,要求共享存储提供有序访问,ZK 采用这种方式

真实系统中,跨网络通信,有几个共性问题:

  1. 消息延迟:由于网络原因,后发送先到达

  2. 处理器性能:由于系统调度原因,消息到达后,延迟处理

  3. 时钟偏移:不同物理主机,时钟发生偏移

ZK 精心设计用于屏蔽上述 3 个共性问题,使得这些问题在应用服务层面完全透明化。

ZooKeeper 特性
ZooKeeper 解决的本质问题

分布式系统的一致性问题:

  1. 消息传递:延迟性,先发送的消息,不一定先到达;

  2. 消息传递:丢失性,发送的消息,可能丢失;

  3. 节点崩溃:分布式系统内,任何一个节点都可能崩溃;

在这种情况下,如何保证数据的一致性?

  1. 提案投票:基于投票策略,2PC

  2. 选举投票:基于投票策略,投出优先级最高的节点(包含最新数据的节点)

Paxos 目标:解决分布式一致性问题,提高分布式系统容错性的一致性算法。

Paxos 本质:基于消息传递高度容错一致性算法

ZooKeeper 定位

ZooKeeper 是:

  1. 分布式协调服务

  2. 高效、可靠

  3. 方便应用程序,聚焦业务逻辑开发,而不需要过多关注分布式进程间协作细节

ZooKeeper 不直接暴露原语,而是,暴露一部分调用方法组成的 API,类似文件系统的 API,支持应用程序实现自己的原语

ZooKeeper 特性

ZooKeeper 可以保证如下分布式一致性特性:

  • 顺序一致性:同一个 Client 发起的事务请求,严格按照发起顺序执行

  • 原子性:事务请求,要么应用到所有节点,要么一个节点都没有应用

  • 单一视图:Client 无论连接到哪个节点,看到的服务端数据都是一致的(Note:不准确,其实是最终一致性

  • 可靠性:事务一旦执行成功,状态永久保留

  • 实时性:事务一旦执行成功,Client 并不能立即看到最新数据,但 ZooKeeper 保证最终一致性

ZooKeeper 设计目标

ZooKeeper 致力于提供高性能高可用顺序一致性的分布式协调服务,保证数据最终一致性

目标一:高性能(简单的数据模型)
  1. 采用树形结构组织数据节点;

  2. 全量数据节点,都存储在内存中;

  3. Follower 和 Observer 直接处理非事务请求;

目标二:高可用(构建集群)
  1. 半数以上机器存活,服务就能正常运行

  2. 自动进行 Leader 选举

目标三:顺序一致性(事务操作的顺序)
  1. 每个事务请求,都会转发给 Leader 处理

  2. 每个事务,会分配全局唯一的递增id(zxid,64位:epoch + 自增 id)

目标四:最终一致性
  1. 通过提议投票方式,保证事务提交的可靠性

  2. 提议投票方式,只能保证 Client 收到事务提交成功后,半数以上节点能够看到最新数据

ZooKeeper 出现之前

ZK 出现之前,分布式系统常用两种方式,实现多进程协作:

  1. 分布式锁管理器

  2. 分布式数据库

ZK 更专注于进程协作,而不提供任何锁接口和通用的存储数据接口。(疑问:ZK 也可以提供啊,我们不使用就行了)

应用服务器,常见的 2 种需求:

  1. Master-Slave Leader 选举:要求提供Master节点选举功能

  2. 进程响应跟踪 崩溃检测:要求提供进程存活状态的跟踪

  3. 分布式锁:互斥排它锁

ZK 为上述 2 种策略提供了基础 API。

ZooKeeper 不适用的场景:

  1. 海量数据存储:ZK 本质是特殊的 FS,但 ZK 用于存储元数据,需要单独存储应用数据

### ZooKeeper 脑裂问题及其解决方案 #### 什么是脑裂问题? 脑裂(Split-brain)是指在分布式系统中,由于网络分区或节点故障,导致集群中的部分节点无法与其他节点通信,从而形成多个子集。每个子集中可能都会选举出一个主节点(Leader),进而导致整个系统的状态不一致。这种情况类似于大脑被分裂成多个部分,各自独立运作,因此被称为“脑裂”[^2]。 #### ZooKeeper 如何解决脑裂问题ZooKeeper 通过一系列机制来确保集群的一致性和协调性,从而避免脑裂问题的发生。 ##### 1. 领导者选举机制(Leader Election) ZooKeeper 使用一种称为领导者选举的机制来选择一个主节点(Leader)。当集群启动或当前 Leader 失效时,所有节点会进行一次选举,选出一个新的 Leader。只有获得超过半数选票的节点才能成为新的 Leader。这种过半机制确保了即使在网络分区的情况下,也只有一个子集能够成功选举出 Leader,从而避免脑裂[^5]。 例如,在一个包含 5 台服务器的集群中,过半数是 3 台。只有当某个节点获得了至少 3 台服务器的选票时,它才能成为 Leader。 ##### 2. 投票机制 除了选举 Leader 外,ZooKeeper 还通过投票机制来解决脑裂问题。在选举过程中,所有的节点都可以对提出的解决方案进行投票。当大多数节点都支持同一个解决方案时,该解决方案才会被执行。这可以确保所有的节点都朝着一致的方向前进[^3]。 ##### 3. 心跳检测与超时机制 ZooKeeper 的 Leader 会定期向其他节点发送心跳信号,以确认它们的状态。如果一个节点长时间没有收到心跳信号,它就会认为该 Leader 已经失效,并重新发起选举过程。这种机制确保了集群能够在 Leader 故障时快速恢复[^3]。 ##### 4. 数据复制与一致性协议 ZooKeeper 使用数据复制和一致性协议(如 ZAB 协议)来确保所有节点的数据同步。即使在网络分区发生时,ZooKeeper 也能通过复制数据来保持一致性,确保不同子集之间的数据不会出现冲突。 ZAB(ZooKeeper Atomic Broadcast)协议是专门为 ZooKeeper 设计的一种一致性协议,它确保了所有写操作都是原子性的,并且按照全局顺序执行。 ##### 5. 快照与日志机制 ZooKeeper 使用快照和事务日志来跟踪集群的状态和操作。当某个节点失败或网络分区发生时,ZooKeeper 可以通过回滚到最近的快照状态来恢复一致性。此外,事务日志记录了所有重要的操作和事件,便于故障排除和恢复。 ##### 6. 分布式锁机制 ZooKeeper 提供了一种称为分布式锁的功能,可以确保在分布式环境中只有一个节点能够执行某些关键操作。这可以防止多个节点同时执行相同的操作,从而避免脑裂问题的发生。 --- ### 示例代码:使用 ZooKeeper 实现简单的分布式锁 ```java import org.apache.zookeeper.*; import org.apache.zookeeper.data.Stat; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; public class DistributedLock implements Watcher { private final ZooKeeper zk; private final String lockPath = "/lock"; private final CountDownLatch connectedSignal = new CountDownLatch(1); private String currentZnodeName; public DistributedLock(String hostPort) throws IOException, InterruptedException, KeeperException { zk = new ZooKeeper(hostPort, 3000, this); connectedSignal.await(); createEphemeralNode(); } @Override public void process(WatchedEvent event) { if (event.getState() == Event.KeeperState.SyncConnected) { connectedSignal.countDown(); } } private void createEphemeralNode() throws KeeperException, InterruptedException { // 创建临时顺序节点 currentZnodeName = zk.create(lockPath + "_", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); } public boolean acquireLock() throws KeeperException, InterruptedException { List<String> children = zk.getChildren("/", false); Collections.sort(children); // 获取当前节点在整个列表中的索引 int index = children.indexOf(currentZnodeName); // 如果当前节点是最小的,则获取锁成功 return index == 0; } public void releaseLock() throws InterruptedException, KeeperException { zk.delete(currentZnodeName, -1); } public static void main(String[] args) throws Exception { DistributedLock lock = new DistributedLock("localhost:2181"); if (lock.acquireLock()) { System.out.println("Lock acquired!"); // 执行需要互斥的操作 Thread.sleep(5000); lock.releaseLock(); System.out.println("Lock released!"); } else { System.out.println("Failed to acquire lock."); } } } ``` 上述代码展示了如何使用 ZooKeeper 实现一个简单的分布式锁。通过创建临时顺序节点,并根据节点顺序判断是否获得锁,可以有效防止多个节点同时执行相同的操作,从而避免脑裂问题的发生。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值