MSIPO技术圈 首页 IT技术 查看内容

【Hadoop大数据技术】——ZooKeeper分布式协调服务(学习笔记)

2024-03-25

📖 前言:ZooKeeper是一个开源的分布式协调服务,它是Google Chubby的开源实现,其设计目标是将那些复杂且容易出错的分布式应用封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。

在这里插入图片描述


🕒 1. ZooKeeper简介

Zookeeper主要用来解决分布式集群中应用系统的一致性问题单点故障问题,例如如何避免同时操作同一数据造成脏读的一致性问题等。

Zookeeper具有全局数据一致性、可靠性、顺序性、原子性以及实时性,可以说Zookeeper的其他特性都是为满足Zookeeper全局数据一致性这一特性。

Zookeeper集群是一个主从集群,它一般是由一个Leader(领导者)和多个Follower(跟随者)组成。此外,针对访问量比较大的Zookeeper集群,还可新增Observer(观察者)。Zookeeper集群中的三种角色各司其职,共同完成分布式协调服务。

在这里插入图片描述

  • Leader是Zookeeper集群工作的核心,也是事务性请求(写操作)的唯一调度和处理者,保证集群事务处理的顺序性,同时负责进行投票的发起和决议,以及更新系统状态。
  • Follower负责处理客户端的非事务(读操作)请求,如果接收到客户端发来的事务性请求,则会转发给Leader,让Leader进行处理,同时还负责在Leader选举过程中参与投票
  • Observer负责观察Zookeeper集群的最新状态的变化,并且将这些状态进行同步。对于非事务性请求可进行独立处理;对于事务性请求,则会转发给Leader服务器进行处理。它不参与任何形式的投票,只提供非事务性的服务

🕒 2. ZooKeeper数据模型

ZooKeeper的视图结构和标准的Unix文件系统非常类似,但没有引入Unix文件系统中目录和文件的相关概念,而是使用了其特有的“数据节点”概念,称之为ZNode。ZNode是ZooKeeper中数据的最小单元,每个ZNode默认能够保存1MB的数据,同时还可以挂载子节点,挂载的子节点也可以单独看作是ZNode,从而构成了一个层次化的命名空间,我们称之为

ZooKeeper的数据模型多个ZNode组成,如“/”“/app1”“/app2”,每个ZNode都可以挂载子节点。每个Znode都是由三部分组成,分别是statdatachildren

在这里插入图片描述
在ZooKeeper中,每个ZNode都是有生命周期的,其生命周期的长短取决于ZNode的类型。在ZooKeeper中,ZNode的类型主要分为永久节点/持久节点(PERSISTENT)、临时节点(EPHEMERAL)和顺序节点(SEQUENTIAL)

  • 永久节点:永久节点是ZooKeeper中最常见的一种ZNode类型,它的生命周期取决于用户何时进行删除操作,永久节点被创建后,便会一直存在于ZooKeeper中,除非主动删除永久节点。
  • 临时节点:与永久节点有所不同,临时节点的生命周期取决于客户端会话。所谓客户端会话是指,客户端与ZooKeeper成功建立连接后所创建的会话,若此时在ZooKeeper中创建临时节点,则客户端与ZooKeeper断开连接时,临时节点便会被自动清理掉。需要注意的是,临时节点不能挂载子节点,只能存储数据。
  • 顺序节点:顺序节点基于永久节点和临时节点所创建,可以将顺序节点分为永久顺序节点和临时顺序节点这两种类型。在创建顺序节点时,默认会在顺序节点的基础上添加一个不断增加的序号,该序号对于当前顺序节点的父节点来说是唯一的,这样便于记录父节点中每个子节点创建的先后顺序。
    序号的格式由10位数字组成,起始序号为0000000000,例如在名称为“/node”的ZNode下先后创建了3个顺序节点,那么这3个顺序节点的序号分别是“0000000000”“000000001”和“0000000002”。

Znode的类型在创建时被指定,一旦创建就无法改变。

Zookeeper中的每个Znode都包含了一系列的属性,具体属性如下所示。

属性名称相关说明属性名称相关说明
czxid节点被创建的时间dataVersion数据版本号
ctime节点最后一次的修改的Zxid值aclVersionACL版本号
mzxid节点最后一次的修改时间ephemeralOwner如果此节点为临时节点,那么该值代表这个节点拥有者的会话ID;否则值为0
mtime与该节点的子节点最后一次修改的Zxid值dataLength节点数据域长度
pZxid子节点被修改的版本号numChildren节点拥有的子节点个数
cversion节点被创建的时间

🕒 3. ZooKeeper的Watcher机制

在ZooKeeper中,引入了Watch机制来实现这种分布式的通知功能。ZooKeeper允许客户端向服务端注册一个Watch监听,当服务端的一些事件触发了这个Watch,那么就会向指定客户端发送一个事件通知,来实现分布式的通知功能。

Watch机制的特点一次性触发、事件封装、异步发送、先注册再触发

同一个事件类型在不同的连接状态中代表的含义有所不同。
常见的连接状态和事件类型如下所示。

连接状态状态含义事件类型事件含义
Disconnected连接失败NodeCreated节点被创建
SyncConnected连接成功NodeDataChanged节点数据变更
AuthFailed认证失败NodeChildrentChanged子节点数据变更
Expired会话过期NodeDeleted节点被删除

🕒 4. ZooKeeper的选举机制

Zookeeper为了保证各节点的协同工作,在工作时需要一个Leader角色,而Zookeeper默认采用FastLeaderElection算法,且投票数大于半数则胜出的机制。

  • 选举ID:选举过程中,Zookeeper服务器有四种状态,分别为竞选状态、随从状态、观察状态、领导者状态。
  • 数据ID:是服务器中存放的最新数据版本号,该值越大则说明数据越新,在选举过程中数据越新权重越大。
  • 服务器ID:设置集群myid参数时,参数分别为服务器1、服务器2、服务器3,编号越大FastLeaderElection算法中权重越大。
  • 逻辑时钟;逻辑时钟被称为投票次数,同一轮投票过程中逻辑时钟值相同,逻辑时钟起始值为0,每投一次票,数据增加。与接收到其它服务器返回的投票信息中数值比较,根据不同值做出不同判断。

Zookeeper选举机制有两种类型,分别为全新集群选举和非全新集群选举。全新集群选举是新搭建起来的,没有数据ID和逻辑时钟的数据影响集群的选举;非全新集群选举时是优中选优,保证Leader是Zookeeper集群中数据最完整、最可靠的一台服务器。

🕘 4.1 全新集群选举

假设有5台编号分别是1~5的服务器,全新集群选举过程如下:

  1. 服务器1启动,先给自己投票;其次,发投票信息,由于其它机器还没有启动所以它无法接收到投票的反馈信息,因此服务器1的状态一直属于竞选状态。
  2. 服务器2启动,先给自己投票;其次,在集群中启动Zookeeper服务的机器发起投票对比,它会与服务器1交换结果,由于服务器2编号大,服务器2胜出,服务器1会将票投给服务器2,此时服务器2的投票数并没有大于集群半数,两个服务器状态依旧是竞选状态。
  3. 服务器3启动,先给自己投票;其次,与之前启动的服务器1、2交换信息,服务器3的编号最大,服务器3胜出,服务器1、2会将票投给服务器3,此时投票数正好大于半数,所以服务器3成为领导者状态,服务器1、2成为追随者状态。
  4. 服务器4启动,先给自己投票;其次,与之前启动的服务器1、2、3交换信息,尽管服务器4的编号大,但是服务器3已经胜,所以服务器4只能成为追随者状态。
  5. 服务器5启动,同服务器4一样,均成为追随者状态。

🕘 4.2 非全新集群选举

  1. 统计逻辑时钟是否相同,逻辑时钟小,则说明途中可能存在宕机问题,因此数据不完整,那么该选举结果被忽略,重新投票选举。
  2. 统一逻辑时钟后,对比数据ID值,数据ID反应数据的新旧程度,因此数据ID大的胜出。
  3. 如果逻辑时钟和数据ID都相同的情况下,那么比较服务器ID(编号),值大则胜出。

🕒 5. ZooKeeper分布式集群部署(伪分布式)

Zookeeper集群搭建通常是由2n+1台服务器组成,这是为了保证 Leader 选举(基于Paxos算法的实现)能够通过半数以上台服务器选举支持,因此,ZooKeeper集群的数量一般为奇数台。

🕘 5.1 Zookeeper的安装

在Ubuntu下打开官网:🔎 ZooKeeper官网 进行下载

在这里插入图片描述

选择最新版下载即可

在这里插入图片描述

下载完成后,打开终端,解压安装包apache-zookeeper-3.9.2-bin.tar.gz至路径 /opt,命令如下:

sudo tar -zxvf apache-zookeeper-3.9.2-bin.tar.gz -C /opt

将解压的文件夹重命名为zookeeper并添加zookeeper的权限

sudo mv apache-zookeeper-3.9.2-bin/ zookeeper # 更名为zookeeper
sudo chown -R hadoop:hadoop zookeeper # 把zookeeper文件夹的权限赋给hadoop用户和hadoop组。

🕘 5.2 Zookeeper的配置

为模拟3个分布式节点,所以须要创建三个节点的配置文件 zoo1.cfg、zoo2.cfg、zoo3.cfg

cd /opt/zookeeper/conf/

将zoo_sample.cfg改名为zoo1.cfg

sudo mv zoo_sample.cfg zoo1.cfg
cp zoo1.cfg zoo2.cfg
cp zoo1.cfg zoo3.cfg

在这里插入图片描述

zoo1.cfg配置如下 (注意以下标红的地方)

sudo vim zoo1.cfg
dataDir=/opt/zookeeper/data/zk1	# 设置数据持久化目录
# 设置客户端连接当前ZooKeeper服务使用的端口号
clientPort=2181
# 设置ZooKeeper集群中每个ZooKeeper服务的地址及端口号
server.1=ubuntu:2888:3888
server.2=ubuntu:2889:3889
server.3=ubuntu:2890:3890

在这里插入图片描述

zoo2.cfgzoo3.cfg修改方式同理,配置如下

dataDir=/opt/zookeeper/data/zk2	
clientPort=2182
server.1=ubuntu:2888:3888
server.2=ubuntu:2889:3889
server.3=ubuntu:2890:3890
dataDir=/opt/zookeeper/data/zk3	
clientPort=2183
server.1=ubuntu:2888:3888
server.2=ubuntu:2889:3889
server.3=ubuntu:2890:3890

配置说明:

  • tickTime:这个时间是作为 Zookeeper server之间或client与server之间维持心跳的时间间隔,也就是每一个 tickTime 时间就会发送一个心跳。
  • initLimit:这个配置项是用来配置 Zookeeper 接受client(这里所说的client不是用户连接 Zookeeper server的client,而是 Zookeeper server集群中连接到 Leader 的 Follower server)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10个心跳的时间(也就是 tickTime)长度后 Zookeeper server还没有收到client的返回信息,那么表明这个client连接失败。
  • syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息。请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度。
  • dataDir:Zookeeper 保存数据的文件夹,默认情况下,Zookeeper 将写数据的日志文件也保存在这个文件夹里。
  • clientPort:这个port就是client连接 Zookeeper server的port。Zookeeper 会监听这个port,接受client的访问请求。
  • server.A=B:C:D:当中 A 是一个数字。表示这个是第几号server;B 是这个server的 ip 地址;C 表示的是这个server与集群中的 Leader server交换信息的port;D 表示的是万一集群中的 Leader server挂了。须要一个port来又一次进行选举。选出一个新的 Leader。而这个port就是用来运行选举时server相互通信的port。假设是伪集群的配置方式,因为 B 都是一样,所以不同的 Zookeeper 实例通信port号不能一样,所以要给它们分配不同的port号。

创建zookeeper保存数据文件:

sudo mkdir -p /opt/zookeeper/data/zk1
sudo mkdir -p /opt/zookeeper/data/zk2
sudo mkdir -p /opt/zookeeper/data/zk3

创建节点标识:

在下面三个文件夹下分别创建myid文件
里面分别写1、2、3用于标识第几号server

cd /opt/zookeeper/data/zk1
sudo echo 1>myid
cd /opt/zookeeper/data/zk2
sudo echo 2>myid
cd /opt/zookeeper/data/zk3
sudo echo 3>myid

🕘 5.3 启动Zookeeper

cd /opt/zookeeper/
sudo chmod -R 777 data/
./bin/zkServer.sh start conf/zoo1.cfg
./bin/zkServer.sh start conf/zoo2.cfg
./bin/zkServer.sh start conf/zoo3.cfg

查看启动状态:

./bin/zkServer.sh status conf/zoo1.cfg
./bin/zkServer.sh status conf/zoo2.cfg
./bin/zkServer.sh status conf/zoo3.cfg

运行截图:
在这里插入图片描述在这里插入图片描述

验证zookeeper安装的正确性:

./bin/zkCli.sh -server 127.0.0.1:2182

在这里插入图片描述

如果连接成功,会有Welcome to zookeeper!和state状态等信息。证明已经成功连接到zookeeper服务。

停止server的命令:

./bin/zkServer.sh stop conf/zoo1.cfg
./bin/zkServer.sh stop conf/zoo2.cfg
./bin/zkServer.sh stop conf/zoo3.cfg

🕒 6. ZooKeeper的Shell操作

🕘 6.1 帮助手册

[zk: 127.0.0.1:2182(CONNECTED) 0] help
ZooKeeper -server host:port -client-configuration properties-file cmd args
	addWatch [-m mode] path # optional mode is one of [PERSISTENT, PERSISTENT_RECURSIVE] - default is PERSISTENT_RECURSIVE
	addauth scheme auth
	close 
	config [-c] [-w] [-s]
	connect host:port
	create [-s] [-e] [-c] [-t ttl] path [data] [acl]
	delete [-v version] path
	deleteall path [-b batch size]
	delquota [-n|-b|-N|-B] path
	get [-s] [-w] path
	getAcl [-s] path
	getAllChildrenNumber path
	getEphemerals path
	history 
	listquota path
	ls [-s] [-w] [-R] path
	printwatches on|off
	quit 
	reconfig [-s] [-v version] [[-file path] | [-members serverID=host:port1:port2;port3[,...]*]] | [-add serverId=host:port1:port2;port3[,...]]* [-remove serverId[,...]*]
	redo cmdno
	removewatches path [-c|-d|-a] [-l]
	set [-s] [-v version] path data
	setAcl [-s] [-v version] [-R] path acl
	setquota -n|-b|-N|-B val path
	stat [-w] path
	sync path
	version 
	whoami 
Command not found: Command not found help
[zk: 127.0.0.1:2182(CONNECTED) 1] 

🕘 6.2 查看当前节点数据

在这里插入图片描述

🕘 6.3 查看当前Zookeeper中所包含的内容

在这里插入图片描述

🕘 6.4 创建节点

创建节点命令格式:create [-s] [-e] [-c] [-t ttl] path [data] [acl]

  • -s:指定ZNode的类型为顺序节点。
  • -e:指定ZNode的类型为临时节点,若不指定,则表示永久节点。
  • path:表示创建的路径
  • data:表示创建节点的数据,这是因为Znode可以像目录一样存在也可以像文件一样保存数据。
  • Acl:进行权限控制。一般不需要了解。

示例:创建临时节点
在这里插入图片描述

🕘 6.5 获取节点

获取节点命令格式:get [-s] [-w] path

  • -s:查看指定ZNode的属性。
  • -w:向指定ZNode注册Watcher。

获取Zookeeper指定节点的数据内容以及属性信息:

在这里插入图片描述

🕘 6.6 修改节点

修改节点命令格式:set [-s] [-v version] path data

  • data:表示要修改的内容,该数据内容不允许出现空格。
  • version:表示数据版本

对前面创建的临时节点testnode-temp进行修改,使得节点内容变成“123”的操作:

在这里插入图片描述

🕘 6.7 监听节点

监听节点就是监听节点变化,概括为三个过程。客户端向服务端注册Watch、服务端事件发生触发Watch、客户端回调Watch得到触发事件的情况。

在这里插入图片描述

🕘 6.8 删除节点

删除节点命令格式:delete [-v version] path

使用delete命令删除节点时,若要删除的节点存在子节点,就无法删除该节点,必须先删除子节点,才可删除父节点。使用rmr命令递归删除节点,不论该节点下是否存在子节点,可以直接删除。

在这里插入图片描述

🕒 7. ZooKeeper的Java API操作

Zookeeper API包含五个包:

org.apache.zookeeper
org.apache.zookeeper.data
org.apache.zookeeper.server
org.apache.zookeeper.server.quorum
org.apache.zookeeper.server.upgrade

org.apache.zookeeper包含Zookeeper类,这也是编程时最常用的类文件,Zookeeper类提供的常用Java API方法。

方法名称方法描述
create创建节点
delete删除节点
exists判断节点是否存在
get/setData获取/修改节点数据
getChildren获取指定节点下的所有子节点列表

🕘 7.1 在Eclipse中搭建ZooKeeper环境

新建Java项目,取名ZooKeeperDemo,之后Next,添加依赖

在这里插入图片描述

导入全部依赖,之后新建类ZKTest,开始写代码

🕘 7.2 创建会话

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class ZKTest {
	public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        getConnect();
        
	}
	
	// 连接ZooKeeper服务
	public static ZooKeeper getConnect()
            throws IOException,InterruptedException {
        String zkServer = "Ubuntu:2181";		// 此处Ubuntu改成自己的主机名
        CountDownLatch countDownLatch = new CountDownLatch(1);
        ZooKeeper zooKeeper = new ZooKeeper(zkServer, 3000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("通知状态:"+watchedEvent.getState() + "\t"
                        + "事件类型:" + watchedEvent.getType() + "\t"
                        + "节点路径:" + watchedEvent.getPath());
                if (Event.KeeperState.SyncConnected == watchedEvent.getState()){
                    countDownLatch.countDown();
                }
            }
        });
        countDownLatch.await();
        return zooKeeper;
    }
}

在这里插入图片描述
如图所示即成功连接。

🕘 7.3 创建ZNode

创建持久节点zkapi,并且在持久节点zkapi中挂载子节点zkChild,该子节点的ZNode类型为持久节点,createNode()方法的代码如下。

public class ZKTest {
	public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        createNode();
	}
	
	// 使用create方法创建ZNode
    public static void createNode()
            throws IOException, InterruptedException, KeeperException {
        ZooKeeper connect = getConnect();
        connect.create(
                "/zkapi",
                "fruit".getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT);
        connect.create(
                "/zkapi/zkChild",
                "apple".getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT);
    }
}

在这里插入图片描述

🕘 7.4 判断ZNode是否存在

判断永久节点zkapi是否存在,existsNode()方法的代码如下所示。

public class ZKTest {
	public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
	    Stat stat = existsNode();
        if (stat != null){
            System.out.println("已存在");
        }else {
            System.out.println("不存在");
        }
	}
	
	// 使用exists方法判断ZNode是否存在
	public static Stat existsNode()	 
			throws IOException, InterruptedException, KeeperException {
		ZooKeeper connect = getConnect();
		Stat exists = connect.exists("/zkapi", true);
		return exists;
	}
}

在这里插入图片描述

🕘 7.5 获取ZNode的数据内容

获取永久节点zkapi的数据内容,getNode()方法的代码如下所示。

public class ZKTest {
	public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
	    byte[] nodeData = getNode();
        System.out.println("持久节点zkapi的数据内容为:"+new String(nodeData));
	}
	
	// 使用getData方法获取ZNode的数据内容
	public static byte[] getNode() 
			 throws IOException, InterruptedException, KeeperException {
		 ZooKeeper connect = getConnect();
		 byte[] data = connect.getData("/zkapi", true, null);
		 return data;
	}
}

在这里插入图片描述

🕘 7.6 修改ZNode的数据内容

将持久节点zkapi的数据内容修改为fruit_new,updateNode()方法的代码如下。

public class ZKTest {
	public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        Stat beforeStat = existsNode();
        System.out.println("持久节点zkapi修改数据内容前的数据版本号:"
                +beforeStat.getVersion());
        System.out.println("持久节点zkapi修改数据内容前的数据内容长度:"
                +beforeStat.getDataLength());
        byte[] beforeNode = getNode();
        System.out.println("持久节点zkapi修改数据内容前的数据内容:"
                +new String(beforeNode));
        Stat afterStat = updateNode();
        System.out.println("持久节点zkapi修改数据内容后的数据版本号:"
                +afterStat.getVersion());
        System.out.println("持久节点zkapi修改数据内容后的数据内容长度:"
                +afterStat.getDataLength());
        byte[] afterNode = getNode();
        System.out.println("持久节点zkapi修改数据内容后的数据内容:"
                +new String(afterNode));
	
	}
	
	// 使用setData方法修改ZNode的数据内容
	public static Stat updateNode()
			throws IOException, InterruptedException, KeeperException {
		ZooKeeper connect = getConnect();
		Stat stat = connect.setData(
		      "/zkapi",
		      "fruit_new".getBytes(),
		      -1);
		return stat;
	}
}

在这里插入图片描述

🕘 7.7 查看ZNode的子节点列表

查看持久节点zkapi的子节点列表,getChildNode()方法的代码如下。

public class ZKTest {
	public static void main(String[] args

相关阅读

热门文章