当前位置: 首页 > news >正文

zookeeper --- 高级篇

一、zookeeper 事件监听机制

1.1、watcher概念

zookeeper提供了数据的发布/订阅功能,多个订阅者可同时监听某一特定主题对象,当该主题对象的自身状态发生变化时(例如节点内容改变、节点下的子节点列表改变等),会实时、主动通知所有订阅者

zookeeper采用了Watcher机制实现数据的发布/订阅功能。该机制在被订阅对象发生变化时会异步通知客户端,因此客户端不必在Watcher注册后轮询阻塞,从而减轻了客户端压力。

zooKeeper 允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。

zooKeeper 原生支持通过注册Watcher来进行事件监听,但是其使用并不是特别方便,需要开发人员自己反复注册Watcher,比较繁琐。

Curator引入了 Cache 来实现对 zooKeeper 服务端事件的监听。

watcher机制实际上与观察者模式类似,也可看作是一种观察者模式在分布式场景下的实现方式。

zooKeeper提供了三种Watcher:

  • NodeCache : 只是监听某一个特定的节点
  • PathChildrenCache : 监控一个ZNode的子节点.
  • TreeCache : 可以监控整个树上的所有节点,类似于PathChildrenCache和NodeCache的组合

1.2、watcher架构

Watcher实现由三个部分组成:

  • Zookeeper服务端
  • Zookeeper客户端
  • 客户端的ZKWatchManager对象

客户端首先将Watcher注册到服务端,同时将Watcher对象保存到客户端的Watch管
理器中。当ZooKeeper服务端监听的数据状态发生变化时,服务端会主动通知客户端,
接着客户端的Watch管理器会触发相关Watcher来回调相应处理逻辑,从而完成整体的数
据发布/订阅流程。

在这里插入图片描述

1.3、watcher特性

特性说明
一次性watcher是一次性的,一旦被触发就会移除,再次使用时需要重新注册
客户端顺序回调watcher回调是顺序串行化执行的,只有回调后客户端才能看到最新的数据状态。一个watcher回调逻辑不应该太多,以免影响别的watcher执行
轻量级WatchEvent是最小的通信单元,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容;
时效性watcher只有在当前session彻底失效时才会无效,若在session有效期内快速重连成功,则watcher依然存在,仍可接收到通知;

1.4、watcher接口设计

Watcher是一个接口,任何实现了Watcher接口的类就是一个新的Watcher。
Watcher内部包含了两个枚举类:KeeperState、EventType
在这里插入图片描述

  • Watcher通知状态(KeeperState)
    • KeeperState是客户端与服务端连接状态发生变化时对应的通知类型。路径为
      org.apache.zookeeper.Watcher.Event.KeeperState,是一个枚举类,其枚举属性
      如下:
枚举属性说明
SyncConnected客户端与服务器正常连接时
Disconnected客户端与服务器断开连接时
Expired会话session失效时
AuthFailed身份认证失败时
  • Watcher事件类型(EventType)
    • EventType是数据节点(znode)发生变化时对应的通知类型。EventType变化时
      KeeperState永远处于SyncConnected通知状态下;当KeeperState发生变化时,
      EventType永远为None。其路径为org.apache.zookeeper.Watcher.Event.EventType,
      是一个枚举类,枚举属性如下:
枚举属性说明
None
NodeCreatedWatcher监听的数据节点被创建时
NodeDeletedWatcher监听的数据节点被删除时
NodeDataChangedWatcher监听的数据节点内容发生变更时(无论内容数据是否变化)
NodeChildrenChangedWatcher监听的数据节点的子节点列表发生变更时

注:客户端接收到的相关事件通知中只包含状态及类型等信息,不包括节点变化前后的具体内容,变化前的数据需业务自身存储,变化后的数据需调用get等方法重新获取;

1.5、捕获相应的事件

上面讲到zookeeper客户端连接的状态和zookeeper对znode节点监听的事件类型,下面我们来讲解如何建立zookeeper的watcher监听。在zookeeper中采用zk.getChildren(path, watch)、zk.exists(path, watch)、zk.getData(path, watcher, stat)这样的方式为某个znode注册监听。

下表以node-x节点为例,说明调用的注册方法和可监听事件间的关系:

注册方式CreatedChildrenChangedChangedDeleted
zk.exists(“/node-x”,watcher)可监控可监控可监控
zk.getData(“/node-x”,watcher)可监控可监控
zk.getChildren(“/node-x”,watcher)可监控可监控

1.6、注册watcher的方法

1.6.1、客服端与服务器的连接状态

KeeperState 通知状态
SyncConnected:客户端与服务器正常连接时
Disconnected:客户端与服务器断开连接时
Expired:会话session失效时
AuthFailed:身份认证失败时
事件类型为:None

1.6.2、检查节点是否存在

// 使用连接对象的监视器
exists(String path, boolean b)
// 自定义监视器
exists(String path, Watcher w)
// NodeCreated:节点创建
// NodeDeleted:节点删除
// NodeDataChanged:节点内容发生变化
  • path: znode路径。
  • b:是否使用连接对象中注册的监视器。
  • w:监视器对象。

1.6.3、查看节点

// 使用连接对象的监视器
getData(String path, boolean b, Stat stat)
// 自定义监视器
getData(String path, Watcher w, Stat stat)
// NodeDeleted:节点删除
// NodeDataChanged:节点内容发生变化
  • path: znode路径。
  • b:是否使用连接对象中注册的监视器。
  • w:监视器对象。
  • stat: 返回znode的元数据。

1.6.4、查看子节点

// 使用连接对象的监视器
getChildren(String path, boolean b)
// 自定义监视器
getChildren(String path, Watcher w)
// NodeChildrenChanged:子节点发生变化
// NodeDeleted:节点删除
  • path: znode路径。
  • b: 是否使用连接对象中注册的监视器。
  • w:监视器对象。

1.7、配置中心案例

工作中有这样的一个场景: 数据库用户名和密码信息放在一个配置文件中,应用读取该配置文件,配置文件信息放入缓存。

若数据库的用户名和密码改变时候,还需要重新加载缓存,比较麻烦,通过ZooKeeper可以轻松完成,当数据库发生变化时自动完成缓存同步。

设计思路:

  1. 连接zookeeper服务器
  2. 读取zookeeper中的配置信息,注册watcher监听器,存入本地变量
  3. 当zookeeper中的配置信息发生变化时,通过watcher的回调方法捕获数据变化事件
  4. 重新获取配置信息

案例:

public class MyConfigCenter implements Watcher {// zk的连接串String IP = "192.168.60.130:2181";// 计数器对象CountDownLatch countDownLatch = new CountDownLatch(1);// 连接对象static ZooKeeper zooKeeper;// 用于本地化存储配置信息private String url;private String username;private String password;@Overridepublic void process(WatchedEvent event) {try {// 捕获事件状态if (event.getType() == Event.EventType.None) {if (event.getState() == Event.KeeperState.SyncConnected){System.out.println("连接成功");countDownLatch.countDown();} else if (event.getState() ==Event.KeeperState.Disconnected) {System.out.println("连接断开!");} else if (event.getState() == Event.KeeperState.Expired){System.out.println("连接超时!");// 超时后服务器端已经将连接释放,需要重新连接服务器端zooKeeper = new ZooKeeper("192.168.60.130:2181",6000,new ZKConnectionWatcher());} else if (event.getState() ==Event.KeeperState.AuthFailed) {System.out.println("验证失败!");}// 当配置信息发生变化时} else if (event.getType() == EventType.NodeDataChanged) {initValue();}} catch (Exception ex) {ex.printStackTrace();}}// 构造方法public MyConfigCenter() {initValue();}// 连接zookeeper服务器,读取配置信息public void initValue() {try {// 创建连接对象zooKeeper = new ZooKeeper(IP, 5000, this);// 阻塞线程,等待连接的创建成功countDownLatch.await();// 读取配置信息this.url = new String(zooKeeper.getData("/config/url", true,null));this.username = new String(zooKeeper.getData("/config/username", true, null));this.password = new String(zooKeeper.getData("/config/password", true, null));} catch (Exception ex) {ex.printStackTrace();}}public static void main(String[] args) {try {MyConfigCenter myConfigCenter = new MyConfigCenter();for (int i = 1; i <= 20; i++) {Thread.sleep(5000);System.out.println("url:"+myConfigCenter.getUrl());System.out.println("username:"+myConfigCenter.getUsername());System.out.println("password:"+myConfigCenter.getPassword());System.out.println("########################################");}} catch (Exception ex) {ex.printStackTrace();}}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}

1.8、生成分布式唯一ID

在过去的单库单表型系统中,通常可以使用数据库字段自带的auto_increment
属性来自动为每条记录生成一个唯一的ID。但是分库分表后,就无法在依靠数据库的
auto_increment属性来唯一标识一条记录了。此时我们就可以用zookeeper在分布式环
境下生成全局唯一ID。

设计思路:

  1. 连接zookeeper服务器
  2. 指定路径生成临时有序节点
  3. 取序列号及为分布式环境下的唯一ID

案例:

public class GloballyUniqueId implements Watcher {// zk的连接串String IP = "192.168.60.130:2181";// 计数器对象CountDownLatch countDownLatch = new CountDownLatch(1);// 用户生成序号的节点String defaultPath = "/uniqueId";// 连接对象ZooKeeper zooKeeper;@Overridepublic void process(WatchedEvent event) {try {// 捕获事件状态if (event.getType() == Watcher.Event.EventType.None) {if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {System.out.println("连接成功");countDownLatch.countDown();} else if (event.getState() == Watcher.Event.KeeperState.Disconnected) {System.out.println("连接断开!");} else if (event.getState() == Watcher.Event.KeeperState.Expired) {System.out.println("连接超时!");// 超时后服务器端已经将连接释放,需要重新连接服务器端zooKeeper = new ZooKeeper(IP, 6000, new ZKConnectionWatcher());} else if (event.getState() == Watcher.Event.KeeperState.AuthFailed) {System.out.println("验证失败!");}}} catch (Exception ex) {ex.printStackTrace();}}// 构造方法public GloballyUniqueId() {try {//打开连接zooKeeper = new ZooKeeper(IP, 5000, this);// 阻塞线程,等待连接的创建成功countDownLatch.await();} catch (Exception ex) {ex.printStackTrace();}}// 生成id的方法public String getUniqueId() {String path = "";try {//创建临时有序节点path = zooKeeper.create(defaultPath, new byte[0],Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);} catch (Exception ex) {ex.printStackTrace();}// /uniqueId0000000001return path.substring(9);}public static void main(String[] args) {GloballyUniqueId globallyUniqueId = new GloballyUniqueId();for (int i = 1; i <= 5; i++) {String id = globallyUniqueId.getUniqueId();System.out.println(id);}}
}

1.9、分布式锁

在这里插入图片描述

分布式锁有多种实现方式,比如通过数据库、redis都可实现。作为分布式协同工具ZooKeeper,当然也有着标准的实现方式。下面介绍在zookeeper中如何实现排他锁。

设计思路:

  1. 每个客户端往/Locks下创建临时有序节点/Locks/Lock_000000001
  2. 客户端取得/Locks下子节点,并进行排序,判断排在最前面的是否为自己,如果自己的锁节点在第一位,代表获取锁成功
  3. 如果自己的锁节点不在第一位,则监听自己前一位的锁节点。例如,自己锁节点Lock_000000001
  4. 当前一位锁节点(Lock_000000002)的逻辑
  5. 监听客户端重新执行第2步逻辑,判断自己是否获得了锁

案例:

public class MyLock {// zk的连接串String IP = "192.168.60.130:2181";// 计数器对象CountDownLatch countDownLatch = new CountDownLatch(1);//ZooKeeper配置信息ZooKeeper zooKeeper;private static final String LOCK_ROOT_PATH = "/Locks";private static final String LOCK_NODE_NAME = "Lock_";private String lockPath;// 打开zookeeper连接public MyLock() {try {zooKeeper = new ZooKeeper(IP, 5000, new Watcher() {@Overridepublic void process(WatchedEvent event) {if (event.getType() == Event.EventType.None) {if (event.getState() ==Event.KeeperState.SyncConnected) {System.out.println("连接成功!");countDownLatch.countDown();}}}});countDownLatch.await();} catch (Exception ex) {ex.printStackTrace();}}//获取锁public void acquireLock() throws Exception {//创建锁节点createLock();//尝试获取锁attemptLock();}//创建锁节点private void createLock() throws Exception {//判断Locks是否存在,不存在创建Stat stat = zooKeeper.exists(LOCK_ROOT_PATH, false);if (stat == null) {zooKeeper.create(LOCK_ROOT_PATH, new byte[0],ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);}// 创建临时有序节点lockPath = zooKeeper.create(LOCK_ROOT_PATH + "/" + LOCK_NODE_NAME, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);System.out.println("节点创建成功:" + lockPath);}//监视器对象,监视上一个节点是否被删除Watcher watcher = new Watcher() {@Overridepublic void process(WatchedEvent event) {if (event.getType() == Event.EventType.NodeDeleted) {synchronized (this) {notifyAll();}}}};//尝试获取锁private void attemptLock() throws Exception {// 获取Locks节点下的所有子节点List<String> list = zooKeeper.getChildren(LOCK_ROOT_PATH, false);// 对子节点进行排序Collections.sort(list);// /Locks/Lock_000000001int index = list.indexOf(lockPath.substring(LOCK_ROOT_PATH.length() + 1));if (index == 0) {System.out.println("获取锁成功!");return;} else {// 上一个节点的路径String path = list.get(index ‐ 1);Stat stat = zooKeeper.exists(LOCK_ROOT_PATH + "/" + path,watcher);if (stat == null) {attemptLock();} else {synchronized (watcher) {watcher.wait();}attemptLock();}}}//释放锁public void releaseLock() throws Exception {//删除临时有序节点zooKeeper.delete(this.lockPath,1);zooKeeper.close();System.out.println("锁已经释放:"+this.lockPath);}public static void main(String[] args) {try {MyLock myLock = new MyLock();myLock.createLock();} catch (Exception ex) {ex.printStackTrace();}}
}
public class TicketSeller {private void sell(){System.out.println("售票开始");// 线程随机休眠数毫秒,模拟现实中的费时操作int sleepMillis = 5000;try {//代表复杂逻辑执行了一段时间Thread.sleep(sleepMillis);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("售票结束");}public void sellTicketWithLock() throws Exception {MyLock lock = new MyLock();// 获取锁lock.acquireLock();sell();//释放锁lock.releaseLock();}public static void main(String[] args) throws Exception {TicketSeller ticketSeller = new TicketSeller();for(int i=0;i<10;i++){ticketSeller.sellTicketWithLock();}}
}

zooKeeper分布式锁原理

  • 核心思想:当客户端要获取锁,则创建节点,使用完锁,则删除该节点。
  • 客户端获取锁时,在lock节点下创建临时顺序节点。
  • 然后获取lock下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁。使用完锁后,将该节点删除。
  • 如果发现自己创建的节点并非lock所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件。
  • 如果发现比自己小的那个节点被删除,则客户端的Watcher会收到相应通知,此时再次判断自己创建的节点是否是lock子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤继续获取到比自己小的一个节点并注册监听。

二、zookeeper 集群搭建

2.1、搭建要求

真实的集群是需要部署在不同的服务器上的,但是在我们测试时同时启动很多个虚拟机内存会吃不消,所以我们通常会搭建伪集群,也就是把所有的服务都搭建在一台虚拟机上,用端口进行区分。

我们这里要求搭建一个三个节点的Zookeeper集群(伪集群)。

2.2、准备工作

重新部署一台虚拟机作为我们搭建集群的测试服务器。

(1)安装JDK 【此步骤省略】。

(2)Zookeeper压缩包上传到服务器
(3)将Zookeeper解压 ,建立/usr/local/zookeeper-cluster目录,将解压后的Zookeeper复制到以下三个目录

/usr/local/zookeeper-cluster/zookeeper-1

/usr/local/zookeeper-cluster/zookeeper-2

/usr/local/zookeeper-cluster/zookeeper-3

[root@localhost ~]# mkdir /usr/local/zookeeper-cluster
[root@localhost ~]# cp -r  apache-zookeeper-3.5.6-bin /usr/local/zookeeper-cluster/zookeeper-1
[root@localhost ~]# cp -r  apache-zookeeper-3.5.6-bin /usr/local/zookeeper-cluster/zookeeper-2
[root@localhost ~]# cp -r  apache-zookeeper-3.5.6-bin /usr/local/zookeeper-cluster/zookeeper-3

(4)创建data目录 ,并且将 conf下zoo_sample.cfg 文件改名为 zoo.cfg

mkdir /usr/local/zookeeper-cluster/zookeeper-1/data
mkdir /usr/local/zookeeper-cluster/zookeeper-2/data
mkdir /usr/local/zookeeper-cluster/zookeeper-3/datamv  /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo_sample.cfg  /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfg
mv  /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo_sample.cfg  /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfg
mv  /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo_sample.cfg  /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfg

(5) 配置每一个Zookeeper 的dataDir 和 clientPort 分别为2181 2182 2183

修改/usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfg

vim /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfgclientPort=2181
dataDir=/usr/local/zookeeper-cluster/zookeeper-1/data

修改/usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfg

vim /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfgclientPort=2182
dataDir=/usr/local/zookeeper-cluster/zookeeper-2/data

修改/usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfg

vim /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfgclientPort=2183
dataDir=/usr/local/zookeeper-cluster/zookeeper-3/data

2.3、配置集群

(1)在每个zookeeper的 data 目录下创建一个 myid 文件,内容分别是1、2、3 。这个文件就是记录每个服务器的ID

echo 1 >/usr/local/zookeeper-cluster/zookeeper-1/data/myid
echo 2 >/usr/local/zookeeper-cluster/zookeeper-2/data/myid
echo 3 >/usr/local/zookeeper-cluster/zookeeper-3/data/myid

(2)在每一个zookeeper 的 zoo.cfg配置客户端访问端口(clientPort)和集群服务器IP列表。

集群服务器IP列表如下

vim /usr/local/zookeeper-cluster/zookeeper-1/conf/zoo.cfg
vim /usr/local/zookeeper-cluster/zookeeper-2/conf/zoo.cfg
vim /usr/local/zookeeper-cluster/zookeeper-3/conf/zoo.cfgserver.1=192.168.149.135:2881:3881
server.2=192.168.149.135:2882:3882
server.3=192.168.149.135:2883:3883

解释:server.服务器ID=服务器IP地址:服务器之间通信端口:服务器之间投票选举端口

2.4、启动集群

启动集群就是分别启动每个实例。

/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh start

在这里插入图片描述

启动后我们查询一下每个实例的运行状态

/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status

先查询第一个服务

在这里插入图片描述

Mode为follower表示是跟随者(从)

再查询第二个服务Mod 为leader表示是领导者(主)

在这里插入图片描述

查询第三个为跟随者(从)
在这里插入图片描述

2.5、模拟集群异常

(1)首先我们先测试如果是从服务器挂掉,会怎么样

把3号服务器停掉,观察1号和2号,发现状态并没有变化

/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh stop
/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status

在这里插入图片描述

由此得出结论,3个节点的集群,从服务器挂掉,集群正常

(2)我们再把1号服务器(从服务器)也停掉,查看2号(主服务器)的状态,发现已经停止运行了。

/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh stop
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status

在这里插入图片描述

由此得出结论,3个节点的集群,2个从服务器都挂掉,主服务器也无法运行。因为可运行的机器没有超过集群总数量的半数。

(3)我们再次把1号服务器启动起来,发现2号服务器又开始正常工作了。而且依然是领导者。

/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh start/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status

在这里插入图片描述

(4)我们把3号服务器也启动起来,把2号服务器停掉,停掉后观察1号和3号的状态。

/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh start
/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh stop/usr/local/zookeeper-cluster/zookeeper-1/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status

在这里插入图片描述

发现新的leader产生了~
由此我们得出结论,当集群中的主服务器挂了,集群中的其他服务器会自动进行选举状态,然后产生新得leader

(5)我们再次测试,当我们把2号服务器重新启动起来启动后,会发生什么?2号服务器会再次成为新的领导吗?我们看结果

/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh start/usr/local/zookeeper-cluster/zookeeper-2/bin/zkServer.sh status
/usr/local/zookeeper-cluster/zookeeper-3/bin/zkServer.sh status

在这里插入图片描述
在这里插入图片描述

我们会发现,2号服务器启动后依然是跟随者(从服务器),3号服务器依然是领导者(主服务器),没有撼动3号服务器的领导地位。

由此我们得出结论,当领导者产生后,再次有新服务器加入集群,不会影响到现任领导者。

2.6、zookeeper 集群角色

在ZooKeeper集群服中务中有三个角色:

  • Leader 领导者 :

    • 处理事务请求
    • 集群内部各服务器的调度者
  • Follower 跟随者 :

    • 处理客户端非事务请求,转发事务请求给Leader服务器
    • 参与Leader选举投票
  • Observer 观察者:

    • 处理客户端非事务请求,转发事务请求给Leader服务器

在这里插入图片描述

三、一致性协议:zab协议

zab协议 的全称是 Zookeeper Atomic Broadcast (zookeeper原子广播)。zookeeper 是通过 zab协议来保证分布式事务的最终一致性

  1. leader从客户端收到一个写请求
  2. leader生成一个新的事务并为这个事务生成一个唯一的ZXID
  3. leader将这个事务提议(propose)发送给所有的follows节点
  4. follower节点将收到的事务请求加入到历史队列(history queue)中,并发送ack给
    leader
  5. 当leader收到大多数follower(半数以上节点)的ack消息,leader会发送commit请
  6. 当follower收到commit请求时,从历史队列中将事务请求commit

四、zookeeper的leader选举

4.1?服务器状态

  • looking:寻找leader状态。当服务器处于该状态时,它会认为当前集群中没有
  • leader,因此需要进入leader选举状态。
  • leading: 领导者状态。表明当前服务器角色是leader。
  • following: 跟随者状态。表明当前服务器角色是follower。
  • observing:观察者状态。表明当前服务器角色是observer。

4.2、服务器启动时期的leader选举

在集群初始化阶段,当有一台服务器server1启动时,其单独无法进行和完成leader选举,当第二台服务器server2启动时,此时两台机器可以相互通信,每台机器都试图找到leader,于是进入leader选举过程。选举过程如下:

    1. 每个server发出一个投票。由于是初始情况,server1和server2都会将自己作为leader服务器来进行投票,每次投票会包含所推举的服务器的myid和zxid,使用(myid, zxid)来表示,此时server1的投票为(1, 0),server2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
    1. 集群中的每台服务器接收来自集群中各个服务器的投票。
    1. 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行pk,pk规则如下
    • 检查zxid。zxid比较大的服务器优先作为leader。
    • 如果zxid相同,那么就比较myid。myid较大的服务器作为leader服务器。
    • 对于Server1而言,它的投票是(1, 0),接收Server2的投票为(2, 0),首先会比较两者的zxid,均为0,再比较myid,此时server2的myid最大,于是更新自己的投票为(2, 0),然后重新投票,对于server2而言,其无须更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。
    1. 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于server1、server2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出了leader
    1. 改变服务器状态。一旦确定了leader,每个服务器就会更新自己的状态,如果是follower,那么就变更为following,如果是leader,就变更为leading。

4.3、服务器运行时期的Leader选举

在zookeeper运行期间,leader与非leader服务器各司其职,即便当有非leader服务器宕机或新加入,此时也不会影响leader,但是一旦leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮leader选举,其过程和启动时期的Leader选举过程基本一致。

假设正在运行的有server1、server2、server3三台服务器,当前leader是server2,若某一时刻leader挂了,此时便开始Leader选举。选举过程如下:

  1. 变更状态。leader挂后,余下的服务器都会将自己的服务器状态变更为looking,然后开始进入leader选举过程。

  2. 每个server会发出一个投票。在运行期间,每个服务器上的zxid可能不同,此时假定server1的zxid为122,server3的zxid为122,在第一轮投票中,server1和server3都会投自己,产生投票(1, 122),(3, 122),然后各自将投票发送给集群中所有机器。

  3. 接收来自各个服务器的投票。与启动时过程相同

  4. 处理投票。与启动时过程相同,此时,server3将会成为leader。

  5. 统计投票。与启动时过程相同。

  6. 改变服务器的状态。与启动时过程相同。

五、observer角色及其配置

observer角色特点:

  • 不参与集群的leader选举
  • 不参与集群中写数据时的ack反馈
  • 为了使用observer角色,在任何想变成observer角色的配置文件中加入如下配
    置:
peerType=observer

并在所有server的配置文件中,配置成observer模式的server的那行配置追加:observer,例如:

server.3=192.168.60.130:2289:3389:observer

六、zookeeperAPI连接集群

ZooKeeper(String connectionString, int sessionTimeout, Watcher watcher)
  • connectionString : zooKeeper集合主机。
  • sessionTimeout : 会话超时(以毫秒为单位)。
  • watcher : 实现“监视器”界面的对象。ZooKeeper集合通过监视器对象返回连接状
    态。
public class ZookeeperConnection {public static void main(String[] args) {try {// 计数器对象CountDownLatch countDownLatch=new CountDownLatch(1);// arg1:服务器的ip和端口// arg2:客户端与服务器之间的会话超时时间 以毫秒为单位的// arg3:监视器对象ZooKeeper zooKeeper=new ZooKeeper("192.168.60.130:2181,192.168.60.130:2182,192.168.60.130:2183",5000, new Watcher() {@Overridepublic void process(WatchedEvent event) {if(event.getState()==Event.KeeperState.SyncConnected){System.out.println("连接创建成功!");countDownLatch.countDown();}}});// 主线程阻塞等待连接对象的创建成功countDownLatch.await();// 会话编号System.out.println(zooKeeper.getSessionId());zooKeeper.close();} catch (Exception ex) {ex.printStackTrace();}}
}

七、zookeeper四字监控命令

zooKeeper支持某些特定的四字命令与其的交互。它们大多是查询命令,用来获取 zooKeeper服务的当前状态及相关信息。用户在客户端可以通过 telnet 或 nc 向zooKeeper提交相应的命令。 zooKeeper常用四字命令见下表 所示:

命令描述
conf输出相关服务配置的详细信息。比如端口、zk数据及日志配置路径、最大连接数,session超时时间、serverId等
cons列出所有连接到这台服务器的客户端连接/会话的详细信息。包括“接受/发送”的包数量、session id 、操作延迟、最后的操作执行等信息
crst重置当前这台服务器所有连接/会话的统计信息
dump列出未经处理的会话和临时节点
envi输出关于服务器的环境详细信息
ruok测试服务是否处于正确运行状态。如果正常返回"imok",否则返回空
stat输出服务器的详细信息:接收/发送包数量、连接数、模式(leader/follower)、节点总数、延迟。 所有客户端的列表
srst重置server状态
wchs列出服务器watches的简洁信息:连接总数、watching节点总数和watches总数
wchc通过session分组,列出watch的所有节点,它的输出是一个与 watch 相关的会话的节点列表
mntr列出集群的健康状态。包括“接受/发送”的包数量、操作延迟、当前服务模式(leader/follower)、节点总数、watch总数、临时节点总数

nc命令工具安装:

#root用户安装
#下载安装包
wget http://vault.centos.org/6.6/os/x86_64/Packages/nc-1.84-
22.el6.x86_64.rpm
#rpm安装
rpm -iUv nc-1.84-22.el6.x86_64.rpm

使用方式,在shell终端输入:echo mntr | nc localhost 2181

九、taokeeper监控工具的使用

基于zookeeper的监控管理工具taokeeper,由淘宝团队开源的zk管理中间件,安装前要求服务前先配置nc 和 sshd

1.下载数据库脚本

wget https://github.com/downloads/alibaba/taokeeper/taokeeper.sql

2.下载主程序

wget https://github.com/downloads/alibaba/taokeeper/taokeepermonitor.tar.gz

3.下载配置文件

wget https://github.com/downloads/alibaba/taokeeper/taokeeper-monitorconfig.propertie

4.配置 taokeeper-monitor-config.properties

#Daily
systemInfo.envName=DAILY
#DBCP
dbcp.driverClassName=com.mysql.jdbc.Driver
#mysql连接的ip地址端口号
dbcp.dbJDBCUrl=jdbc:mysql://192.168.60.130:3306/taokeeper
dbcp.characterEncoding=GBK
#用户名
dbcp.username=root
#密码
dbcp.password=root
dbcp.maxActive=30
dbcp.maxIdle=10
dbcp.maxWait=10000
#SystemConstant
#用户存储内部数据的文件夹
#创建/home/zookeeper/taokeeperdata/ZooKeeperClientThroughputStat
SystemConstent.dataStoreBasePath=/home/zookeeper/taokeeperdata
#ssh用户
SystemConstant.userNameOfSSH=zookeeper
#ssh密码
SystemConstant.passwordOfSSH=zookeeper
#Optional
SystemConstant.portOfSSH=22

5.安装配置 tomcat,修改catalina.sh

#指向配置文件所在的位置
JAVA_OPTS=-DconfigFilePath="/home/zookeeper/taokeeper-monitortomcat/webapps/ROOT/conf/taokeeper-monitor-config.properties

6.部署工程启动

在这里插入图片描述

相关文章:

zookeeper --- 高级篇

一、zookeeper 事件监听机制 1.1、watcher概念 zookeeper提供了数据的发布/订阅功能&#xff0c;多个订阅者可同时监听某一特定主题对象&#xff0c;当该主题对象的自身状态发生变化时(例如节点内容改变、节点下的子节点列表改变等)&#xff0c;会实时、主动通知所有订阅者 …...

TypeScript【enum 枚举】

导语 在 TypeScript 中&#xff0c;新增了很多具有特性的一些数据类型处理方法&#xff0c;enum 【枚举】就是其中&#xff0c;很具有代表性的一种&#xff0c;所以本章节就来聊聊 在 TypeScript 中如何去运用 enum 【枚举】。 枚举的概念&#xff1a; 枚举&#xff08;Enum&am…...

SpringBoot项目增加logback日志文件

一、简介 在开发和调试过程中&#xff0c;日志是一项非常重要的工具。它不仅可以帮助我们快速定位和解决问题&#xff0c;还可以记录和监控系统的运行状态。Spring Boot默认提供了一套简单易用且功能强大的日志框架logback&#xff0c;本文将介绍如何在Spring Boot项目中配置和…...

复习之selinux的管理

一、什么是selinux? SELinux&#xff0c;Security Enhanced Linux 的缩写&#xff0c;也就是安全强化的 Linux&#xff0c;是由美国国家安全局&#xff08;NSA&#xff09;联合其他安全机构&#xff08;比如 SCC 公司&#xff09;共同开发的&#xff0c;旨在增强传统 Linux 操…...

无涯教程-Lua - 文件I/O

I/O库用于在Lua中读取和处理文件。 Lua中有两种文件操作&#xff0c;即隐式(Implicit)和显式(Explicit)操作。 对于以下示例&#xff0c;无涯教程将使用例文件test.lua&#xff0c;如下所示。 -- sample test.lua -- sample2 test.lua 一个简单的文件打开操作使用以下语句。…...

java+ssm民宿酒店客房推荐预订系统_2k78b--论文

摘 要 互联网日益成熟&#xff0c;走进千家万户&#xff0c;改变多个行业传统的工作方式。民宿推荐管理以用户需求为基础&#xff0c;借由发展迅猛的互联网平台实现民宿推荐管理的信息化&#xff0c;简化旧时民宿推荐管理所需的纸质记录这一繁杂过程&#xff0c;从而大幅提高民…...

Docker实战-关于Docker镜像的相关操作(一)

导语   镜像&#xff0c;Docker中三大核心概念之一&#xff0c;并且在运行Docker容器之前需要本地存储对应的镜像。那么下面我们就来介绍一下在Docker中如何使用镜像。 如何获取镜像&#xff1f; 镜像作为容器运行的前提条件&#xff0c;在Docker Hub上提供了各种各样的开放的…...

Jenkins Gerrit Trigger实践

1.创建Gerrit Trigger 2.jenkins master节点生成gerrit用户的密钥 这里的用户名得写登录gerrit后个人信息中的 Username 3.gerrit 配置刚刚jenkins生成密钥的公钥 4.gerrit 用户加入群组 不加这个群组&#xff0c;下一步测试就会报错“User aeshare has no capability conn…...

Xcode protobuf2.5添加arm64编译器补丁生成静态库

项目需求&#xff0c;protobuf源码编成静态库使用 但是&#xff0c;github上的protobuf源码没有对应arm64的编译器定义&#xff0c;编译出来的静态库使用时报错。 下面的连接是arm64编译器代码补丁包&#xff0c;把编译器代码放到src/google/protobuf/stubs/atomicops_intern…...

计算机毕设 深度学习疫情社交安全距离检测算法 - python opencv cnn

文章目录 0 前言1 课题背景2 实现效果3 相关技术3.1 YOLOV43.2 基于 DeepSort 算法的行人跟踪 4 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&#xff0c;这两…...

四数之和——力扣18

文章目录 题目描述双指针法题目描述 双指针法 class Solution {public:vector<vector<int>>...

Serializable 和 Externalizable区别?

Serializable接口 java.io.Serializable 接口没有方法或字段&#xff0c;仅用于标识可序列化的语义。 public interface Serializable { }可序列化类的所有子类型本身都是可序列化的。在进行序列化操作时&#xff0c;会判断要被序列化的类是否是Enum、Array和 Serializable类…...

2023 电赛 E 题 K210 方案--K210实现矩形识别

相关库介绍 sensor&#xff08;摄像头&#xff09; sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.skip_frames(10) reset()&#xff1a;重置并初始化单目摄像头 set_pixformat()&#xff1a;设置摄像头输出格式&#xff0c…...

【雕爷学编程】MicroPython动手做(29)——物联网之SIoT 2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…...

chapter13:springboot与任务

Spring Boot与任务视频 1. 异步任务 使用注解 Async 开启一个异步线程任务&#xff0c; 需要在主启动类上添加注解EnableAsync开启异步配置&#xff1b; Service public class AsyncService {Asyncpublic void hello() {try {Thread.sleep(3000);} catch (InterruptedExcept…...

(十一)大数据实战——hadoop高可用之HDFS手动模式高可用

前言 本节内容我们介绍一下hadoop在手动模式下如何实现HDFS的高可用&#xff0c;HDFS的高可用功能是通过配置多个 NameNodes(Active/Standby)实现在集群中对 NameNode 的热备来解决上述问题。如果出现故障&#xff0c;如机器崩溃或机器需要升级维护&#xff0c;这时可通过此种…...

problem(3):python IDE和python解释器

为什么写这篇文章呢&#xff1f;遇到了下面的问题&#xff0c;相同的解释器&#xff0c;如果运行angr库的代码&#xff0c;会出现 这样的情况&#xff0c;但是用spyder IDE 会显示正常&#xff0c;很奇怪 应该就是IDE的原因 IDE的循环导入问题 检查IDE配置&#xff1a; 如果可…...

【C语言进阶篇】模拟实现通讯录 (内附源码)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言一 、 通讯录的简介1.1 联系人的类型定义1.2 通讯录的定义1.3 通讯录要实现的功能 二 、 如何…...

Python web实战之 Django 的模板语言详解

关键词&#xff1a; Python、web开发、Django、模板语言 概要 作为 Python Web 开发的框架之一&#xff0c;Django 提供了一套完整的 MVC 模式&#xff0c;其中的模板语言为开发者提供了强大的渲染和控制前端的能力。本文介绍 Django 的模板语言。 1. Django 模板语言入门 Dj…...

使用ChatGPT编写技术文档

技术文档对于任何项目都是至关重要的&#xff0c;因为它确保所有利益相关者都在同一层面上&#xff0c;并允许有效的沟通和协作。创建详细而准确的技术文档可能既耗时又具有挑战性&#xff0c;特别是对于那些不熟悉主题或缺乏强大写作技巧的人来说。ChatGPT 是一个强大的人工智…...

Java超级玛丽小游戏制作过程讲解 第四天 创建并完成常量类03

今天继续来完成常量类。 package com.sxt;import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List;public class StaticValue {//背景public static Buff…...

webpack基础知识八:说说如何借助webpack来优化前端性能?

一、背景 随着前端的项目逐渐扩大&#xff0c;必然会带来的一个问题就是性能 尤其在大型复杂的项目中&#xff0c;前端业务可能因为一个小小的数据依赖&#xff0c;导致整个页面卡顿甚至奔溃 一般项目在完成后&#xff0c;会通过webpack进行打包&#xff0c;利用webpack对前…...

JAVA SE -- 第十五天

&#xff08;全部来自“韩顺平教育”&#xff09; 多线程 一、线程相关概念 1、程序&#xff1a;是为完成特定任务、用某种语言编写的一组指令的集合。 2、进程&#xff1a;是指运行中的程序&#xff0c;如QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程…...

macOS 环境变量加载探究

使用 macOS 安装环境&#xff0c;见到过很数种环境变量配置方法&#xff0c;每次也都是按照别人的代码&#xff0c;人家配置在哪 我就配置在哪&#xff0c;其实不太清楚有什么区别&#xff0c;决定记录下。 本机 macOS 13.3&#xff0c;从 macOS Catalina(10.15) 开始&#xf…...

在程序中如何判断该线程的线程id(get_id())的返回值是一个无效值

std::thread::id() 是std::thread::id的默认构造函数&#xff0c;它会创建一个空的std::thread::id对象。一个空的std::thread::id对象代表一个无效的线程标识符。 可以通过 std::thread::id 的成员函数 std::thread::id::operator() 来判断一个 std::thread::id 是否是一个空值…...

ffmpeg-ffplay代码架构简述

全局变量 /* Minimum SDL audio buffer size, in samples. */ // 最小音频缓冲 #define SDL_AUDIO_MIN_BUFFER_SIZE 512 /* Calculate actual buffer size keeping in mind not cause too frequent audio callbacks */ // 计算实际音频缓冲大小&#xff0c;并不需要太频繁…...

⛳ 多线程面试-什么是多线程上下文切换?

目录 ⛳ 多线程面试&#xff0d;什么是多线程上下文切换&#xff1f;&#x1f381; Java中用到的线程调度算法是什么&#xff1f;&#x1f3a8; 什么是线程饥饿 &#xff1f;你对线程优先级的理解是什么&#xff1f; ⛳ 多线程面试&#xff0d;什么是多线程上下文切换&#xff…...

vb+SQL车辆管理系统设计与实现

摘 要 随着信息时代的到来,信息高速公路的兴起,全球信息化进入了一个新的发展时期。人们越来越认识到计算机强大的信息模块处理功能,使之成为信息产业的基础和支柱。 我国经济的快速发展,汽车已经成为人们不可缺少的交通工具。对于拥有大量车辆的机关企事业来说,车辆的…...

java的枚举类

枚举类的概念和使用 1.枚举类的理解&#xff1a;类的对象只有有限个&#xff0c;确定的。我们称此为枚举类。 2.当需要定义一组常量时&#xff0c;强烈建议使用枚举类。对象便是所指的常量。 3.如果枚举类中只有一个对象&#xff0c;则可以作为单例模式的实现方式。 定义枚举类…...

基于java早餐店点餐系统源码设计与实现

摘 要 多姿多彩的世界带来了美好的生活&#xff0c;行业的发展也是形形色色的离不开技术的发展。作为时代进步的发展方面&#xff0c;信息技术至始至终都是成就行业发展的重要秘密。不论何种行业&#xff0c;大到国家、企业&#xff0c;小到团体、个人都在多方位的结合信息化技…...