ZooKeeper概述
Zookeeper是一个基于观察者模式设计的分布式服务管理框架,Zookeeper负责存储和管理数据,观察者通过在Zookeeper上注册,并接收Zookeeper观察到的数据状态变化的通知.
Zookeeper=文件系统+通知机制
特点
-
Zookeeper:一个Leader,多个Follower组成的集群
-
集群只要有半数以上节点存活,就还能正常服务
说明:
以6台服务器的zk集群为例,必须满足4台节点存活,容错为2台,
5台服务器的集群只需3台存活,容错也是2台,
因此奇数台集群相比偶数,容错更高,推荐zk集群安装奇数台服务器
-
全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的.
说明:
1)数据一致的前提是,客户端从正常服务的zk集群中的一台Server读写数据,而不适用于未启动zk集群的某个Server
2)Zookeeper底层使用了基于Paxos算法改进的Zab算法完成数据一致
-
来自Client的更新请求顺序进行:请求队列执行FIFO策略(先进先出)
-
数据更新原子性:一次数据更新要么成功,要么失败。
-
实时性,在一定时间范围内,Client能读到最新数据。
数据结构
Zookeeper角色之一是文件系统,类似Unix文件系统,使用目录树结构,每个节点称为ZNode,每个ZNode默认能存储1MB的数据,每个ZNode可以通过其路径唯一标识
应用场景
提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。
-
统一命名服务:满足分布式环境下对应用/服务统一命名的需求
-
统一配置管理:
1)集群中所有节点配置信息一致,比如Kafka集群
2)分布式环境下同步配置文件:将配置信息写入Zookeeper上一个Znode
3)客户端服务器监听该Znode,一旦其中数据被修改,ZK将通知各个客户端服务器
-
统一集群管理:ZK实时监控节点状态变化
-
软负载均衡:在Zookeeper中记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求
-
服务器动态上下线[下图]:客户端能实时洞察到服务器上下线
ZK本地模式安装部署
-
准备工作:安装好jdk,解压Zookeeper安装包到指定目录
-
配置修改:
1)zookeeper-3.5.7/conf路径下的zoo_sample.cfg修改为zoo.cfg;
2)修改zoo.cfg中内容:dataDir=/opt/module/zookeeper-3.5.7/zkData
3)对应的位置创建 zkData
4)将zookeeper-3.5.7/bin添加到环境变量
-
操作Zookeeper
1)启动Zookeeper: zkServer.sh start
2)查看状态: zkServer.sh status
3)启动客户端: zkCli.sh (注意这里没有参数start)
4)退出客户端: quit
5)停止Zookeeper: zkServer.sh stop
Zookeeper集群操作
ZK分布式安装部署
-
准备工作:同步zookeeper-3.5.7目录到三个节点
-
配置服务器编号:zkData目录下创建myid,三个节点分别编辑内容:2/3/4
-
zoo.cfg配置文件修改并同步:
#######################cluster##########################
server.2=hadoop102:2888:3888
server.3=hadoop103:2888:3888
server.4=hadoop104:2888:3888
说明:
这里server.A=B:C:D中
A对应myid的内容,对应将三个服务器认定为zk集群节点
B是这个服务器的地址
C是这个服务器Follower与集群中的Leader服务器交换信息的端口
D是选举Leader时的相互通信的端口
-
修改环境变量
-
分别在三个节点启动Zookeeper(以hadoop102,hadoop103,hadoop104为例):zkServer.sh start
-
分别查看状态:zkServer.sh status
注意:
1)单个节点启动Zookeeper,集群启动失败(半数以上),而且客户端无法连接该节点
2)屏幕输出中可以看到,这里三者角色分别为:follower,leader,follower
由此引出Leader选举机制
选举机制
- Zookeeper第一次启动的选举机制:
- 非第一次启动的选举机制:
选举Leader规则:
①EPOCH大的直接胜出 ②EPOCH相同,事务id大的胜出 ③事务id相同,服务器id大的胜出
节点类型
注意:
- 创建节点时,要赋值
- 同一节点下可以创建相同值的相同名称的有序节点,zk自动将节点名称增加序号,序号按原节点下已有n个节点的n开始
监听器原理
- 客户端注册监听某节点–>该节点变化(增删改)–>Zookeeper通知客户端
- Zookeeper以此机制保证数据的改变快速响应到注册监听的应用程序
- 注册一次,只能监听一次。想再次监听,需要再次注册。
客户端命令行操作
命令基本语法 | 功能描述 |
---|---|
help | 显示所有操作命令 |
ls path | 使用 ls 命令来查看当前 znode 的子节点 [可监听] -w 监听子节点变化 -s 附加次级信息 |
create | 普通创建 -s 含有序列 -e 临时(重启或者超时消失) |
get path | 获得节点的值 [可监听] -w 监听节点内容变化 -s 附加次级信息 |
set | 设置节点的具体值 |
stat | 查看节点状态 |
delete | 删除节点 |
deleteall | 递归删除节点 |
客户端API操作
如何使用客户端API实现,获取子节点并监听节点变化?(以根节点为例)
-
创建Zookeeper客户端:
new ZooKeeper(connectString,sessionTimeout,new Watcher());
重写new Watcher()匿名子类的process()方法:调用getChildren("/",false)获取子节点.
-
调用getChildren("/",true),参数传入true表示开启监听,并让当前所在线程延时阻塞Thread.sleep();
-
此时通过其他连接到集群的客户端操作"/"根节点即可监听到数据变化
个人认为:
- 程序的最后,即主线程末尾设置了延时阻塞,说明监听线程为守护线程
- 上述开启监听方法的第2步可省略,换为直接在1中getChildren()参数设置为true,并让线程阻塞.(客户端在完成注册时会监听一次)
客户端向服务端写数据流程
-
写入请求直接发给Leader节点:
客户端发送写请求到Leader–>Leader节点完成数据写入–>将写入请求广播给1个Follower–>该Follower收到通知完成写入,回复Leader完成写入–>Leader收到ack,判断满足集群一半以上节点完成写入–>Leader回复客户端集群已完成数据写入–>Leader广播剩下的Follower完成写入然后收到ack
-
写入请求发给Follower节点:
除了将客户端的写入请求转发给Leader和集群完成写入的ack回复给客户端,这两处流程与1中不同,其他基本相同
服务器动态上下线监听案例
需求
某分布式系统中,主节点可以有多台,可以动态上下线,任意一台客户端都能实时感知到主节点服务器的上下线。
实现
-
集群上创建/servers节点
-
IDEA中代码实现:
编写服务器端代码:
1)获取集群连接: new ZooKeeper();
2)注册: 调用create()在"/servers"下创建"/servers/hadoop102"节点
3)业务代码: 延时阻塞
编写客户端代码:
1)获取集群连接: new ZooKeeper();
2)监听: 调用getChildren("/servers",true);
3)业务代码: 延时阻塞
ZooKeeper分布式锁
一个进程在使用该资源时,会先获得锁,保持对该资源独占,其他进程就无法访问该资源用完该资源以后就将锁释放掉,让其他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的访问该临界资源。
分布式环境下的这个锁叫作分布式锁。
Curator框架实现分布式锁:
1)原生的 Java API 开发存在的问题:
(1)会话连接是异步的,需要自己去处理。比如使用 CountDownLatch
(2)Watch 需要重复注册,不然就不能生效
(3)开发的复杂性还是比较高的
(4)不支持多节点删除和创建。需要自己去递归
2)Curator 是一个专门解决分布式锁的框架,解决了原生Java API
生产集群安装
生产经验推荐(奇数台):
10 台服务器:3 台 zk;
20 台服务器:5 台 zk;
100 台服务器:11 台 zk;
200 台服务器:11 台 zk
服务器台数多:好处,提高可靠性;坏处:提高通信延时(投票半数以上机制)