### 概述
* ZooKeeper 是 Apache 软件基金会的一个软件项目,它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。
* ZooKeeper 的架构通过冗余服务实现高可用性。
* Zookeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。

一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
### ZooKeeper文件系统

每个子目录项如 NameService 都被称作为 znode(目录节点),和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。
#### 有四种类型的znode:
* **PERSISTENT-持久化目录节点**
客户端与zookeeper断开连接后,该节点依旧存在
* **PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点**
客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
* **EPHEMERAL-临时目录节点**
客户端与zookeeper断开连接后,该节点被删除
* **EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点**
客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
#### Zookeeper特点

> 1)Zookeeper:一个领导者(Leader),多个跟随者(Follower)组成的集群。
2)集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。所 以Zookeeper适合安装奇数台服务器。
3)全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的。
4)更新请求顺序执行,来自同一个Client的更新请求按其发送顺序依次执行。
5)数据更新原子性,一次数据更新要么成功,要么失败。
6)实时性,在一定时间范围内,Client能读到最新数据。
#### 应用场景
提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。
* 统一命名服务:在分布式环境下,经常需要对应用/服务进行统一命名,便于识别。例如:IP不容易记住,而域名容易记住。
* 统一配置管理
一般要求一个集群中,所有节点的配置信息是一致的,比如 Kafka 集群;对配置文件修改后,希望能够快速同步到各个节点上。
* 统一集群管理
ZooKeeper可以实现实时监控节点状态变化,可将节点信息写入一个ZNode中,监听这个ZNode可获取它的实时状态变化
* 服务器动态上下线
同一个程序我们会部署在相同的几台服务器中,这时我们可以通过负载均衡服务器去调度,但是我们并不能很快速的获知哪台服务器挂掉了,这时我们就可以使用zookeeper来解决这个问题。 zookeeper的动态感知实利用的就是zookeeper的watch功能。
* 软负载均衡
在Zookeeper中记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求
#### 搭建zookeeper
* 单机版
利用docker进行搭建
```bash
docker run -id -p2181:2181 --name zookeeper-2181 -d zookeeper:3.4.13
```
安装nc
```bash
yum -y install nc
```
通过nc查看zk的情况
```bash
echo stat | nc 127.0.0.1 2181
```

`Mode: standalone`:表示单机版
* 集群版本
1)安装docker-compose
```bash
curl -L "https://get.daocloud.io/docker/compose/releases/download/1.27.3/docker-compose-$(uname -s)-$(uname -m)" -o
```
2)加上可执行权限
```bash
chmod +x /usr/local/bin/docker-compose
```
3)查看版本
```bash
docker-compose version
```

* 编写docker-compose.yml
```yml
version: '2'
services:
zoo1:
image: zookeeper:3.4.13
restart: always
container_name: zoo1
ports:
- "2185:2181"
environment:
ZOO_MY_ID: 1
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
zoo2:
image: zookeeper:3.4.13
restart: always
container_name: zoo2
ports:
- "2186:2181"
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
zoo3:
image: zookeeper:3.4.13
restart: always
container_name: zoo3
ports:
- "2187:2181"
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
```
> 这个配置文件会告诉 Docker 分别运行三个 zookeeper 镜像, 并分别将本地的 2185, 2186, 2187 端口绑定到对应的容器的2181端口上
> * ZOO_MY_ID :ZK 服务的 id, 它是1-255 之间的整数, 必须在集群中唯一
> * ZOO_SERVERS: ZK 集群的主机列表
* 通过` docker-compose up -d`运行脚本

> Creating network "zk_default" with the default driver
第一次启动发现docker-compose为zk集群创建了单独的docker网络
* `docker-compose ps`查看zk集群情况

**注意:docker-compose命令一定要在docker-compose.yml所在目录执行**
* 连接zk集群
```bash
docker run -it --rm \
--link zoo1:zk1 \
--link zoo2:zk2 \
--link zoo3:zk3 \
--net zk_default \
zookeeper:3.4.13 zkCli.sh -server zk1:2181,zk2:2181,zk3:2181
```
* 命令行语法
| 命令基本语法 |功能描述 |
|-------|-------|
|help |显示所有操作命令|
|ls path |使用 ls 命令来查看当前 znode 的子节点 [可监听] -w 监听子节点变化-s 附加次级信息|
|create|普通创建-s 含有序列 -e 临时(重启或者超时消失)|
|get path|获得节点的值 [可监听] -w 监听节点内容变化-s 附加次级信息|
|stat|查看节点状态|
|delete|删除节点|
|deleteall|递归删除节点|
* 通过 nc 命令连接到指定的 ZK 服务器, 然后发送 stat 可以查看 ZK 服务的状态
```bash
[root@localhost zk]# echo stat | nc 127.0.0.1 2185
Zookeeper version: 3.4.13-2d71af4dbe22557fda74f9a9b4309b15a7487f03, built on 06/29/2018 04:05 GMT
Clients:
/172.21.0.1:58648[0](queued=0,recved=1,sent=0)
Latency min/avg/max: 0/0/0
Received: 1
Sent: 0
Connections: 1
Outstanding: 0
Zxid: 0x10000000a
Mode: follower
Node count: 4
[root@localhost zk]# echo stat | nc 127.0.0.1 2186
Zookeeper version: 3.4.13-2d71af4dbe22557fda74f9a9b4309b15a7487f03, built on 06/29/2018 04:05 GMT
Clients:
/172.21.0.1:37762[0](queued=0,recved=1,sent=0)
Latency min/avg/max: 0/1/33
Received: 97
Sent: 96
Connections: 1
Outstanding: 0
Zxid: 0x10000000a
Mode: follower
Node count: 4
[root@localhost zk]# echo stat | nc 127.0.0.1 2187
Zookeeper version: 3.4.13-2d71af4dbe22557fda74f9a9b4309b15a7487f03, built on 06/29/2018 04:05 GMT
Clients:
/172.21.0.1:38378[0](queued=0,recved=1,sent=0)
Latency min/avg/max: 0/0/0
Received: 2
Sent: 1
Connections: 1
Outstanding: 0
Zxid: 0x10000000a
Mode: leader
Node count: 4
Proposal sizes last/min/max: 32/32/82
```
通过日志我们发现 只有2187机器是`leader` ,其他机器是`follower`
#### Zookeeper 选举机制
* Zookeeper选举机制 ---- 第一次启动

>1)服务器1启 动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持为
LOOKING;
2)服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息:此时服务器1发现服务器2的myid比自己目前投票推举的(服务器1) 大,更改选票为推举服务器2。此时服务器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器1,2状态保持LOOKING
3)服务器3启动,发起一次选举。此时服务器1和2都会更改选票为服务器3。此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服
务器3的票数已经超过半数,服务器3当选Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING;
4)服务器4启动,发起一次选举。此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为 1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING; (5)服务器5启动,同4一样当小弟。
* Zookeeper选举机制——非第一次启动

>1)当ZooKeeper集群中的一台服务器出现以下两种情况之一时,就会开始进入Leader选举:
• 服务器初始化启动。
• 服务器运行期间无法和Leader保持连接。
2)而当一台机器进入Leader选举流程时,当前集群也可能会处于以下两种状态:
• 集群中本来就已经存在一个Leader。
对于第一种已经存在Leader的情况,机器试图去选举Leader时,会被告知当前服务器的Leader信息,对于该机器来说,仅仅需要和Leader机器建立连接,并进行状态同步即可。
• 集群中确实不存在Leader。
假设ZooKeeper由5台服务器组成,SID分别为1、2、3、4、5,ZXID分别为8、8、8、7、7,并且此时SID为3的服务器是Leader。某一时刻,
3和5服务器出现故障,因此开始进行Leader选举。
SID为1、2、4的机器投票情况:
1->(EPOCH,ZXID,SID ): (1,8,1)
2->(EPOCH,ZXID,SID ): (1,8,2)
4->(EPOCH,ZXID,SID ): (1,7,4)
**选举Leader规则: ①EPOCH大的直接胜出 ②EPOCH相同,事务id大的胜出 ③事务id相同,服务器id大的胜出**

zookeeper入门