分布式应用程序协调服务 ZooKeeper
1、简介:
ZooKeeper 是一个分布的、开源的协调服务,它主要是用来解决分布式应用中经常遇到的一些数据管理问题。统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等,简化分布式应用协调及其管理的难度,提供高性能的分布式服务。
2、ZooKeeper 目标:
封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
3、ZooKeeper 工作方式:
本身可以以 Standalone 模式安装运行,不过它的长处在于通过分布式 ZooKeeper 集群(一个Leader,多个 Follower),基于一定的策略来保证 ZooKeeper 集群的稳定性和可用性,从而实现分布式应用的可靠性。
最新的版本可以在官网 http://zookeeper.apache.org/releases.html 来下载 zookeeper 的最新版本。
4、ZooKeeper 特点:
最终一致性:为客户端展示同一个视图,这是 ZooKeeper 里面一个非常重要的功能。
可靠性:如果消息被一台服务器接受,那么它将被所有的服务器接受。
实时性:ZooKeeper 不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用 sync() 接口。
独立性:各个 Client 之间互不干预。
原子性:更新只能成功或者失败,没有中间状态。
顺序性:所有 Server,同一消息发布顺序一致。
5、ZooKeeper 工作原理:
每个 Server 在内存中存储了一份数据;
ZooKeeper 启动时,将从实例中选举一个 Leader(Paxos 协议);
Leader 负责处理数据更新等操作(Zab 协议);
一个更新操作成功,当且仅当大多数 Server 在内存中成功修改数据。
选举机制:
全新集群选举:每个机器都给自己投票、投票数过半选举结束。myid 大的权重大。
非全新集群选举:数据ID、服务器ID和逻辑时钟
逻辑时钟小的选举结果被忽略,重新投票;
统一逻辑时钟后,数据 ID 大的胜出;
数据 ID 相同,服务器 ID 大的胜出。
Paxos 协议:
它是一个基于消息传递的一致性算法。Paxos 有一个前提:没有拜占庭将军问题。就是说 Paxos 只有在一个可信的计算环境中才能成立,这个环境是不会被入侵所破坏的。
工作流程:提出提议,议员超过半数则提议生效,通知所有。顺序性提议编号。
ZAB 协议:
用来保障分布式数据一致性。ZAB 是一种支持崩溃恢复的消息广播协议,采用类似 2PC 的广播模式保证正常运行时性能,并使用基于 Paxos 的策略保证崩溃恢复时的一致性。
ZAB 协议中节点存在四种状态:
Leading:当前节点为集群 Leader,负责协调事务
Following:当前节点为 Follower 在 Leader 协调下执行事务
Looking:集群没有正在运行的 Leader, 正处于选举过程
Observing:节点跟随 Leader 保存系统最新的状态提供读服务,但不参与选举和事务投票
ZAB 协议的两种工作模式:
广播模式:当集群正常运行过程中,Leader 使用广播模式保证各 Follower 节点的一致性
恢复模式:集群启动或 Leader 崩溃时系统进入恢复模式,选举 Leader 并将集群中各节点的数据同步到最新状态
ZooKeeper 集群中每个节点都会存储系统数据的完整副本,可以独立处理读请求。
当 Follower 收到写请求时会将其转发给 Leader, Leader 为每个写请求分配唯一的全局有序的事务ID(Zookeeper Transaction Id, ZXID)。
Leader 在广播模式下协调各 Follower 完成事务,并保证集群更新到一致的状态。
6、ZooKeeper 角色:
领导者(Leader):领导者负责进行投票的发起和决议,更新系统状态,处理写请求。
跟随者(Follwer):Follower 用于接收客户端的读写请求并向客户端返回结果,在选主过程中参与投票。
观察者(Observer):观察者可以接收客户端的读写请求,并将写请求转发给 Leader,但 Observer节点不参与投票过程,只同步 leader 状态,Observer 的目的是为了扩展系统,提高读取速度。
客户端(Client):执行读写请求的发起方。
7、ZooKeeper 的数据模型:
树形结构的命名空间,与文件系统类似,层次化的目录结构,命名符合常规文件系统规范;
数据大小不超过 1MB(可配置),数据读写要保证完整性;
每个节点在 zookeeper 中叫做 znode,并且其有一个唯一的路径标识;
节点(znode)都可以存数据,可以有子节点,节点不支持重命名;
节点 Znode 可以包含数据和子节点(临时节点不能有子节点);
Znode 中的数据可以有多个版本,比如某一个路径下存有多个数据版本,那么查询这个路径下的数据需带上版本;
客户端应用可以在节点上设置监视器(Watcher);
节点不支持部分读写,而是一次性完整读写;
Znode 有两种类型,短暂的(ephemeral)和持久的(persistent);
Znode 的类型在创建时确定并且之后不能再修改;
短暂 znode 的客户端会话结束时,zookeeper 会将该短暂 znode 删除,短暂 znode 不可以有子节点;
持久 znode 不依赖于客户端会话,只有当客户端明确要删除该持久 znode 时才会被删除;
Znode 有 四 种 形 式 的 目 录 节 点 ,PERSISTENT、PERSISTENT_SEQUENTIAL、EPHEMERAL、EPHEMERAL_SEQUENTIAL。
节点属性:
dataVersion(数据版本号)
cversion(子节点版本号)
ACLVersion
cZxid(创建的事务id)
mZxid(修改的事务id)
ctime(节点创建时间)
mtime(节点更新时间)
ephemeralOwner(0x0:永久节点)
8、数据发布与订阅:
创建数据目录,订阅者初次启动的时候去 ZK 指定的节点获取相关的订阅信息;获取数据的同时,设置监听(监听节点数据的变化);一旦节点数据改变,触发监听,订阅者收到事件通知,获取数据,再次设置监听。
注:统一管理的数据不能太大
9、ZooKeeper 主要应用场景:
(1)统一命名服务:
分布式环境下,经常需要对应用/服务进行统一命名,便于识别不同服务;
类似于域名与 ip 之间对应关系,域名容易记住;
通过名称来获取资源或服务的地址,提供者等信息;
按照层次结构组织服务/应用名称;
可将服务名称以及地址信息写到 ZooKeeper 上,客户端通过 ZooKeeper 获取可用服务列表类。
(2)配置管理服务:
分布式环境下,配置文件管理和同步是一个常见问题;
一个集群中,所有节点的配置信息是一致的;
对配置文件修改后,希望能够快速同步到各个节点上;
配置管理可交由 ZooKeeper 实现,可将配置信息写入 ZooKeeper 的一个 znode 上,各个节点监听这个 znode ,一旦 znode 中的数据被修改,ZooKeeper 将通知各个节点。
(3)集群管理:
分布式环境中,实时掌握每个节点的状态是必要的;
可根据节点实时状态作出一些调整;
可交由 ZooKeeper 实现,可将节点信息写入 ZooKeeper 的一个 znode 上,监听这个 znode 可获取它的实时状态变化。
实现思路:
在 ZooKeeper 上创建一个临时节点,然后每个 Server 在它们创建目录节点的父目录节点上调用 getChildren(String path, boolean watch) 方法并设置 watch 为 true,由于是临时节点,当创建它的 Server 死去,这个目录节点也随之被删除,所以 Children 将会变化,这时 getChildren 上的 Watch 将会被调用,所以其它 Server 就知道已经有某台 Server 死去了。新增 Server 也是同样的原理。
(4)分布式通知和协调:
分布式环境中,经常存在一个服务需要知道它所管理的子服务的状态;
NameNode 须知道各 DataNode 的状态
JobTracker 须知道各 TaskTracker 的状态
心跳检测机制可通过 ZooKeeper 实现,信息推送可由 ZooKeeper 实现(发布/订阅模式)
(5)分布式锁:保持独占、控制时序(序列化)
Client 首先在 ZK 上指定的目录创建节点(节点是一个非序列化的临时节点),只能有一个创建成功,谁创建成功,谁就获得访问数据文件的权限,操作完成断开和 ZK 的连接,其他应用如果需要操作这个文件,就去监听这个目录是否存在。
(6)分布式队列:
两种队列:
当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达,这种是同步队列。
队列按照 FIFO 先进先出方式进行入队和出队操作,例如实现生产者和消费者模型。(可通过分布式锁实现)
同步队列:
一个 job 由多个 task 组成,只有所有任务完成后,job 才运行完成。
可为 job 创建一个/job 目录,然后在该目录下,为每个完成的 task 创建一个临时 znode,一旦临时节点数目达到 task 总数,则 job 运行完成。
同步队列实现思路:
创建一个父目录 /synchronizing,每个成员都监控标志(Set Watch)位目录 /synchronizing/start 是否存在,然后每个成员都加入这个队列,加入队列的方式就是创建 /synchronizing/member_i 的临时目录节点,然后每个成员获取 / synchronizing 目录的所有目录节点,也就是 member_i。判断 i 的值是否已经是成员的个数,如果小于成员个数等待 /synchronizing/start 的出现,如果已经相等就创建/synchronizing/start。
FIFO 队列实现思路:
实现的思路也非常简单,就是在特定的目录下创建 SEQUENTIAL 类型的子目录 /queue_i,这样就能保证所有成员加入队列时都是有编号的,出队列时通过 getChildren( ) 方法可以返回当前所有的队列中的元素,然后消费其中最小的一个,这样就能保证 FIFO。
10、ZooKeeper 集群配置:
(1)卸载 openjdk,安装 jdk 环境,配置环境变量
(2)时间同步、防火墙处理
(3)配置主机名到 ip 地址的映射
[root@znode1 ~]# cat /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 192.168.100.31 znode1 192.168.100.32 znode2 192.168.100.33 znode3
(4)三个节点分别:下载、解压 zookeeper-3.4.14.tar.gz 至安装目录(例:/usr/local/src/zookeeper-3.4.14)
(5)创建同名软链接
(6)修改 ZooKeeper 配置文件
[root@znode1 ~]# cd /usr/local/src/zookeeper/conf/ [root@znode1 conf]# cp zoo_sample.cfg zoo.cfg [root@znode1 conf]# vim zoo.cfg dataDir=/data/zookeeper clientPort=2181 server.1=znode1:2888:3888 #心跳端口、选举端口 server.2=znode2:2888:3888 server.3=znode3:2888:3888 # 参数说明 tickTime=2000 #心跳间隔 initLimit=10 #Follower 服务器初始化连接时最长能忍受多少个心跳时间间隔数 syncLimit=5 # Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度 dataDir #Zookeeper保存数据的目录,默认情况下Zookeeper将写数据的日志文件也保存在这个目录里。 clientPort #这个端口就是客户端(应用程序)连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口接受客户端的访问请求。 server.A=B:C:D #A 为数字,表示第几号服务器;B 是这个服务器的 IP 地址或主机名映射;C 第一个端口用来集群成员的信息交换,表示这个服务器与集群中的 Leader 服务器交换信息的端口;D 是在 leader 挂掉时专门用来进行选举 leader 所用的端口。
(7)在数据目录下创建 myid 文件
[root@znode1 ~]# cat /data/zookeeper/myid 1
(8)启动 ZooKeeper 集群
[root@znode1 ~]# /usr/local/src/zookeeper/bin/zkServer.sh start ZooKeeper JMX enabled by default Using config: /usr/local/src/zookeeper/bin/../conf/zoo.cfg Starting zookeeper ... STARTED [root@znode1 ~]# jps # 查看进程 1397 Jps 1372 QuorumPeerMain [root@znode1 ~]# /usr/local/src/zookeeper/bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /usr/local/src/zookeeper/bin/../conf/zoo.cfg Mode: follower
[root@znode2 ~]# /usr/local/src/zookeeper/bin/zkServer.sh start ZooKeeper JMX enabled by default Using config: /usr/local/src/zookeeper/bin/../conf/zoo.cfg Starting zookeeper ... STARTED [root@znode2 ~]# jps 1291 Jps 1260 QuorumPeerMain [root@znode2 ~]# /usr/local/src/zookeeper/bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /usr/local/src/zookeeper/bin/../conf/zoo.cfg Mode: leader
[root@znode3 ~]# /usr/local/src/zookeeper/bin/zkServer.sh start ZooKeeper JMX enabled by default Using config: /usr/local/src/zookeeper/bin/../conf/zoo.cfg Starting zookeeper ... STARTED [root@znode3 ~]# jps 1330 QuorumPeerMain 1355 Jps [root@znode3 ~]# /usr/local/src/zookeeper/bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /usr/local/src/zookeeper/bin/../conf/zoo.cfg Mode: follower
(9)链接
[root@znode1 ~]# /usr/local/src/zookeeper/bin/zkCli.sh # 本地链接 ...... [zk: localhost:2181(CONNECTED) 0] [root@znode2 ~]# /usr/local/src/zookeeper/bin/zkCli.sh -server znode3 # 远程链接 ...... [zk: znode3(CONNECTED) 0]
(9)简单命令
创建:create [-se] [path]
-s:序列化;-e:临时节点
ls、get、ls2
[zk: localhost:2181(CONNECTED) 0] ls / [zookeeper] [zk: localhost:2181(CONNECTED) 1] create /data 123 Created /data [zk: localhost:2181(CONNECTED) 2] ls / [zookeeper, data] [zk: localhost:2181(CONNECTED) 3] get /data 123 cZxid = 0x400000005 ctime = Fri May 22 18:23:24 CST 2020 mZxid = 0x400000005 mtime = Fri May 22 18:23:24 CST 2020 pZxid = 0x400000005 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 3 numChildren = 0 [zk: localhost:2181(CONNECTED) 4] ls2 /data [] cZxid = 0x400000005 ctime = Fri May 22 18:23:24 CST 2020 mZxid = 0x400000005 mtime = Fri May 22 18:23:24 CST 2020 pZxid = 0x400000005 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 3 numChildren = 0
删除:delete [-se] [path]
rmr [path]
quota
setquota -n|-b [val] [path]
n : 表示子节点的最大个数
b : 表示数据值的最大长度
val : 子节点最大个数或数据值的最大长度
listquota
delquota
history
redo