河北省建设信息中心网站,js网站评论框,公众号怎么编辑制作,潍坊高级网站建设推广Zookeeper 1、ZooKeeper 介绍2、znode 节点里面的存储3、znode 节点上监听机制4、ZooKeeper 集群部署5、ZooKeeper 选举机制6、何为集群脑裂7、如何保证数据一致性8、讲一下 zk 分布式锁实现原理吧9、Eureka 与 Zk 有什么区别 1、ZooKeeper 介绍
ZooKeeper 的核心特性
高可用… Zookeeper 1、ZooKeeper 介绍2、znode 节点里面的存储3、znode 节点上监听机制4、ZooKeeper 集群部署5、ZooKeeper 选举机制6、何为集群脑裂7、如何保证数据一致性8、讲一下 zk 分布式锁实现原理吧9、Eureka 与 Zk 有什么区别 1、ZooKeeper 介绍
ZooKeeper 的核心特性
高可用性ZooKeeper 通过多节点集群实现高可用性即使部分节点故障服务仍然可用。一致性ZooKeeper 使用 ZABZooKeeper Atomic Broadcast协议保证数据的一致性。顺序性ZooKeeper 保证客户端的操作顺序与请求顺序一致。高性能ZooKeeper 的设计目标是高吞吐量和低延迟。
ZNode 的类型 ZooKeeper 的数据模型类似于文件系统的树形结构每个节点称为 ZNode。ZNode 可以存储数据并且可以有子节点。 持久PERSISTENT节点 一旦创建就一直存在即使 ZooKeeper 集群宕机直到将其删除。临时EPHEMERAL节点 临时节点的生命周期是与 客户端会话session 绑定的会话消失则节点消失 。并且临时节点只能做叶子节点 不能创建子节点。持久顺序PERSISTENT_SEQUENTIAL节点 除了具有持久PERSISTENT节点的特性之外 子节点的名称还具有顺序性。比如 /node1/app0000000001 、/node1/app0000000002 。临时顺序EPHEMERAL_SEQUENTIAL节点 除了具备临时EPHEMERAL节点的特性之外子节点的名称还具有顺序性
ZooKeeper 的架构 ZooKeeper 集群通常由多个节点组成其中一个节点是 Leader其他节点是 Follower。 Leader负责处理写请求并将写操作广播给 Follower。Follower处理读请求并参与 Leader 选举和写操作的投票。
ZooKeeper 的核心功能
配置管理 ZooKeeper 可以用于存储和管理分布式系统的配置信息。示例HBase 使用 ZooKeeper 存储 RegionServer 的配置。 命名服务 ZooKeeper 可以用于实现分布式系统中的命名服务。示例Kafka 使用 ZooKeeper 管理 Broker 的命名和元数据。 分布式锁 ZooKeeper 可以用于实现分布式锁确保多个节点之间的互斥访问。示例使用临时顺序节点实现分布式锁。 领导者选举 ZooKeeper 可以用于实现分布式系统中的领导者选举。示例HBase 使用 ZooKeeper 选举 Master 节点。 服务发现 ZooKeeper 可以用于实现服务发现动态管理服务的注册和发现。示例Dubbo 使用 ZooKeeper 实现服务注册和发现。
2、znode 节点里面的存储
stat 状态信息。
Stat 类中包含了一个数据节点的所有状态信息的字段包括事务 IDcZxid、节点创建时间ctime 和子节点个数numChildren 等等如下 data znode 存储业务数据信息。acl 记录客户端对 znode 节点访问权限如 IP 等。 ZooKeeper 采用 ACLAccessControlLists策略来进行权限控制类似于 UNIX 文件系统的权限控制。对于 znode 操作的权限ZooKeeper 提供了以下 5 种 CREATE : 能创建子节点READ 能获取节点数据和列出其子节点WRITE : 能设置/更新节点数据DELETE : 能删除子节点ADMIN : 能设置节点 ACL 的权限 其中尤其需要注意的是CREATE 和 DELETE 这两种权限都是针对 子节点 的权限控制。对于身份认证提供了以下几种方式 world 默认方式所有用户都可无条件访问。auth :不使用任何 id代表任何已认证的用户。digest :用户名:密码认证方式 username:password 。ip : 对指定 ip 进行限制 child 当前节点子节点引用。
3、znode 节点上监听机制
Watcher 为事件监听器是 zk 非常重要的一个特性很多功能都依赖于它它有点类似于订阅的方式即客户端向服务端注册指定的 watcher 当服务端符合了 watcher 的某些事件或要求则会向客户端发送事件通知 客户端收到通知后找到自己定义的 Watcher 然后执行相应的回调方法 。 可以把 Watcher 理解成客户端注册在某个 Znode 上触发器当这个 Znode 节点发生变化时增删改查就会触发 Znode 对应注册事件注册客户端就会收到异步通知然后做出业务改变。
zookeeper 监听原理
zookeeper的监听事件有四种
nodedatachanged 节点数据改变nodecreate 节点创建事件nodedelete 节点删除事件nodechildrenchanged 子节点改变事件 ZooKeeper Watcher 机制主要包括客户端线程、客户端WatcherManager、Zookeeper 服务器三部分。
客户端向 ZooKeeper 服务器注册 Watcher 同时会将 Watcher 对象存储在客户端 WatchManager 中。当 zookeeper 服务器触发 watcher 事件后会向客户端发送通知 客户端线程从 WatcherManager 中取出对应Watcher 对象来执行回调逻辑。
4、ZooKeeper 集群部署
为了保证高可用最好是以集群形态来部署 ZooKeeper这样只要集群中大部分机器是可用的能够容忍一定的机器故障那么 ZooKeeper 本身仍然是可用的。通常 3 台服务器就可以构成一个 ZooKeeper 集群了。ZooKeeper 官方提供的架构图就是一个 ZooKeeper 集群整体对外提供服务。 上图中每一个 Server 代表一个安装 ZooKeeper 服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态并且每台服务器之间都互相保持着通信。集群间通过 ZAB 协议来保持数据的一致性。
最典型集群模式 Master/Slave 模式主备模式。在这种模式中通常 Master 服务器作为主服务器提供写服务其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。
ZooKeeper 集群角色
ZooKeeper 集群中的所有机器通过一个 Leader 选举过程 来选定一台称为 “Leader” 的机器Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程也不参与写操作的“过半写成功”策略因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。 5、ZooKeeper 选举机制
ZooKeeper 的选举机制 是保证其高可用性和一致性的核心部分。ZooKeeper 使用 ZABZooKeeper Atomic Broadcast协议 来实现领导者选举和数据同步。
选举的触发条件
集群启动当 ZooKeeper 集群启动时所有节点会参与选举。
Leader 失效当 Leader 节点失效时Follower 节点会重新选举 Leader。
① 选举初始化
当 ZooKeeper 集群启动或 Leader 节点失效时集群中的节点会进入选举状态。每个节点会尝试选举自己为 Leader。
② 投票
每个节点会投票给自己并将投票信息包括节点的 ID 和事务 ID广播给其他节点。节点收到其他节点的投票后会比较投票信息 如果收到的投票的事务 ID 更大则更新自己的投票。如果事务 ID 相同则比较节点 ID选择更大的节点 ID。
③ 确定 Leader
当某个节点收到超过半数的投票时该节点被选举为 Leader。Leader 会向其他节点发送确认消息其他节点确认后成为 Follower。
④ 数据同步
Leader 会将自己的数据状态同步给 Follower确保集群中的数据一致性。同步完成后集群进入正常工作状态。
6、何为集群脑裂
对于一个集群想要提高这个集群的可用性通常会采用多机房部署比如现在有一个由6台zkServer所组成的一个集群部署在了两个机房 正常情况下此集群只会有一个Leader那么如果机房之间的网络断了之后两个机房内的zkServer还是可以相互通信的如果不考虑过半机制那么就会出现每个机房内部都将选出一个Leader。 这就相当于原本一个集群被分成了两个集群出现了两个“大脑”这就是脑裂。
对于这种情况我们也可以看出来原本应该是统一的一个集群对外提供服务的现在变成了两个集群同时对外提供服务如果过了一会断了的网络突然联通了那么此时就会出现问题了两个集群刚刚都对外提供服务了数据该怎么合并数据冲突怎么解决等等问题。
刚刚在说明脑裂场景时有一个前提条件就是没有考虑过半机制所以实际上Zookeeper集群中是不会出现脑裂问题的而不会出现的原因就跟过半机制有关。
过半机制
举个简单的例子 如果现在集群中有6台zkServer也就是说至少要4台zkServer才能选出来一个Leader才会符合过半机制才能选出来一个Leader。 所以对于机房1来说它不能选出一个Leader同样机房2也不能选出一个Leader这种情况下整个集群当机房间的网络断掉后整个集群将没有Leader。
如果假设我们现在只有5台机器也部署在两个机房 也就是至少要3台服务器才能选出一个Leader此时机房件的网络断开了对于机房1来说是没有影响的Leader依然还是Leader对于机房2来说是选不出来Leader的此时整个集群中只有一个Leader。
所以我们可以总结得出有了过半机制对于一个Zookeeper集群要么没有Leader要没只有1个Leader这样就避免了脑裂问题。
过半机制是如何防止脑裂现象产生的
ZooKeeper 的过半机制导致不可能产生 2 个 leader因为少于等于一半是不可能产生 leader 的这就使得不论机房的机器如何分配都不可能发生脑裂。
ZooKeeper 集群为啥最好奇数台
ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后如果剩下的 ZooKeeper 服务器个数大于宕掉的个数的话整个 ZooKeeper 才依然可用。假如我们的集群中有 n 台 ZooKeeper 服务器那么也就是剩下的服务数必须大于 n/2。先说一下结论2n 和 2n-1 的容忍度是一样的都是 n-1大家可以先自己仔细想一想这应该是一个很简单的数学问题了。
比如假如我们有 3 台那么最大允许宕掉 1 台 ZooKeeper 服务器如果我们有 4 台的的时候也同样只允许宕掉 1 台。 假如我们有 5 台那么最大允许宕掉 2 台 ZooKeeper 服务器如果我们有 6 台的的时候也同样只允许宕掉 2 台。
综上何必增加那一个不必要的 ZooKeeper 呢
7、如何保证数据一致性
在 ZooKeeper 集群中所有客户端的请求都是写入到 Leader 进程中的然后由 Leader 同步到其他节点称为 Follower。在集群数据同步的过程中如果出现 Follower 节点崩溃或者 Leader 进程崩溃时都会通过 Zab 协议来保证数据一致性。
ZAB 协议的核心思想
ZAB 协议是一种原子广播协议用于在分布式系统中实现数据的一致性和可靠性。它的核心思想包括
原子广播确保所有写操作以相同的顺序被所有节点接收和执行。领导者选举在集群启动或 Leader 失效时选举新的 Leader 来协调写操作。数据同步确保 Leader 和 Follower 之间的数据一致性。
ZAB 协议的工作流程
ZAB 协议的工作流程分为两个阶段
选举阶段 当集群启动或 Leader 失效时ZooKeeper 会进入选举阶段。通过投票机制选举新的 Leader确保集群中只有一个 Leader。 广播阶段 Leader 负责接收客户端的写请求并将写操作广播给所有 Follower。Follower 接收到写操作后将其应用到本地状态并向 Leader 发送确认。当 Leader 收到多数 Follower 的确认后提交写操作并通知客户端。
消息广播阶段ZooKeeper中的一个节点被选为leader节点它接收来自客户端的事务提交请求并将这些请求作为proposal广播给其他follower节点。每个follower节点收到proposal后会进行反馈leader节点根据收集到的反馈决定是否执行commit操作。为了保证数据一致性ZooKeeper使用了quorum选举机制来决定大多数节点上的commit结果。 client端发起请求读请求由follower和observer直接返回写请求由它们转发给leader。Leader 首先为这个事务分配一个全局单调递增的唯一事务ID (即 ZXID )。然后发起proposal给followerLeader 会为每一个 Follower 都各自分配一个单独的队列然后将需要广播的事务 Proposal 依次放入这些队列中去并且根据 FIFO策略进行消息发送。每一个 Follower 在接收到这个事务 Proposal 之后都会首先将其以事务日志的形式写入到本地磁盘中去并且在成功写入后反馈给 Leader 服务器一个 Ack 响应。当 Leader 服务器接收到超过半数 Follower 的 Ack 响应后就会广播一个Commit 消息给所有的 Follower 服务器以通知其进行事务提交同时Leader 自身也会完成对事务的提交。
8、讲一下 zk 分布式锁实现原理吧
实现分布式锁要借助临时顺序节点和watch首先我们要有一个持久节点客户端获取锁就是在持久节点下创建临时顺序节点。客户端创建的临时顺序节点创建成功后会判断节点是不是最小节点如果是最小节点那么获取锁成功否则回去锁失败。如果获取锁失败则说明有其他客户端已成功获得锁这时候也不需要循环尝试去加锁而是给前一个节点注册一个事件监听器这个监听器作用就是当前一个节点释放后也就是节点删除后通知自己让自己获得锁这样的好处是不会通知到所有的节点去争夺锁避免无效自旋。所以使用Zookeeper实现的分布式锁是公平锁。
为什么要用临时顺序节点
临时节点相比持久节点最主要的是对会话失效的情况处理不一样临时节点会话消失则对应的节点消失。这样的话如果客户端发生异常导致没来得及释放锁也没关系会话失效节点自动被删除不会发生死锁的问题。
使用 Redis 实现分布式锁的时候我们是通过过期时间来避免锁无法被释放导致死锁问题的而 ZooKeeper 直接利用临时节点的特性即可。
假设不适用顺序节点的话所有尝试获取锁的客户端都会对持有锁的子节点加监听器。当该锁被释放之后势必会造成所有尝试获取锁的客户端来争夺锁这样对性能不友好。使用顺序节点之后只需要监听前一个节点就好了对性能更友好。
为什么要设置对前一个节点的监听
同一时间段内可能会有很多客户端同时获取锁但只有一个可以获取成功。如果获取锁失败则说明有其他的客户端已经成功获取锁。获取锁失败的客户端并不会不停地循环去尝试加锁而是在前一个节点注册一个事件监听器。
这个事件监听器的作用是 当前一个节点对应的客户端释放锁之后也就是前一个节点被删除之后监听的是删除事件通知获取锁失败的客户端唤醒等待的线程Java 中的 wait/notifyAll 让它尝试去获取锁然后就成功获取锁了。
原生API 分布式锁
package com.example.test.other.zk;import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;public class ZookepeerLock1 {private final String connectionString 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183;private final int sessionTimeout 2000;private final ZooKeeper zk;private CountDownLatch countDownLatch new CountDownLatch(1);private CountDownLatch waitLatch new CountDownLatch(1);private String waitPath;private String currentMode;public ZookepeerLock1() throws IOException, InterruptedException, KeeperException {// 获取连接zk new ZooKeeper(connectionString, sessionTimeout, new Watcher() {Overridepublic void process(WatchedEvent watchedEvent) {// connectLatch 如果连接上zk 可以释放if (watchedEvent.getState() Event.KeeperState.SyncConnected) {countDownLatch.countDown();}// waitLatch 需要释放if (watchedEvent.getType() Event.EventType.NodeDeleted watchedEvent.getPath().equals(waitPath)) {waitLatch.countDown();}}});// 等待zk正常连接后往下走程序countDownLatch.await();// 判断根节点/locks是否存在Stat stat zk.exists(/lockZookeeper, false);if (stat null) {// 创建根节点这是⼀个完全开放的ACL持久节点zk.create(/lockZookeeper, lockZookeeper.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);}}// 对zk加锁public void zkLock() {try {// 创建对应的临时顺序节点currentMode zk.create(/lockZookeeper/ seq-, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);// 判断创建的节点是否是序号最小的节点如果是获取到锁如果不是监听他序号前一个节点ListString children zk.getChildren(/lockZookeeper, false);if (children.size() 1) {return;} else {//[seq-0000000016, seq-0000000017]Collections.sort(children);// 获取节点名称 /locks/seq-0000000017 - seq-0000000017String thisNode currentMode.substring(/lockZookeeper/.length());// 通过seq-0000000017获取该节点在children集合的位置int index children.indexOf(thisNode);// 判断if (index -1) {System.out.println(数据异常);} else if (index 0) {// 就一个节点可以获取锁了return;} else {// 需要监听 他前一个结点的变化waitPath /lockZookeeper/ children.get(index - 1);zk.getData(waitPath, true, null);// 等待监听waitLatch.await();}}} catch (KeeperException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}// 解锁public void unZkLock() {// 删除节点try {zk.delete(currentMode, -1);} catch (InterruptedException e) {e.printStackTrace();} catch (KeeperException e) {e.printStackTrace();}}
}Curator 分布式锁
package com.example.test.other.zk;import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.KeeperException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.io.IOException;
/*** Description: 1、Bean单独注解方法时每次调用方法都是执行方法内的逻辑并返回新创建的对象bean而且SpringIOC并没有该bean的存在。* 2、Bean Configuration 在调用Bean注解的方法时返回的实例bean是从IOC容器获取的已经注入的且是单例的而不是新创建的。* 3、Bean Component虽然Bean注解的方法返回的实例已经注入到SpringIOC容器中但是每次调用Bean注解的方法时都会创建新的对象实例bean返回并不会从IOC容器中获取。* Author: yangjj_tc* Date: 2023/5/18 13:25*/
Configuration
public class ZookeperLock1Test {private final String ZOOKEEPER_ADDRESS 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183;/*** Description: Author:* yangjj_tc* 1、会话连接是异步的需要自己去处理。比如使用CountDownLatch* 2、Watch需要重复注册不然就不能生效* 3、开发的复杂性还是比较高的* 4、不支持多节点删除和创建。需要自己去递归* Date: 2023/5/18 12:48*/Beanpublic CuratorFramework getCuratorFramework(){// 重试策略重试间隔时间为1秒重试次数为3次。curator管理了zookeeper的连接在操作zookeeper的过程中出现连接问题会自动重试。RetryPolicy retryPolicy new ExponentialBackoffRetry(1000, 3);// 初始化客户端通过工厂创建连接// zk地址 会话超时时间默认60秒 连接超时时间默认15秒 重试策略CuratorFramework zkClient CuratorFrameworkFactory.newClient(ZOOKEEPER_ADDRESS, 5000, 15000, retryPolicy);// 开始连接zkClient.start();return zkClient;}public static void main(String[] args) throws InterruptedException, IOException, KeeperException {// 获得两个客户端CuratorFramework client1 new ZookeperLock1Test().getCuratorFramework();CuratorFramework client2 new ZookeperLock1Test().getCuratorFramework();// 可重入锁 意味着同一个客户端在拥有锁的同时可以多次获取不会被阻塞。如想重入则需要使用同一个InterProcessMutex对象。final InterProcessLock lock1 new InterProcessMutex(client1, /lockCurator);final InterProcessLock lock2 new InterProcessMutex(client2, /lockCurator);// 不可重入锁区别在于该锁是不可重入的在同一个线程中不可重入final InterProcessSemaphoreMutex lock3 new InterProcessSemaphoreMutex(client1, /lockCurator);final InterProcessSemaphoreMutex lock4 new InterProcessSemaphoreMutex(client2, /lockCurator);// 模拟两个线程new Thread(() - {try {// 线程加锁lock3.acquire();lock3.acquire();System.out.println(线程1获取锁);// 线程沉睡Thread.sleep(5 * 1000);// 线程解锁lock1.release();System.out.println(线程1释放了锁);} catch (Exception e) {e.printStackTrace();}}).start();// 线程2new Thread(() - {// 线程加锁try {lock2.acquire();System.out.println(线程2获取到锁);// 线程沉睡Thread.sleep(5 * 1000);lock2.release();System.out.println(线程2释放锁);} catch (Exception e) {e.printStackTrace();}}).start();}
}9、Eureka 与 Zk 有什么区别
Eureka采用 AP 模型优先保证可用性允许短暂的数据不一致。通过心跳机制和客户端缓存实现最终一致性。ZooKeeper采用 CP 模型在分区故障时可能牺牲可用性。使用 ZAB 协议保证强一致性。Eureka是对等架构Eureka 节点之间是对等的每个节点都可以接收注册和查询请求。客户端缓存客户端会缓存服务注册表即使 Eureka 服务器不可用客户端仍然可以获取服务信息。ZooKeeper主从架构ZooKeeper 集群中有 Leader 和 Follower 节点写操作由 Leader 处理。强一致性所有节点数据保持一致客户端总是读取到最新数据。