Datahub 安装失败记录

背景介绍

参考链接: https://blog.csdn.net/ddxygq/article/details/123437072

DataHub是由LinkedIn的数据团队开源的一款提供元数据搜索与发现的工具。

提到LinkedIn,不得不想到大名鼎鼎的Kafka,Kafka就是LinkedIn开源的。LinkedIn开源的Kafka直接影响了整个实时计算领域的发展,而LinkedIn的数据团队也一直在探索数据治理的问题,不断努力扩展其基础架构,以满足不断增长的大数据生态系统的需求。随着数据的数量和丰富性的增长,数据科学家和工程师要发现可用的数据资产,了解其出处并根据见解采取适当的行动变得越来越具有挑战性。为了帮助增长的同时继续扩大生产力和数据创新,创建了通用的元数据搜索和发现工具DataHub。

Datahub作为新一代的元数据管理平台,大有取代老牌元数据管理工具Atlas之势。首先,阿里云也有一款名为DataHub的产品,是一个流式处理平台,本文所述DataHub与其无关。

市面上常见的元数据管理系统有如下几个:

笔者之前白嫖了亚马逊的EC2服务器,在链接文章的教程下尝试安装datahub, 系统默认环境python2.7、python3.7;

安装datahub过程

1、笔者尝试安装了 python3.8,本来开始没有安装3.8,但是在安装完datahub的时候,尝试验证版本号时抱如下第一个错误

python3 -m datahub version
DataHub CLI version: 0.10.0.1
Python version: 3.7.16 (default, Dec 15 2022, 23:24:54) 
[GCC 7.3.1 20180712 (Red Hat 7.3.1-15)]
Exception ignored in: <generator object configure_logging at 0x7f8fcca5a050>
Traceback (most recent call last):
  File "/home/ec2-user/.local/lib/python3.7/site-packages/datahub/utilities/logging_manager.py", line 187, in configure_logging
  File "/usr/lib64/python3.7/contextlib.py", line 486, in __exit__
AttributeError: 'NoneType' object has no attribute 'exc_info'

2、上边的错误出现后没有找到合适的解决方案,就直接升级python版本到3.8了,以后就有了一系列的坑,如下第二个错误,发生在对python3.8编译的过程中。

    wget https://www.python.org/ftp/python/3.8.3/Python-3.8.3.tgz

    tar zxvf Python-3.8.3.tgz

    ./configure --prefix=/usr/lib/python3.8
    checking build system type... x86_64-pc-linux-gnu
    checking host system type... x86_64-pc-linux-gnu
    checking for python3.8... no
    checking for python3... python3
    checking for --enable-universalsdk... no
    checking for --with-universal-archs... no
    checking MACHDEP... "linux"
    checking for gcc... no
    checking for cc... no
    checking for cl.exe... no
    configure: error: in `/home/ec2-user/Python-3.8.3':
    configure: error: no acceptable C compiler found in $PATH
    See `config.log' for more details

通过安装 gcc 解决了上边的问题; 因为ec2有权限限制,非root用户执行命令时尽可能带上sudo

    sudo yum -y install gcc-c++

    sudo make && sudo make install

3、然后make install的时候遇到问题三,如下提示,通过安装zlib-devel解决

zipimport.ZipImportError: can't decompress data; zlib not available

    sudo yum install zlib-devel

编译完并通过ln -s 命令修改了python3的软链之后,执行pip3的操作开始报如下的错误,通过安装openssl 和 openssl-devel解决,但是还得从新编译python3.8,从.config开始从新执行之前的命令

python3 -m pip install --upgrade pip wheel setuptools

WARNING: pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.
WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError("Can't connect to HTTPS URL because the SSL module is not available.")': /simple/pip/
WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError("Can't connect to HTTPS URL because the SSL module is not available.")': /simple/pip/
    sudo yum -y install openssl openssl-devel

    python3 -m pip install --upgrade acryl-datahub

4、之后再次执行datahub的安装,入到如下问题, 通过安装 libffi-devel 解决

 File "/usr/lib/python3.8/lib/python3.8/ctypes/__init__.py", line 7, in 
      from _ctypes import Union, Structure, Array
  ModuleNotFoundError: No module named '_ctypes'
  [end of output]

note: This error originates from a subprocess, and is likely not a problem with pip.
ERROR: Failed building wheel for avro

  File "/usr/lib/python3.8/lib/python3.8/ctypes/__init__.py", line 7, in 
      from _ctypes import Union, Structure, Array
  ModuleNotFoundError: No module named '_ctypes'
  [end of output]

note: This error originates from a subprocess, and is likely not a problem with pip.
ERROR: Failed building wheel for click-default-group

    sudo yum install libffi-devel

5、继续执行datahub的安装,遇到如下问题, 通过安装bzip2-devel 同时从新编译解决(如果不从新编译还会报错)

    File "/usr/lib/python3.8/lib/python3.8/bz2.py", line 19, in 
    from _bz2 import BZ2Compressor, BZ2Decompressor

ModuleNotFoundError: No module named '_bz2'
    sudo yum install bzip2-devel 

    sudo make && sudo make install

直到此时解决以上的问题,才算是把datahub安装好了。

启动datahub

6、如果docker服务没有启动会可能有如下问题;启动服务失败,报标题中的错误,试试启动服务命令前加上 sudo。

The name org.freedesktop.PolicyKit1 was not provided by any .service files See system logs and 'systemctl status docker.service' for details.

7、docker出现的问题如下

ERROR: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/info": dial unix /var/run/docker.sock: connect: permission denied

通过修改docker 组来处理

Try adding your user to the docker group:

    Run usermod -aG docker "${USER}", then
    either log out and log back in, or run newgrp docker.
    After this you have to restart your docker daemon: sudo service docker restart.

到现在所有的坑基本上踩完了,直接执行如下启动datahub的命令并得到如下提示,GAME OVER!

python3 -m datahub docker quickstart

Total Docker memory configured 0.96GB is below the minimum threshold 3.8GB. You can increase the memory allocated to Docker in the Docker settings.

是的,这是一次全程失败的实验过程,但是笔者认为除了最后设备硬件不够用之外,主要的安装踩坑过程基本上就差不多了。

一致性hash 算法

一、场景描述

假设有三台缓存服务器,用于缓存图片,我们为这三台缓存服务器编号为 01号、02号、03号,现在有3万张图片需要缓存,我们希望这些图片被均匀的缓存到这3台服务器上,以便它们能够分摊缓存的压力。也就是说,我们希望每台服务器能够缓存1万张左右的图片,那么我们应该怎样做呢?

常见的做法是对缓存项的键进行哈希,将hash后的结果对缓存服务器的数量进行取模操作,通过取模后的结果,决定缓存项将会缓存在哪一台服务器上

hash(图片名称)% N

存在的问题:

当服务器数量发生改变时,比如新增加一台缓存服务器,所有缓存在一定时间内是失效的,当应用无法从缓存中获取数据时,则会向后端服务器请求数据;

同理,假设突然有一台缓存服务器出现了故障,那么我们则需要将故障机器移除,那么缓存服务器数量从3台变为2台,同样会导致大量缓存在同一时间失效,造成了缓存的雪崩,后端服务器将会承受巨大的压力,整个系统很有可能被压垮。为了解决这种情况,就有了一致性哈希算法。

二、一致性hash 算法是什么?

一致性哈希算法也是使用取模的方法,但是取模算法是对服务器的数量进行取模,而理论上一致性哈希算法是对 2^32 取模,具体步骤如下:

  • 步骤一:一致性哈希算法将整个哈希值空间按照顺时针方向组织成一个虚拟的圆环,称为 Hash 环;

    将 2^32 想象成一个圆,像钟表一样,钟表的圆可以理解成由60个点组成的圆,而此处我们把这个圆想象成由2^32个点组成的圆    
  • 步骤二:接着将各个服务器使用 Hash 函数进行哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,从而确定每台机器在哈希环上的位置

    哈希算法:hash(服务器的IP) % 2^32;计算结果一定是 0 到 2^32-1 之间的整数,那么 hash
    环上必定有一个点与这个整数对应,所以我们可以使用这个整数代表服务器,也就是服务器就可以映射到这个环上 
  • 步骤三:最后使用算法定位数据访问到相应服务器:将数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针寻找,第一台遇到的服务器就是其应该定位到的服务器

    图片的名称作为 key,所以我们使用下面算法将图片映射在哈希环上:hash(图片名称) % 2^32;
    只要从图片的位置开始,沿顺时针方向遇到的第一个服务器就是图片存放的服务器了
graph LR
A(())

三、一致性hash的优缺点

优点:

一致性Hash算法对于节点的增减都只需重定位环空间中的一小部分数据,只有部分缓存会失效,不至于将所有压力都在同一时间集中到后端服务器上,具有较好的容错性和可扩展性。

缺点:

一致性哈希算法在服务节点太少的情况下,容易因为节点分部不均匀而造成数据倾斜问题,也就是被缓存的对象大部分集中缓存在某一台服务器上,从而出现数据分布不均匀的情况,这种情况就称为 hash 环的倾斜。

通常优化方案:

为了解决这种数据倾斜问题,一致性哈希算法引入了虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点,一个实际物理节点可以对应多个虚拟节点,虚拟节点越多,hash环上的节点就越多,缓存被均匀分布的概率就越大,hash环倾斜所带来的影响就越小,同时数据定位算法不变,只是多了一步虚拟节点到实际节点的映射。 具体做法可以在服务器ip或主机名的后面增加编号来实现

四、python实现一致性hash

# -*- coding: utf-8 -*-
import hashlib

class ConsistHash(object):
    def __init__(self, nodes=None, n_number=12) -> None:
        """
        nodes:           所有的节点
        n_number:        一个节点对应多少个虚拟节点
        """
        self._n_number = n_number   #每一个节点对应多少个虚拟节点,这里默认是3个
        self._node_dict = dict()    #用于将虚拟节点的hash值与node的对应关系
        self._sort_list = []        #用于存放所有的虚拟节点的hash值,这里需要保持排序
        if nodes:
            for node in nodes:
                self.add_node(node)

    def add_node(self, node) ->None:
        """
        添加node,首先要根据虚拟节点的数目,创建所有的虚拟节点,并将其与对应的node对应起来
        当然还需要将虚拟节点的hash值放到排序的里面
        这里在添加了节点之后,需要保持虚拟节点hash值的顺序

        """
        for i in range(self._n_number):
            node_str = "%s#%s" % (node,i)    #虚拟节点=n#真实节点#n
            key = self._gen_key(node_str)     #计算虚拟节点的key
            self._node_dict[key] = node       #key和真实节点的对应关系
            self._sort_list.append(key)
        self._sort_list.sort()

    @staticmethod
    def _gen_key(key_str) ->str:
        """
        通过key,返回当前key的hash值,这里采用md5
        """
        return hashlib.md5(key_str.encode(encoding='UTF-8')).hexdigest()   #16进制

    def get_node(self, key_str):
        """
        返回这个字符串应该对应的node,这里先求出字符串的hash值,然后找到第一个小于等于的虚拟节点,然后返回node
        如果hash值大于所有的节点,那么用第一个虚拟节点
        """
        if self._sort_list:
            key = self._gen_key(key_str)
            for node_key in self._sort_list:
                if key <= node_key:
                    return self._node_dict[node_key]
            return self._node_dict[self._sort_list[0]]
        else:
            return None

    def remove_node(self, node):
        """
        这里一个节点的退出,需要将这个节点的所有的虚拟节点都删除
        """
        for i in range(self._n_number):
            node_str = "%s#%s" % (node, i)
            key = self._gen_key(node_str)
            del self._node_dict[key]
            self._sort_list.remove(key)

if __name__ == "__main__":
    cons = ConsistHash(["192.168.1.1","192.168.1.2","192.168.1.3","192.168.1.4"])
    print(cons.get_node("DV001"))
    print(cons.get_node("DV002"))
    print(cons.get_node("DV003"))
    print(cons.get_node("DV004"))
    print(cons.get_node("DV005"))
    print(cons.get_node("DV006"))
    print(cons.get_node("DV007"))

参考链接: https://blog.csdn.net/a745233700/article/details/120814088

Hadoop HA 基本认知

Hadoop HA(High Available)经过同时配置两个处于Active/Passive模式的Namenode,分别叫Active Namenode和Standby Namenode。 Standby Namenode做为热备份,从而容许在机器发生故障时可以快速进行故障转移,同时在平常维护的时候使用优雅的方式进行Namenode切换。Namenode只能配置一主一备,不能多于两个Namenode。

Hadoop HA基本流程

集群总体上可以分为三部分:NameNode集群、JournalNode集群和Zookeeper集群。NameNode在某一时刻只有一个处于活跃状态,其他的都处于standby状态;JournalNode负责把edits文件传到standby的NameNode上;Zookeeper负责监控NameNode宕机情况,ZKFC(ZookeeperFailoverController)是专门监控NameNode健康的。

为了同步NameNode的元数据一致,有专门的JournalNode来同步元数据文件,活跃的NameNode的edits文件会写入journalnode集群,其他standby的结点会去读取journalnode上的edits文件,以此来同步自身的元数据。

  1. ZKFC的HealthyMonitor是监控NameNode的进程,是专门监控NameNode将康情况的进程。
  2. HealthyMonitor会定时想ZKFC进程报告NameNode情况。
  3. 当HealthyMonitor出现汇报了NameNode,ZKFC就会向AcitveStandbyEloctor报告。
  4. AcitveStandbyEloctor接到NameNode宕机报告就会通知zk集群选举出新的NameNode。
  5. zk集群经过内部选举,返回一个standby的NameNode给AcitveStandbyEloctor。
  6. AcitveStandbyEloctor想ZKFC报告选举结果。
  7. ZKFC为了防止是网络原因导致NameNode假死,就会结束NameNode进程。
  8. zk集群就会通知另一个ZKFC要求它修改它监控的NameNode的进程为活跃节点。

HA技术关键点

HA问题中需要解决的两个问题:

  • 元数据一致性:Standby节点和Active节点的元数据一致性。
  • 主备自动切换:Active节点服务中断时,Standby节点可以立即启动对外提供服务。

为了确保故障转移可以快速完成,Standby Namenode须要维护最新的Block位置信息,即每一个Block副本存放在集群中的哪些节点上。为了达到这一点,Datanode同时配置主备两个Namenode,并同时发送Block报告和心跳到两台Namenode。

确保任什么时候刻只有一个Namenode处于Active状态很是重要,不然可能出现数据丢失或者数据损坏。当两台Namenode都认为本身的Active Namenode时,会同时尝试写入数据(不会再去检测和同步数据)。为了防止这种脑裂现象,Journal Nodes只容许一个Namenode写入数据,内部经过维护epoch数来控制,从而安全地进行故障转移。

HA其本质上就是要保证主备NN元数据是保持一致的,即保证fsimage和editlog在备NN上也是完整的。元数据的同步很大程度取决于EditLog的同步,而这步骤的关键就是共享文件系统

有两种方式能够进行edit log共享:

  1. 使用QJM(Quorum Journal Manager)共享edit log
  2. 使用NFS(Network File System)共享edit log(存储在NAS/SAN)

NFS的方式

all name space edits logged to shared storage;Block reports are sent to both name nodes

显然NFS做为主备Namenode的共享存储。这种方案可能会出现脑裂(split-brain),即两个节点都认为本身是主Namenode并尝试向edit log写入数据,这可能会致使数据损坏。经过配置fencin脚原本解决这个问题,fencing脚本用于:

  • 将以前的Namenode关机
  • 禁止以前的Namenode继续访问共享的edit log文件

使用这种方案,管理员就能够手工触发Namenode切换,而后进行升级维护。但这种方式存在如下问题:

  • 只能手动进行故障转移,每次故障都要求管理员采起措施切换。
  • NAS/SAN设置部署复杂,容易出错,且NAS自己是单点故障。
  • Fencing 很复杂,常常会配置错误。
  • 没法解决意外(unplanned)事故,如硬件或者软件故障。oop
    所以须要另外一种方式来处理这些问题:
  • 自动故障转移(引入ZooKeeper达到自动化)
  • 移除对外界软件硬件的依赖(NAS/SAN)
  • 同时解决意外事故及平常维护致使的不可用

Quorum Journal Manager

QJM(Quorum Journal Manager)是Hadoop专门为Namenode共享存储开发的组件,一般是奇数点结点组成。其集群运行一组Journal Node,每一个Journal 节点暴露一个简单的RPC接口,容许Namenode读取和写入数据,数据存放在Journal节点的本地磁盘。当Namenode写入edit log时,NameNode会同时向所有JournalNode并行写文件,当超过半数节点回复确认成功写入以后,edit log就认为是成功写入。

1、 QJM写过程

NameNode 会把 EditLog 同时写到本地和 JournalNode 中。写本地由配置中的参数dfs.namenode.name.dir来控制,写JN由参数dfs.namenode.shared.edits.dir控制,在写EditLog时会由两个不同的输出流来控制日志的写过程,分别是:

  • EditLogFileOutputStream(本地输出流)
  • QuorumOutputStream(JN输出流)

NameNode在写EditLog时,并不是直接写到磁盘中,为保证高吞吐,NameNode会分别为EditLogFileOutputStream和QuorumOutputStream定义两个同等大小的Buffer,大小大概是512KB,一个写Buffer(buffCurrent),一个同步Buffer(buffReady),这样可以一边写一边同步,所以EditLog是一个异步写过程,同时也是一个批量同步的过程,避免每写一笔就同步一次日志。

这个是怎么实现边写边同步的呢,这中间其实是有一个缓冲区交换的过程,即bufferCurrent和buffReady在达到条件时会触发交换,如bufferCurrent在达到阈值同时bufferReady的数据又同步完时,bufferReady数据会清空,同时会将bufferCurrent指针指向bufferReady以满足继续写,另外会将bufferReady指针指向bufferCurrent以提供继续同步EditLog。

flowchart TD
    A((Client)) --修改--> B(NameNode)
    B-->C(本地 \n EditLogFileOutputStream)
    B-->D(JournalNode \n QuorumOutputStream)
    C-->E(bufferCurrent)
    C-->F(bufferReady)
    D-->H(bufferCurrent)
    D-->I(bufferReady)
    E-->M(本地目录)
    F-->M
    H-->N(JournalNode)
    I-->N

这里有一个问题,既然EditLog是异步写的,怎么保证缓存中的数据不丢呢,其实这里虽然是异步,但实际所有日志都需要通过logSync同步成功后才会给client返回成功码,假设某一时刻NameNode不可用了,其内存中的数据其实是未同步成功的,所以client会认为这部分数据未写成功。

(1)隔离双写

在Active NN每次同步EditLog到JN时,先要保证不会有两个NN同时向JN同步日志。这涉及一个很重要的概念Epoch Numbers,很多分布式系统都会用到。

成为Active结点时,其会被赋予一个EpochNumber,每个EpochNumber是惟一的,不会有相同的EpochNumber出现。EpochNumber有严格顺序保证,每次NN切换后其EpochNumber都会自增1,后面生成的EpochNumber都会大于前面的EpochNumber。QJM是怎么保证上面特性的呢,主要有以下几点:

  1. 在对EditLog作任何修改前,QJM(NameNode上)必须被赋予一个EpochNumber;
  2. QJM把自己的EpochNumber通过newEpoch(N)的方式发送给所有JN结点;
  3. 当JN收到newEpoch请求后,会把QJM的EpochNumber保存到一个lastPromisedEpoch变量中并持久化到本地磁盘;
  4. ANN同步日志到JN的任何RPC请求(如logEdits(),startLogSegment()等),都必须包含ANN的EpochNumber;
  5. JN在收到RPC请求后,会将之与lastPromisedEpoch对比,如果请求的EpochNumber小于lastPromisedEpoch,将会拒绝同步请求,反之,会接受同步请求并将请求的EpochNumber保存在lastPromisedEpoch;

这样就能保证主备NN发生切换时,就算同时向JN同步日志,也能保证日志不会写乱,因为发生切换后,原ANN的EpochNumber肯定是小于新ANN的EpochNumber,所以原ANN向JN的发起的所有同步请求都会拒绝,实现隔离功能,防止了脑裂。

(2)恢复in-process日志

如果在写过程中写失败了,可能各个JN上的EditLog的长度都不一样,需要在开始写之前将不一致的部分恢复。恢复机制如下:

  1. Active NN先向所有JN发送getJournalState请求;
  2. JN会向ANN返回一个Epoch(lastPromisedEpoch);
  3. Active NN收到大多数JN的Epoch后,选择最大的一个并加1作为当前新的Epoch,然后向JN发送新的newEpoch请求,把新的Epoch下发给JN;
  4. JN收到新的Epoch后,和lastPromisedEpoch对比,若更大则更新到本地并返回给Active NN自己本地一个最新EditLogSegment起始事务Id,若小则返回NN错误;
  5. Active NN收到多数JN成功响应后认为Epoch生成成功,开始准备日志恢复;
  6. Active NN会选择一个最大的EditLogSegment事务ID作为恢复依据,然后向JN发送prepareRecovery; RPC请求,对应Paxos协议2p阶段的Phase1a,若多数JN响应prepareRecovery成功,则可认为Phase1a阶段成功;
  7. Active NN选择进行同步的数据源,向JN发送acceptRecovery RPC请求,并将数据源作为参数传给JN。
  8. JN收到acceptRecovery请求后,会从JournalNodeHttpServer下载EditLogSegment并替换到本地保存的EditLogSegment,对应Paxos协议2p阶段的Phase1b,完成后返回Active NN请求成功状态。
  9. Active NN收到多数JN的响应成功请求后,向JN发送finalizeLogSegment请求,表示数据恢复完成,这样之后所有JN上的日志就能保持一致。 数据恢复后,Active NN上会将本地处于in-process状态的日志更名为finalized状态的日志,形式如editsstart-txidstop-txid。

(3)日志同步

  1. 执行logSync过程,将ANN上的日志数据放到缓存队列中
  2. 将缓存中数据同步到JN,JN有相应线程来处理logEdits请求
  3. JN收到数据后,先确认EpochNumber是否合法,再验证日志事务ID是否正常,将日志刷到磁盘,返回ANN成功码
  4. ANN收到JN成功请求后返回client写成功标识,若失败则抛出异常

通过上面一些步骤,日志能保证成功同步到JN,同时保证JN日志的一致性,进而备NN上同步日志时也能保证数据是完整和一致的。

2、QJM读过程

读过程是面向备NN(Standby NN)的,Standby NN定期检查JournalNode上EditLog的变化,然后将EditLog拉回本地。Standby NN上有一个线程StandbyCheckpointer,会定期将Standby NN上FSImage和EditLog合并,并将合并完的FSImage文件传回主NN(Active NN)上,就是所说的Checkpointing过程。下面我们来看下Checkpointing是怎么进行的。

在2.x版本中,已经将原来的由SecondaryNameNode主导的Checkpointing替换成由Standby NN主导的Checkpointing。

  1. 在Standby NN上先检查前置条件,前置条件包括两个方面:距离上次Checkpointing的时间间隔和EditLog中事务条数限制。
  2. 前置条件任何一个满足都会触发Checkpointing,然后SNN会将最新的NameSpace数据即SNN内存中当前状态的元数据保存到一个临时的fsimage文件( fsimage.ckpt)
  3. 然后比对从JN上拉到的最新EditLog的事务ID,将fsimage.ckpt_中没有,EditLog中有的所有元数据修改记录合并一起并重命名成新的fsimage文件,同时生成一个md5文件。
  4. 将最新的fsimage再通过HTTP请求传回ANN。

通过定期合并fsimage有什么好处?

  1. 可以避免EditLog越来越大,合并成新fsimage后可以将老的EditLog删除
  2. 可以避免主NN(ANN)压力过大,合并是在SNN上进行的
  3. 可以保证fsimage保存的是一份最新的元数据,故障恢复时避免数据丢失

三、主备自动切换
Hadoop的主备选举依赖于ZooKeeper。
整个切换过程是由ZKFC来控制的,ZKFC是实现主备切换的组件。每个运行的NameNode上都会有一个ZKFC进程(实际是一个Hadoop进程)。主要的功能如下:

  1. 健康检测:ZKFC会使用健康检测命令定期的ping同节点中的NameNode,只要该NameNode及时的回复健康,则任务当前NameNode是健康的;
  2. Zookeeper会话管理: 当本地NameNode是健康的,ZKFC会保持一个在Zookeeper中打开的会话。如果本地NameNode处于Active状态,ZKFC会保持一个特殊的znode锁,如果回话中断,锁节点讲自动删除;
  3. 基于Zookeeper的选举: 如果本地的NameNode是健康的,且ZKFC发现没有其他的节点持有当前的znode锁,它会为自己获取该锁。如果成功则进行故障切换,并且确保之前的NameNode的进程中断,将本地NameNode切换为Active;

在故障切换期间,ZooKeeper主要是发挥什么作用有以下几点:

  1. 失败保护:集群中每一个NameNode都会在ZooKeeper维护一个持久的session,机器一旦挂掉,session就会过期,故障迁移就会触发;
  2. Active NameNode选择:ZooKeeper有一个选择ActiveNN的机制,一旦现有的ANN宕机,其他NameNode可以向ZooKeeper申请排他成为下一个Active节点;
  3. 防脑裂: ZK本身是强一致和高可用的,可以用它来保证同一时刻只有一个活动节点;

参考链接:
https://blog.csdn.net/weixin_43854618/article/details/108808274

https://blog.csdn.net/shan19920501/article/details/124911283