序言
Apache ZooKeeper是由集群(节点组)使用的一种服务,用于在自身之间协调,并通过稳健的同步技术维护共享数据。ZooKeeper本身是一个分布式应用程序,为写入分布式应用程序提供服务。
1.zookeeper特性
- 一致性:数据一致性,数据按照顺序分批入库。
- 原子性:事务要么成功要么失败,不会局部化。
- 单一视图:客户端连接集群中任一zk节点,数据都是一致的。
- 可靠性:每次对zk的操作状态都会保存在服务端。
- 实时性:客户端可以读取到zk服务器的最新数据。
2.zookeeper应用场景
2.1 数据发布与订阅
发布与订阅即所谓的配置管理,顾名思义就是有系统将数据发布到zk节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。例如全局的配置信息,地址列表等就非常适合使用。(Diamond和ConfigServer在这方面也具备功能)
应用中的具体使用
- 索引信息和集群中机器节点状态存放在zk的一些指定节点,供各个客户端使用;
- 系统日志(经过处理后的)存储,这些日志通常2-3天后被清除;
- 应用中用到的一些配置信息集中管理,在应用启动的时候主动来获取一次,并且在节点上注册一个Watcher,以后每次配置有更新,实时通知到应用。
- 业务逻辑中需要用到的一些全局变量,比如一些消息中间件的消息队列通常有个offset,这个offset存放在zk上,这样集群中每个发送者都能知道当前的发送进度
- 系统中有些信息需要动态获取,并且还会存在人工手动去修改这个信息。以前通常是暴露出接口,例如JMX接口,有了zk后,只要将这些信息存放到zk节点上即可。
2.2 分布通知/协调
ZooKeeper中特有watcher注册与异步通知机制,能够很好的实现分布式环境下不同系统之间的通知与协调,实现对数据变更的实时处理。
使用方法:通常是不同系统都对ZK上同一个znode进行注册,监听znode的变化(包括znode本身内容及子节点的),其中一个系统update了znode,那么另一个系统能够收到通知,并作出相应处理。
2.3 分布式锁
分布式锁,这个主要得益于ZooKeeper为我们保证了强一致性,即用户只要完全信任每时每刻,zk集群中任意节点(一个zk server)上的相同znode的数据是一定是相同的。
锁服务可以分为两类: 一个是保持独占,另一个是控制时序。
2.4 集群管理
Hbase Master选举则是zookeeper经典的使用场景;
Storm集群管理;
2.5 分布式队列
3. Zookeeper架构(Architecture)
看看下面的图表。它描述了ZooKeeper的“客户端-服务器架构”。
作为ZooKeeper架构的一部分的每个组件在下表中进行了说明。
部分 | 描述 |
---|---|
Client(客户端) | 客户端,我们的分布式应用集群中的一个节点,从服务器访问信息。对于特定的时间间隔,每个客户端向服务器发送消息以使服务器知道客户端是活跃的。类似地,当客户端连接时,服务器发送确认码。如果连接的服务器没有响应,客户端会自动将消息重定向到另一个服务器。 |
Server(服务器) | 服务器,我们的ZooKeeper总体中的一个节点,为客户端提供所有的服务。向客户端发送确认码以告知服务器是活跃的。 |
Ensemble | ZooKeeper服务器组。形成ensemble所需的最小节点数为3。 |
Leader | 服务器节点,如果任何连接的节点失败,则执行自动恢复。Leader在服务启动时被选举。 |
Follower | 跟随leader指令的服务器节点。 |
4.Zookeeper层次命名空间
下图描述了用于内存表示的ZooKeeper文件系统的树结构。ZooKeeper节点称为 znode 。每个znode由一个名称标识,并用路径(/)序列分隔。
- 在图中,首先有一个由“/”分隔的znode。在根目录下,你有两个逻辑命名空间 config 和 workers。
- config 命名空间用于集中式配置管理,workers 命名空间用于命名。
- 在 config 命名空间下,每个znode最多可存储1MB的数据。这与UNIX文件系统相类似,除了父znode也可以存储数据。这种结构的主要目的是存储同步数据并描述znode的元数据。此结构称为 ZooKeeper数据模型。
ZooKeeper数据模型中的每个znode都维护着一个 stat 结构。一个stat仅提供一个znode的元数据。它由版本号,操作控制列表(ACL),时间戳和数据长度组成。
- 版本号: 每个znode都有版本号,这意味着每当与znode相关联的数据发生变化时,其对应的版本号也会增加。当多个zookeeper客户端尝试在同一znode上执行操作时,版本号的使用就很重要。
- 操作控制列表(ACL): ACL基本上是访问znode的认证机制。它管理所有znode读取和写入操作。
- 时间戳: 时间戳表示创建和修改znode所经过的时间。它通常以毫秒为单位。ZooKeeper从“事务ID"(zxid)标识znode的每个更改。Zxid 是唯一的,并且为每个事务保留时间,以便你可以轻松地确定从一个请求到另一个请求所经过的时间。
- 数据长度: 存储在znode中的数据总量是数据长度。你最多可以存储1MB的数据。
Znode的类型
Znode被分为持久(persistent)节点,顺序(sequential)节点和临时(ephemeral)节点。
- 持久节点: 即使在创建该特定znode的客户端断开连接后,持久节点仍然存在。默认情况下,除非另有说明,否则所有znode都是持久的。
- 临时节点: 客户端活跃时,临时节点就是有效的。当客户端与ZooKeeper集合断开连接时,临时节点会自动删除。注意: 因此,只有临时节点不允许有子节点。如果临时节点被删除,则下一个合适的节点将填充其位置。临时节点在leader选举中起着重要作用。
- 顺序节点: 顺序节点可以是持久的或临时的。当一个新的znode被创建为一个顺序节点时,ZooKeeper通过将10位的序列号附加到原始名称来设置znode的路径。例如,如果将具有路径 /myapp 的znode创建为顺序节点,则ZooKeeper会将路径更改为 /myapp0000000001 ,并将下一个序列号设置为0000000002。如果两个顺序节点是同时创建的,那么ZooKeeper不会对每个znode使用相同的数字。顺序节点在锁定和同步中起重要作用。
5. Sessions(会话)
会话对于ZooKeeper的操作非常重要。会话中的请求按FIFO顺序执行。一旦客户端连接到服务器,将建立会话并向客户端分配会话ID 。
客户端以特定的时间间隔发送心跳以保持会话有效。如果ZooKeeper集合在超过服务器开启时指定的期间(会话超时)都没有从客户端接收到心跳,则它会判定客户端死机。
会话超时通常以毫秒为单位。当会话由于任何原因结束时,在该会话期间创建的临时节点也会被删除。
6 Watches(监视)
监视是一种简单的机制,使客户端收到关于ZooKeeper集合中的更改的通知。客户端可以在读取特定znode时设置Watches。Watches会向注册的客户端发送任何znode(客户端注册表)更改的通知。
Znode更改: 是与znode相关的数据的修改或znode的子项中的更改。
只触发一次watches: 如果客户端想要再次通知,则必须通过另一个读取操作来完成。
连接会话过期时: 客户端将与服务器断开连接,相关的watches也将被删除。
7. Zookeeper 工作流
ZAB协议
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议.
Zab协议有两种模式,它们分别是恢复模式(选主:leader election)和广播模式(同步:Atomic broadcast)。
当服务启动或者在领导者崩溃后,Zab就进入了恢复模式,当领导者被选举出来,且大多数Server完成了和leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
paxos算法
在一个分布式数据库系统中,如果各节点的初始状态一致,每个节点都执行相同的操作序列,那么他们最后能得到一个一致的状态;
Paxos算法解决的什么问题
解决的就是保证每个节点执行相同的操作序列。好吧,这还不简单,master维护一个全局写队列,所有写操作都必须 放入这个队列编号,那么无论我们写多少个节点,只要写操作是按编号来的,就能保证一致性。
Paxos算法通过投票来对写操作进行全局编号,同一时刻,只有一个写操作被批准,同时并发的写操作要去争取选票,只有获得过半数选票的写操作才会被 批准(所以永远只会有一个写操作得到批准),其他的写操作竞争失败只好再发起一轮投票,就这样,在日复一日年复一年的投票中,所有写操作都被严格编号排 序。编号严格递增,当一个节点接受了一个编号为100的写操作,之后又接受到编号为99的写操作(因为网络延迟等很多不可预见原因),它马上能意识到自己 数据不一致了,自动停止对外服务并重启同步过程。任何一个节点挂掉都不会影响整个集群的数据一致性(总2n+1台,除非挂掉大于n台)。
ZooKeeper集合启动,它将等待客户端连接。客户端将连接到ZooKeeper集合中的一个节点。它可以是leader或follower节点。一旦客户端被连接,节点将向特定客户端分配会话ID并向该客户端发送确认。如果客户端没有收到确认,它将尝试连接ZooKeeper集合中的另一个节点。 一旦连接到节点,客户端将以有规律的间隔向节点发送心跳,以确保连接不会丢失。
- 如果客户端想要读取特定的znode,它将会向具有znode路径的节点发送读取请求,并且节点通过从其自己的数据库获取来返回所请求的znode。为此,在ZooKeeper集合中读取速度很快。
- 如果客户端想要将数据存储在ZooKeeper集合中,则会将znode路径和数据发送到服务器。连接的服务器将该请求转发给leader,然后leader将向所有的follower重新发出写入请求。如果只有大部分节点成功响应,而写入请求成功,则成功返回代码将被发送到客户端。 否则,写入请求失败。绝大多数节点被称为 Quorum 。
下图描述了ZooKeeper工作流,后面的表说明了它的不同组件。
组件 | 描述 |
---|---|
写入(write) | 写入过程由leader节点处理。leader将写入请求转发到所有znode,并等待znode的回复。如果一半的znode回复,则写入过程完成。 |
读取(read) | 读取由特定连接的znode在内部执行,因此不需要与集群进行交互。 |
复制数据库(replicated database) | 它用于在zookeeper中存储数据。每个znode都有自己的数据库,每个znode在一致性的帮助下每次都有相同的数据。 |
Leader | Leader是负责处理写入请求的Znode。 |
Follower | follower从客户端接收写入请求,并将它们转发到leader znode。 |
请求处理器(request processor) | 只存在于leader节点。它管理来自follower节点的写入请求。 |
原子广播(atomic broadcasts) | 负责广播从leader节点到follower节点的变化。 |
ZooKeeper集合中的节点
分析在ZooKeeper集合中拥有不同数量的节点的效果。
- 如果我们有单个节点,则当该节点故障时,ZooKeeper集合将故障。它有助于“单点故障",不建议在生产环境中使用。
- 如果我们有两个节点而一个节点故障,我们没有占多数,因为两个中的一个不是多数。
- 如果我们有三个节点而一个节点故障,那么我们有大多数,因此,这是最低要求。ZooKeeper集合在实际生产环境中必须至少有三个节点。
- 如果我们有四个节点而两个节点故障,它将再次故障。类似于有三个节点,额外节点不用于任何目的,因此,最好添加奇数的节点,例如3,5,7。
我们知道写入过程比ZooKeeper集合中的读取过程要贵,因为所有节点都需要在数据库中写入相同的数据。因此,对于平衡的环境拥有较少数量(例如3,5,7)的节点比拥有大量的节点要好
8. Zookeeper leader选举
让我们分析如何在ZooKeeper集合中选举leader节点。考虑一个集群中有N个节点。leader选举的过程如下:
- 1.所有节点创建具有相同路径 /app/leader_election/guid_ 的顺序、临时节点。
- 2.ZooKeeper集合将附加10位序列号到路径,创建的znode将是 /app/leader_election/guid_0000000001,/app/leader_election/guid_0000000002等。
- 3.对于给定的实例,在znode中创建最小数字的节点成为leader,而所有其他节点是follower。
- 4.每个follower节点监视下一个具有最小数字的znode。例如,创建znode/app/leader_election/guid_0000000008的节点将监视znode/app/leader_election/guid_0000000007,创建znode/app/leader_election/guid_0000000007的节点将监视znode/app/leader_election/guid_0000000006。
- 5.如果leader关闭,则其相应的znode/app/leader_electionN会被删除。
- 6.下一个在线follower节点将通过监视器获得关于leader移除的通知。
- 7.下一个在线follower节点将检查是否存在其他具有最小数字的znode。如果没有,那么它将承担leader的角色。否则,它找到的创建具有最小数字的znode的节点将作为leader。
- 8.类似地,所有其他follower节点选举创建具有最小数字的znode的节点作为leader。
leader选举是一个复杂的过程,但ZooKeeper服务使它非常简单。
9.Zookeeper Cli
ZooKeeper命令行界面(CLI): 用于与ZooKeeper集合进行交互以进行开发。它有助于调试和解决不同的选项。
要执行ZooKeeper CLI操作,首先打开ZooKeeper服务器(“bin/zkServer.sh start”),然后打开ZooKeeper客户端(“bin/zkCli.sh”)。一旦客户端启动,你可以执行以下操作:
执行功能 | 语法 | 特点 |
---|---|---|
创建znode |
持久节点 create /path /data 顺序节点 create -s /path /data 临时节点 create -e /path /data |
顺序节点: 保证znode路径将是唯一的 临时节点: 当会话过期或客户端断开连接时,临时节点将被自动删除。 |
获取数据 | get /path |
顺序节点: 访问时必须输入znode的完整路径。eg:get /FirstZnode0000000001 |
监视znode的变化 | get /path [watch] 1 eg:get /FirstZnode 1 | 当指定的znode或znode的子数据更改时,监视器会显示通知。你只能在 get 命令中设置watch。 输出类似于普通的 get 命令,但它会等待后台等待znode更改。 |
设置数据 | set /path /data | 如果此znote节点设置了监视,执行命令后,监视此节点的客户端会收到通知 |
创建znode的子节点 | create /parent/path/subnode/path /data | 创建子节点类似于创建新的znode。唯一的区别是,子znode的路径也将具有父路径。 |
列出znode的子节点 | ls /path | 此命令用于列出和显示znode的子项。 |
检查状态 | stat /path | 状态描述指定的znode的元数据。它包含时间戳,版本号,ACL,数据长度和子znode等细项。 |
移除/删除znode | 移除 rmr /path 删除 delete/path |
rmr /path : 移除指定的znode并递归其所有子节点 delete/path : 命令类似于 remove 命令,除了它只适用于没有子节点的znode。 |
举个监听通知的例子,当我们监听的节点数据有变动的时候,客户端会收到通知如下
[zk: localhost:2181(CONNECTED) 1] get /FirstZnode “Mysecondzookeeper-app"
WATCHER: :
WatchedEvent state:SyncConnected type:NodeDataChanged path:/FirstZnode
cZxid = 0x7f
ctime = Tue Sep 29 16:15:47 IST 2015
mZxid = 0x84
mtime = Tue Sep 29 17:14:47 IST 2015
pZxid = 0x7f
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 23
numChildren = 0
属性 | 解释 |
---|---|
WatchedEvent | state: type: path: |
cZxid | 对应为该节点的创建时间(Create):创建节点时的事务ID |
ctime | 创建节点时的时间 |
mZxid | 对应该节点的最近一次修改的时间(Mofify):最后修改节点时的事务ID |
mtime | 最后修改节点时的时间 |
pZxid | 表示该节点的子节点列表最后一次修改的事务ID,添加子节点或删除子节点就会影响子节点列表,但是修改子节点的数据内容则不影响该ID; |
cversion | 子节点版本号,子节点每次修改版本号加1 |
dataVersion | 数据版本号,数据每次修改该版本号加1 |
aclVersion | 权限版本号,权限每次修改该版本号加1 |
ephemeralOwner | - |
dataLength | 该节点的数据长度 |
numChildren | 该节点拥有子节点的数量 |
znote版本号作用
普及悲观锁 | 乐观锁
- 悲观锁:它会假定所有不同事务的处理一定会出现干扰,数据库中最严格的并发控制策略,如果一个事务A正在对数据处理,那么在整个事务过程中,其他事务都无法对这个数据进行更新操作,直到A事务释放了这个锁。
- 乐观锁:它假定所有不同事务的处理不一定会出现干扰,所以在大部分操作里不许加锁,但是既然是并发就有出现干扰的可能,如何解决冲突就是一个问题。在乐观锁中当你在提交更新请求之前,你要先去检查你读取这个数据之后该数据是否发生了变化,如果有那么你此次的提交就要放弃,如果没有就可以提交。
Zookeeper中的版本号就是乐观锁,你修改节点数据之前会读取这个数据并记录该数据版本号,当你需要更新时会携带这个版本号去提交,如果你此时携带的版本号(就是你上次读取出来的)和当前节点的版本号相同则说明该数据没有被修改过,那么你的提交就会成功,如果提交失败说明该数据在你读取之后和提交之前这段时间内被修改了。
这里通过set命令并携带版本号提交更新,版本号相同更新就会成功。
如果你再次更新并使用之前的版本号那么就会失败。
10. Zookeeper和Redis区别
区别点 | Zookeeper | Redis |
---|---|---|
侧重点 | 解决分布式一致性 | 缓存 |
存储量 | 每个znote节点最多只能存储1M | Redis则根据硬盘情况而定 |
存储方式 | String | 五种数据类型 |
读写性能 | 相对比较差 | 优 |
监测机制 | Watch,有变动广而告之 | 无 |
数据同步 | 集群 + Session | - |
疑问
问题1 客户端在连接zookeeper的时候,连接的IP地址必须要填写全部的吗
测试三台zookeeper服务器A,B,C,对应的ip为A_ip,B_ip,C_ip
经测试发现,
- 1.如果只连接集群中单个A_ip,则这台A_ip宕机了,客户端就不能连接到zookeeper集群;
- 2.如果连接多个,不连接全部,只连接了A_ip和B_ip,如果A服务挂掉(B服务是ok的),客户端断掉后会立马再次连接,也会连接成功;
连接全部zookeeper服务ip容错率好些.
问题2 会不会有同时两个leader服务器存在的情况,如果有,怎么处理的;
初始状态: zookeeper服务器A,B,C三台服务器服务器中,启动后A被投为Leader;
当A宕机后,B和C重新选择出B为Leader,当A再重新能访问会发起新一轮领导选举,发现zookeeper集群中的大多数服务器(B和C)都选择了A,那么A也服从大多数,变为Follower;
参考连接:https://mp.weixin.qq.com/s/t2oH5RDw8Ckx3MkciVVnqQ中的4.4:旧Leader恢复后发起选举
解释
单点故障:(英语:single point of failure,缩写SPOF)是指系统中一点失效,就会让整个系统无法运作的部件,换句话说,单点故障即会整体故障。
参考
https://blog.csdn.net/qq_36660812/article/details/81058358
https://www.cnblogs.com/oxspirt/p/7427969.html
Zookeeper中的Znode特性
https://www.w3cschool.cn/zookeeper/zookeeper_overview.html
https://www.cnblogs.com/aspirant/p/9088322.html
https://mp.weixin.qq.com/s/t2oH5RDw8Ckx3MkciVVnqQ