关于java:ZooKeeper实战

67次阅读

共计 8644 个字符,预计需要花费 22 分钟才能阅读完成。

概述

ZooKeeper 是什么?

Zoo 是动物园的意思,Keeper 是管理员的意思,动物园里有各种动物,它们有的脾气火暴,有的能爬树,有的能唱歌,还有的甚至能跳舞,所以咱们须要一个管理员来治理,动物园管理员就是 ZooKeeper。

咱们的程序千奇百怪各种各样,当然也须要一个 Boss 来治理咯!

本文组织构造为跳跃式,如果你哪个名词概念不了解,能够看看前面的章节。

xdm 我在这放一个官网文档链接,我感觉官网文档写的蛮分明的,倡议你先看看。如果感觉我哪里写的不好就评论通知我,这玩意我才看了一周就完结了,必定少了不少实践。

起步、HelloWorld

这一步比较简单,就是下载,解压缩,配置一下货色,而后跑。

官网的教程其实曾经十分明了了,我就不再多演示了。

算了我还是逼逼两句吧。

单例模式

  • 在 conf 文件夹下创立一个 zoo.cfg 文件,ZK 会检测 conf 文件夹,如果发现存在用户自定义的文件,那就应用,否则应用默认的。
# 时钟滴答数,毫秒,作为以及工夫根本单位,前面的工夫都是它的 N 倍。tickTime=2000
# 数据目录,指出要在哪里存放数据。dataDir=/var/lib/zookeeper
# 裸露给服务端去连贯的端口,就是监听端口
clientPort=2181
  • 在 bin 文件夹下./zkServer.sh start 进行开始。
./zkServer.sh start
  • 应用命令行连贯到服务器,须要在 bin 文件夹下执行./zkCli.sh -server 127.0.0.1:2181
./zkCli.sh -server 127.0.0.1:2181
  • 连贯胜利会看到如下输入:
Connecting to localhost:2181
log4j:WARN No appenders could be found for logger (org.apache.zookeeper.ZooKeeper).
log4j:WARN Please initialize the log4j system properly.
Welcome to ZooKeeper!
JLine support is enabled
[zkshell: 0]

更多命令详情请看官网。

每个 ZK 在初始状态下都有一个根结点 ’/’,而后你能够在这上面创立一个节点,比方我创立了一个 test_data 节点,那这个节点的门路就是 /test_data,同时每个节点还能存放数据。这一点和 Unix/Linux 文件系统很像,你能够把节点看成文件夹,节点里的数据看成文件;如果以后节点不是叶子结点,那这个节点就蕴含一个或多个“文件夹”和一个“文件”,心愿这能让读者了解 ZK 的节点概念。

当初咱们通过客户端创立一个节点并写入数据:

首先查看根结点:

根结点上面有一个 zookeeper 的节点,这是自带的,不必管。

而后咱们创立一个节点,就叫 zk_test,并写入数据:

查看节点状况和节点里的数据:

咱们也能够设置新的数据并读取:

至此,一的单例的 ZK 就完结了!其余命令详见官网。

伪集群模式

如果你和我一样没有钱或者只是想在本地调试,那咱们能够思考应用伪集群模式,在本地开多个 ZK 实例实现集群。

家喻户晓,一个网络过程能够由 IP:PORT 惟一确定,所以咱们能够设置多个 ZK 实例,让它们不要监听同一个端口就行了。

  • 1⃣️首先创立多个 ZK 实例文件夹:

我这里创立了三个文件夹,每个文件夹上面都有一个 ZK 实例,这里为什么要三个呢?而且最好是奇数呢?因为 ZK 要求集群中只有有一半以上可用,集群就是可用的,所以是奇数,所以起码三个。

  • 2⃣️接下来记得在数据目录 (就是配置文件里的那个目录) 里创立一个 myid 的文件,外面蕴含一个数字,表明以后服务器的 ID。
echo [服务器 ID] > myid

在这里我的每一个实例的数据保留在 data 文件夹里,所以我 cd data 而后输出 echo [server.id] > myid,回车即可。

  • 3⃣️而后批改每个实例的配置文件:

zk1:

tickTime=2000
dataDir=/usr/local/zk/zk1/data
clientPort=8391
# 初始连接时间 =10 * tickTime,如果超时阐明它与指标服务器不可达
initLimit=10
# 同步工夫 =5 * tickTime,如果超时阐明同步失败,可能断网了或指标服务器宕机了
syncLimit=5
admin.serverPort=8491
# 表明怎么找到服务器 1
server.1=127.0.0.1:2888:3888
# 怎么找到服务器 2
server.2=127.0.0.1:2889:3889
# 怎么找到服务器 3,这里留神,有两个端口,第一个端口是 follow 和 leader,leader 和 follow 之间通信用的,第二个端口是在 leader 宕机时,follow 与 follow 之间投票选举用的。server.3=127.0.0.1:2890:3890

zk2 和 zk3 同理。⚠️zk2 和 zk3 的 clientPort 和 admin.serverPort 须要改一下,这样每个实例都会监听不同的客户端端口。

  • 4⃣️最初咱们就能够顺次开启服务器来实现伪集群了。

咱们首先看看各个服务器的数据:

还记得咱们在单例模式创立了一个节点并设置了数据吗?我把那个节点作为了 1 号服务器,当初咱们通过 2 号服务器查问失去如下:

能够很显著的看到,数据被同步了,设置在 1 号服务器的数据呈现在了 2 号服务器,阐明集群胜利了!

而后读者能够尝试在三个客户端任意设置创立节点,设置数据,看其余节点的上面有没有被同步。

集群模式

这个简略的很,把伪集群的 IP 和 PORT 换成理论服务器就行了,就完结了。

设计模式

官网在这

ZK 的数据模型。咱们下面略微提了一下,就是每个 ZK 节点都有一个根结点 ’/’,每个根结点能够设置咱们须要的子节点,子节点也能够设置子节点 …,每个节点 能够领有 0 / N 的子节点 ,同时能够 绑定 0 / 1 个数据。如果咱们把每个 ZK 实例类比为一个文件系统,那么每个节点就是一个文件夹,这个文件夹只能蕴含最多一个文件和 N 个文件夹。

ZNode

ZK 中的每一个节点,咱们称为 ZNode,每一个 ZNode 通过门路进行惟一标识,就像文件夹通过文件夹门路惟一标识一样。

每个 ZNode 蕴含一个 状态 的数据结构,用来记录数据版本号、工夫戳等信息以及拜访权限。数据版本号和工夫戳能够验证数据的更新是否无效,因为 每次对数据更新,版本号都是主动 +1。每一次客户端获取这个节点的数据,同时也会失去这个数据的版本号,每次对数据更新时会把本人失去的版本号发过来,ZK 看看是不是等于以后版本号,如果不是,阐明被其余数据更新了。

ZNode 是客户端拜访的次要指标,所以须要咱们好好提提。

Watches

客户端能够在一个 ZNode 上增加一个 Watches,每次节点产生了变动,比方被删除了,子节点被删除了,数据被删除了,数据被更新了,Watches 都会告诉设置它的客户端,而后它就被删除了,任何一个 Watches 的存活周期都是一个 ZNode 状态变更。

数据拜访

ZNode 数据的读写都是原子的,每次读会读取全副数据,写会笼罩原始数据并写入全副数据。

此外,ZNode 能保留的数据大小不超过 1M,也就是 1024KB,官网给出的倡议是越小越好,不然大数据波及到更多的 IO 和网络操作,会造成同步呈现提早景象。这么小的数据咱们能够放配置文件,也能够放要害数据,比方 Redis 的键,如果你非要放大数据,能够把数据存在别的中央,而后这里放寄存地点的指针。

长期节点

长期节点,顾名思义,由客户端创立,并在客户端连贯敞开后主动删除。

这一个性有很多利用,比方咱们能够做集群监控:每一个服务器会在启动时在其余服务器上创立一个长期节点,这样当这个服务器宕机时,其余服务器里保留的这个服务器创立的长期节点就会被删除,这样大家就晓得谁 down 了,谁还在工作。

长久化节点

长久化节点,顾名思义,在客户端连贯断开时不会被删除。

程序长久化节点

程序长久化节点是有序的,连贯断开它不会被删除。为什么说它是程序的呢?

当客户端申请在某个节点下创立程序节点时,ZK 会在新创建的节点名前面增加一个计数器,这个计数器是枯燥递增的,且格局为 %010d。这样每次创立的节点就是有序的,同时命名也是惟一的,既然命名惟一,那就能够做集群中的命名服务。

程序长期节点

有序然而长期的节点。一个很好的利用就是分布式锁。

如果咱们想用程序长期节点实现分布式锁,能够这么做:

  • 在指定门路下 (比方 /locks) 创立一个长期程序节点。
  • 通过 getChildren()办法获取 /locks 下的所有节点。
  • 如果以后节点是最小的,阐明失去了锁,执行;否则在以后节点前一个节点注册一个监听。
  • 如果失去了锁,执行结束,开释锁,开释锁通过删除本人的长期节点实现,这个操作会触发监听在这个节点上的 Watches,而后告诉下一个节点来获取锁。

当初来答复几个问题。

一、为什么是长期程序节点?程序节点好了解,为了确保每次都是以后节点列表里最小的节点取得了锁,长期节点的意义在于当锁拥有者宕机时,节点会主动删除,不会触发死锁。
二、这样做有什么益处?益处之一就是防止的惊群效应,也就是一把锁的开释不会导致所有过程来竞争锁,同时实现了偏心锁。

说到锁,咱们再来说一下如何创立非偏心、抢占式的排他锁,以及共享锁。

先说排他锁:

  • 在指定门路下,创立一个同名长期节点,因为 ZK 会保障多个客户端创立时只会有一个胜利,所以只会有一个客户端拿到了锁(创立节点胜利了)。
  • 如果节点创立失败,阐明有别的客户端拿到了锁,咱们能够在这个节点上注册一个监听,当节点被删除,就能够再次进行竞争。
  • 如果拿到了锁,就执行,而后开释锁(删除节点)。

再来看看共享锁:

  • 在指定门路下创立长期程序节点,并指出节点类型(通过在节点前缀加 R / W 来标识是读操作还是写操作),这里顺便一提,程序节点的自增和节点名没有任何关系,你只有创立了一个程序节点,这个程序节点的后缀就是自增的。
  • 当想要进行读操作,就看本人是不是最小的,如果不是,就看本人后面节点有没有写操作;如果本人是最小的 / 后面没有写操作,就能够进行读了。否则在后面最大的写节点上注册 Watches。
  • 当想要进行读操作,就看本人是不是最小的,如果不是,就在本人后面的节点注册一个 Watches。

菜鸟教程说的很明确,大家能够看看。

同时也有一些封装好的基于 ZK 的分布式锁,大家能够间接应用。

容器节点

新个性,临时不提

TTL 节点

新个性,临时不提

ZK 工夫格局

这个理解就行,我不翻译了,我间接贴:

  • Zxid Every change to the ZooKeeper state receives a stamp in the form of a zxid (ZooKeeper Transaction Id). This exposes the total ordering of all changes to ZooKeeper. Each change will have a unique zxid and if zxid1 is smaller than zxid2 then zxid1 happened before zxid2.
  • Version numbers Every change to a node will cause an increase to one of the version numbers of that node. The three version numbers are version (number of changes to the data of a znode), cversion (number of changes to the children of a znode), and aversion (number of changes to the ACL of a znode).
  • Ticks When using multi-server ZooKeeper, servers use ticks to define timing of events such as status uploads, session timeouts, connection timeouts between peers, etc. The tick time is only indirectly exposed through the minimum session timeout (2 times the tick time); if a client requests a session timeout less than the minimum session timeout, the server will tell the client that the session timeout is actually the minimum session timeout.
  • Real time ZooKeeper doesn’t use real time, or clock time, at all except to put timestamps into the stat structure on znode creation and znode modification.

ZK 状态构造

后面提到,状态构造用来记录节点的状态信息等,我也不翻译了,间接贴,看看状态构造保留了哪些信息:

  • czxid The zxid of the change that caused this znode to be created.
  • mzxid The zxid of the change that last modified this znode.
  • pzxid The zxid of the change that last modified children of this znode.
  • ctime The time in milliseconds from epoch when this znode was created.
  • mtime The time in milliseconds from epoch when this znode was last modified.
  • version The number of changes to the data of this znode.
  • cversion The number of changes to the children of this znode.
  • aversion The number of changes to the ACL of this znode.
  • ephemeralOwner The session id of the owner of this znode if the znode is an ephemeral node. If it is not an ephemeral node, it will be zero.
  • dataLength The length of the data field of this znode.
  • numChildren The number of children of this znode.

ZK 的 Watches

一个 Watches 就是一个事件触发器,每次它监听的节点数据变动时,它就会被触发,而后告诉设置它的客户端,而后被删除。

任何对于节点的读操作都能够附带地进行一个 Watches 的设置操作。比方 getData(),getChildren()和 exists()。

通过下面那段话,咱们能够总结出三个对于 Watches 的个性:

  • 一次性触发。Watches 事件一旦被触发,Watches 就会被删除,除非客户端再次设置,否则同一个节点的再次更改也无奈让客户端失去告诉。
  • 对客户端的告诉。如果一个客户端没有设置 Watches,那么无论 ZK 的节点怎么变,客户端都不晓得;此外,即便设置了 Watches,也不能保障永远看到最新的数据;怎么了解呢?比方客户端在某个节点设置了一个 Watches,而后数据更改,Watches 被发送会设置它的客户端,然而在 发送途中又有其余客户端批改了数据 ,此时的节点又产生了变动,而因为 Watches 还在发送,所以 数据的第二次更新就失落了。起因在于 Watches 的发送是异步的,ZK 不会期待 Watches 被胜利发送才进行下一步操作。其实也很好了解,毕竟让整个集群期待网卡的用户是不可能的。
  • 作用于不同类型的 Watches。Watches 能够分为两种,一种是对节点数据变更的监督,另一种是对子节点的监督,尽管客户端能够通过读节点操作设置 Watches,然而设置的 Watches 却不肯定是同类型的。比如说,setData()会触发这个节点上的 DataWatches,而 create()则会触发父节点的 ChildWatches 和 DataWatches,delete()操作则会触发 DataWatches 和父节点的 ChildWatchs 以及子节点的 ChildWatches。

当客户端断开重连后,之前设置的 Watches 能够持续应用;如果连贯到新的服务器,那就会触发 ZK 的会话事件。

对于 Watches 的触发,只能被三种读操作触发,当初来看看具体的细节:

  • Created Event: 由 exists()触发。
  • Deleted Event: 由 exists(),getData(),和 getChildren()触发。
  • Changed Event: 由 exists()和 getData()触发。
  • Child Event: 由 getChildren()触发。

ZK 的访问控制

来看一下 ZK 反对的访问控制:

  • CREATE: you can create a child node
  • READ: you can get data from a node and list its children.
  • WRITE: you can set data for a node
  • DELETE: you can delete a child node
  • ADMIN: you can set permissions

ZK 的一致性保障

ZK 提供如下的同步保障:

  • 程序同步。同一个客户端发动的多个更新操作被执行的程序和它们被发送的程序统一。
  • 原子性。更新操作只有胜利或者失败两个后果。
  • 繁多镜像。无论客户端连贯到了哪个实例,它所看到的都是统一的,整个 ZK 集群对外裸露的就是一个实例镜像。
  • 可靠性。一旦更新胜利,数据就会长久化,直到下一次更新产生。
  • 时效性。ZK 能够保障客户端在某一时间范畴内看到的零碎是最新的,在这个工夫范畴内,零碎的更新也能够被客户端得悉。

ZooKeeperJavaClient

这节次要通知你怎么用 Java 拜访 ZK 服务器并进行操作。

当应用客户端连贯时,能够指定多个 IP:PORT,客户端会轻易选一个,而后连贯,如果失败,就尝试另一个直至胜利。如果中途断开,也会尝试从新连贯。

这里仅给出最简略的用法:

public class Main {public static void main(String[] args) throws IOException, InterruptedException, KeeperException {AtomicInteger atomicInteger = new AtomicInteger(0);
        ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:8392", 2000, event -> System.out.println(atomicInteger.incrementAndGet() + ":" + event.toString()));
        if (zooKeeper.exists("/zk_test1", true) == null) {// System.out.println("节点: /zk_test1 不存在,筹备创立");
            zooKeeper.create("/zk_test1", "test_data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        if (zooKeeper.exists("/zk_test1/sub1", true) == null) {// System.out.println("节点: /zk_test1/sub1 不存在,筹备创立");
            zooKeeper.create("/zk_test1/sub1", "sub_test_data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        if (zooKeeper.exists("/zk_test1/sub1", true) != null) {Stat stat = new Stat();
            zooKeeper.getData("/zk_test1/sub1", true, stat);
            // System.out.println("data ver:" + stat.getVersion());
            // set 数据时,zookeeper 会主动把数据版本 +1
            zooKeeper.setData("/zk_test1/sub1", "sub_test_data0".getBytes(), stat.getVersion());
            zooKeeper.getData("/zk_test1/sub1", true, stat);
            // System.out.println("data ver:" + stat.getVersion());
        }
        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
        zooKeeper.delete("/zk_test1/sub1", -1);
        zooKeeper.delete("/zk_test1", -1);
        zooKeeper.close();}
}

而后放几个其余的例子

ZooKeeper 基本操作

参考

ZooKeeper 官网文档

如何构建 ZooKeeper 集群

正文完
 0