mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-06-20 22:17:09 +08:00
[refractor]zookeeper部分文章重构完善
This commit is contained in:
parent
98ad66183c
commit
7fd6cedc8b
@ -321,9 +321,9 @@ SSO(Single Sign On)即单点登录说的是用户登陆多个子系统的其中
|
||||
|
||||
> 前两篇文章可能有内容重合部分,推荐都看一遍。
|
||||
|
||||
1. [【入门】ZooKeeper 相关概念总结](docs/system-design/framework/ZooKeeper.md)
|
||||
2. [【进阶】Zookeeper 原理简单入门!](docs/system-design/framework/ZooKeeper-plus.md)
|
||||
3. [【拓展】ZooKeeper 数据模型和常见命令](docs/system-design/framework/ZooKeeper数据模型和常见命令.md)
|
||||
1. [【入门】ZooKeeper 相关概念总结 01](docs/system-design/framework/zookeeper-intro.md)
|
||||
2. [【进阶】ZooKeeper 相关概念总结 02](docs/system-design/framework/zookeeper-plus.md)
|
||||
3. [【实战】ZooKeeper 实战](docs/system-design/framework/zookeeper-in-action.md)
|
||||
|
||||
#### 其他
|
||||
|
||||
|
@ -1,8 +1,41 @@
|
||||
[FrancisQ](https://juejin.im/user/5c33853851882525ea106810) 投稿。
|
||||
|
||||
# ZooKeeper
|
||||
|
||||
## 好久不见
|
||||
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->
|
||||
|
||||
<!-- code_chunk_output -->
|
||||
|
||||
- [1. 好久不见](#1-好久不见)
|
||||
- [2. 什么是ZooKeeper](#2-什么是zookeeper)
|
||||
- [3. 一致性问题](#3-一致性问题)
|
||||
- [4. 一致性协议和算法](#4-一致性协议和算法)
|
||||
- [4.1. 2PC(两阶段提交)](#41-2pc两阶段提交)
|
||||
- [4.2. 3PC(三阶段提交)](#42-3pc三阶段提交)
|
||||
- [4.3. `Paxos` 算法](#43-paxos-算法)
|
||||
- [4.3.1. prepare 阶段](#431-prepare-阶段)
|
||||
- [4.3.2. accept 阶段](#432-accept-阶段)
|
||||
- [4.3.3. `paxos` 算法的死循环问题](#433-paxos-算法的死循环问题)
|
||||
- [5. 引出 `ZAB`](#5-引出-zab)
|
||||
- [5.1. `Zookeeper` 架构](#51-zookeeper-架构)
|
||||
- [5.2. `ZAB` 中的三个角色](#52-zab-中的三个角色)
|
||||
- [5.3. 消息广播模式](#53-消息广播模式)
|
||||
- [5.4. 崩溃恢复模式](#54-崩溃恢复模式)
|
||||
- [6. Zookeeper的几个理论知识](#6-zookeeper的几个理论知识)
|
||||
- [6.1. 数据模型](#61-数据模型)
|
||||
- [6.2. 会话](#62-会话)
|
||||
- [6.3. ACL](#63-acl)
|
||||
- [6.4. Watcher机制](#64-watcher机制)
|
||||
- [7. Zookeeper的几个典型应用场景](#7-zookeeper的几个典型应用场景)
|
||||
- [7.1. 选主](#71-选主)
|
||||
- [7.2. 分布式锁](#72-分布式锁)
|
||||
- [7.3. 命名服务](#73-命名服务)
|
||||
- [7.4. 集群管理和注册中心](#74-集群管理和注册中心)
|
||||
- [8. 总结](#8-总结)
|
||||
|
||||
<!-- /code_chunk_output -->
|
||||
|
||||
|
||||
## 1. 好久不见
|
||||
|
||||
离上一篇文章的发布也快一个月了,想想已经快一个月没写东西了,其中可能有期末考试、课程设计和驾照考试,但这都不是借口!
|
||||
|
||||
@ -10,7 +43,7 @@
|
||||
|
||||
> 文章很长,先赞后看,养成习惯。❤️ 🧡 💛 💚 💙 💜
|
||||
|
||||
## 什么是ZooKeeper
|
||||
## 2. 什么是ZooKeeper
|
||||
|
||||
`ZooKeeper` 由 `Yahoo` 开发,后来捐赠给了 `Apache` ,现已成为 `Apache` 顶级项目。`ZooKeeper` 是一个开源的分布式应用程序协调服务器,其为分布式系统提供一致性服务。其一致性是通过基于 `Paxos` 算法的 `ZAB` 协议完成的。其主要功能包括:配置维护、分布式同步、集群管理、分布式事务等。
|
||||
|
||||
@ -34,7 +67,7 @@
|
||||
|
||||
比如各个分布式组件如何协调起来,如何减少各个系统之间的耦合度,分布式事务的处理,如何去配置整个分布式系统等等。`ZooKeeper` 主要就是解决这些问题的。
|
||||
|
||||
## 一致性问题
|
||||
## 3. 一致性问题
|
||||
|
||||
设计一个分布式系统必定会遇到一个问题—— **因为分区容忍性(partition tolerance)的存在,就必定要求我们需要在系统可用性(availability)和数据一致性(consistency)中做出权衡** 。这就是著名的 `CAP` 定理。
|
||||
|
||||
@ -44,7 +77,7 @@
|
||||
|
||||
而上述前者就是 `Eureka` 的处理方式,它保证了AP(可用性),后者就是我们今天所要将的 `ZooKeeper` 的处理方式,它保证了CP(数据一致性)。
|
||||
|
||||
## 一致性协议和算法
|
||||
## 4. 一致性协议和算法
|
||||
|
||||
而为了解决数据一致性问题,在科学家和程序员的不断探索中,就出现了很多的一致性协议和算法。比如 2PC(两阶段提交),3PC(三阶段提交),Paxos算法等等。
|
||||
|
||||
@ -56,7 +89,7 @@
|
||||
|
||||
而为什么要去解决数据一致性的问题?你想想,如果一个秒杀系统将服务拆分成了下订单和加积分服务,这两个服务部署在不同的机器上了,万一在消息的传播过程中积分系统宕机了,总不能你这边下了订单却没加积分吧?你总得保证两边的数据需要一致吧?
|
||||
|
||||
### 2PC(两阶段提交)
|
||||
### 4.1. 2PC(两阶段提交)
|
||||
|
||||
两阶段提交是一种保证分布式系统数据一致性的协议,现在很多数据库都是采用的两阶段提交协议来完成 **分布式事务** 的处理。
|
||||
|
||||
@ -86,7 +119,7 @@
|
||||
* **阻塞问题**,即当协调者发送 `prepare` 请求,参与者收到之后如果能处理那么它将会进行事务的处理但并不提交,这个时候会一直占用着资源不释放,如果此时协调者挂了,那么这些资源都不会再释放了,这会极大影响性能。
|
||||
* **数据不一致问题**,比如当第二阶段,协调者只发送了一部分的 `commit` 请求就挂了,那么也就意味着,收到消息的参与者会进行事务的提交,而后面没收到的则不会进行事务提交,那么这时候就会产生数据不一致性问题。
|
||||
|
||||
### 3PC(三阶段提交)
|
||||
### 4.2. 3PC(三阶段提交)
|
||||
|
||||
因为2PC存在的一系列问题,比如单点,容错机制缺陷等等,从而产生了 **3PC(三阶段提交)** 。那么这三阶段又分别是什么呢?
|
||||
|
||||
@ -104,13 +137,13 @@
|
||||
|
||||
所以,要解决一致性问题还需要靠 `Paxos` 算法⭐️ ⭐️ ⭐️ 。
|
||||
|
||||
### `Paxos` 算法
|
||||
### 4.3. `Paxos` 算法
|
||||
|
||||
`Paxos` 算法是基于**消息传递且具有高度容错特性的一致性算法**,是目前公认的解决分布式一致性问题最有效的算法之一,**其解决的问题就是在分布式系统中如何就某个值(决议)达成一致** 。
|
||||
|
||||
在 `Paxos` 中主要有三个角色,分别为 `Proposer提案者`、`Acceptor表决者`、`Learner学习者`。`Paxos` 算法和 `2PC` 一样,也有两个阶段,分别为 `Prepare` 和 `accept` 阶段。
|
||||
|
||||
#### prepare 阶段
|
||||
#### 4.3.1. prepare 阶段
|
||||
|
||||
* `Proposer提案者`:负责提出 `proposal`,每个提案者在提出提案时都会首先获取到一个 **具有全局唯一性的、递增的提案编号N**,即在整个集群中是唯一的编号 N,然后将该编号赋予其要提出的提案,在**第一阶段是只将提案编号发送给所有的表决者**。
|
||||
* `Acceptor表决者`:每个表决者在 `accept` 某提案后,会将该提案编号N记录在本地,这样每个表决者中保存的已经被 accept 的提案中会存在一个**编号最大的提案**,其编号假设为 `maxN`。每个表决者仅会 `accept` 编号大于自己本地 `maxN` 的提案,在批准提案时表决者会将以前接受过的最大编号的提案作为响应反馈给 `Proposer` 。
|
||||
@ -119,7 +152,7 @@
|
||||
|
||||

|
||||
|
||||
#### accept 阶段
|
||||
#### 4.3.2. accept 阶段
|
||||
|
||||
当一个提案被 `Proposer` 提出后,如果 `Proposer` 收到了超过半数的 `Acceptor` 的批准(`Proposer` 本身同意),那么此时 `Proposer` 会给所有的 `Acceptor` 发送真正的提案(你可以理解为第一阶段为试探),这个时候 `Proposer` 就会发送提案的内容和提案编号。
|
||||
|
||||
@ -135,7 +168,7 @@
|
||||
|
||||
> 对于 `Learner` 来说如何去学习 `Acceptor` 批准的提案内容,这有很多方式,读者可以自己去了解一下,这里不做过多解释。
|
||||
|
||||
#### `paxos` 算法的死循环问题
|
||||
#### 4.3.3. `paxos` 算法的死循环问题
|
||||
|
||||
其实就有点类似于两个人吵架,小明说我是对的,小红说我才是对的,两个人据理力争的谁也不让谁🤬🤬。
|
||||
|
||||
@ -147,15 +180,15 @@
|
||||
|
||||
那么如何解决呢?很简单,人多了容易吵架,我现在 **就允许一个能提案** 就行了。
|
||||
|
||||
## 引出 `ZAB`
|
||||
## 5. 引出 `ZAB`
|
||||
|
||||
### `Zookeeper` 架构
|
||||
### 5.1. `Zookeeper` 架构
|
||||
|
||||
作为一个优秀高效且可靠的分布式协调框架,`ZooKeeper` 在解决分布式数据一致性问题时并没有直接使用 `Paxos` ,而是专门定制了一致性协议叫做 `ZAB(ZooKeeper Automic Broadcast)` 原子广播协议,该协议能够很好地支持 **崩溃恢复** 。
|
||||
|
||||

|
||||
|
||||
### `ZAB` 中的三个角色
|
||||
### 5.2. `ZAB` 中的三个角色
|
||||
|
||||
和介绍 `Paxos` 一样,在介绍 `ZAB` 协议之前,我们首先来了解一下在 `ZAB` 中三个主要的角色,`Leader 领导者`、`Follower跟随者`、`Observer观察者` 。
|
||||
|
||||
@ -165,7 +198,7 @@
|
||||
|
||||
在 `ZAB` 协议中对 `zkServer`(即上面我们说的三个角色的总称) 还有两种模式的定义,分别是 **消息广播** 和 **崩溃恢复** 。
|
||||
|
||||
### 消息广播模式
|
||||
### 5.3. 消息广播模式
|
||||
|
||||
说白了就是 `ZAB` 协议是如何处理写请求的,上面我们不是说只有 `Leader` 能处理写请求嘛?那么我们的 `Follower` 和 `Observer` 是不是也需要 **同步更新数据** 呢?总不能数据只在 `Leader` 中更新了,其他角色都没有得到更新吧?
|
||||
|
||||
@ -185,7 +218,7 @@
|
||||
|
||||
定义这个的原因也是为了顺序性,每个 `proposal` 在 `Leader` 中生成后需要 **通过其 `ZXID` 来进行排序** ,才能得到处理。
|
||||
|
||||
### 崩溃恢复模式
|
||||
### 5.4. 崩溃恢复模式
|
||||
|
||||
说到崩溃恢复我们首先要提到 `ZAB` 中的 `Leader` 选举算法,当系统出现崩溃影响最大应该是 `Leader` 的崩溃,因为我们只有一个 `Leader` ,所以当 `Leader` 出现问题的时候我们势必需要重新选举 `Leader` 。
|
||||
|
||||
@ -229,13 +262,13 @@
|
||||
|
||||

|
||||
|
||||
## Zookeeper的几个理论知识
|
||||
## 6. Zookeeper的几个理论知识
|
||||
|
||||
了解了 `ZAB` 协议还不够,它仅仅是 `Zookeeper` 内部实现的一种方式,而我们如何通过 `Zookeeper` 去做一些典型的应用场景呢?比如说集群管理,分布式锁,`Master` 选举等等。
|
||||
|
||||
这就涉及到如何使用 `Zookeeper` 了,但在使用之前我们还需要掌握几个概念。比如 `Zookeeper` 的 **数据模型** 、**会话机制**、**ACL**、**Watcher机制** 等等。
|
||||
|
||||
### 数据模型
|
||||
### 6.1. 数据模型
|
||||
|
||||
`zookeeper` 数据存储结构与标准的 `Unix` 文件系统非常相似,都是在根节点下挂很多子节点(树型)。但是 `zookeeper` 中没有文件系统中目录与文件的概念,而是 **使用了 `znode` 作为数据节点** 。`znode` 是 `zookeeper` 中的最小数据单元,每个 `znode` 上都可以保存数据,同时还可以挂载子节点,形成一个树形化命名空间。
|
||||
|
||||
@ -264,13 +297,13 @@
|
||||
* `numChildre`:该节点的子节点个数,如果为临时节点为0。
|
||||
* `pzxid`:该节点子节点列表最后一次被修改时的事务ID,注意是子节点的 **列表** ,不是内容。
|
||||
|
||||
### 会话
|
||||
### 6.2. 会话
|
||||
|
||||
我想这个对于后端开发的朋友肯定不陌生,不就是 `session` 吗?只不过 `zk` 客户端和服务端是通过 **`TCP` 长连接** 维持的会话机制,其实对于会话来说你可以理解为 **保持连接状态** 。
|
||||
|
||||
在 `zookeeper` 中,会话还有对应的事件,比如 `CONNECTION_LOSS 连接丢失事件` 、`SESSION_MOVED 会话转移事件` 、`SESSION_EXPIRED 会话超时失效事件` 。
|
||||
|
||||
### ACL
|
||||
### 6.3. ACL
|
||||
|
||||
`ACL` 为 `Access Control Lists` ,它是一种权限控制。在 `zookeeper` 中定义了5种权限,它们分别为:
|
||||
|
||||
@ -280,19 +313,19 @@
|
||||
* `DELETE`:删除子节点的权限。
|
||||
* `ADMIN`:设置节点 ACL 的权限。
|
||||
|
||||
### Watcher机制
|
||||
### 6.4. Watcher机制
|
||||
|
||||
`Watcher` 为事件监听器,是 `zk` 非常重要的一个特性,很多功能都依赖于它,它有点类似于订阅的方式,即客户端向服务端 **注册** 指定的 `watcher` ,当服务端符合了 `watcher` 的某些事件或要求则会 **向客户端发送事件通知** ,客户端收到通知后找到自己定义的 `Watcher` 然后 **执行相应的回调方法** 。
|
||||
|
||||

|
||||
|
||||
## Zookeeper的几个典型应用场景
|
||||
## 7. Zookeeper的几个典型应用场景
|
||||
|
||||
前面说了这么多的理论知识,你可能听得一头雾水,这些玩意有啥用?能干啥事?别急,听我慢慢道来。
|
||||
|
||||

|
||||
|
||||
### 选主
|
||||
### 7.1. 选主
|
||||
|
||||
还记得上面我们的所说的临时节点吗?因为 `Zookeeper` 的强一致性,能够很好地在保证 **在高并发的情况下保证节点创建的全局唯一性** (即无法重复创建同样的节点)。
|
||||
|
||||
@ -306,7 +339,7 @@
|
||||
|
||||
总的来说,我们可以完全 **利用 临时节点、节点状态 和 `watcher` 来实现选主的功能**,临时节点主要用来选举,节点状态和`watcher` 可以用来判断 `master` 的活性和进行重新选举。
|
||||
|
||||
### 分布式锁
|
||||
### 7.2. 分布式锁
|
||||
|
||||
分布式锁的实现方式有很多种,比如 `Redis` 、数据库 、`zookeeper` 等。个人认为 `zookeeper` 在实现分布式锁这方面是非常非常简单的。
|
||||
|
||||
@ -330,13 +363,13 @@
|
||||
|
||||
具体怎么做呢?其实也很简单,你可以让 **读请求监听比自己小的最后一个写请求节点,写请求只监听比自己小的最后一个节点** ,感兴趣的小伙伴可以自己去研究一下。
|
||||
|
||||
### 命名服务
|
||||
### 7.3. 命名服务
|
||||
|
||||
如何给一个对象设置ID,大家可能都会想到 `UUID`,但是 `UUID` 最大的问题就在于它太长了。。。(太长不一定是好事,嘿嘿嘿)。那么在条件允许的情况下,我们能不能使用 `zookeeper` 来实现呢?
|
||||
|
||||
我们之前提到过 `zookeeper` 是通过 **树形结构** 来存储数据节点的,那也就是说,对于每个节点的 **全路径**,它必定是唯一的,我们可以使用节点的全路径作为命名方式了。而且更重要的是,路径是我们可以自己定义的,这对于我们对有些有语意的对象的ID设置可以更加便于理解。
|
||||
|
||||
### 集群管理和注册中心
|
||||
### 7.4. 集群管理和注册中心
|
||||
|
||||
看到这里是不是觉得 `zookeeper` 实在是太强大了,它怎么能这么能干!
|
||||
|
||||
@ -352,7 +385,7 @@
|
||||
|
||||

|
||||
|
||||
## 总结
|
||||
## 8. 总结
|
||||
|
||||
看到这里的同学实在是太有耐心了👍👍👍,如果觉得我写得不错的话点个赞哈。
|
||||
|
||||
|
@ -1,185 +0,0 @@
|
||||

|
||||
## 前言
|
||||
|
||||
相信大家对 ZooKeeper 应该不算陌生。但是你真的了解 ZooKeeper 是个什么东西吗?如果别人/面试官让你给他讲讲 ZooKeeper 是个什么东西,你能回答到什么地步呢?
|
||||
|
||||
我本人曾经使用过 ZooKeeper 作为 Dubbo 的注册中心,另外在搭建 solr 集群的时候,我使用到了 ZooKeeper 作为 solr 集群的管理工具。前几天,总结项目经验的时候,我突然问自己 ZooKeeper 到底是个什么东西?想了半天,脑海中只是简单的能浮现出几句话:“①Zookeeper 可以被用作注册中心。 ②Zookeeper 是 Hadoop 生态系统的一员;③构建 Zookeeper 集群的时候,使用的服务器最好是奇数台。” 可见,我对于 Zookeeper 的理解仅仅是停留在了表面。
|
||||
|
||||
所以,**通过本文,希望带大家稍微详细的了解一下 ZooKeeper 。如果没有学过 ZooKeeper ,那么本文将会是你进入 ZooKeeper 大门的垫脚砖。如果你已经接触过 ZooKeeper ,那么本文将带你回顾一下 ZooKeeper 的一些基础概念。**
|
||||
|
||||
最后,**本文只涉及 ZooKeeper 的一些概念,并不涉及 ZooKeeper 的使用以及 ZooKeeper 集群的搭建。** 网上有介绍 ZooKeeper 的使用以及搭建 ZooKeeper 集群的文章,大家有需要可以自行查阅。
|
||||
|
||||
## 一 什么是 ZooKeeper
|
||||
|
||||
### ZooKeeper 的由来
|
||||
|
||||
**下面这段内容摘自《从Paxos到Zookeeper 》第四章第一节的某段内容,推荐大家阅读以下:**
|
||||
|
||||
> Zookeeper最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,**雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。**
|
||||
>
|
||||
>关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的Pig项目),雅虎的工程师希望给这个项目也取一个动物的名字。时任研究院的首席科学家RaghuRamakrishnan开玩笑地说:“在这样下去,我们这儿就变成动物园了!”此话一出,大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起,**雅虎的整个分布式系统看上去就像一个大型的动物园了,而Zookeeper正好要用来进行分布式环境的协调一一于是,Zookeeper的名字也就由此诞生了。**
|
||||
|
||||
|
||||
### 1.1 ZooKeeper 概览
|
||||
|
||||
ZooKeeper 是一个开源的分布式协调服务,ZooKeeper框架最初是在“Yahoo!"上构建的,用于以简单而稳健的方式访问他们的应用程序。 后来,Apache ZooKeeper成为Hadoop,HBase和其他分布式框架使用的有组织服务的标准。 例如,Apache HBase使用ZooKeeper跟踪分布式数据的状态。**ZooKeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。**
|
||||
|
||||
> **原语:** 操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性·即原语的执行必须是连续的,在执行过程中不允许被中断。
|
||||
|
||||
**ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。**
|
||||
|
||||
**Zookeeper 一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心(提供发布订阅服务)。** 服务生产者将自己提供的服务注册到Zookeeper中心,服务的消费者在进行服务调用的时候先到Zookeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据。如下图所示,在 Dubbo架构中 Zookeeper 就担任了注册中心这一角色。
|
||||
|
||||

|
||||
|
||||
### 1.2 结合个人使用情况的讲一下 ZooKeeper
|
||||
|
||||
在我自己做过的项目中,主要使用到了 ZooKeeper 作为 Dubbo 的注册中心(Dubbo 官方推荐使用 ZooKeeper注册中心)。另外在搭建 solr 集群的时候,我使用 ZooKeeper 作为 solr 集群的管理工具。这时,ZooKeeper 主要提供下面几个功能:1、集群管理:容错、负载均衡。2、配置文件的集中管理3、集群的入口。
|
||||
|
||||
|
||||
我个人觉得在使用 ZooKeeper 的时候,最好是使用 集群版的 ZooKeeper 而不是单机版的。官网给出的架构图就描述的是一个集群版的 ZooKeeper 。通常 3 台服务器就可以构成一个 ZooKeeper 集群了。
|
||||
|
||||
**为什么最好使用奇数台服务器构成 ZooKeeper 集群?**
|
||||
|
||||
所谓的zookeeper容错是指,当宕掉几个zookeeper服务器之后,剩下的个数必须大于宕掉的个数的话整个zookeeper才依然可用。假如我们的集群中有n台zookeeper服务器,那么也就是剩下的服务数必须大于n/2。先说一下结论,2n和2n-1的容忍度是一样的,都是n-1,大家可以先自己仔细想一想,这应该是一个很简单的数学问题了。
|
||||
比如假如我们有3台,那么最大允许宕掉1台zookeeper服务器,如果我们有4台的的时候也同样只允许宕掉1台。
|
||||
假如我们有5台,那么最大允许宕掉2台zookeeper服务器,如果我们有6台的的时候也同样只允许宕掉2台。
|
||||
|
||||
综上,何必增加那一个不必要的zookeeper呢?
|
||||
|
||||
## 二 关于 ZooKeeper 的一些重要概念
|
||||
|
||||
### 2.1 重要概念总结
|
||||
|
||||
- **ZooKeeper 本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)。**
|
||||
- **为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。**
|
||||
- **ZooKeeper 将数据保存在内存中,这也就保证了 高吞吐量和低延迟**(但是内存限制了能够存储的容量不太大,此限制也是保持znode中存储的数据量较小的进一步原因)。
|
||||
- **ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。**(“读”多于“写”是协调服务的典型场景。)
|
||||
- **ZooKeeper有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。**
|
||||
- ZooKeeper 底层其实只提供了两个功能:①管理(存储、读取)用户程序提交的数据;②为用户程序提供数据节点监听服务。
|
||||
|
||||
**下面关于会话(Session)、 Znode、版本、Watcher、ACL概念的总结都在《从Paxos到Zookeeper 》第四章第一节以及第七章第八节有提到,感兴趣的可以看看!**
|
||||
|
||||
### 2.2 会话(Session)
|
||||
|
||||
Session 指的是 ZooKeeper 服务器与客户端会话。**在 ZooKeeper 中,一个客户端连接是指客户端和服务器之间的一个 TCP 长连接**。客户端启动的时候,首先会与服务器建立一个 TCP 连接,从第一次连接建立开始,客户端会话的生命周期也开始了。**通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向Zookeeper服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的Watch事件通知。** Session的`sessionTimeout`值用来设置一个客户端会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,**只要在`sessionTimeout`规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。**
|
||||
|
||||
**在为客户端创建会话之前,服务端首先会为每个客户端都分配一个sessionID。由于 sessionID 是 Zookeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 sessionID 的,因此,无论是哪台服务器为客户端分配的 sessionID,都务必保证全局唯一。**
|
||||
|
||||
### 2.3 Znode
|
||||
|
||||
**在谈到分布式的时候,我们通常说的“节点"是指组成集群的每一台机器。然而,在Zookeeper中,“节点"分为两类,第一类同样是指构成集群的机器,我们称之为机器节点;第二类则是指数据模型中的数据单元,我们称之为数据节点一一ZNode。**
|
||||
|
||||
Zookeeper将所有数据存储在内存中,数据模型是一棵树(Znode Tree),由斜杠(/)的进行分割的路径,就是一个Znode,例如/foo/path1。每个上都会保存自己的数据内容,同时还会保存一系列属性信息。
|
||||
|
||||
**在Zookeeper中,node可以分为持久节点和临时节点两类。所谓持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。而临时节点就不一样了,它的生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。** 另外,ZooKeeper还允许用户为每个节点添加一个特殊的属性:**SEQUENTIAL**.一旦节点被标记上这个属性,那么在这个节点被创建的时候,Zookeeper会自动在其节点名后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字。
|
||||
|
||||
### 2.4 版本
|
||||
|
||||
在前面我们已经提到,Zookeeper 的每个 ZNode 上都会存储数据,对应于每个ZNode,Zookeeper 都会为其维护一个叫作 **Stat** 的数据结构,Stat 中记录了这个 ZNode 的三个数据版本,分别是version(当前ZNode的版本)、cversion(当前ZNode子节点的版本)和 aversion(当前ZNode的ACL版本)。
|
||||
|
||||
|
||||
### 2.5 Watcher
|
||||
|
||||
**Watcher(事件监听器),是Zookeeper中的一个很重要的特性。Zookeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是Zookeeper实现分布式协调服务的重要特性。**
|
||||
|
||||
### 2.6 ACL
|
||||
|
||||
Zookeeper采用ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。Zookeeper 定义了如下5种权限。
|
||||
|
||||

|
||||
|
||||
其中尤其需要注意的是,CREATE和DELETE这两种权限都是针对子节点的权限控制。
|
||||
|
||||
## 三 ZooKeeper 特点
|
||||
|
||||
- **顺序一致性:** 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
|
||||
- **原子性:** 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
|
||||
- **单一系统映像 :** 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
|
||||
- **可靠性:** 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。
|
||||
|
||||
## 四 ZooKeeper 设计目标
|
||||
|
||||
### 4.1 简单的数据模型
|
||||
|
||||
ZooKeeper 允许分布式进程通过共享的层次结构命名空间进行相互协调,这与标准文件系统类似。 名称空间由 ZooKeeper 中的数据寄存器组成 - 称为znode,这些类似于文件和目录。 与为存储设计的典型文件系统不同,ZooKeeper数据保存在内存中,这意味着ZooKeeper可以实现高吞吐量和低延迟。
|
||||
|
||||

|
||||
|
||||
### 4.2 可构建集群
|
||||
|
||||
**为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么zookeeper本身仍然是可用的。** 客户端在使用 ZooKeeper 时,需要知道集群机器列表,通过与集群中的某一台机器建立 TCP 连接来使用服务,客户端使用这个TCP链接来发送请求、获取结果、获取监听事件以及发送心跳包。如果这个连接异常断开了,客户端可以连接到另外的机器上。
|
||||
|
||||
**ZooKeeper 官方提供的架构图:**
|
||||
|
||||

|
||||
|
||||
上图中每一个Server代表一个安装Zookeeper服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 Zab 协议(Zookeeper Atomic Broadcast)来保持数据的一致性。
|
||||
|
||||
### 4.3 顺序访问
|
||||
|
||||
**对于来自客户端的每个更新请求,ZooKeeper 都会分配一个全局唯一的递增编号,这个编号反应了所有事务操作的先后顺序,应用程序可以使用 ZooKeeper 这个特性来实现更高层次的同步原语。** **这个编号也叫做时间戳——zxid(Zookeeper Transaction Id)**
|
||||
|
||||
### 4.4 高性能
|
||||
|
||||
**ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)**
|
||||
|
||||
## 五 ZooKeeper 集群角色介绍
|
||||
|
||||
**最典型集群模式: Master/Slave 模式(主备模式)**。在这种模式中,通常 Master服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。
|
||||
|
||||
但是,**在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了Leader、Follower 和 Observer 三种角色**。如下图所示
|
||||
|
||||

|
||||
|
||||
**ZooKeeper 集群中的所有机器通过一个 Leader 选举过程来选定一台称为 “Leader” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。**
|
||||
|
||||

|
||||
|
||||
**当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进人恢复模式并选举产生新的Leader服务器。这个过程大致是这样的:**
|
||||
|
||||
1. Leader election(选举阶段):节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。
|
||||
2. Discovery(发现阶段):在这个阶段,followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。
|
||||
3. Synchronization(同步阶段):同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后
|
||||
准 leader 才会成为真正的 leader。
|
||||
4. Broadcast(广播阶段)
|
||||
到了这个阶段,Zookeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。
|
||||
|
||||
## 六 ZooKeeper &ZAB 协议&Paxos算法
|
||||
|
||||
### 6.1 ZAB 协议&Paxos算法
|
||||
|
||||
Paxos 算法应该可以说是 ZooKeeper 的灵魂了。但是,ZooKeeper 并没有完全采用 Paxos算法 ,而是使用 ZAB 协议作为其保证数据一致性的核心算法。另外,在ZooKeeper的官方文档中也指出,ZAB协议并不像 Paxos 算法那样,是一种通用的分布式一致性算法,它是一种特别为Zookeeper设计的崩溃可恢复的原子消息广播算法。
|
||||
|
||||
### 6.2 ZAB 协议介绍
|
||||
|
||||
**ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。**
|
||||
|
||||
### 6.3 ZAB 协议两种基本的模式:崩溃恢复和消息广播
|
||||
|
||||
ZAB协议包括两种基本的模式,分别是 **崩溃恢复和消息广播**。当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进人恢复模式并选举产生新的Leader服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。其中,**所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致**。
|
||||
|
||||
**当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步,那么整个服务框架就可以进人消息广播模式了。** 当一台同样遵守ZAB协议的服务器启动后加人到集群中时,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么新加人的服务器就会自觉地进人数据恢复模式:找到Leader所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。正如上文介绍中所说的,ZooKeeper设计成只允许唯一的一个Leader服务器来进行事务请求的处理。Leader服务器在接收到客户端的事务请求后,会生成对应的事务提案并发起一轮广播协议;而如果集群中的其他机器接收到客户端的事务请求,那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器。
|
||||
|
||||
关于 **ZAB 协议&Paxos算法** 需要讲和理解的东西太多了,说实话,笔主到现在不太清楚这俩兄弟的具体原理和实现过程。推荐阅读下面两篇文章:
|
||||
|
||||
- [图解 Paxos 一致性协议](http://codemacro.com/2014/10/15/explain-poxos/)
|
||||
- [Zookeeper ZAB 协议分析](https://dbaplus.cn/news-141-1875-1.html)
|
||||
|
||||
关于如何使用 zookeeper 实现分布式锁,可以查看下面这篇文章:
|
||||
|
||||
-
|
||||
[10分钟看懂!基于Zookeeper的分布式锁](https://blog.csdn.net/qiangcuo6087/article/details/79067136)
|
||||
|
||||
## 六 总结
|
||||
|
||||
通过阅读本文,想必大家已从 **①ZooKeeper的由来。** -> **②ZooKeeper 到底是什么 。**-> **③ ZooKeeper 的一些重要概念**(会话(Session)、 Znode、版本、Watcher、ACL)-> **④ZooKeeper 的特点。** -> **⑤ZooKeeper 的设计目标。**-> **⑥ ZooKeeper 集群角色介绍** (Leader、Follower 和 Observer 三种角色)-> **⑦ZooKeeper &ZAB 协议&Paxos算法。** 这七点了解了 ZooKeeper 。
|
||||
|
||||
## 参考
|
||||
|
||||
- 《从Paxos到Zookeeper 》
|
||||
- https://cwiki.apache.org/confluence/display/ZOOKEEPER/ProjectDescription
|
||||
- https://cwiki.apache.org/confluence/display/ZOOKEEPER/Index
|
||||
- https://www.cnblogs.com/raphael5200/p/5285583.html
|
||||
- https://zhuanlan.zhihu.com/p/30024403
|
||||
|
@ -1,200 +0,0 @@
|
||||
<!-- MarkdownTOC -->
|
||||
|
||||
- [ZooKeeper 数据模型](#zookeeper-数据模型)
|
||||
- [ZNode\(数据节点\)的结构](#znode数据节点的结构)
|
||||
- [测试 ZooKeeper 中的常见操作](#测试-zookeeper-中的常见操作)
|
||||
- [连接 ZooKeeper 服务](#连接-zookeeper-服务)
|
||||
- [查看常用命令\(help 命令\)](#查看常用命令help-命令)
|
||||
- [创建节点\(create 命令\)](#创建节点create-命令)
|
||||
- [更新节点数据内容\(set 命令\)](#更新节点数据内容set-命令)
|
||||
- [获取节点的数据\(get 命令\)](#获取节点的数据get-命令)
|
||||
- [查看某个目录下的子节点\(ls 命令\)](#查看某个目录下的子节点ls-命令)
|
||||
- [查看节点状态\(stat 命令\)](#查看节点状态stat-命令)
|
||||
- [查看节点信息和状态\(ls2 命令\)](#查看节点信息和状态ls2-命令)
|
||||
- [删除节点\(delete 命令\)](#删除节点delete-命令)
|
||||
- [参考](#参考)
|
||||
|
||||
<!-- /MarkdownTOC -->
|
||||
|
||||
> 看本文之前如果你没有安装 ZooKeeper 的话,可以参考这篇文章:[《使用 SpringBoot+Dubbo 搭建一个简单分布式服务》](https://github.com/Snailclimb/springboot-integration-examples/blob/master/md/springboot-dubbo.md) 的 “开始实战 1 :zookeeper 环境安装搭建” 这部分进行安装(Centos7.4 环境下)。如果你想对 ZooKeeper 有一个整体了解的话,可以参考这篇文章:[《可能是把 ZooKeeper 概念讲的最清楚的一篇文章》](https://github.com/Snailclimb/JavaGuide/blob/master/%E4%B8%BB%E6%B5%81%E6%A1%86%E6%9E%B6/ZooKeeper.md)
|
||||
|
||||
### ZooKeeper 数据模型
|
||||
|
||||
ZNode(数据节点)是 ZooKeeper 中数据的最小单元,每个ZNode上都可以保存数据,同时还是可以有子节点(这就像树结构一样,如下图所示)。可以看出,节点路径标识方式和Unix文件
|
||||
系统路径非常相似,都是由一系列使用斜杠"/"进行分割的路径表示,开发人员可以向这个节点中写人数据,也可以在节点下面创建子节点。这些操作我们后面都会介绍到。
|
||||

|
||||
|
||||
提到 ZooKeeper 数据模型,还有一个不得不得提的东西就是 **事务 ID** 。事务的ACID(Atomic:原子性;Consistency:一致性;Isolation:隔离性;Durability:持久性)四大特性我在这里就不多说了,相信大家也已经挺腻了。
|
||||
|
||||
在Zookeeper中,事务是指能够改变 ZooKeeper 服务器状态的操作,我们也称之为事务操作或更新操作,一般包括数据节点创建与删除、数据节点内容更新和客户端会话创建与失效等操作。**对于每一个事务请求,ZooKeeper 都会为其分配一个全局唯一的事务ID,用 ZXID 来表示**,通常是一个64位的数字。每一个ZXID对应一次更新操作,**从这些 ZXID 中可以间接地识别出Zookeeper处理这些更新操作请求的全局顺序**。
|
||||
|
||||
### ZNode(数据节点)的结构
|
||||
|
||||
每个 ZNode 由2部分组成:
|
||||
|
||||
- stat:状态信息
|
||||
- data:数据内容
|
||||
|
||||
如下所示,我通过 get 命令来获取 根目录下的 dubbo 节点的内容。(get 命令在下面会介绍到)
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 6] get /dubbo
|
||||
# 该数据节点关联的数据内容为空
|
||||
null
|
||||
# 下面是该数据节点的一些状态信息,其实就是 Stat 对象的格式化输出
|
||||
cZxid = 0x2
|
||||
ctime = Tue Nov 27 11:05:34 CST 2018
|
||||
mZxid = 0x2
|
||||
mtime = Tue Nov 27 11:05:34 CST 2018
|
||||
pZxid = 0x3
|
||||
cversion = 1
|
||||
dataVersion = 0
|
||||
aclVersion = 0
|
||||
ephemeralOwner = 0x0
|
||||
dataLength = 0
|
||||
numChildren = 1
|
||||
|
||||
```
|
||||
这些状态信息其实就是 Stat 对象的格式化输出。Stat 类中包含了一个数据节点的所有状态信息的字段,包括事务ID、版本信息和子节点个数等,如下图所示(图源:《从Paxos到Zookeeper 分布式一致性原理与实践》,下面会介绍通过 stat 命令查看数据节点的状态)。
|
||||
|
||||
**Stat 类:**
|
||||
|
||||

|
||||
|
||||
关于数据节点的状态信息说明(也就是对Stat 类中的各字段进行说明),可以参考下图(图源:《从Paxos到Zookeeper 分布式一致性原理与实践》)。
|
||||
|
||||

|
||||
|
||||
### 测试 ZooKeeper 中的常见操作
|
||||
|
||||
|
||||
#### 连接 ZooKeeper 服务
|
||||
|
||||
进入安装 ZooKeeper文件夹的 bin 目录下执行下面的命令连接 ZooKeeper 服务(Linux环境下)(连接之前首选要确定你的 ZooKeeper 服务已经启动成功)。
|
||||
|
||||
```shell
|
||||
./zkCli.sh -server 127.0.0.1:2181
|
||||
```
|
||||

|
||||
|
||||
从上图可以看出控制台打印出了很多信息,包括我们的主机名称、JDK 版本、操作系统等等。如果你成功看到这些信息,说明你成功连接到 ZooKeeper 服务。
|
||||
|
||||
#### 查看常用命令(help 命令)
|
||||
|
||||
help 命令查看 zookeeper 常用命令
|
||||
|
||||

|
||||
|
||||
#### 创建节点(create 命令)
|
||||
|
||||
通过 create 命令在根目录创建了node1节点,与它关联的字符串是"node1"
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 34] create /node1 “node1”
|
||||
```
|
||||
通过 create 命令在根目录创建了node1节点,与它关联的内容是数字 123
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 1] create /node1/node1.1 123
|
||||
Created /node1/node1.1
|
||||
```
|
||||
|
||||
#### 更新节点数据内容(set 命令)
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 11] set /node1 "set node1"
|
||||
```
|
||||
|
||||
#### 获取节点的数据(get 命令)
|
||||
|
||||
get 命令可以获取指定节点的数据内容和节点的状态,可以看出我们通过set 命令已经将节点数据内容改为 "set node1"。
|
||||
|
||||
```shell
|
||||
set node1
|
||||
cZxid = 0x47
|
||||
ctime = Sun Jan 20 10:22:59 CST 2019
|
||||
mZxid = 0x4b
|
||||
mtime = Sun Jan 20 10:41:10 CST 2019
|
||||
pZxid = 0x4a
|
||||
cversion = 1
|
||||
dataVersion = 1
|
||||
aclVersion = 0
|
||||
ephemeralOwner = 0x0
|
||||
dataLength = 9
|
||||
numChildren = 1
|
||||
|
||||
```
|
||||
|
||||
#### 查看某个目录下的子节点(ls 命令)
|
||||
|
||||
通过 ls 命令查看根目录下的节点
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 37] ls /
|
||||
[dubbo, zookeeper, node1]
|
||||
```
|
||||
通过 ls 命令查看 node1 目录下的节点
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 5] ls /node1
|
||||
[node1.1]
|
||||
```
|
||||
zookeeper 中的 ls 命令和 linux 命令中的 ls 类似, 这个命令将列出绝对路径path下的所有子节点信息(列出1级,并不递归)
|
||||
|
||||
#### 查看节点状态(stat 命令)
|
||||
|
||||
通过 stat 命令查看节点状态
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 10] stat /node1
|
||||
cZxid = 0x47
|
||||
ctime = Sun Jan 20 10:22:59 CST 2019
|
||||
mZxid = 0x47
|
||||
mtime = Sun Jan 20 10:22:59 CST 2019
|
||||
pZxid = 0x4a
|
||||
cversion = 1
|
||||
dataVersion = 0
|
||||
aclVersion = 0
|
||||
ephemeralOwner = 0x0
|
||||
dataLength = 11
|
||||
numChildren = 1
|
||||
```
|
||||
上面显示的一些信息比如cversion、aclVersion、numChildren等等,我在上面 “ZNode(数据节点)的结构” 这部分已经介绍到。
|
||||
|
||||
#### 查看节点信息和状态(ls2 命令)
|
||||
|
||||
|
||||
ls2 命令更像是 ls 命令和 stat 命令的结合。ls2 命令返回的信息包括2部分:子节点列表 + 当前节点的stat信息。
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 7] ls2 /node1
|
||||
[node1.1]
|
||||
cZxid = 0x47
|
||||
ctime = Sun Jan 20 10:22:59 CST 2019
|
||||
mZxid = 0x47
|
||||
mtime = Sun Jan 20 10:22:59 CST 2019
|
||||
pZxid = 0x4a
|
||||
cversion = 1
|
||||
dataVersion = 0
|
||||
aclVersion = 0
|
||||
ephemeralOwner = 0x0
|
||||
dataLength = 11
|
||||
numChildren = 1
|
||||
|
||||
```
|
||||
|
||||
#### 删除节点(delete 命令)
|
||||
|
||||
这个命令很简单,但是需要注意的一点是如果你要删除某一个节点,那么这个节点必须无子节点才行。
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 3] delete /node1/node1.1
|
||||
```
|
||||
|
||||
在后面我会介绍到 Java 客户端 API的使用以及开源 Zookeeper 客户端 ZkClient 和 Curator 的使用。
|
||||
|
||||
|
||||
### 参考
|
||||
|
||||
- 《从Paxos到Zookeeper 分布式一致性原理与实践》
|
||||
|
323
docs/system-design/framework/zookeeper-in-action.md
Normal file
323
docs/system-design/framework/zookeeper-in-action.md
Normal file
@ -0,0 +1,323 @@
|
||||
|
||||
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->
|
||||
|
||||
<!-- code_chunk_output -->
|
||||
|
||||
- [1. 前言](#1-前言)
|
||||
- [2. ZooKeeper 安装和使用](#2-zookeeper-安装和使用)
|
||||
- [2.1. 使用Docker 安装 zookeeper](#21-使用docker-安装-zookeeper)
|
||||
- [2.2. 连接 ZooKeeper 服务](#22-连接-zookeeper-服务)
|
||||
- [2.3. 常用命令演示](#23-常用命令演示)
|
||||
- [2.3.1. 查看常用命令(help 命令)](#231-查看常用命令help-命令)
|
||||
- [2.3.2. 创建节点(create 命令)](#232-创建节点create-命令)
|
||||
- [2.3.3. 更新节点数据内容(set 命令)](#233-更新节点数据内容set-命令)
|
||||
- [2.3.4. 获取节点的数据(get 命令)](#234-获取节点的数据get-命令)
|
||||
- [2.3.5. 查看某个目录下的子节点(ls 命令)](#235-查看某个目录下的子节点ls-命令)
|
||||
- [2.3.6. 查看节点状态(stat 命令)](#236-查看节点状态stat-命令)
|
||||
- [2.3.7. 查看节点信息和状态(ls2 命令)](#237-查看节点信息和状态ls2-命令)
|
||||
- [2.3.8. 删除节点(delete 命令)](#238-删除节点delete-命令)
|
||||
- [3. ZooKeeper Java客户端 Curator简单使用](#3-zookeeper-java客户端-curator简单使用)
|
||||
- [3.1. 连接 ZooKeeper 客户端](#31-连接-zookeeper-客户端)
|
||||
- [3.2. 数据节点的增删改查](#32-数据节点的增删改查)
|
||||
- [3.2.1. 创建节点](#321-创建节点)
|
||||
- [3.2.2. 删除节点](#322-删除节点)
|
||||
- [3.2.3. 获取/更新节点数据内容](#323-获取更新节点数据内容)
|
||||
- [3.2.4. 获取某个节点的所有子节点路径](#324-获取某个节点的所有子节点路径)
|
||||
|
||||
<!-- /code_chunk_output -->
|
||||
|
||||
|
||||
## 1. 前言
|
||||
|
||||
这篇文章简单给演示一下 ZooKeeper 常见命令的使用以及 ZooKeeper Java客户端 Curator 的基本使用。介绍到的内容都是最基本的操作,能满足日常工作的基本需要。
|
||||
|
||||
如果文章有任何需要改善和完善的地方,欢迎在评论区指出,共同进步!
|
||||
|
||||
## 2. ZooKeeper 安装和使用
|
||||
|
||||
### 2.1. 使用Docker 安装 zookeeper
|
||||
|
||||
**a.使用 Docker 下载 ZooKeeper**
|
||||
|
||||
```shell
|
||||
docker pull zookeeper:3.5.8
|
||||
```
|
||||
|
||||
**b.运行 ZooKeeper**
|
||||
|
||||
```shell
|
||||
docker run -d --name zookeeper -p 2181:2181 zookeeper:3.5.8
|
||||
```
|
||||
|
||||
### 2.2. 连接 ZooKeeper 服务
|
||||
|
||||
**a.进入ZooKeeper容器中**
|
||||
|
||||
先使用 `docker ps` 查看 ZooKeeper 的 ContainerID,然后使用 `docker exec -it ContainerID /bin/bash` 命令进入容器中。
|
||||
|
||||
**b.先进入 bin 目录,然后通过 `./zkCli.sh -server 127.0.0.1:2181`命令连接ZooKeeper 服务**
|
||||
|
||||
```bash
|
||||
root@eaf70fc620cb:/apache-zookeeper-3.5.8-bin# cd bin
|
||||
```
|
||||
|
||||
如果你看到控制台成功打印出如下信息的话,说明你已经成功连接 ZooKeeper 服务。
|
||||
|
||||

|
||||
|
||||
### 2.3. 常用命令演示
|
||||
|
||||
#### 2.3.1. 查看常用命令(help 命令)
|
||||
|
||||
通过 `help` 命令查看 ZooKeeper 常用命令
|
||||
|
||||
#### 2.3.2. 创建节点(create 命令)
|
||||
|
||||
通过 `create` 命令在根目录创建了 node1 节点,与它关联的字符串是"node1"
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 34] create /node1 “node1”
|
||||
```
|
||||
|
||||
通过 `create` 命令在根目录创建了 node1 节点,与它关联的内容是数字 123
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 1] create /node1/node1.1 123
|
||||
Created /node1/node1.1
|
||||
```
|
||||
|
||||
#### 2.3.3. 更新节点数据内容(set 命令)
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 11] set /node1 "set node1"
|
||||
```
|
||||
|
||||
#### 2.3.4. 获取节点的数据(get 命令)
|
||||
|
||||
`get` 命令可以获取指定节点的数据内容和节点的状态,可以看出我们通过 `set` 命令已经将节点数据内容改为 "set node1"。
|
||||
|
||||
```shell
|
||||
set node1
|
||||
cZxid = 0x47
|
||||
ctime = Sun Jan 20 10:22:59 CST 2019
|
||||
mZxid = 0x4b
|
||||
mtime = Sun Jan 20 10:41:10 CST 2019
|
||||
pZxid = 0x4a
|
||||
cversion = 1
|
||||
dataVersion = 1
|
||||
aclVersion = 0
|
||||
ephemeralOwner = 0x0
|
||||
dataLength = 9
|
||||
numChildren = 1
|
||||
|
||||
```
|
||||
|
||||
#### 2.3.5. 查看某个目录下的子节点(ls 命令)
|
||||
|
||||
通过 `ls` 命令查看根目录下的节点
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 37] ls /
|
||||
[dubbo, ZooKeeper, node1]
|
||||
```
|
||||
|
||||
通过 `ls` 命令查看 node1 目录下的节点
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 5] ls /node1
|
||||
[node1.1]
|
||||
```
|
||||
|
||||
ZooKeeper 中的 ls 命令和 linux 命令中的 ls 类似, 这个命令将列出绝对路径 path 下的所有子节点信息(列出 1 级,并不递归)
|
||||
|
||||
#### 2.3.6. 查看节点状态(stat 命令)
|
||||
|
||||
通过 `stat` 命令查看节点状态
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 10] stat /node1
|
||||
cZxid = 0x47
|
||||
ctime = Sun Jan 20 10:22:59 CST 2019
|
||||
mZxid = 0x47
|
||||
mtime = Sun Jan 20 10:22:59 CST 2019
|
||||
pZxid = 0x4a
|
||||
cversion = 1
|
||||
dataVersion = 0
|
||||
aclVersion = 0
|
||||
ephemeralOwner = 0x0
|
||||
dataLength = 11
|
||||
numChildren = 1
|
||||
```
|
||||
|
||||
上面显示的一些信息比如 cversion、aclVersion、numChildren 等等,我在上面 “znode(数据节点)的结构” 这部分已经介绍到。
|
||||
|
||||
#### 2.3.7. 查看节点信息和状态(ls2 命令)
|
||||
|
||||
`ls2` 命令更像是 `ls` 命令和 `stat` 命令的结合。 `ls2` 命令返回的信息包括 2 部分:
|
||||
|
||||
1. 子节点列表
|
||||
2. 当前节点的 stat 信息。
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 7] ls2 /node1
|
||||
[node1.1]
|
||||
cZxid = 0x47
|
||||
ctime = Sun Jan 20 10:22:59 CST 2019
|
||||
mZxid = 0x47
|
||||
mtime = Sun Jan 20 10:22:59 CST 2019
|
||||
pZxid = 0x4a
|
||||
cversion = 1
|
||||
dataVersion = 0
|
||||
aclVersion = 0
|
||||
ephemeralOwner = 0x0
|
||||
dataLength = 11
|
||||
numChildren = 1
|
||||
|
||||
```
|
||||
|
||||
#### 2.3.8. 删除节点(delete 命令)
|
||||
|
||||
这个命令很简单,但是需要注意的一点是如果你要删除某一个节点,那么这个节点必须无子节点才行。
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 3] delete /node1/node1.1
|
||||
```
|
||||
|
||||
在后面我会介绍到 Java 客户端 API 的使用以及开源 ZooKeeper 客户端 ZkClient 和 Curator 的使用。
|
||||
|
||||
## 3. ZooKeeper Java客户端 Curator简单使用
|
||||
|
||||
Curator 是Netflix公司开源的一套 ZooKeeper Java客户端框架,相比于 Zookeeper 自带的客户端 zookeeper 来说,Curator 的封装更加完善,各种 API 都可以比较方便地使用。
|
||||
|
||||

|
||||
|
||||
下面我们就来简单地演示一下 Curator 的使用吧!
|
||||
|
||||
Curator4.0+版本对ZooKeeper 3.5.x支持比较好。开始之前,请先将下面的依赖添加进你的项目。
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-framework</artifactId>
|
||||
<version>4.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId>
|
||||
<artifactId>curator-recipes</artifactId>
|
||||
<version>4.2.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 3.1. 连接 ZooKeeper 客户端
|
||||
|
||||
通过 `CuratorFrameworkFactory` 创建 `CuratorFramework` 对象,然后再调用 `CuratorFramework` 对象的 `start()` 方法即可!
|
||||
|
||||
```java
|
||||
private static final int BASE_SLEEP_TIME = 1000;
|
||||
private static final int MAX_RETRIES = 3;
|
||||
|
||||
// Retry strategy. Retry 3 times, and will increase the sleep time between retries.
|
||||
RetryPolicy retryPolicy = new ExponentialBackoffRetry(BASE_SLEEP_TIME, MAX_RETRIES);
|
||||
CuratorFramework zkClient = CuratorFrameworkFactory.builder()
|
||||
// the server to connect to (can be a server list)
|
||||
.connectString("127.0.0.1:2181")
|
||||
.retryPolicy(retryPolicy)
|
||||
.build();
|
||||
zkClient.start();
|
||||
```
|
||||
|
||||
对于一些基本参数的说明:
|
||||
|
||||
- `baseSleepTimeMs`:重试之间等待的初始时间
|
||||
- `maxRetries` :最大重试次数
|
||||
- `connectString` :要连接的服务器列表
|
||||
- `retryPolicy` :重试策略
|
||||
|
||||
### 3.2. 数据节点的增删改查
|
||||
|
||||
#### 3.2.1. 创建节点
|
||||
|
||||
我们在 [ZooKeeper常见概念解读](./zookeeper-intro.md) 中介绍到,我们通常是将 znode 分为 4 大类:
|
||||
|
||||
- **持久(PERSISTENT)节点** :一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
|
||||
- **临时(EPHEMERAL)节点** :临时节点的生命周期是与 **客户端会话(session)** 绑定的,**会话消失则节点消失** 。并且,临时节点 **只能做叶子节点** ,不能创建子节点。
|
||||
- **持久顺序(PERSISTENT_SEQUENTIAL)节点** :除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 `/node1/app0000000001` 、`/node1/app0000000002` 。
|
||||
- **临时顺序(EPHEMERAL_SEQUENTIAL)节点** :除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性。
|
||||
|
||||
你在使用的ZooKeeper 的时候,会发现 `CreateMode` 类中实际有 7种 znode 类型 ,但是用的最多的还是上面介绍的 4 种。
|
||||
|
||||
**a.创建持久化节点**
|
||||
|
||||
你可以通过下面两种方式创建持久化的节点。
|
||||
|
||||
```java
|
||||
//注意:下面的代码会报错,下文说了具体原因
|
||||
zkClient.create().forPath("/node1/00001");
|
||||
zkClient.create().withMode(CreateMode.PERSISTENT).forPath("/node1/00002");
|
||||
```
|
||||
|
||||
但是,你运行上面的代码会报错,这是因为的父节点`node1`还未创建。
|
||||
|
||||
你可以先创建父节点 `node1` ,然后再执行上面的代码就不会报错了。
|
||||
|
||||
```java
|
||||
zkClient.create().forPath("/node1");
|
||||
```
|
||||
|
||||
更推荐的方式是通过下面这行代码, **`creatingParentsIfNeeded()` 可以保证父节点不存在的时候自动创建父节点,这是非常有用的。**
|
||||
|
||||
```java
|
||||
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath("/node1/00001");
|
||||
```
|
||||
|
||||
**b.创建临时节点**
|
||||
|
||||
```java
|
||||
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/node1/00001");
|
||||
```
|
||||
|
||||
**c.创建节点并指定数据内容**
|
||||
|
||||
```java
|
||||
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/node1/00001","java".getBytes());
|
||||
zkClient.getData().forPath("/node1/00001");//获取节点的数据内容,获取到的是 byte数组
|
||||
```
|
||||
|
||||
**d.检测节点是否创建成功**
|
||||
|
||||
```java
|
||||
zkClient.checkExists().forPath("/node1/00001");//不为null的话,说明节点创建成功
|
||||
```
|
||||
|
||||
#### 3.2.2. 删除节点
|
||||
|
||||
**a.删除一个子节点**
|
||||
|
||||
```java
|
||||
zkClient.delete().forPath("/node1/00001");
|
||||
```
|
||||
|
||||
**b.删除一个节点以及其下的所有子节点**
|
||||
|
||||
```java
|
||||
zkClient.delete().deletingChildrenIfNeeded().forPath("/node1");
|
||||
```
|
||||
|
||||
#### 3.2.3. 获取/更新节点数据内容
|
||||
|
||||
```java
|
||||
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/node1/00001","java".getBytes());
|
||||
zkClient.getData().forPath("/node1/00001");//获取节点的数据内容
|
||||
zkClient.setData().forPath("/node1/00001","c++".getBytes());//更新节点数据内容
|
||||
```
|
||||
|
||||
#### 3.2.4. 获取某个节点的所有子节点路径
|
||||
|
||||
```java
|
||||
List<String> childrenPaths = zkClient.getChildren().forPath("/node1");
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
297
docs/system-design/framework/zookeeper-intro.md
Normal file
297
docs/system-design/framework/zookeeper-intro.md
Normal file
@ -0,0 +1,297 @@
|
||||
|
||||
<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->
|
||||
|
||||
<!-- code_chunk_output -->
|
||||
|
||||
- [1. 前言](#1-前言)
|
||||
- [2. ZooKeeper 介绍](#2-zookeeper-介绍)
|
||||
- [2.1. ZooKeeper 由来](#21-zookeeper-由来)
|
||||
- [2.2. ZooKeeper 概览](#22-zookeeper-概览)
|
||||
- [2.3. ZooKeeper 特点](#23-zookeeper-特点)
|
||||
- [2.4. ZooKeeper 典型应用场景](#24-zookeeper-典型应用场景)
|
||||
- [2.5. 有哪些著名的开源项目用到了 ZooKeeper?](#25-有哪些著名的开源项目用到了-zookeeper)
|
||||
- [3. ZooKeeper 重要概念解读](#3-zookeeper-重要概念解读)
|
||||
- [3.1. Data model(数据模型)](#31-data-model数据模型)
|
||||
- [3.2. znode(数据节点)](#32-znode数据节点)
|
||||
- [3.2.1. znode 4种类型](#321-znode-4种类型)
|
||||
- [3.2.2. znode 数据结构](#322-znode-数据结构)
|
||||
- [3.3. 版本(version)](#33-版本version)
|
||||
- [3.4. ACL(权限控制)](#34-acl权限控制)
|
||||
- [3.5. Watcher(事件监听器)](#35-watcher事件监听器)
|
||||
- [3.6. 会话(Session)](#36-会话session)
|
||||
- [4. ZooKeeper 集群](#4-zookeeper-集群)
|
||||
- [4.1. ZooKeeper 集群角色](#41-zookeeper-集群角色)
|
||||
- [4.2. ZooKeeper 集群中的服务器状态](#42-zookeeper-集群中的服务器状态)
|
||||
- [4.3. ZooKeeper 集群为啥最好奇数台?](#43-zookeeper-集群为啥最好奇数台)
|
||||
- [5. ZAB 协议和Paxos 算法](#5-zab-协议和paxos-算法)
|
||||
- [5.1. ZAB 协议介绍](#51-zab-协议介绍)
|
||||
- [5.2. ZAB 协议两种基本的模式:崩溃恢复和消息广播](#52-zab-协议两种基本的模式崩溃恢复和消息广播)
|
||||
- [6. 总结](#6-总结)
|
||||
- [7. 参考](#7-参考)
|
||||
|
||||
<!-- /code_chunk_output -->
|
||||
|
||||
|
||||
## 1. 前言
|
||||
|
||||
相信大家对 ZooKeeper 应该不算陌生。但是你真的了解 ZooKeeper 到底有啥用不?如果别人/面试官让你给他讲讲对于 ZooKeeper 的认识,你能回答到什么地步呢?
|
||||
|
||||
拿我自己来说吧!我本人曾经使用 Dubbo 来做分布式项目的时候,使用了 ZooKeeper 作为注册中心。为了保证分布式系统能够同步访问某个资源,我还使用 ZooKeeper 做过分布式锁。另外,我在学习 Kafka 的时候,知道 Kafka 很多功能的实现依赖了 ZooKeeper。
|
||||
|
||||
前几天,总结项目经验的时候,我突然问自己 ZooKeeper 到底是个什么东西?想了半天,脑海中只是简单的能浮现出几句话:
|
||||
|
||||
1. ZooKeeper 可以被用作注册中心、分布式锁;
|
||||
2. ZooKeeper 是 Hadoop 生态系统的一员;
|
||||
3. 构建 ZooKeeper 集群的时候,使用的服务器最好是奇数台。
|
||||
|
||||
由此可见,我对于 ZooKeeper 的理解仅仅是停留在了表面。
|
||||
|
||||
所以,通过本文,希望带大家稍微详细的了解一下 ZooKeeper 。如果没有学过 ZooKeeper ,那么本文将会是你进入 ZooKeeper 大门的垫脚砖。如果你已经接触过 ZooKeeper ,那么本文将带你回顾一下 ZooKeeper 的一些基础概念。
|
||||
|
||||
另外,本文不光会涉及到 ZooKeeper 的一些概念,后面的文章会介绍到 ZooKeeper 常见命令的使用以及使用 Apache Curator 作为 ZooKeeper 的客户端。
|
||||
|
||||
## 2. ZooKeeper 介绍
|
||||
|
||||
### 2.1. ZooKeeper 由来
|
||||
|
||||
正式介绍 ZooKeeper 之前,我们先来看看 ZooKeeper 的由来,还挺有意思的。
|
||||
|
||||
下面这段内容摘自《从 Paxos 到 ZooKeeper 》第四章第一节,推荐大家阅读以下:
|
||||
|
||||
> ZooKeeper 最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。
|
||||
>
|
||||
> 关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的 Pig 项目),雅虎的工程师希望给这个项目也取一个动物的名字。时任研究院的首席科学家 RaghuRamakrishnan 开玩笑地说:“在这样下去,我们这儿就变成动物园了!”此话一出,大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了,而 ZooKeeper 正好要用来进行分布式环境的协调一一于是,ZooKeeper 的名字也就由此诞生了。
|
||||
|
||||
### 2.2. ZooKeeper 概览
|
||||
|
||||
ZooKeeper 是一个开源的**分布式协调服务**,它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。
|
||||
|
||||
> **原语:** 操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性·即原语的执行必须是连续的,在执行过程中不允许被中断。
|
||||
|
||||
**ZooKeeper 为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。**
|
||||
|
||||
另外,**ZooKeeper 将数据保存在内存中,性能是非常棒的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景)。**
|
||||
|
||||
### 2.3. ZooKeeper 特点
|
||||
|
||||
- **顺序一致性:** 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
|
||||
- **原子性:** 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
|
||||
- **单一系统映像 :** 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
|
||||
- **可靠性:** 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。
|
||||
|
||||
### 2.4. ZooKeeper 典型应用场景
|
||||
|
||||
ZooKeeper 概览中,我们介绍到使用其通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
|
||||
|
||||
下面选 3 个典型的应用场景来专门说说:
|
||||
|
||||
1. **分布式锁** : 通过创建唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁。
|
||||
2. **命名服务** :可以通过 ZooKeeper 的顺序节点生成全局唯一 ID
|
||||
3. **数据发布/订阅** :通过 **Watcher 机制** 可以很方便地实现数据发布/订阅。当你将数据发布到 ZooKeeper 被监听的节点上,其他机器可通过监听 ZooKeeper 上节点的变化来实现配置的动态更新。
|
||||
|
||||
实际上,这些功能的实现基本都得益于 ZooKeeper 可以保存数据的功能,但是 ZooKeeper 不适合保存大量数据,这一点需要注意。
|
||||
|
||||
### 2.5. 有哪些著名的开源项目用到了 ZooKeeper?
|
||||
|
||||
1. **Kafka** : ZooKeeper 主要为 Kafka 提供 Broker 和 Topic 的注册以及多个 Partition 的负载均衡等功能。
|
||||
2. **Hbase** : ZooKeeper 为 Hbase 提供确保整个集群只有一个 Master 以及保存和提供 regionserver 状态信息(是否在线)等功能。
|
||||
3. **Hadoop** : ZooKeeper 为 Namenode 提供高可用支持。
|
||||
|
||||
## 3. ZooKeeper 重要概念解读
|
||||
|
||||
_破音:拿出小本本,下面的内容非常重要哦!_
|
||||
|
||||
### 3.1. Data model(数据模型)
|
||||
|
||||
ZooKeeper 数据模型采用层次化的多叉树形结构,每个节点上都可以存储数据,这些数据可以是数字、字符串或者是二级制序列。并且。每个节点还可以拥有 N 个子结点,最上层是根节点以“/”来代表。每个数据节点在 ZooKeeper 中被称为 **znode **,它是 ZooKeeper 中数据的最小单元。并且,每个 znode 都一个唯一的路径标识。
|
||||
|
||||
强调一句:**ZooKeeper 主要是用来协调服务的,而不是用来存储业务数据的,所以不要放比较大的数据在 znode 上,ZooKeeper 给出的上线是每个结点的数据大小最大是 1M。**
|
||||
|
||||
从下图可以更直观地看出:ZooKeeper 节点路径标识方式和 Unix 文件系统路径非常相似,都是由一系列使用斜杠"/"进行分割的路径表示,开发人员可以向这个节点中写人数据,也可以在节点下面创建子节点。这些操作我们后面都会介绍到。
|
||||
|
||||

|
||||
|
||||
### 3.2. znode(数据节点)
|
||||
|
||||
介绍了 ZooKeeper 树形数据模型之后,我们知道每个数据节点在 ZooKeeper 中被称为 **znode **,它是 ZooKeeper 中数据的最小单元。你要存放的数据就放在上面,是你使用 ZooKeeper 过程中经常需要解除到的一个概念。
|
||||
|
||||
#### 3.2.1. znode 4种类型
|
||||
|
||||
我们通常是将 znode 分为 4 大类:
|
||||
|
||||
- **持久(PERSISTENT)节点** :一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
|
||||
- **临时(EPHEMERAL)节点** :临时节点的生命周期是与 **客户端会话(session)** 绑定的,**会话消失则节点消失** 。并且,临时节点 **只能做叶子节点** ,不能创建子节点。
|
||||
- **持久顺序(PERSISTENT_SEQUENTIAL)节点** :除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 `/node1/app0000000001` 、`/node1/app0000000002` 。
|
||||
- **临时顺序(EPHEMERAL_SEQUENTIAL)节点** :除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性。
|
||||
|
||||
#### 3.2.2. znode 数据结构
|
||||
|
||||
每个 znode 由 2 部分组成:
|
||||
|
||||
- **stat** :状态信息
|
||||
- **data** : 节点存放的数据的具体内容
|
||||
|
||||
如下所示,我通过 get 命令来获取 根目录下的 dubbo 节点的内容。(get 命令在下面会介绍到)。
|
||||
|
||||
```shell
|
||||
[zk: 127.0.0.1:2181(CONNECTED) 6] get /dubbo
|
||||
# 该数据节点关联的数据内容为空
|
||||
null
|
||||
# 下面是该数据节点的一些状态信息,其实就是 Stat 对象的格式化输出
|
||||
cZxid = 0x2
|
||||
ctime = Tue Nov 27 11:05:34 CST 2018
|
||||
mZxid = 0x2
|
||||
mtime = Tue Nov 27 11:05:34 CST 2018
|
||||
pZxid = 0x3
|
||||
cversion = 1
|
||||
dataVersion = 0
|
||||
aclVersion = 0
|
||||
ephemeralOwner = 0x0
|
||||
dataLength = 0
|
||||
numChildren = 1
|
||||
```
|
||||
|
||||
Stat 类中包含了一个数据节点的所有状态信息的字段,包括事务 ID-cZxid、节点创建时间-ctime 和子节点个数-numChildren 等等。
|
||||
|
||||
下面我们来看一下每个 znode 状态信息究竟代表的是什么吧!(下面的内容来源于《从 Paxos 到 ZooKeeper 分布式一致性原理与实践》,因为 Guide 确实也不是特别清楚,要学会参考资料的嘛! ) :
|
||||
|
||||
| znode 状态信息 | 解释 |
|
||||
| -------------- | ------------------------------------------------------------ |
|
||||
| cZxid | create ZXID,即该数据节点被创建时的事务 id |
|
||||
| ctime | create time,即该节点的创建时间 |
|
||||
| mZxid | modified ZXID,即该节点最终一次更新时的事务 id |
|
||||
| mtime | modified time,即该节点最后一次的更新时间 |
|
||||
| pZxid | 该节点的子节点列表最后一次修改时的事务 id,只有子节点列表变更才会更新 pZxid,子节点内容变更不会更新 |
|
||||
| cversion | 子节点版本号,当前节点的子节点每次变化时值增加 1 |
|
||||
| dataVersion | 数据节点内容版本号,节点创建时为 0,每更新一次节点内容(不管内容有无变化)该版本号的值增加 1 |
|
||||
| aclVersion | 节点的 ACL 版本号,表示该节点 ACL 信息变更次数 |
|
||||
| ephemeralOwner | 创建该临时节点的会话的 sessionId;如果当前节点为持久节点,则 ephemeralOwner=0 |
|
||||
| dataLength | 数据节点内容长度 |
|
||||
| numChildren | 当前节点的子节点个数 |
|
||||
|
||||
### 3.3. 版本(version)
|
||||
|
||||
在前面我们已经提到,ZooKeeper 的每个 znode 上都会存储数据,对应于每个 znode,ZooKeeper 都会为其维护一个叫作 **Stat** 的数据结构,Stat 中记录了这个 znode 的三个数据版本:
|
||||
|
||||
- **dataVersion** :当前 znode 节点的版本号
|
||||
- **cversion** : 当前 znode 子节点的版本
|
||||
- **aclVersion** : 当前 znode 的 ACL 的版本。
|
||||
|
||||
### 3.4. ACL(权限控制)
|
||||
|
||||
ZooKeeper 采用 ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。
|
||||
|
||||
对于 znode 操作的权限,ZooKeeper 提供了以下 5 种:
|
||||
|
||||
- **CREATE** : 能创建子节点
|
||||
- **READ** :能获取节点数据和列出其子节点
|
||||
- **WRITE** : 能设置/更新节点数据
|
||||
- **DELETE** : 能删除子节点
|
||||
- **ADMIN** : 能设置节点 ACL 的权限
|
||||
|
||||
其中尤其需要注意的是,**CREATE** 和 **DELETE** 这两种权限都是针对 **子节点** 的权限控制。
|
||||
|
||||
对于身份认证,提供了以下几种方式:
|
||||
|
||||
- **world** : 默认方式,所有用户都可无条件访问。
|
||||
- **auth** :不使用任何 id,代表任何已认证的用户。
|
||||
- **digest** :用户名:密码认证方式: _username:password_ 。
|
||||
- **ip** : 对指定 ip 进行限制。
|
||||
|
||||
### 3.5. Watcher(事件监听器)
|
||||
|
||||
Watcher(事件监听器),是 ZooKeeper 中的一个很重要的特性。ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。
|
||||
|
||||

|
||||
|
||||
_破音:非常有用的一个特性,都能出小本本记好了,后面用到 ZooKeeper 基本离不开 Watcher(事件监听器)机制。_
|
||||
|
||||
### 3.6. 会话(Session)
|
||||
|
||||
Session 可以看作是 ZooKeeper 服务器与客户端的之间的一个 TCP 长连接,通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向 ZooKeeper 服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的 Watcher 事件通知。
|
||||
|
||||
Session 有一个属性叫做:`sessionTimeout` ,`sessionTimeout` 代表会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在`sessionTimeout`规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。
|
||||
|
||||
另外,在为客户端创建会话之前,服务端首先会为每个客户端都分配一个 `sessionID`。由于 `sessionID`是 ZooKeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 `sessionID` 的,因此,无论是哪台服务器为客户端分配的 `sessionID`,都务必保证全局唯一。
|
||||
|
||||
## 4. ZooKeeper 集群
|
||||
|
||||
为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。通常 3 台服务器就可以构成一个 ZooKeeper 集群了。ZooKeeper 官方提供的架构图就是一个 ZooKeeper 集群整体对外提供服务。
|
||||
|
||||

|
||||
|
||||
上图中每一个 Server 代表一个安装 ZooKeeper 服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 ZAB 协议(ZooKeeper Atomic Broadcast)来保持数据的一致性。
|
||||
|
||||
**最典型集群模式: Master/Slave 模式(主备模式)**。在这种模式中,通常 Master 服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。
|
||||
|
||||
### 4.1. ZooKeeper 集群角色
|
||||
|
||||
但是,在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了 Leader、Follower 和 Observer 三种角色。如下图所示
|
||||
|
||||

|
||||
|
||||
ZooKeeper 集群中的所有机器通过一个 **Leader 选举过程** 来选定一台称为 “**Leader**” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,**Follower** 和 **Observer** 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。
|
||||
|
||||
| 角色 | 说明 |
|
||||
| -------- | ------------------------------------------------------------ |
|
||||
| Leader | 为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态。 |
|
||||
| Follower | 为客户端提供读服务,如果是写服务则转发给 Leader。在选举过程中参与投票。 |
|
||||
| Observer | 为客户端提供读服务器,如果是写服务则转发给 Leader。不参与选举过程中的投票,也不参与“过半写成功”策略。在不影响写性能的情况下提升集群的读性能。此角色于 ZooKeeper3.3 系列新增的角色。 |
|
||||
|
||||
当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,就会进入 Leader 选举过程,这个过程会选举产生新的 Leader 服务器。
|
||||
|
||||
这个过程大致是这样的:
|
||||
|
||||
1. **Leader election(选举阶段)**:节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。
|
||||
2. **Discovery(发现阶段)** :在这个阶段,followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。
|
||||
3. **Synchronization(同步阶段)** :同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后
|
||||
准 leader 才会成为真正的 leader。
|
||||
4. **Broadcast(广播阶段)** :到了这个阶段,ZooKeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。
|
||||
|
||||
### 4.2. ZooKeeper 集群中的服务器状态
|
||||
|
||||
- **LOOKING** :寻找 Leader。
|
||||
- **LEADING** :Leader 状态,对应的节点为 Leader。
|
||||
- **FOLLOWING** :Follower 状态,对应的节点为 Follower。
|
||||
- **OBSERVING** :Observer 状态,对应节点为 Observer,该节点不参与 Leader 选举。
|
||||
|
||||
### 4.3. ZooKeeper 集群为啥最好奇数台?
|
||||
|
||||
ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooKeeper 服务器个数大于宕掉的个数的话整个 ZooKeeper 才依然可用。假如我们的集群中有 n 台 ZooKeeper 服务器,那么也就是剩下的服务数必须大于 n/2。先说一下结论,2n 和 2n-1 的容忍度是一样的,都是 n-1,大家可以先自己仔细想一想,这应该是一个很简单的数学问题了。
|
||||
比如假如我们有 3 台,那么最大允许宕掉 1 台 ZooKeeper 服务器,如果我们有 4 台的的时候也同样只允许宕掉 1 台。
|
||||
假如我们有 5 台,那么最大允许宕掉 2 台 ZooKeeper 服务器,如果我们有 6 台的的时候也同样只允许宕掉 2 台。
|
||||
|
||||
综上,何必增加那一个不必要的 ZooKeeper 呢?
|
||||
|
||||
## 5. ZAB 协议和Paxos 算法
|
||||
|
||||
Paxos 算法应该可以说是 ZooKeeper 的灵魂了。但是,ZooKeeper 并没有完全采用 Paxos算法 ,而是使用 ZAB 协议作为其保证数据一致性的核心算法。另外,在ZooKeeper的官方文档中也指出,ZAB协议并不像 Paxos 算法那样,是一种通用的分布式一致性算法,它是一种特别为Zookeeper设计的崩溃可恢复的原子消息广播算法。
|
||||
|
||||
### 5.1. ZAB 协议介绍
|
||||
|
||||
ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
|
||||
|
||||
### 5.2. ZAB 协议两种基本的模式:崩溃恢复和消息广播
|
||||
|
||||
ZAB 协议包括两种基本的模式,分别是
|
||||
|
||||
- **崩溃恢复** :当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进人恢复模式并选举产生新的Leader服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。其中,**所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和Leader服务器的数据状态保持一致**。
|
||||
- **消息广播** :**当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步,那么整个服务框架就可以进人消息广播模式了。** 当一台同样遵守ZAB协议的服务器启动后加人到集群中时,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么新加人的服务器就会自觉地进人数据恢复模式:找到Leader所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。
|
||||
|
||||
关于 **ZAB 协议&Paxos算法** 需要讲和理解的东西太多了,说实话,笔主到现在不太清楚这俩兄弟的具体原理和实现过程。推荐阅读下面两篇文章:
|
||||
|
||||
- [图解 Paxos 一致性协议](http://codemacro.com/2014/10/15/explain-poxos/)
|
||||
- [Zookeeper ZAB 协议分析](https://dbaplus.cn/news-141-1875-1.html)
|
||||
|
||||
## 6. 总结
|
||||
|
||||
1. ZooKeeper 本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)。
|
||||
2. 为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。
|
||||
3. ZooKeeper 将数据保存在内存中,这也就保证了 高吞吐量和低延迟(但是内存限制了能够存储的容量不太大,此限制也是保持 znode 中存储的数据量较小的进一步原因)。
|
||||
4. ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地明显,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景。)
|
||||
5. ZooKeeper 有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个 znode 被创建了,除非主动进行 znode 的移除操作,否则这个 znode 将一直保存在 ZooKeeper 上。
|
||||
6. ZooKeeper 底层其实只提供了两个功能:① 管理(存储、读取)用户程序提交的数据;② 为用户程序提供数据节点监听服务。
|
||||
|
||||
## 7. 参考
|
||||
|
||||
1. 《从 Paxos 到 ZooKeeper 分布式一致性原理与实践》
|
Loading…
x
Reference in New Issue
Block a user