基本介绍
Apache ZooKeeper 是由Apache Hadoop的子项目发展而来,为分布式应用提供高效且可靠的分布式协调服务。
-
在解决分布式数据一致性方面,ZK没有直接采用Paxos算法,而是采用了ZAB(ZooKeeper Atomic Broadcast)协议。
ZK可以提供诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知,集群管理,Master选举,分布式锁,分布式队列等功能。
「它具有以下特性:」
-
「顺序一致性」:从一个客户端发起的事务请求,最终都会严格按照其发起顺序被应用到 Zookeeper 中;
-
「原子性」:要么所有应用,要么不应用;不存在部分机器应用了该事务,而「另一部分没有应用」的情况;
-
「单一视图」:所有客户端看到的服务端数据模型都是一致的,无论客户连接的是哪个ZK服务器;
-
「可靠性」:一旦服务端成功应用了一个事务,则其引起的改变会一直保留,直到被另外一个事务所更改;
-
「实时性」:一旦一个事务被成功应用后,Zookeeper 可以保证客户端立即可以读取到这个事务变更后的最新状态的数据(「一段时间」)。
数据模型
ZooKeeper 中的数据模型是一种树形结构,非常像电脑中的文件系统,有一个根文件夹,下面还有很多子文件夹。
-
ZooKeeper的数据模型也具有一个固定的根节点
(/)
,我们可以在根节点下创建子节点,并在子节点下继续创建下一级节点。 -
ZooKeeper 树中的每一层级用斜杠
(/)
分隔开,且只能用绝对路径(如get /work/task
)的方式查询 ZooKeeper 节点,而不能使用相对路径。
「为什么 ZooKeeper 不能采用相对路径查找节点呢?」
❝这是因为 ZooKeeper 大多是应用场景是定位数据模型上的节点,并在相关节点上进行操作。
❞
像这种查找与给定值相等的记录问题最适合用散列来解决。
因此 ZooKeeper 在底层实现的时候,使用了一个 hashtable,即 hashtableConcurrentHashMap<String, DataNode> nodes
,用节点的完整路径来作为 key 存储节点数据。
这样就大大提高了 ZooKeeper 的性能。
「节点类型」
ZooKeeper 中的数据节点也分为持久节点、临时节点和有序节点三种类型:
❝1、持久节点
❞
一旦将节点创建为持久节点,该数据节点会一直存储在 ZooKeeper 服务器上,即使创建该节点的客户端与服务端的会话关闭了,该节点依然不会被删除。如果我们想删除持久节点,就要显式调用 delete 函数进行删除操作。
❝2、临时节点
❞
如果将节点创建为临时节点,那么该节点数据不会一直存储在 ZooKeeper 服务器上。
当创建该临时节点的客户端会话因超时或发生异常而关闭时,该节点也相应在 ZooKeeper 服务器上被删除,同样,我们可以像删除持久节点一样主动删除临时节点。
在平时的开发中,我们可以利用临时节点的这一特性来做服务器集群内机器运行情况的统计,将集群设置为/servers
节点,并为集群下的每台服务器创建一个临时节点/servers/host
,当服务器下线时该节点自动被删除,最后统计临时节点个数就可以知道集群中的运行情况。
❝3、有序节点
❞
节点有序是说在我们创建有序节点的时候,ZooKeeper 服务器会自动使用一个单调递增的数字作为后缀,追加到我们创建节点的后边。
例如一个客户端创建了一个路径为 works/task-
的有序节点,那么 ZooKeeper 将会生成一个序号并追加到该节点的路径后,最后该节点的路径为works/task-1
。
-
通过这种方式我们可以直观的查看到节点的创建顺序。
ZooKeeper 中的每个节点都维护有这些内容:一个二进制数组(byte data[])
,用来存储节点的数据、ACL 访问控制信息、子节点数据(因为临时节点不允许有子节点,所以其子节点字段为 null),除此之外每个数据节点还有一个记录自身状态信息的字段 stat。
「节点的状态结构」
执行stat /zk_test
,可以看到控制台输出了一些信息,这些就是节点状态信息。
每一个节点都有一个自己的状态属性,记录了节点本身的一些信息:
「状态属性」 | 「说明」 |
---|---|
czxid | 数据节点创建时的事务 ID |
ctime | 数据节点创建时的时间 |
mzxid | 数据节点最后一次更新时的事务 ID |
mtime | 数据节点最后一次更新时的时间 |
pzxid | 数据节点的子节点最后一次被修改时的事务 ID |
「cversion」 | 「子节点的版本」 |
「version」 | 「当前节点数据的版本」 |
「aversion」 | 「节点的 ACL 的版本」 |
ephemeralOwner | 如果节点是临时节点,则表示创建该节点的会话的 SessionID;如果节点是持久节点,则该属性值为 0 |
dataLength | 数据内容的长度 |
numChildren | 数据节点当前的子节点个数 |
「数据节点的版本」
在 ZooKeeper 中为数据节点引入了版本的概念,每个数据节点有 3 种类型的版本信息,对数据节点的任何更新操作都会引起版本号的变化。
ZooKeeper 的版本信息表示的是对节点数据内容、子节点信息或者是 ACL 信息的修改次数。
数据存储
从存储位置上来说,事务日志和数据快照一样,都存储在本地磁盘上;而从业务角度来讲,内存数据就是我们创建数据节点、添加监控等请求时直接操作的数据。
-
事务日志数据主要用于记录本地事务性会话操作,用于 ZooKeeper 集群服务器之间的数据同步。
-
事务快照则是将内存数据持久化到本地磁盘。
❝这里要注意的一点是,数据快照是每间隔一段时间才把内存数据存储到本地磁盘,因此数据并不会一直与内存数据保持一致。
❞
在单台 ZooKeeper 服务器运行过程中因为异常而关闭时,可能会出现数据丢失等情况。
「内存数据」
ZooKeeper 的数据模型可以看作一棵树形结构,而数据节点就是这棵树上的叶子节点。
从数据存储的角度看,ZooKeeper 的数据模型是存储在内存中的。
我们可以把 ZooKeeper 的数据模型看作是存储在内存中的数据库,而这个数据库不但存储数据的节点信息,还存储每个数据节点的 ACL 权限信息以及 stat 状态信息等。
-
而在底层实现中,ZooKeeper 数据模型是通过 DataTree 类来定义的。
DataTree 类定义了一个 ZooKeeper 数据的内存结构。
DataTree 的内部定义类 nodes 节点类型、root 根节点信息、子节点的 WatchManager 监控信息等数据模型中的相关信息。
可以说,一个 DataTree 类定义了 ZooKeeper 内存数据的逻辑结构。
「事务日志」
为了整个 ZooKeeper 集群中数据的一致性,Leader 服务器会向 ZooKeeper 集群中的其他角色服务发送数据同步信息,在接收到数据同步信息后, ZooKeeper 集群中的 Follow 和 Observer 服务器就会进行数据同步。
❝而这两种角色服务器所接收到的信息就是 Leader 服务器的事务日志。
❞
在接收到事务日志后,并在本地服务器上执行。这种数据同步的方式,避免了直接使用实际的业务数据,减少了网络传输的开销,提升了整个 ZooKeeper 集群的执行性能。
Watch机制
ZooKeeper 的客户端可以通过 Watch 机制来订阅当服务器上某一节点的数据或状态发生变化时收到相应的通知;
「如何实现:」
我们可以通过向 ZooKeeper 客户端的构造方法中传递 Watcher 参数的方式实现:
new ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
上面代码的意思是定义了一个了 ZooKeeper 客户端对象实例,并传入三个参数:
-
connectString 服务端地址
-
sessionTimeout:超时时间
-
Watcher:监控事件
这个 Watcher 将作为整个 ZooKeeper 会话期间的上下文 ,一直被保存在客户端 ZKWatchManager 的 defaultWatcher 中。
除此之外,ZooKeeper 客户端也可以通过 getData、exists 和 getChildren 三个接口来向 ZooKeeper 服务器注册 Watcher,从而方便地在不同的情况下添加 Watch 事件:
getData(String path, Watcher watcher, Stat stat)
触发通知的条件:
上图中列出了客户端在不同会话状态下,相应的在服务器节点所能支持的事件类型。
-
例如在客户端连接服务端的时候,可以对数据节点的创建、删除、数据变更、子节点的更新等操作进行监控。
「当服务端某一节点发生数据变更操作时,所有曾经设置了该节点监控事件的客户端都会收到服务器的通知吗?」
答案是否定的,Watch 事件的触发机制取决于会话的连接状态和客户端注册事件的类型,所以当客户端会话状态或数据节点发生改变时,都会触发对应的 Watch 事件。
「订阅发布场景实现」
❝提到 ZooKeeper 的应用场景,你可能第一时间会想到最为典型的发布订阅功能。
❞
发布订阅功能可以看作是一个一对多的关系,即一个服务或数据的发布者可以被多个不同的消费者调用。
一般一个发布订阅模式的数据交互可以分为消费者主动请求生产者信息的拉取模式,和生产者数据变更时主动推送给消费者的推送模式。
ZooKeeper 采用了两种模式结合的方式实现订阅发布功能。
❝下面我们来分析一个具体案例:
❞
在系统开发的过程中会用到各种各样的配置信息,如数据库配置项、第三方接口、服务地址等,这些配置操作在我们开发过程中很容易完成,但是放到一个大规模的集群中配置起来就比较麻烦了。
通常这种集群中,我们可以用配置管理功能自动完成服务器配置信息的维护,利用ZooKeeper 的发布订阅功能就能解决这个问题。
我们可以把诸如数据库配置项这样的信息存储在 ZooKeeper 数据节点中。
如/confs/data_item1
。
-
服务器集群客户端对该节点添加 Watch 事件监控,当集群中的服务启动时,会读取该节点数据获取数据配置信息。
-
而当该节点数据发生变化时,ZooKeeper 服务器会发送 Watch 事件给各个客户端,集群中的客户端在接收到该通知后,重新读取节点的数据库配置信息。
我们使用 Watch 机制实现了一个分布式环境下的配置管理功能,通过对 ZooKeeper 服务器节点添加数据变更事件,实现当数据库配置项信息变更后,集群中的各个客户端能接收到该变更事件的通知,并获取最新的配置信息。
❝要注意一点是,我们提到 Watch 具有一次性,所以当我们获得服务器通知后要再次添加 Watch 事件。
❞