写点什么

Zookeeper 基础原理 & 应用场景详解

发布于: 2021 年 04 月 21 日
Zookeeper基础原理&应用场景详解

简单了解 Zookeeper

Tips: 如果之前对 Zookeeper 不了解的话,这里大概留个印象就好了


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


例如,Kafka 中就是用 Zookeeper 来保存其集群中的相关元数据,例如 Broker、Topic 以及 Partition 等等。同时,基于 Zookeeper 的 Watch 监听机制,还可以用其实现发布、订阅的功能。


在平常的常规业务使用场景下,我们几乎只会使用到分布式锁这一个用途。

Zookeeper 内部运行机制

Zookeeper 的底层存储原理,有点类似于 Linux 中的文件系统。Zookeeper 中的文件系统中的每个文件都是节点(Znode)。根据文件之间的层级关系,Zookeeper 内部就会形成这个这样一个文件树。



在 Linux 中,文件(节点)其实是分类型的,例如分为文件、目录。在 Zookeeper 中同理,Znode 同样的有类型。在 Zookeeper 中,所有的节点类型如下:


  • 持久节点(Persistent)

  • 持久顺序节点(Persistent Sequential)

  • 临时节点(Ephemeral)

  • 临时顺序节点(Ephemeral Sequential)


所谓持久节点,就和我们自己在电脑上新建一个文件一样,除非你主动删除,否则一直存在。


持久顺序节点除了继承了持久节点的特性之外,还会为其下创建的子节点保证其先后顺序,并且会自动地为节点加上 10 位自增序列号作为节点名,以此来保证节点名的唯一性。这一点上图中的subfiles已经给出了示例。


临时节点,其生命周期和 client 的连接是否活跃相关,如果 client 一旦断开连接,该节点(可以理解为文件)就都会被删除,并且临时节点无法创建子节点


PS:这里的断开连接其实不是我们直觉上理解的断开连接,Zookeeper 有其 Session 机制,当某个 client 的 Session 过期之后,会将对应的 client 创建的节点全部删除

Zookeeper 的节点创建方式

接下来我们来分别看看几种节点的创建方式,给出几个简单的示例。

创建持久节点

create /node_name SH的全栈笔记 
复制代码



这里需要注意的是,命令中所有的节点名称必须要以/开头,否则会创建失败,因为在 Zookeeper 中是不能使用相对路径,必须要使用绝对路径。


创建持久顺序节点

create -s /node_name SH的全栈笔记
复制代码



可以看到,Zookeeper 为 key 自动的加上了 10 位的自增后缀。

创建临时节点

create -e /test SH的全栈笔记
复制代码


创建临时顺序节点

create -e -s /node_name SH的全栈笔记
复制代码


Zookeeper 的用途

我们通过一些具体的例子,来了解 Zookeeper 的详细用途,它不仅仅只是被当作分布式锁使用。

元数据管理

我们都知道,Kafka 在运行时会依赖一个 Zookeeper 的集群。Kafka 通过 Zookeeper 来管理集群的相关元数据,并通过 Zookeeper 进行 Leader 选举。


Tips: 但是即将发布的 Kafka 2.8 版本中,Zookeeper 已经不是一个必需的组件了。这块我暂时还没有时间去细看,不过我估计可能会跟 RocketMQ 中处理的方式差不多,将其集群的元数据放到 Kafka 本身来处理。

分布式锁

基于 Zookeeper 的分布式锁其实流程很简单。首先我们需要知道加分布式锁的本质是什么?


答案是创建临时顺序节点


当某个客户端加锁成功之后,实际上则是成功的在 Zookeeper 上创建了临时顺序节点。我们知道,分布式锁能够使同一时间只能有一个能够访问某种资源。那这就必然会涉及到分布式锁的竞争,那问题来了,当前这个客户端是如何感知抢到了锁呢?


其实在客户端侧会有一定的逻辑,假设加锁的 key 为/locks/modify_users


首先,客户端会发起加锁请求,然后会在 Zookeeper 上创建持久节点locks,然后会在该节点下创建临时顺序节点。临时顺序节点的创建示例,如下图所示。



当客户端成功创建了节点之后,还会获取其同级的所有节点。也就是上图中的所有modify_users000000000x的节点。


此时客户端会根据 10 位的自增序号去判断,当前自己创建的节点是否是所有的节点中最小的那个,如果是最小的则自己获取到了分布式锁


你可能会问,那如果我不是最小的怎么办呢?而且我的节点都已经创建了。如果不是最小的,说明当前客户端并没有抢到锁。按照我们的认知,如果没有竞争到分布式锁,则会等待。等待的底层都做了什么?我们用实际例子来捋一遍。


假设 Zookeeper 中已经有了如下的节点。



例如当前客户端是 B 创建的节点是modify_users0000000002,那么很明显 B 没有抢到锁,因为已经有比它还要小的由客户端 A 创建的节点modify_users0000000001


此时客户端 B 会对节点modify_users0000000001注册一个监听器,对于该节点的任意更新都将触发对应的操作。



当其被删除之后,就会唤醒客户端 B 的线程,此时客户端 B 会再次进行判断自己是否是序号最小的一个节点,此时modify_users0000000002明显是最小的节点,故客户端 B 加锁成功


为了让你更加直观的了解这个过程,我把流程浓缩成了下面这幅流程图。


分布式协调

我们都知道,在很多场景下要保证一致性都会采用经典的 2PC(两阶段提交),例如 MySQL 中 Redo Log 和 Binlog 提交的数据一致性保障就是采用的 2PC,详情可以看基于Redo Log和Undo Log的MySQL崩溃恢复流程


在 2PC 中存在两种角色,分别是参与者(Participant)协调者(Coordinator),协调者负责统一的调度所有分布式节点的执行逻辑。具体协调啥呢?举个例子。


例如在 2PC 的 Commit 阶段,两个参与者 A、B,A 的 commit 操作成功了,但不幸的是 B 失败了。此时协调者就需要向 A 发送 Rollback 操作。Zookeeper 大概就是这样一个角色。

发布订阅

由于 Zookeeper 自带了监听器(Watch)的功能,所以发布订阅也顺理成章的成为了 Zookeeper 的应用之一。例如在某个配置节点上注册了监听器,那么该配置一旦发布变更,对应的服务就能实时的感知到配置更改,从而达到配置的动态更新的目的。


给个简单的 Watch 使用示例。


命名服务

用大白话来说,命名服务主要有两种。


  • 单纯的利用 Zookeeper 的文件系统特性,存储结构化的文件

  • 利用文件特性和顺序节点的特性,来生成全局的唯一标识


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


欢迎微信搜索关注【SH 的全栈笔记】,回复【队列】获取 MQ 学习资料,包含基础概念解析和 RocketMQ 详细的源码解析,持续更新中。

好了以上就是本篇博客的全部内容了,如果你觉得这篇文章对你有帮助,还麻烦点个赞关个注分个享留个言



发布于: 2021 年 04 月 21 日阅读数: 25
用户头像

公众号【SH的全栈笔记】 2018.10.17 加入

还未添加个人简介

评论

发布
暂无评论
Zookeeper基础原理&应用场景详解