本文将从以下几点来介绍Zookekper及其基本原理:
- Zookeeper项目简介
- Zookeeper有哪些功能
- Zookeeper基本概念
- 什么是Zab协议
(一)Zookeeper项目简介
Apache Zookeeper是由Apache Hadoop子项目发展而来,于2010年11月正式成为了Apache顶级项目。Zookeeper是一个为分布式应用提供高效、可靠的一致性服务的基础服务。
(二)Zookeeper有哪些功能(实际应用)
1:配置管理(配置中心)
为什么作为配置中心?
项目中用到的数据库信息一般都是写在配置文件里,如果需要修改配置信息,通常先要修改配置文件然后再进行部署。假如集群中有几百个节点上的App需要修改配置,再使用修改后再部署的方式就会非常麻烦,这个时候就可以用到统一配置管理。
配置中心原理说明:
- 配置项放于ZooKeeper中
- 对公共配置修改后发布到ZooKeeper
- 对ZK配置节点监听,配置一旦被修改,应用可实时监听到并获取
2:分布式锁
线程锁:只在同一JVM中有效果,在根本上是依靠线程之间共享内存实现的。
分布式锁:多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的主机需要访问共享资源,在访问这些资源的时候需要通过一些互斥手段防止彼此之间的干扰。
分布式锁主要用于在分布式环境中保护跨进程、跨主机、跨网络的共享资源实现互斥访问,以达到保证数据的一致性。
图一
图二
图一的整个区域表示一个Zookeeper集群,LOCK是Zookeeper的一个持久节点,0000001、0000002、0000003、000000n是LOCK这个持久节点下面的临时顺序节点,它们分别代表来自Client_1、Client_2、Client_3、Client_n的客户端请求。
分布式锁算法思路:利用名称唯一性,加锁操作时,只需要所有客户端一起创建/test/Lock节点,只有一个创建成功,成功者获得锁。解锁时,只需删除/Lock节点,其余客户端再次进入竞争创建节点,直到所有客户端都获得锁。
特点:这种方案的正确性和可靠性是ZooKeeper机制保证的,实现简单。缺点是会产生惊群效应,假如许多客户端在等待一把锁,当锁释放时候所有客户端都被唤醒,仅仅有一个客户端得到锁。
优化算法:
- 在获取分布式锁的时候在LOCK节点下创建临时顺序节点,释放锁的时候删除该临时节点。客户端调用createNode方法在LOCK下创建临时顺序节点。然后调用getChildren(“LOCK”)来获取LOCK下面的所有子节点,注意此时不用设置任何Watcher。
- 客户端获取到所有的子节点path之后,如果发现自己在之前创建的子节点序号最小,那么就认为该客户端获取到了锁。如果发现自己创建的节点并非LOCK所有子节点中最小的,说明自己还没有获取到锁。
- 此时客户端需要找到比自己小的那个节点,然后对其调用exist()方法,同时对其注册事件监听器。
- 之后,如果这个被关注的节点删除,则客户端的Watcher会收到相应通知,此时再次判断自己创建的节点是否是LOCK子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤继续获取到比自己小的一个节点并注册监听。
3:master选举
首先思考为啥需要master选举?
对外提供7*24小时服务的系统,如采用的是Master+Slave集群,不会存在单点故障。集群中由主机向外提供服务,备机监听主机状态,一旦主机宕机,备机必需迅速接管主机继续向外提供服务。在这个过程中,从备机选出一台机作为主机的过程,就是Master选举。
在这里Zookeeper是实现的master选举机制完成这一过程,选举机制分两种:
第一种:谁先创建master临时节点,谁就是master,当一个master挂掉了,master节点就消失了,别的节点就会监听到,就会继续去创建master临时节点,以此类推,利用Zookeeper的两个特点(一个节点只能成功创建一次、利用监听的机制)
第二种:在master下面创建临时有序节点,那个节点最小,那个就是master,节点挂掉,下面那个临时节点就会监听到上面的临时节点挂掉了,从而取代成为master,以此类推,(利用Zookeeper创建节点临时有序的特性)
两种选举机制示意图:
第一种:
第二种:
4:服务注册/发现
服务注册:
每个服务向注册中心注册登记自己提供的服务,服务注册之后,注册中心会维护这份注册清单,服务提供者会周期性地向Server发送心跳以续约自己的信息。如:Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(默认90秒)。
服务发现:
在微服务中,服务的调用不再通过指定的地址来实现,而是通过向服务名发起请求调用实现。
解决的问题:
- 服务提供者和调用者间的解耦
- 使用服务名称而不是IP+Port端口号访问服务
简单点说,ZooKeeper = 文件系统 + 通知机制
(三)Zookeeper基础核心
1:服务器角色
在分布式环境中,最典型的集群模式就是Master/Slave(主从模式)。而在ZooKeeper中,并没有沿用MS的概念,而是使用了Leader、Follower和Observer三种角色。
Leader:ZooKeeper 启动时需要在所有服务器中选举出一个 Leader ,然后让这个 Leader 来负责管理集群。此时,集群中的其它服务器则成为此 Leader 的 Follower 。并且当 Leader 故障的时候,需要 ZooKeeper 能够快速地在 Follower 中选举出下一个 Leader 。Leader是整个集群工作机制中的核心,它的主要职责有以下2个:
- 集群中所有机器通过Leader选举选定的一台机器即为Leader,提供读和写服务,所有zk事务操作均通过Leader发起,待Follower过半响应(n/2+1)Ack后Commit事务确认事务成功
- 集群内部各服务器的调度者。
注意:过半响应指的是符合 (n/2+1) 其中n为总结点数包括leader。这么多节点ack才行。
Follower
- 处理客户端非事务请求,转发事务请求给Leader服务器。
- 参与事务请求Proposal的投票
- 提供读服务,参与Leader选举,参与过半写成功策略
Observer
提供读服务,不参与Leader选举,不参与过半写成功,即Observer只提供读服务,可水平拓展Observer机器来提升Zk集群读性能。通常用于提升服务器集群非事务处理能力。对于非事务请求,都可以独立处理,而对于事务请求,则会转发给Leader进行处理。Follower和Observer都能提供读服务,唯一的区别在于:Observer机器不参与Leader选举,也不参与写操作“过半写成功”策略。
2:通知机制(Watch)
ZooKeeper允许客户端向服务器注册一个Watcher监听,当服务端的发生了一些指定的事件(如:节点创建、节点删除、数据更新等)就会触发这个Wather,那么服务端就会向指定客户端发送一个事件通知。ZooKeeper就是使用Watcher机制实现分布式数据的发布/订阅功能。
ZooKeeper的Watcher机制主要包括客户端线程、客户端WatcherManager、ZooKeeper服务器三部分。客户端在向ZooKeeper服务器注册的同时,会将Watcher对象存储在客户端的WatcherManager当中。当ZooKeeper服务器触发Watcherls /事件后,会向客户端发送通知,客户端线程从WatcherManager中取出对应的Watcher对象来执行回调逻辑。
3:数据模型
ZooKeeper的视图结构和标准Unix文件系统非常相似,但是没有引入传统文件系统中的目录和文件相关的概念,而是使用其特有的“数据节点”概念,称之为ZNode。数据节点还可以增加子节点,因此构成了一个层次化的数据结构,称之为树。
Zxid
在Zookeeper中,事务是指能够改变ZooKeeper服务器状态的操作,一般包括节点创建与删除、数据节点内容更新和客户端会话创建与失效。
对于每个事务请求,Zookeeper都会为其分配一个全局唯一的事务ID,用ZXID表示,通常是64位的数字,每个ZXID对应一次更新操作,从这些ZXID中可以间接地识别出Zookeeper处理这些更新操作请求的全局顺序。
Znode
ZNode是Zookeeper中数据的最小单元,每个ZNode上都可以保存数据,同时还可以增加子节点。
在Zookeeper中,每个数据节点都有其生命周期,类型不同则生命周期不同。节点类型可以分为如下四种类型节点。
- 持久节点(PERSISTENT)。节点创建后便一直存在于ZooKeeper服务器上,除非有操作主动删除该节点。
- 持久顺序节点(PERSISTENT_SEQUENTIAL)。相比持久节点,其新增了顺序特性,每个父节点都会为它的第一级子节点维护一份顺序,用于记录每个子节点创建的先后顺序。在创建节点时,会自动添加一个数字后缀,作为新的节点名,该数字后缀的上限是整形的最大值。
- 临时节点(EPEMERAL)。临时节点的生命周期与客户端会话绑定,客户端失效,节点会被自动清理。同时,ZooKeeper规定不能基于临时节点来创建子节点,即临时节点只能作为叶子节点。
- 临时顺序节点(EPEMERAL_SEQUENTIAL)。在临时节点的基础添加了顺序特性。
ZNode节点属性说明
4:版本
ZooKeeper使用版本来保证分布式数据原子性操作。每个数据节点都具有三种类型的版本信息,对数据节点的任何更新操作都会引起版本号的变化。
- version 当前数据节点数据内容的版本号
- dataVersion 当前数据节点内容的版本号
- aversion 当前数据节点ACL变更版本号
和传统意义上的软件版本概念有区别的是:Zookeeper中的版本表示对数据节点的数据内容、子节点列表,或是节点ACL信息的修改次数。强调的是修改次数,因此即使前后两次变更并没有使数据内容发生变化,version的值依然会更新。
如version为1表示对数据节点的内容变更了一次。即使前后两次变更并没有改变数据内容,version的值仍然会改变。version可以用于写入验证,类似于CAS。
5:ACL
如何ZooKeeper中数据的安全,从而避免因误操作而导致的数据异常十分重要,Zookeeper提供了一套完善的ACL权限控制机制来保障数据的安全。ACL无递归机制,任何一个ZNode创建后,都需要单独设置ACL,无法继承父节点的ACL设置。
ACL机制包含三个方面:
- 权限模式(Scheme)
- 授权对象(ID)
- 权限(Permission)
通常使用"scheme🆔permission"来标识一个有效的ACL信息。
(1):权限模式
权限模式用来确定权限验证过程中使用的检验策略,有如下模式:
命令行使用DigestAuthenticationProvider进行加密:
java -cp ./zookeeper-3.4.12.jar:./lib/log4j-1.2.17.jar:./lib/slf4j-api-1.7.25.jar:./lib/slf4j-log4j12-1.7.25.jar org.apache.zookeeper.server.auth.DigestAuthenticationProvider test:123456\
# 显示结果
test:123456->test:PbXQT4DQMDcaYC1X0EY0B2RZCwM=
(2):授权对象
授权对象是指权限赋予的用户或一个指定实体,如IP地址或机器等。不同的权限模式通常有不同的授权对象。
权限是指通过权限检查可以被允许执行的操作,Zookeeper对所有数据的操作权限分为:
- CREATE(节点创建权限)
- DELETE(节点删除权限)
- READ(节点读取权限)
- WRITE(节点更新权限)
- ADMIN(节点管理权限)
权限详见源码类“org.apache.zookeeper.ZooDefs”
@InterfaceAudience.Public
public interface Perms {
int READ = 1 << 0;
int WRITE = 1 << 1;
int CREATE = 1 << 2;
int DELETE = 1 << 3;
int ADMIN = 1 << 4;
int ALL = READ | WRITE | CREATE | DELETE | ADMIN;
}
ACL操作示例:
# 查看权限
getAcl /
# 创建节点带IP权限
create /Test123 123 ip:192.168.56.105:cdrwa
create /Tese123 123 ip:127.0.0.1:r
# 增加Auth权限验证
addauth digest test:123456
# 创建节点带Digest权限
create /Test123 123 digest:test:123456:cdrwa
# 设置节点的Acl
setAcl /Test123 ip:192.168.56.105:cdrwa
# 删除带权限节点
delete /Test123
6:会话与客户端
Zookeeper客户端和服务器连接成功后,就建立了一个会话。在整个运行的生命周期中,会话会在不同状态间切换,这些状态分别是:CONNECTING、CONNECTED、RECONNECTING、RECONNECTED和CLOSE等。
SessionID初始化关键源码类:“org.apache.zookeeper.server.SessionTrackerImpl”
public static long initializeNextSession(long id) {
long nextSid = 0;
// 1. 获取时间
// 2. 时间进行左移24位
// 3. 时间再无符号右移8位
nextSid = (Time.currentElapsedTime() << 24) >>> 8;
// 服务器ID左移56位,并与nextSid进行“|”操作
nextSid = nextSid | (id <<56);
return nextSid;
}
7:会话激活
为了保持客户端会话的有效性,ZooKeeper在运行过程中,客户端会在会话超时时间范围内发送ping到服务器以保持会话的有效性,俗称“心跳检测”。同时,服务器需要不断接到来自客户端的心跳检测,并且根据需要重新激活对应的客户端会话,这个激活过程称为touchSession。
- 检测改会话是否被关闭
- 计算该会话的下一次超时时间
- 定位当前会话的区块
- 迁移会话
8:会话分桶
ZooKeeper的会话管理主要是由SessionTracker负责的,其采用了一种特殊的会话管理方式,称其为“分桶策略”。所谓分桶策略,是将超时时间相近的会议放到同一个桶中来进行管理,以减少管理的复杂度。在检查超时的会话时,只需要检查桶中剩下的会话即可(没有被转移走的会话全是超时的)。
分桶的原则:每个会话的“下个超时时间点”,对于一个新创建的会话,ZK在创建完毕的时候就会计算下一个超时时间点。
private long roundToInterval(long time) {
// 计算会话的下个过期点
return (time / expirationInterval + 1) * expirationInterval;
}
public SessionTrackerImpl(SessionExpirer expirer,
ConcurrentHashMap<Long, Integer> sessionsWithTimeout, int tickTime,
long sid, ZooKeeperServerListener listener)
{
super("SessionTracker", listener);
this.expirer = expirer;
this.expirationInterval = tickTime;
this.sessionsWithTimeout = sessionsWithTimeout;
// 计算会话的下个过期点
nextExpirationTime = roundToInterval(Time.currentElapsedTime());
// 初始化SessionID
this.nextSessionId = initializeNextSession(sid);
// session分桶并激活session
for (Entry<Long, Integer> e : sessionsWithTimeout.entrySet()) {
addSession(e.getKey(), e.getValue());
}
}
9:数据和存储
使用Zookeeper过程中,会有dataDir和dataLogDir两个目录,分别用于snapshot和事务日志的输出(默认情况下只有dataDir目录,snapshot和事务日志都保存在这个目录中)。正常运行过程中,ZK会不断地把快照数据和事务日志输出到这两个目录,并且如果没有人为操作的话,ZK自己是不会清理这些文件的。
10:ZKDatabase
ZooKeeper的内存数据库,负责管理ZooKeeper的所有会话、DataTree存储和事务日志。ZKDatabase会定时向磁盘dump快照数据。同时在ZooKeeper服务器启动的时候,会通过磁盘上的事务日志和快照数据文件恢复成一个完整的内存数据库。
11:事务日志
事务日志的特点:
- 文件初始大小是4K。一旦内容超过4K,则会进行“预分配”扩容成67108880KB,即64MB;
- 文件后缀名都是十六进制数字。而且随着时间的推移,这个十六进制后缀会变大。
文件后缀名的含义:
事务日志文件名的后缀是一个事务ID,并且是写入该事物日志文件的第一条记录的ZXID。使用ZXID作为文件后缀。
日志查看命令:
java -cp ./zookeeper-3.4.12.jar:./lib/log4j-1.2.17.jar:./lib/slf4j-api-1.7.25.jar:./lib/slf4j-log4j12-1.7.25.jar org.apache.zookeeper.server.LogFormatter /home/hadoop/zookeeper/server3/logs/version-2/log.8b00000001
12:数据快照
快照是Zookeeper数据存储中另外一个非常核心的运行机制。数据快照用来记录Zookeeper服务器上某个特定时刻的全量内存数据,并将其写入到指定的磁盘文件中。
数据快照文件的命名规则和日志一致,后缀标识本次数据快照开始时刻服务器最新的ZXID。在恢复阶段,Zookeeper会根据该ZXID来确定数据恢复的起始点。
和日志文件不同的是,Zookeeper的快照数据没有采用“预分配”机制,因此不会像事务日志文件内容中包含大量的0。每个数据快照文件的所有内容都是有效的,因此该文件的大小在一定程度上能反映当前Zookeeper内存中的全量数据大小。
ZooKeeper快照采用取“过半随机”策略:logcount > (snapcount/2+randRoll)
● logcount 代表当前记录日志数量
● randroll为1-snapcount/2之间的随机数。snapcount默认值为100000,那么ZooKeeper会在50000-100000次事务日志记录后进行一次数据快照。
快照查看命令:
java -cp ./zookeeper-3.4.12.jar:./lib/log4j-1.2.17.jar:./lib/slf4j-api-1.7.25.jar:./lib/slf4j-log4j12-1.7.25.jar org.apache.zookeeper.server.SnapshotFormatter /home/hadoop/zookeeper/server2/data/version-2/snapshot.340000007f