08-Hadoop之Zookeeper详解

Zookeeper入门

一、 zookeeper 概述

1.1 概述

zookeeper 是一个开源的分布式协调系统,为分布式应用提供协调服务的Apache项目。

工作机制:
	从设计角度:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接收观察者的注册,一旦这些数据的状态发生变化,zookeeper就将负责通知已经在zookeeper上个注册的那些观察者做出相应的反应。
	
设计目标:
	将那些复杂且容易出错的分布式服务封装起来,构成一个高效可靠的分布式服务框架,并给用户提供一系列简单易用的接口提供给用户。
官方文档的设计目标:
	1.ZooKeeper is simple。(简单)
	2.ZooKeeper is replicated (被复制的)
	3.ZooKeeper is ordered (有序的)
	4.ZooKeeper is fast (速度快)

观察者模式,又叫发布-订阅模式,定义对象间一种一对多的依赖关系,使得每当一个对象改变状态时,所有依赖于它的对象都会得到通知并自动更新。

设计模式总共有 23种设计模式 ,并分成三大类: 创建型模式、结构型模式、行为型模式,每一种设计模式都解决一种特定的场景。

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种 : 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式, 共十一种 : 策略模式、 模板方法模式、观察者模式、 迭代子模式、 责任链模式、 命令模式、 备忘录模式、 访问者模式、 中介者模式、 解释器模式、状态模式。

1.2 特点

(1) zookeeper是一个领导者(Leader), 多个跟随者(Follower)组成的集群。

(2) 集群中只要半数以上节点存活, zookeeper集群就能正常服务。

(3) 全局一致性: 每个 Server 保存一份相同的数据副本, Client 无论连接到哪个 Server , 数据都是一致的。

(4) 更新请求顺序进行, 来自同一个 Client 的更新请求按其发送顺序依次执行的。

(5) 数据更新原子性: 一次数据更新要么成功,要么失败

(6) 实时性 : 在一定时间范围内, Client 能读到最新数据。

1.3 数据结构

zookeeper 数据模型的结构与 Unix ( 或Linux )文件系统很类似, 整体上可以看作是一棵树,每个节点称做一个ZNode。 每一个ZNode 默认能够存储 1MB 的数据, 每个ZNode 都可以通过其路径唯一标识

在这里插入图片描述

1.4 应用场景

zookeeper提供的服务包括: 统一命名服务、 统一配置管理、 统一集群管理、 服务器节点动态上下线、 软负载均衡等。

统一命名服务

在这里插入图片描述

统一配置管理

在这里插入图片描述

统一集群管理
在这里插入图片描述

服务器节点动态上下线

在这里插入图片描述

软负载均衡

在这里插入图片描述

1.5 下载地址

**官方地址: https://zookeeper.apache.org/ **

下载步骤:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

二、 zookeeper 安装

2.1 本地模式安装部署

2.1.1 安装前准备
(1) 安装 JDK — Hadoop集群已配置
(2) 拷贝zookeeper安装包到Linux系统下

将压缩包放到 /opt/software

(3) 解压到指定目录
  1. 进入到 /opt/software/

  2. 将压缩文件解压到 /opt/module/ , 命令为:

tar -zxvf zookeeper-3.5.7.tar.gz -C /opt/module/

(4) 更改zookeeper目录名

mv apache-zookeeper-3.5.7-bin zookeeper-3.5.7

2.1.2 配置更改

(1) 将 /opt/module/zookeeper-3.5.7/conf 下的 zoo_sample.cfg 修改为 zoo.cfg

cd /opt/module/zookeeper-3.5.7/conf
mv zoo_sample.cfg zoo.cfg

(2) 打开 zoo.cfg 文件, 修改 dataDir 路径。

修改内容如下

dataDir=/opt/module/zookeeper-3.5.7/zkData

原来的配置文件给出的data是放在tmp下的,只是一个例子,使用的时候需要更改

(3) 创建 (2)时 dataDir 文件

mkdir zkData

注意:一定要和配置文件设置的路径一样,一般将它放在zookeeper的根目录下

(4) 配置环境变量

配置环境变量主要是要看:后期运行时需不要使用到,如 Hadoop就需要 JAVA_HOME 环境变量。

1. 打开环境配置文件
sudo vim /etc/profile.d/my_env.sh 

2. 添加以下内容
export ZOOKEEPER_HOME=/opt/module/zookeeper-3.5.7
export PATH=$PATH:$ZOOKEEPER_HOME/bin

3. 保存退出 并且 source 配置文件
source /etc/profile.d/my_env

(5) 测试 zookeeper

bin/zkServer.sh start   --->  启动 zookeeper 服务
zkServer.sh status   --->   查看 zookeeper 服务状态

zkCli.sh   --->  启动 zookeeper 客户端

quit   --->   退出客户端

zkServer.sh restart   --->  重新启动 zookeeper 服务
zkServer.sh stop   --->  关闭 zookeeper 服务

2.2 配置参数解读

对 zookeeper 中的配置文件 zoo.cfg 中参数含义解读:
1)tickTime =2000:通信心跳数,Zookeeper服务器与客户端心跳时间,单位毫秒
Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒。
它用于心跳机制,并且设置最小的session超时时间为两倍心跳时间。(session的最小超时时间是2*tickTime)

2)initLimit =10:LF初始通信时限
集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限。

3)syncLimit =5:LF同步通信时限
集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。

4)dataDir:数据文件目录+数据持久化路径
主要用于保存Zookeeper中的数据。

5)clientPort =2181:客户端连接端口
监听客户端连接的端口。

三、 zookeeper 实战(开发重点)

3.1 分布式安装部署

3.1.1 集群规划

集群模式是在本地模式的配置上进行修改,所以需要先配置本地模式

在 hadoop102 、 hadoop103 、 hadoop104 三个节点上部署 zookeeper

3.1.2 将 zookeeper 服务停掉
zkServer.sh stop
3.1.3 配置服务器编号

(1) 在 /opt/module/zookeeper-3.5.7/zkData 下创建一个 myid 的文件

touch myid

(2) 在myid 文件中添加一个数值

该数值表示该节点在集群中的唯一id

hadoop102中的myid文件中写2
hadoop103中的myid文件中写3
hadoop104中的myid文件中写4

注意: myid文件的数字不能有空格、不能有多余行

3.1.4 配置 zoo.cfg 文件
在zoo.cfg中添加以下内容:
	server.2=hadoop102:2888:3888
	server.3=hadoop103:2888:3888
	server.4=hadoop104:2888:3888
	
解释   server.A=B:C:D
        A:myid 中的值
        B: myid 中的值对应的服务器的地址
        C: leader 和 follower 通信的端口号
        D: 选举 leader 时相互通信的端口号
zookeeper 在启动时会读取 zkData(dataDir)下的myid文件,拿到里面的数据与 zoo.cfg 里面的配置信息比较, 从而判断到底是哪一个server
3.1.5 集群操作
(1) 手动启动集群
需要每台节点单独启动
zkServer.sh start

注意: zookeeper 的特点中已经解释过: 集群中要有半数以上节点存活, zookeeper集群才能正常服务所以当启动的节点数不超过zoo.cfg配置的数量的一半时,zookeeper集群启动失败

(2) 脚本群起

在zookeeper的bin目录下 创建

#!/bin/bash
if [ $# -ne 1 ]
	then
		echo "args number error!!!"
		exit
fi

var=""

case $1 in
"start")
	var="start"
	;;
"stop")
	var="stop"
	;;
"status")
	var="status"
	;;
*)
	echo "args info error!!!"
	exit
	;;
esac

for host in hadoop102 hadoop103 hadoop104
do
	echo "==========================$host======================"
	ssh $host /opt/module/zookeeper-3.5.7/bin/zkServer.sh $var
done

3.2 客户端命令行操作

命令基本语法功能描述
help显示所有操作命令
ls path使用 ls 命令来查看当前znode的子节点 -w 监听子节点变化 -s 附加次级信息
create普通创建 -s 含有序列 -e 临时(重启或者超时消失)
get path获得节点的值 -w 监听节点内容变化 -s 附加次级信息
set设置节点的具体值
stat查看节点状态
delete删除节点
deleteall递归删除节点

常用客户端命令

(1)  启动客户端
		bin/zkCli.sh
(2)  显示所有操作命令
		help
(3)  查看当前 znode 中所包含的内容
		ls /
(4)  查看当前节点详细数据
		ls -s /
		或者 
		ls2 /    备注:ls2 / 已经被弃用了,会提示使用 ls -s path代替,但是还可以得到结果
(5)  分别创建2个普通节点
        create /sanguo "jinlian"
        create /sanguo/shuguo "liubei"
(6)  获取节点的值
		get /sanguo
(7)  创建短暂节点
		create -e /sanguo/wuguo "zhouyu"
(8)  创建带序号的节点
		create -s /sanguo/weiguo/xiaoqiao "jinlian"
(9)  修改节点数据值
		set /sanguo/weiguo "simayi"
(10) 节点的值变化监听
		监听三国节点
		get /sanguo watch   --- hadoop102
		其他机器对"三国"节点发生修改则会返回信息
		set /sanguo "xisi"  --- hadoop103
		返回信息如下:
		WATCHER::
	WatchedEvent state:SyncConnected type:NodeDataChanged path:/sanguo
(11) 节点的子节点变化监听(路径监听)
		监听节点
		ls /sanguo watch  --- hadoop102
		创建“三国”子节点
		create /sanguo/jin "simayi"  --- hadoop103
		返回信息如下:
		WATCHER::
	WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/sanguo
(12) 删除节点
		delete /sanguo/jin
(13) 递归删除节点
		deleteall /sanguo/shuguo
		或者
		rmr /sanguo/shuguo  备注:已经被弃用
(14) 查看节点状态
		stat /sanguo
3.3 API 应用
3.3.1 idea 环境搭建
(1) 创建 Maven 工程
(2) 添加 pom 文件

添加依赖内容如下

<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>2.8.2</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
		<dependency>
			<groupId>org.apache.zookeeper</groupId>
			<artifactId>zookeeper</artifactId>
			<version>3.5.7</version>
		</dependency>
</dependencies>
(3) 创建 log4j.properties

需要在项目的src/main/resources目录下,新建一个File,命名为log4j.properties,在文件中填入。

log4j.rootLogger=INFO, stdout  
log4j.appender.stdout=org.apache.log4j.ConsoleAppender  
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout  
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender  
log4j.appender.logfile.File=target/spring.log  
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout  
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n  
3.3.2 zookeeper客户端操作
(1) API使用
package zk;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
/*
    通过代码操作Zookeeper:
    1.创建客户端对象
    2.具体操作
    3.关闭资源
 */
public class ZKDemo {
    private ZooKeeper zk;
    /*
        1.创建客户端对象
     */
    @Before
    public void before() throws IOException {
        /*
        ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
        connectString : zk服务器地址
        sessionTimeout : session超时时间(一般为两倍的心跳时间)
        watcher : 一个监听器对象,当ZK服务器需要有事件通知客户端事就会调用该对象中的process方法
                总监听事件对象一般不用。
         */
        String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
        zk = new ZooKeeper(connectString, 4000, new Watcher() {
            /*
                在该方法中实现当监听事件发生后需要处理的业务逻辑代码。
             */
            public void process(WatchedEvent event) {

            }
        });
    }
    /*
        3.关闭资源
     */
    @After
    public void after(){
        if (zk != null){
            try {
                zk.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    /*
        创建子节点
     */
    @Test
    public void test() throws KeeperException, InterruptedException {
        /*
         create(final String path, byte data[], List<ACL> acl,CreateMode createMode)
         path : 节点的路径
         data : 节点中的数据
         acl : 访问控制权限(节点权限)
         createMode :节点的类型
         */
        String s = zk.create("/xiyouji/longge", "longgeliaobude".getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

        System.out.println(s);
    }

    /*
        判断子节点是否存在
     */
    @Test
    public void test2() throws KeeperException, InterruptedException {
        /*
            exists(String path, boolean watch)
            path : 节点路径
            watch : 是否使用总监听器对象
            注意:如果返回的对象为null则说明节点不存在,不为null则存在
         */
        Stat exists = zk.exists("/xiyouji/longge123", false);
        System.out.println(exists==null?"不存在" : "存在");
    }
    /*
        获取子节点并监听子节点变化
     */
    @Test
    public void test3() throws KeeperException, InterruptedException {
        /*
            getChildren(final String path, Watcher watcher)
            path : 节点的路径
            watcher :  一个监听器对象,当ZK服务器需要有事件(子节点变化事件)通知客户端时就会调用该对象中的process方法
                    process方法:在该方法中实现当监听事件(子节点变化事件)发生后需要处理的业务逻辑代码。
         */
        List<String> children = zk.getChildren("/xiyouji", new Watcher() {
            public void process(WatchedEvent event) {
                System.out.println("节点发生改变了");
            }
        });
        //遍历子节点
        for (String child : children) {
            System.out.println(child);
        }
        //不能让程序结束否则无法接受事件的消息
        Thread.sleep(Long.MAX_VALUE);
    }


    @Test
    public void test4() throws KeeperException, InterruptedException {
        listener();
        //不能让程序结束否则无法接受事件的消息
        Thread.sleep(Long.MAX_VALUE);
    }
    public void listener() throws KeeperException, InterruptedException {
        List<String> children = zk.getChildren("/xiyouji", new Watcher() {
            public void process(WatchedEvent event) {
                //当监听发生时:1.业务逻辑处理   2.再次注册监听
                System.out.println("节点发生改变了");
                try {
                    listener();
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //遍历子节点
        for (String child : children) {
            System.out.println(child);
        }
    }
}

(2) 监听服务器节点动态上下线案例

a. 服务器节点端 (zkServer.java)

package zk2;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
/*
    1.创建客户端对象
    2.判断父节点是否存在,如果不存在则创建
    3.创建临时节点
 */
public class ZKServer {
    public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
        //1.创建客户端对象
        String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
        ZooKeeper zk = new ZooKeeper(connectString, 4000, new Watcher() {
            public void process(WatchedEvent event) {

            }
        });
        //2.判断父节点是否存在,如果不存在则创建
        Stat exists = zk.exists("/server", false);
        if (exists==null){//父节点不存则创建
            zk.create("/server","".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
        //3.创建临时节点
        zk.create("/server/" + args[0],args[1].getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL);
        //4.不能让程序结束
        Thread.sleep(Long.MAX_VALUE);
    }
}

b. 客户端 (zkClient.java)

package zk2;

import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.List;
/*
    1.创建客户端对象
    2.判断父节点是否存在(有没有必要取决于先启动谁)
    3.获取子节点并监听子节点变化
    4.不能让程序停止
 */
public class ZKClient {
    private static ZooKeeper zk;
    public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
        //1.创建客户端对象
        String connectString = "hadoop102:2181,hadoop103:2181,hadoop104:2181";
        zk = new ZooKeeper(connectString, 4000, new Watcher() {
            public void process(WatchedEvent event) {

            }
        });
        // 3.获取子节点并监听子节点变化
        listener();

        //4.不能让程序停止
        Thread.sleep(Long.MAX_VALUE);
    }

    public static void listener() throws KeeperException, InterruptedException {
        List<String> children = zk.getChildren("/server", new Watcher() {
            public void process(WatchedEvent event) {
                //1.业务逻辑处理(遍历所有子节点-有几台服务器就有几个子节点) 2.再次监听
                try {
                    listener();
                } catch (KeeperException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //遍历子节点
        for (String child : children) {
            System.out.println(child);
        }
        System.out.println("===================================================");
    }
}

四、 zookeeper 内部原理

4.1 节点类型

在这里插入图片描述

4.2 stat 结构体

(1)czxid-创建节点的事务zxid  --- 必须了解
    每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。
    事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。

(2)ctime - znode被创建的毫秒数(从1970年开始)

(3)mzxid - znode最后更新的事务zxid

(4)mtime - znode最后修改的毫秒数(从1970年开始)

(5)pZxid-znode最后更新的子节点zxid

(6)cversion - znode子节点变化号,znode子节点修改次数

(7)dataversion - znode数据变化号

(8)aclVersion - znode访问控制列表的变化号

(9)ephemeralOwner- 如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0。

(10)dataLength- znode的数据长度

(11)numChildren - znode子节点数量

4.3 监听器原理 (面试重点)

在这里插入图片描述

4.4 paxos 算法 (了解)

Paxos算法一种基于消息传递且具有高度容错特性的一致性算法。

Paxos 算法解决的问题是在一个可能发生上述异常的分布式系统中如何就某个值达成一致,保证不论发生以上任何异常,都不会破坏决议的一致性

算法流程

(1)Prepare: Proposer生成全局唯一且递增的Proposal ID (可使用时间戳加Server ID),向所有Acceptors发送Prepare请求,这里无需携带提案内容,只携带Proposal ID即可。

(2)Promise: Acceptors收到Prepare请求后,做出“两个承诺,一个应答”。
两个承诺:
    不再接受Proposal ID小于等于(注意:这里是<= )当前请求的Prepare请求。
    不再接受Proposal ID小于(注意:这里是< )当前请求的Propose请求。
一个应答:
	不违背以前做出的承诺下,回复已经Accept过的提案中Proposal ID最大的那个提案的Value和Proposal ID,没有则返回空值。
	
(3)Propose: Proposer 收到多数Acceptors的Promise应答后,从应答中选择Proposal ID最大的提案的Value,作为本次要发起的提案。如果所有应答的提案Value均为空值,则可以自己随意决定提案Value。然后携带当前Proposal ID,向所有Acceptors发送Propose请求。

(4)Accept: Acceptor收到Propose请求后,在不违背自己之前做出的承诺下,接受并持久化当前Proposal ID和提案Value。

(5)Learn: Proposer收到多数Acceptors的Accept后,决议形成,将形成的决议发送给所有Learners。

Paxos算法缺陷:在网络复杂的情况下,一个应用Paxos算法的分布式系统,可能很久无法收敛,甚至陷入活锁的情况。

造成原因:系统中有一个以上的Proposer,多个Proposers相互争夺Acceptors,造成迟迟无法达成一致的情况。针对这种情况,一种改进的Paxos算法被提出:从系统中选出一个节点作为Leader,只有Leader能够发起提案。这样,一次Paxos流程中只有一个Proposer,不会出现活锁的情况,此时只会出现例子中第一种情况。

4.5 选举机制 (面试重点)

4.5.1 前言
(1) SID:服务器ID。用来唯一标识一台ZooKeeper集群中的机器,每台机器不能重复,和myid一致。

(2) ZXID:事务ID。ZXID是一个事务ID,用来标识一次服务器状态的变更。在某一时刻,集群中的每台机器的ZXID值不一定完全一致,这和ZooKeeper服务器对于客户端“更新请求”的处理逻辑有关。

(3) Epoch:每个Leader任期的代号。没有Leader时同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加

(4) 半数机制: 集群中半数以上机器存活,集群可以用。所以zookeeper适合安装奇数台服务器。

(5) 在zookeeper配置文件中,虽然没有指定 Master 和 Slave,但是在zookeeper
4.5.2 选举过程

以 5台服务器组成的zookeeper集群为例:(其他也一样)

参考:

公众号:大数据那些事 链接:https://mp.weixin.qq.com/s/jhYEuZSOnGJDxznEEhxREg

a. 第一次启动zookeeper

在这里插入图片描述

选举流程:

zookeeper集群有5台服务器,那么根据半数机制,zookeeper集群正常启动至少需要 3 台,当票数相同时会比较服务器的myid的值,myid的值是唯一的。

(1) 当 server1 启动时,发起第一次选举。此时,server1 会投给自己一票,此时 server1 的票数为1 < 3,不满足半数机制,leader选举未完成,集群无法正常启动,server1 保持为 looking 状态;

(2)当 server2 启动时, 发起第二次选举。 server1 和 server2 会先投自己一票并相互交换选票信息,交互过程:server1 和 server2 的投票票数相同, 会比较 myid, server1 发现 server2 的myid 比自己投票推举的(server1)大, 改投 server2。 此时, server1 的票数为0, server2 的票数为2,不满足半数机制,选举无法完成, 集群无法正常启动,server1 和 server2 保持为 looking 状态;

(3)server3 启动时, 发起第三次选举。先给自己投一票后交换选票信息,然后比较myid, server3 的myid最大,server1 和 server2 都会更改选票信息,改投server3 。此时, server1 的票数为0 , server2 的票数为0, server3 的票数为 3 。 当前已经满足半数机制, server3 当选leader。server1 和 server2 更改状态为 following, server3 的状态为 leading;

(4)server4 启动时, 发起第四次选举。 由于server1、server2、server3已经不是looking状态,不会更改选票信息。此时,server1 的票数为0 , server2 的票数为0, server3 的票数为 3, server4 的票数为0 。 server4服从多数,更改选票信息为 server3 并 更改状态为 following, server3 的 票数为4 。(也可以理解为:集群中 老大已经诞生了,其他都会甘愿做小弟,把票投给老大)

(5)server5 启动, 跟server4 的情况一样, 最终状态为 following。

b. 非第一次启动zookeeper

在这里插入图片描述

选举流程为:

zookeeper集群有5台服务器,zookeeper集群正常启动至少需要 3 台。由于不是第一次启动,会有zxid(事务id)产生,所以相对于第一次启动时选举机制发生改变

(1)当zookeeper集群启动时,出现以下情况的某一种时,就会进入leader选举:

​ a. 服务器初始化启动。

​ b. 服务器运行期间无法和leader保持正常连接。

(2)而当一台服务器进入leader选举流程时,当前集群也可能处于以下两种状态:

​ a. 集群中本来就已经存在一个leader。

对于第一种已经存在Leader的情况,机器试图去选举Leader时,会被告知当前服务器的Leader信息,对于该机器来说,仅仅需要和Leader机器建立连接,并进行状态同步即可。

​ b. 集群中确实不存在leader。(leader所在服务器发生故障)

假设ZooKeeper由5台服务器组成,SID分别为1、2、3、4、5,ZXID分别为8、8、8、7、7,并且此时SID为3的服务器是Leader。某一时刻,3和5服务器出现故障,因此开始进行Leader选举。

则 SID 为 1 、 2 、 4 的机器投票情况

(EPOCH,ZXID,SID)(EPOCH,ZXID,SID)(EPOCH,ZXID,SID)
(1,8,1)(1,8,2)(1,7,4)

此时选举leader的规则:

​ ① EPOCH 大的直接胜出

​ ② EPOCH 相同, 事务id (zxid)大的胜出

​ ③ 事务id相同,服务器id (myid)大的胜出

4.6 写数据流程

在这里插入图片描述

五、 zookeeper的数据存储

本章节参考以下文章:

作者:yannhuang 链接:https://www.jianshu.com/p/8fba732af0cd

5.1 内存数据

zookeeper的数据结构是树, 在内存数据库中,存储了树的所有内容,内容包括所有的节点路径、节点数据、ACL信息,并且zookeeper会定时将这个数据存储到磁盘上。 — 类似 HDFS

变量名解释
parent父节点信息
data[]数据信息
ACL访问控制信息
stat持久化到磁盘上的统计信息
children子节点信息
(1)DataTree

DataTree 是整个 zookeeper 内存数据库的核心,代表内存中一份完整的数据。DataTree不包含任何与网络、客户端连接及请求处理相关的业务逻辑,是一个独立的组件。

这棵树维护了2个并行的数据结构,一个是哈希表(全路径到数据节点的映射),一个是一棵由数据节点构成的树。所有的访问都是通过哈希表来映射到数据节点的。

(2)DataNode

DataNode是数据存储的最小单元,其内部除了保存了节点的数据内容、ACL列表、节点状态之外,还记录了父节点的引用子节点列表两个属性, 其也提供了对子节点列表进行操作的接口。

(3)ZKDatabase

zookeeper的内存数据库,管理zookeeper的所有会话、DataTree存储和事务日志。

ZKDatabase会定时向磁盘dump快照数据,同时在zookeeper启动时,会通过磁盘的事务日志和快照文件恢复成一个完整的内存数据库。 — 和NameNode工作机制类似。

5.2 事务日志
(1) 文件存储

在配置Zookeeper集群时需要配置dataDir目录,用来存储事务日志文件。当前集群配置的文件名为zookeeper根目录下的 zkData,那么Zookeeper在运行过程中会在该目录下建立一个名字为version-2的子目录,该目录确定了当前Zookeeper使用的事务日志格式版本号,当下次某个Zookeeper版本对事务日志格式进行变更时,此目录也会变更,即在version-2子目录下会生成一系列文件大小一致(64MB)的文件。

zkData文件夹内容进行解读:

文件名文件类型作用
myid文件存放server的值,值唯一
version-2文件夹保存事务日志文件
zookeeper_server.pid文件zookeeper进程的端口号
(2) 日志格式

启动zookeeper后,在zookeeper创建节点会在 /zkData/version-2/ 目录下生成一个日志文件 log 文件,该文件主要存储数据,是一个序列化文件,直接查看会乱码。

(3) 日志写入

FileTxnLog负责维护事务日志对外的接口,包括事务日志的写入和读取等。Zookeeper的事务日志写入过程大体可以分为如下6个步骤。

  1. 确定是否有事务日志可写:当Zookeeper服务器启动完成需要进行第一次事务日志的写入,或是上一次事务日志写满时,都会处于与事务日志文件断开的状态,即Zookeeper服务器没有和任意一个日志文件相关联。因此在进行事务日志写入前,Zookeeper首先会判断FileTxnLog组件是否已经关联上一个可写的事务日志文件。若没有,则会使用该事务操作关联的ZXID作为后缀创建一个事务日志文件,同时构建事务日志的文件头信息,并立即写入这个事务日志文件中去,同时将该文件的文件流放入streamToFlush集合,该集合用来记录当前需要强制进行数据落盘的文件流。
  2. 确定事务日志文件是否需要扩容(预分配):Zookeeper会采用磁盘空间预分配策略。当检测到当前事务日志文件剩余空间不足4096字节时,就会开始进行文件空间扩容,即在现有文件大小上,将文件增加65536KB(64MB),然后使用"0"填充被扩容的文件空间。
  3. 事务序列化:对事务头和事务体的序列化,其中事务体又可分为会话创建事务、节点创建事务、节点删除事务、节点数据更新事务等。
  4. 生成Checksum:为保证日志文件的完整性和数据的准确性,Zookeeper在将事务日志写入文件前,会计算生成Checksum。
  5. 写入事务日志文件流:将序列化后的事务头、事务体和Checksum写入文件流中,此时并为写入到磁盘上。
  6. 事务日志刷入磁盘:由于步骤5中的缓存原因,无法实时地写入磁盘文件中,因此需要将缓存数据强制刷入磁盘。
(4) 日志截断

在Zookeeper运行过程中,可能出现非Leader记录的事务ID比Leader上大,这是非法运行状态。此时,需要保证所有机器必须与该Leader的数据保持同步,即Leader会发送TRUNC命令给该机器,要求进行日志截断,Learner收到该命令后,就会删除所有包含或大于该事务ID的事务日志文件。

5.3 snapshot 数据快照

数据快照是Zookeeper数据存储中非常核心的运行机制,数据快照用来记录Zookeeper服务器上某一时刻的全量内存数据内容,并将其写入指定的磁盘文件中。

(1)文件存储

与事务文件类似,Zookeeper快照文件也可以指定特定磁盘目录,通过dataDir属性来配置。若指定dataDir为/home/admin/zkData/zk_data,则在运行过程中会在该目录下创建version-2的目录,该目录确定了当前Zookeeper使用的快照数据格式版本号。在Zookeeper运行时,会生成一系列文件。

(2)数据快照

FileSnap负责维护快照数据对外的接口,包括快照数据的写入和读取等,将内存数据库写入快照数据文件其实是一个序列化过程。针对客户端的每一次事务操作,Zookeeper都会将他们记录到事务日志中,同时也会将数据变更应用到内存数据库中,Zookeeper在进行若干次事务日志记录后,将内存数据库的全量数据Dump到本地文件中,这就是数据快照。其步骤如下

  • 确定是否需要进行数据快照:每进行一次事务日志记录之后,Zookeeper都会检测当前是否需要进行数据快照,考虑到数据快照对于Zookeeper机器的影响,需要尽量避免Zookeeper集群中的所有机器在同一时刻进行数据快照。采用过半随机策略进行数据快照操作。
  • 切换事务日志文件:表示当前的事务日志已经写满,需要重新创建一个新的事务日志。
  • 创建数据快照异步线程:创建单独的异步线程来进行数据快照以避免影响Zookeeper主流程。
  • 获取全量数据和会话信息:从ZKDatabase中获取到DataTree和会话信息。
  • 生成快照数据文件名:Zookeeper根据当前已经提交的最大ZXID来生成数据快照文件名。
  • 数据序列化:首先序列化文件头信息,然后再对会话信息和DataTree分别进行序列化,同时生成一个Checksum,一并写入快照数据文件中去。

六、 zookeeper 选举机制 — 进阶

具体请到 :

公众号:大数据那些事 链接:https://mp.weixin.qq.com/s/jhYEuZSOnGJDxznEEhxREg

选举执行源码概览:

在这里插入图片描述

选举执行源码概览:

在这里插入图片描述

zookeeper是分布式开发的基础,可以根据源码图详细看

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值