首页 » PHP教程 » php网站集群同步技巧_搞懂 ZooKeeper 集群的数据同步

php网站集群同步技巧_搞懂 ZooKeeper 集群的数据同步

访客 2024-12-12 0

扫一扫用手机浏览

文章目录 [+]

本文作者:HelloGitHub-老荀

Hi,这里是 HelloGitHub 推出的 HelloZooKeeper 系列,免费开源、有趣、入门级的 ZooKeeper 教程,面向有编程根本的新手。

php网站集群同步技巧_搞懂 ZooKeeper 集群的数据同步

项目地址:https://github.com/HelloGitHub-Team/HelloZooKeeper

php网站集群同步技巧_搞懂 ZooKeeper 集群的数据同步
(图片来自网络侵删)

前一篇文章我们先容了 ZK 是如何进行持久化的,这章我们将正式学习 Follower 或 Observer 是如何在选举之后和 Leader 进行数据同步的。

一、选举完成

经历了选举之后,我们的马果果光彩当选当前办事处集群的 Leader,以是现在假设各个办事处的关系图是这样:

我们现在就来说说马小云和马小腾是如何同马果果进行数据同步的。

结束了累人的选举后,马小云和马小腾以微弱的上风输掉了竞争,只能委曲成为 Follower。
整理完各自的感情后,他们要做的第一件事情便是通过话务员上报自己的信息给马果果,利用了专门的暗号 FOLLOWERINFO, 数据紧张有自己的 epoch 和 myid:

然后是马果果这边,他收到 FOLLOWERINFO 之后也会进行统计,直到达到半数以上后,综合各个 Follower 给的信息司帐算出新的 epoch,然后将这个新的 epoch 随着暗号 LEADERINFO 回发给其他 Follower

然后再回到马小云和马小腾这边,收到 LEADERINFO 之后将新的 epoch 记录下来,然后回答给马果果一个 ACKEPOCH 暗号并带上自己这边的最大 zxid,表示刚刚的 LEADERINFO 收到了

然后马果果这边也会等待半数以上的 ACKEPOCH 的关照,收到之后会根据各个 Follower 的信息给出不同的同步策略。
关于不同的同步策略,这里我先入为主的给大家先容一下:

DIFF,如果 Follower 的记录和 Leader 的记录相差的不多,利用增量同步的办法将一个一个写要求发送给 FollowerTRUNC,这个情形的涌当代表 Follower 的 zxid 是领先于当前的 Leader 的(可能因此前的 Leader),须要 Follower 自行把多余的部分给截断,降级到和 Leader 同等SNAP,如果 Follower 的记录和当前 Leader 相差太多,Leader 直接将自己的全体内存数据发送给 Follower

至于采取哪一种策略,是如何进行判断的,接下来逐一进行讲解。

1.1 DIFF

每一个 ZK 节点在收到写要求后,会掩护一个写要求行列步队(默认是 500 大小,通过 zookeeper.commitLogCount 配置),将写要求记录在个中,这个行列步队中的最早进入的写要求当时的 zxid 便是 minZxid(以下简称 min),末了一个进入的写要求的 zxid 便是 maxZxid(以下简称 max),达到上限后,会移除最早进入的写要求,知道了这两个值之后,我们来看看 DIFF 是怎么判断的。

1.1.1 从内存中的写要求行列步队规复

一种情形便是如果当 Follower 通过 ACKEPOCH 上报的 zxid 是在 min 和 max 之间的话,就采取 DIFF 策略进行数据同步。

我们的例子中 Leader 的 zxid 是 99,解释这个存储 500 个写要求的行列步队根本没有放满,以是 min 是 1 max 是 99,很显然 77 以及 88 是在这个区间内的,那马果果就会为其余两位 Follower 找到他们各自所须要的区间,先发送一个 DIFF 给 Follower,然后将一条条的写要求包装成 PROPOSAL 和 COMMIT 的顺序发给他们

1.1.2 从磁盘文件 log 规复

另一种情形是如果 Follower 的 zxid 不在 min 和 max 的区间内时,但当 zookeeper.snapshotSizeFactor 配置大于 0 的话(默认是 0.33),会考试测验利用 log 进行 DIFF,但是须要同步的 log 文件的总大小不能超过当前最新的 snapshot 文件大小的三分之一(以默认 0.33 为例)的话,才可以通过读取 log 文件中的写要求记录进行 DIFF 同步。
同步的方法也和上面一样,先发送一个 DIFF 给 Follower 然后从 log 文件中找到该 Follower 的区间,再一条条的发送 PROPOSAL 和 COMMIT。

而 Follower 收到 PROPOSAL 的暗号后,就会像处理客户端要求那样去一条条处理,逐步就会将数据规复成和 Leader 是同等的。

1.2 SNAP

假设现在三个办事处是这样的

马果果的写要求行列步队在默认配置下记录了 277 至 777 的写要求,又假设现在的场景不知足上面 1.1.2 的情形,马果果就知道当前须要通过 SNAP 的情形进行同步了。

马果果会先发送一个 SNAP 的要求给马小云和马小腾让他们准备起来

紧接着就会当前内存中的数据全体序列化(和 snapshot 文件是一样的)然后一起发送给马小云和马小腾。

而马小云和马小腾收到马果果发来的全体 snapshot 之后会先清空自己当前的数据库的所有信息,接着直接将收到的 snapshot 反序列化就完成了全体内存数据的规复。

1.3 TRUNC

末了一种策略的场景假设是这样:

假设马小腾是上一个 Leader,但是经历了停电往后规复重新以 Follower 的身份加入集群,但是他的 zxid 要比 max 还大,这个时候马果果就会给马小腾发送 TRUNC,(至于图中为什么马小云不举例为 TRUNC,由于如果马小云的 zxid 也比马果果要大的话,马果果在当前场景下就不可能当选 Leader 了)。

马果果就会发送 TRUNC 给马小腾(这里忽略马小云)

假设马小腾确当地 log 文件目录下是这样的:

/tmp└──zookeeper└──log└──version-2└──log.0└──log.500└──log.800

而马小腾收到 TRUNC 之后,会找到本地 log 文件中所有大于 777 的 log 文件删除,即这里的 log.800 ,然后会在 log.500 这个文件找到 777 这个 zxid 记录并且把当前文件的读写指针修正至 777 的位置,之后针对该文件的读写操作就会从 777 开始,这样就会把之后的那些记录给覆盖了。

而马果果这边当判断完同步策略并发送给其余两马之后,便会发送一个 NEWLEADER 的信息给他们

而马小云和马小腾在收到 NEWLEADER 之后,若之前是通过 SNAP 办法同步数据的话,这里会逼迫快照一份新的 snapshot 文件在自己这里。
然后会回答给马果果一个 ACK 的,见告他自己的同步数据已经完成了

然后马果果同样会等待半数一样的 ACK 吸收完成后,再发送一个 UPTODATE 给其他两马,见告他们现在办事处数据已经都同等了,可以开始对外供应做事了

然后马小云和马果果收到 UPTODATE 之后会再回答一个 ACK 给马果果,但是这次马果果收到这次的 ACK 之后不会做处理,以是在 UPTODATE 之后,各个办事处就已经算可以正式对外供应做事了。

上面说了这么多,但是马小云和马小腾都是 Follower,如果是 Observer 呢?怎么用上面的步骤同步呢?

差异就在第一步,Follower 发送的是 FOLLOWERINFO,而 Observer 发送的是 OBSERVERINFO 除此之外没有任何差异,和 Follower 是一样的步骤进行数据同步。

二、连续深挖

现在把个中的一些细节再用猿话解释一下,三种不同的数据同步策略,Leader 在发送 Follower 的时候采取的详细方法是不太相同的

2.1 三种策略发送办法

如果采取的是 DIFF 或者 TRUNC 的同步方法的话,Leader 实在不是在找到有差异数据的时候发送过去的,而是按照顺序先放入一个行列步队,末了再统一启动一个线程去一个个发送的

DIFF :

TRUNC:

但是以 SNAP 办法同步的话就不会放入该行列步队,无论是 SNAP 还是之后全体序列化后的内存快照 snapshot 都会直接通过做事端间的 socket 直接写入。

2.2 上帝视角

让我们把三种策略交互的全过程再看一遍,这里就以马小云举例了

2.2.1 DIFF

2.2.2 TRUNC

2.2.3 SNAP

可以看到首尾是一样的,便是中间的要求根据不同的策略会有不同的要求发送。
差不多到这里关于 Follower 或 Observer 是如何同 Leader 同步,整体的逻辑都先容完了。

2.3 小结Follower 和 Observer 同步数据的办法一共有三种:DIFF、SNAP、TRUNCDIFF 须要 Follower 或 Observer 和 Leader 的数据相差在 min 和 max 范围内,或者配置了许可从 log 文件中规复TRUNC 是当 Follower 或 Observer 的 zxid 比 Leader 还要大的时候,该节点须要主动删除多余 zxid 干系的数据,降级至 Leader 同等SNAP 作为末了的数据同步手段,由 Leader 直接将内存数据全体序列化完并发送给 Follower 或 Observer,以达到规复数据的目的

我看了下文章的字数还行,决定加一点料,开一个小篇讲一下 ACL,这个我拖了良久没阐明的坑。

三、没有规矩,不成周遭

先带大家重拾影象,之前创建节点代码片段中的 ZooDefs.Ids.OPEN_ACL_UNSAFE 便是 ACL 的参数

client.create("/更新视频/舞蹈/20201101","这是Data,既可以记录一些业务数据也可以随便写".getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);

首先如果配置了 zookeeper.skipACL 该参数为 yes(把稳大小写),表示当前节点放弃 ACL 校验,默认是 no

那这个 ACL 是怎么规定的,有哪些权限,又是怎么在做事端表示的呢?首先 ACL 整体分为 Permission 和 Scheme 两部分,Permission 是针对操作的权限,而 Scheme 是指定利用哪一种鉴权模式,下面我们一起来理解下。

3.1 权限 Permission 先容

首先 ZK 将权限分为 5 种:

READ(以下简称 R),获取节点数据或者获取子节点列表WRITE(以下简称 W),设置节点数据CREATE(以下简称 C),创建节点DELETE(以下简称 D),删除节点ADMIN(以下简称 A),设置节点的 ACL 权限

然后该 5 种权限在代码层面便是大略的 int 数据,而判断是否有权限只须要用 & 操作即可,和目标权限 & 完结果只要不即是 0 就解释拥有该权限,细节如下:

intbinaryR100001W200010C400100D801000A1610000

假设现在的客户端权限为 RWC,对应的数值便是各个权限相加 1 + 2 + 4 = 7

intbinaryRWC700111

对任意有 R、W、C 权限需求的节点,求 & 的结果都不为 0,以是就能判断该客户端是拥有 RWC 这 3 个权限的。

但是如果当该客户端对目标节点进行删除时,做 & 判断权限的话,可以得到结果为 0,表示该客户端不具备删除的权限,就会返回给客户端权限缺点

intbinaryRWC700111D8&01000------------------结果0000003.2 Scheme 先容

Scheme 有 4 种,分别是 ip、world、digest、super,但是实在便是两大类,一种是针对 IP 地址的 ip,另一种是利用类似“用户名:密码”的 world、digest、super。
实在全体 ACL 是分三个部分的,scheme:id:perms ,id 的取值取决于 scheme 的种类,这里是 ip 以是 id 的取值便是详细的 IP 地址,而 perms 则是我上一小节先容的 RWCDA。

这三部分的前两部分 scheme:id 相称于见告做事端 “我是谁?”,而末了的部分 perms 则是代表了 “我能做什么?”,这两个问题,任意一个问题出错都会导致做事端抛出 NoAuthException 的非常,见告客户端权限不足。

3.2.1 IP

我们先来直接看一段代码,个中的 IP 10.11.12.13 我是随便写的

ZooKeeperclient=newZooKeeper("127.0.0.1:2181",3000,null);List<ACL>aclList=newArrayList<>();aclList.add(newACL(ZooDefs.Perms.ALL,newId("ip","10.11.12.13")));Stringpath=client.create("/abc","test".getBytes(),aclList,CreateMode.PERSISTENT);System.out.println(path);//输出/abcclient.close();

可以看到 /abc 是可以被精确输出的,而且通过查看 / 的子节点列表是可以看到 /abc 节点的

ZooKeeperclient=newZooKeeper("127.0.0.1:2181",3000,null);List<String>children=client.getChildren("/",false);System.out.println(children);//输出[abc,zookeeper]client.close();

但是现在如果去访问该节点的数据的话就会得到报错

ZooKeeperclient=newZooKeeper("127.0.0.1:2181",3000,null);byte[]data=client.getData("/abc",false,null);System.out.println(newString(data));client.close();

Exceptioninthread"main"org.apache.zookeeper.KeeperException$NoAuthException:KeeperErrorCode=NoAuthfor/abc

读者可以试试把上面的 IP 改成 127.0.0.1 重新创建节点,之后就能正常访问了,一样平常生产环境中 IP 模式用的不多(也可能是我用的不多),如果要用 IP 掌握访问的话,通过防火墙白名单之类的手段即可,这个层面我认为不须要 ZK 去管。

3.2.2 World

这个模式该当是最常用的(手动狗头)

我们还是来看一段代码

ZooKeeperclient=newZooKeeper("127.0.0.1:2181",3000,null);List<ACL>aclList=newArrayList<>();aclList.add(newACL(ZooDefs.Perms.READ,newId("world","anyone")));//差异是这行Stringpath=client.create("/abc","test".getBytes(),aclList,CreateMode.PERSISTENT);System.out.println(path);//输出/abcclient.close();

我把 scheme 改成了 World 模式,而 World 模式的 id 取值便是固定的 anyone 不能用其他值,而且我还设置了 perms 为 R,以是这个节点只能读数据,但不能做其他操作,如果利用 setData 对其进行数据修正的话也会得到权限的缺点

ZooKeeperclient=newZooKeeper("127.0.0.1:2181",3000,null);Statstat=client.setData("/abc","newData".getBytes(),-1);//NoAuthfor/abc

现在再转头看之前的 ZooDefs.Ids.OPEN_ACL_UNSAFE,实在便是 ZK 供应的常用的静态常量,代表不校验权限

IdANYONE_ID_UNSAFE=newId("world","anyone");ArrayList<ACL>OPEN_ACL_UNSAFE=newArrayList<ACL>(Collections.singletonList(newACL(Perms.ALL,ANYONE_ID_UNSAFE)));3.2.3 Digest

这个便是我们熟习的用户名密码了,还是先上代码

ZooKeeperclient=newZooKeeper("127.0.0.1:2181",3000,null);List<ACL>aclList=newArrayList<>();aclList.add(newACL(ZooDefs.Perms.ALL,newId("digest",DigestAuthenticationProvider.generateDigest("laoxun:kaixin"))));//1Stringpath=client.create("/abc","test".getBytes(),aclList,CreateMode.PERSISTENT);System.out.println(path);client.close();

这个写法中必须要把稳的是 1 处的 username:password 的字符串必须通过 DigestAuthenticationProvider.generateDigest 的方法包装一下,用这个方法会对传入的字符串进行编码。

包装完后 laoxun:kaixin 实在变成了 laoxun:/xQjqfEf7WHKtjj2csJh1/aEee8=,这个过程如下:

laoxun:kaixin 对全体字符串前辈行 SHA1 加密对加密后的结果进行 Base64 编码将用户名和编码后的结果拼接

上面的代码还有一种写法如下,利用 addAuthInfo 在客户端高下文中添加权限信息

ZooKeeperclient=newZooKeeper("127.0.0.1:2181",3000,null);client.addAuthInfo("digest","laoxun:kaixin".getBytes());//1.List<ACL>aclList=newArrayList<>();aclList.add(newACL(ZooDefs.Perms.ALL,newId("auth","")));//2.这里的Id是固定写法Stringpath=client.create("/abc","test".getBytes(),aclList,CreateMode.PERSISTENT);System.out.println(path);client.close();

这里有两个改动,在 1 处利用 addAuthInfo 的方法可以在当前客户真个会话中添加 auth 信息,Digest 的 id 取值为 username:password 直接用明文即可,无论是 username 还是 password 都是自定义的。

然后是查询代码

ZooKeeperclient=newZooKeeper("127.0.0.1:2181",3000,null);client.addAuthInfo("digest","laoxun:kaixin".getBytes());//这行如果注释的话就会报错byte[]data=client.getData("/abc",false,null);System.out.println(newString(data));//test

不管创建的时候是何种写法,查询的时候都要利用 addAuthInfo 在会话中添加权限信息,才能对该节点进行查询

3.2.4 Super

听名字就知道这个模式是管理员的模式了,由于之前创建的那些节点,如果设置了用户名密码,其他客户端是无法访问的,如果该客户端自己退出了,这些节点就无法去操作了,以是须要管理员这一个角色来对其进行降维打击。

首先 Super 模式是要开启的,我这里假设管理员的用户名为 HelloZooKeeper,密码为 niubi,经由编码后便是 HelloZooKeeper:PT8Sb6Exg9YyPCS7fYraLCsqzR8=, 然后须要在做事端启动的环境中指定 zookeeper.DigestAuthenticationProvider.superDigest 配置,参数便是 HelloZooKeeper:PT8Sb6Exg9YyPCS7fYraLCsqzR8= 即可。

创建节点假设还是以 laoxun:kaixin 的模式,然后通过管理员的密码也能进行正常的访问

ZooKeeperclient=newZooKeeper("127.0.0.1:2181",3000,null);client.addAuthInfo("digest","HelloZooKeeper:niubi".getBytes());//1.byte[]data=client.getData("/abc",false,null);System.out.println(newString(data));//testclient.close();

这里可以看到 1 处的 Super 模式实质上还是 Digest,指定的 scheme 为 digest,然后之后的 id 取值采取的是明文,而非编码后的格式,牢记!

3.3 Permission 汇总表格

我这里列出大部分做事端供应的操为难刁难应的 Permission 权限:

可以看到删除和创建节点看的是父节点的权限,只有读写才是看的自己本身的权限。
其余如果表格中没有涌现的操作可以认为不须要 ACL 权限校验,其他要么是只须要客户端是一个合法的 session 或者本身是一些比较分外的功能,例如:createSession、closeSession 等。
至于关于 session 的更多内容,留到下一篇再讲吧~哈哈

3.4 ACL 背后的事理

我们刚刚花了一点篇幅先容了 ACL 是什么,怎么用?现在深入理解下 ACL 在 ZK 的做事端底层是怎么去实现的吧~为了节约篇幅,这次就直接进入猿话讲解了。

首先祭出之前的一张图,唤醒下大家的影象

图中权限部分(蓝色字体)之前的文章直接省略跳过了,没有进行阐明,本日我们就好好讲讲这个权限字段。

从图上也能看到权限这个字段是直接以数字(long 类型,64 位的整型数字)的办法保存在做事真个节点中的,而 -1 是一个分外的值代表不进行权限的校验对应的便是之前的 OPEN_ACL_UNSAFE 常量。

而 ACL 权限无论是创建节点时供应的(ACL 参数是一个 List),还是通过 addAuth 方法供应的(这个方法可以被调用多次),这两种设计都表示一个客户端是可以拥有多种权限的,比如:多个用户名密码,多个 IP 地址等等。

ACL 我之前讲过是由 3 个部分组成的,即 scheme:id:perms 为了简洁的表示我会在之后利用该形式去表示一个 ACL。

做事端会利用两个哈希表把目前吸收到的 ACL 列表和其对应的数字双向的关系保存起来,类似这样(图中的 ACL 取值是我随意编造的):

ZK 做事端会掩护一个从 1 开始的数字,收到一个新的 ACL 会同时放入这两个哈希表(源码中对应的便是两个 Map,一个是 Map<List<ACL>, Long>,一个是 Map<Long, List<ACL>>),除了这两个哈希表以外,ZK 做事端还为每一个客户端都掩护了一个会话中的权限信息,该权限信息便是客户端通过 addAuth 添加的,但是这个客户真个权限信息只保存了 scheme:id 部分,以是结合以下三个信息就可以对客户真个本次操作进行权限校验了:

两个哈希表表示的节点的信息 scheme:id:perms,可以有多个客户端会话高下文中的权限信息仅 id:perms ,可以有多个本次操为难刁难应的权限哀求,即 3.3 表格中列出的所需权限

校验的流程如下:

这里额外提一下,校验器是可以自定义的,用户可以自定义自己的 scheme 以及自己的校验逻辑,须要在做事真个环境变量中配置以 zookeeper.authProvider. 开头的配置,对应的值则对应一个 class 类全路径,这个类必须实现 org.apache.zookeeper.server.auth.AuthenticationProvider 接口,而且这个类必须能被 ZK 做事端加载到,这样就可以解析自定义的 scheme 掌握全体校验逻辑了,这个功能比较高等,我也没用过,大家就当补充知识理解下~

本日我们理解了 Follower 和 Observer 是如何同 Leader 进行数据同步的,以及 ZK 供应的权限管理 ACL 究竟是怎么回事,下一篇我们将聊聊 ZK 的 session 管理,客户端和做事端之间是怎么保持会话的,以及做事端不同节点之间的心跳又是怎么保持的?

末了给文章点个赞吧~什么?你说不想点?

标签:

相关文章

猪哥php视频技巧_PHP原来是这么不正经的

目测无误,发自拍心情比什么都好大人都不忍直视何况小朋友什么是前女友?这,现在盛行碰狗瓷吗?这煎肉技能貌似有点不好,不过我并不在意我...

PHP教程 2024-12-14 阅读0 评论0