Namenode的Ha机制、HDFS读写数据的过程

为什么要Namenode HA?

前言:在Hadoop 1.x版本,HDFS集群的NameNode一直存在单点故障问题:集群只存在一个NameNode节点,它维护了HDFS所有的元数据信息,当该节点所在服务器宕机或者服务不可用,整个HDFS集群都将处于不可用状态,极大限制了HDFS在生产环境的应用场景。直到Hadoop 2.0版本才提出了高可用 (High Availability, HA) 解决方案,并且经过多个版本的迭代更新,已经广泛应用于生产环境。
解决方案:在同一个HDFS集群,运行两个互为主备的NameNode节点。一台为主Namenode节点,处于Active状态,一台为备NameNode节点,处于Standby状态。其中只有Active NameNode对外提供读写服务,Standby NameNode会根据Active NameNode的状态变化,在必要时切换成Active状态。

HA机制

已知导致服务可靠性不高的原因是namenode节点宕机,那么怎么才能避免这个namenode节点宕机呢?一个容易想到的解决方案是部署两台namenode节点,形成主备模式(active/standby模式),这样一旦active节点宕机,standby节点立即切换到active模式。事实上HA机制就是采取的这种方案。要想实现该机制,需要解决以下问题:
1.为什么选择主备模式,而不是主主模式(active/active模式),也即让两个namenode节点都响应客户端的请求一个显然的前提是,两台namenode节点需要保存一致的元数据。

        我们知道namenode节点是用来管理这些元数据的,响应客户端请求时(上传)需要增加元数据信息,如果使用主主模式,那么两个节点都将对元数据进行写操作,怎么同步是个很困难的问题。因此,只能有一台机器响应请求,也即处在active状态的节点(可称为主节点),而另一台namenode在主节点正常工作情况下仅用来同步active节点的元数据信息,这个namenode称为备用节点(处在standby状态),可见,要解决的问题主要是怎么同步active节点的元数据信息。

2.怎么同步两个namenode节点的元数据
      响应客户端请求的是active节点,因此只有active节点保存了最新的元数据。元数据分为两部分,一部分是刚写入新的元数据(edits),另一部分是合并后的较旧的(fsimage)。HA机制解决同步问题的方法是将active节点新写入的edits元数据放在zookeeper集群上(zookeeper集群主要功能是实现少量数据的分布式同步管理),standby节点在active节点正常情况下只需要将zookeeper集群上edits文件同步到自己的fsimage中就可以。

       hadoop框架为这个集群专门写了个分布式应用qjournal(依赖zookeeper实现),实现qjournal的节点称为journalnode。

3.怎么感知active节点是否宕机,并将standby节点快速切换到active状态?
        解决方案是专门在namenode节点上启动一个监控进程,时刻监控namenode的状态。对于处在active状态的namenode,如果发现不正常就向zookeeper集群中写入一些数据。对于处在standby状态的namenode,监控进程从zookeeper集群中读数据,从而感知到active节点是否正常。如果发现异常,监控进程负责将standby状态切换到active状态。这个监控进程在hadoop中叫做zkfc(依赖zookeeper实现)。

4.如何在状态切换时避免brain split(脑裂)?
        脑裂:active namenode工作不正常后,zkfc在zookeeper中写入一些数据,表明异常,这时standby namenode中的zkfc读到异常信息,并将standby节点置为active。但是,如果之前的active namenode并没有真的死掉,出现了假死(死了一会儿后又正常了,哈哈,是不是很搞笑),这样,就有两台namenode同时工作了。这种现象称为脑裂。

        解决方案:standby namenode感知到主用节点出现异常后并不会立即切换状态,zkfc会首先通过ssh远程杀死active节点的 namenode进程(kill -9 进程号)。但是(这样还不行,惊讶),如果kill指令没有执行成功咋办??如果在一段时间内没有收到执行成功的回执,standby节点会执行一个自定义脚本,尽量保证不会出现脑裂问题!这个机制在hadoop中称为fencing(包括ssh发送kill指令,执行自定义脚本两道保障)

方式基于zookeeper的选举方式实现

为了实现热备,增加FailoverControllerZK,FailoverController与ZK通信,通过ZK选主,FailoverController通过RPC让NN转换为active或standby。

ZKFC

zkfc是什么? ZooKeeperFailoverController 
它是什么?是Hadoop中通过ZK实现FC功能的一个实用工具。 
主要作用:作为一个ZK集群的客户端,用来监控NN的状态信息。 
谁会用它?每个运行NN的节点必须要运行一个zkfc
ZKFC即ZKFailoverController,作为独立进程存在,负责控制NameNode的主备切换,ZKFC会监测NameNode的健康状况,当发现Active NameNode出现异常时会通过Zookeeper集群进行一次主备选举,完成Active和Standby状态的切换;

HealthMonitor
定时调用NameNode的HAServiceProtocol RPC接口(monitorHealth和getServiceStatus),监控NameNode的健康状态并向ZKFC反馈;

ActiveStandbyElector
接收ZKFC的选举请求,通过Zookeeper自动完成主备选举,选举完成后回调ZKFC的主备切换方法对NameNode进行Active和Standby状态的切换;

JouranlNode集群
共享存储系统,负责存储HDFS的元数据,Active NameNode(写入)和Standby NameNode(读取)通过共享存储系统实现元数据同步,在主备切换过程中,新的Active NameNode必须确保元数据同步完成才能对外提供服务;

   a: Standby Namenode拥有之前Active namenode的对外提供的所有服务信息,这就需要ZK维护一组守护进程journal node,,处于工作状态的Active  node需要将自己对外提供的所有服务信息写在一半以上的journode node的目录里(这些信息被记为editlog) 处于热备状态的 Standby namenode会随时监听journalnode的工作目录,只要有所更新改变,热备状态下的Standby  node会读取这些信息。并更新自己内部的名命空间,此时Standy namenode和Active  namenode都拥有对外提供的服务信息

   b: 所有的Datanode都要向主Namenode和热备Namenode进行心跳报告,使得2个namenode都了解datanode的健康状态以及数据存放在哪个Datanode上.

ZKFC的设计

1. FailoverController实现下述几个功能

  (a) 监控NN的健康状态

  (b) 向ZK定期发送心跳,使自己可以被选举。

  (c) 当自己被ZK选为主时,active FailoverController通过RPC调用使相应的NN转换为active。

2. 为什么要作为一个deamon进程从NameNode分离出来

  (1) 防止因为NN的GC失败导致心跳受影响。

  (2) FailoverController功能的代码应该和应用的分离,提高的容错性。

  (3) 使得主备选举成为可插拔式的插件。

3. FailoverController主要包括三个组件,

  (1) HealthMonitor 监控NameNode是否处于unavailable或unhealthy状态并向ZKFC反馈。当前通过RPC调用NameNode相应的方法完成。

  (2) ActiveStandbyElector 管理和监控自己在ZK中的状态。接收ZKFC的选举请求,通过Zookeeper自动完成主备选举,选举完    成后回调ZKFC的主备切换方法对NameNode进行Active和Standby状态的切换;

  (3) ZKFailoverController 它订阅HealthMonitor 和ActiveStandbyElector 的事件,并管理NameNode的状态。

【ZKFC工作原理】

ZKFailoverController在启动时同时会初始化HealthMonitor和ActiveStandbyElector服务,同时也会向HealthMonitor和ActiveStandbyElector注册相应的回调方法:

private int doRun(String[] args) throws Exception {
    try {
      initZK();   //初始化ActiveStandbyElector服务
    } catch (KeeperException ke) {
      LOG.error( ^……);
      return ERR_CODE_NO_ZK;
    }
    ......
    try {
      initRPC();
      initHM();    //初始化HealthMonitor服务
      startRPC();
      mainLoop();
    } catch (Exception e) {
      LOG.error("The failover controller encounters runtime error: ", e);
      throw e;
    } finally {
      rpcServer.stopAndJoin();
      
      elector.quitElection(true);
      healthMonitor.shutdown();
      healthMonitor.join();
    }
    return 0;
  }

一. 状态监控

HealthMonitor检测NameNode的两类状态,HealthMonitor.State和HealthMonitor.HAServiceStatus。

在程序上启动一个线程循环调用NameNode的HAServiceProtocol RPC接口的方法来检测NameNode 的状态,并将状态的变化通过回调的方式来通知ZKFailoverController。

 当HealthMonitor检测到NameNode的健康状态或角色状态发生变化时,ZKFC会根据状态的变化决定是否需要进行主备选举。

 HealthMonitor.State包括:
INITIALIZING:The health monitor is still starting up;
SERVICE_NOT_RESPONDING:The service is not responding to health check RPCs;
SERVICE_HEALTHY:The service is connected and healthy;
SERVICE_UNHEALTHY:The service is running but unhealthy;
HEALTH_MONITOR_FAILED:The health monitor itself failed unrecoverably and can no longer provide accurate information;


HealthMonitor.HAServiceStatus包括:
INITIALIZING:NameNode正在启动中;
ACTIVE:当前NameNode角色为Active;
STANDBY:当前NameNode角色为Standby;
STOPPING:NameNode已经停止运行;

二. 主备选举

HealthMonitor.State状态变化导致的不同后续措施:

/**
   * Check the current state of the service, and join the election,if it should be in the election.
   */
  private void recheckElectability() {
    // Maintain lock ordering of elector -> ZKFC
    synchronized (elector) {
      synchronized (this) {
        boolean healthy = lastHealthState == State.SERVICE_HEALTHY;
    
        long remainingDelay = delayJoiningUntilNanotime - System.nanoTime(); 
        if (remainingDelay > 0) {
          if (healthy) {
            LOG.info("Would have joined master election, but this node is " + "prohibited from doing so for " +
                TimeUnit.NANOSECONDS.toMillis(remainingDelay) + " more ms");
          }
          scheduleRecheck(remainingDelay);
          return;
        }
    
        switch (lastHealthState) {
        case SERVICE_HEALTHY:
          //调用ActiveStandbyElector的joinElection发起一次主备选举;
          elector.joinElection(targetToData(localTarget));
          if (quitElectionOnBadState) {
            quitElectionOnBadState = false;
          }
          break;
          
        case INITIALIZING:
          LOG.info("Ensuring that " + localTarget + " does not " +  "participate in active master election");
          //调用ActiveStandbyElector的quitElection(false)从ZK上删除已经建立的临时节点退出主备选举,不进行隔离;
          elector.quitElection(false);
          serviceState = HAServiceState.INITIALIZING;
          break;
    
        case SERVICE_UNHEALTHY:
        case SERVICE_NOT_RESPONDING:
          LOG.info("Quitting master election for " + localTarget + " and marking that fencing is necessary");
          //调用ActiveStandbyElector的quitElection(true)从ZK上删除已经建立的临时节点退出主备选举,并进行隔离;
          elector.quitElection(true);
          serviceState = HAServiceState.INITIALIZING;
          break;
          
        case HEALTH_MONITOR_FAILED:
          fatalError("Health monitor failed!");
          break;
          
        default:
          throw new IllegalArgumentException("Unhandled state:"  + lastHealthState);
        }
      }
    }
  }

HAServiceStatus在状态检测之中仅起辅助的作用,当HAServiceStatus发生变化时,ZKFC会判断NameNode返回的HAServiceStatus与ZKFC所期望的是否相同,如果不相同,ZKFC会调用ActiveStandbyElector的quitElection方法删除当前已经在ZK上建立的临时节点退出主备选举。

ZKFC通过ActiveStandbyElector的joinElection方法发起NameNode的主备选举,这个过程通过Zookeeper的写一致性和临时节点机制实现:
a. 当发起一次主备选举时,Zookeeper会尝试创建临时节点/hadoop-ha/${dfs.nameservices}/ActiveStandbyElectorLock,Zookeeper的写一致性保证最终只会有一个ActiveStandbyElector创建成功,创建成功的 ActiveStandbyElector对应的NameNode就会成为主NameNode,ActiveStandbyElector回调ZKFC的方法将对应的NameNode切换为Active状态。而创建失败的ActiveStandbyElector对应的NameNode成为备NameNode,ActiveStandbyElector回调ZKFC的方法将对应的NameNode切换为Standby状态;

b.不管是否选举成功,所有ActiveStandbyElector都会向Zookeeper注册一个Watcher来监听这个节点的状态变化事件;

c.如果Active NameNode对应的HealthMonitor检测到NameNode状态异常时,ZKFC会删除在Zookeeper上创建的临时节点ActiveStandbyElectorLock,这样处于Standby NameNode的ActiveStandbyElector注册的Watcher就会收到这个节点的 NodeDeleted事件。收到这个事件后,会马上再次创建ActiveStandbyElectorLock,如果创建成功,则Standby NameNode被选举为Active NameNode。

【防止脑裂】
在分布式系统中脑裂又称为双主现象,由于Zookeeper的“假死”,长时间的垃圾回收或其它原因都可能导致双Active NameNode现象,此时两个NameNode都可以对外提供服务,无法保证数据一致性。对于生产环境,这种情况的出现是毁灭性的,必须通过自带的隔离(Fencing)机制预防这种现象的出现。
ActiveStandbyElector为了实现fencing隔离机制,在成功创建hadoop-ha/dfs.nameservices/ActiveStandbyElectorLock临时节点后,会创建另外一个/hadoop−ha/{dfs.nameservices}/ActiveBreadCrumb持久节点,这个持久节点保存了Active NameNode的地址信息。当Active NameNode在正常的状态下断开Zookeeper Session (注意由于/hadoop-ha/dfs.nameservices/ActiveStandbyElectorLock是临时节点,也会随之删除),会一起删除持久节点/hadoop−ha/{dfs.nameservices}/ActiveBreadCrumb。但是如果ActiveStandbyElector在异常的状态下关闭Zookeeper Session,那么由于/hadoop-ha/${dfs.nameservices}/ActiveBreadCrumb是持久节点,会一直保留下来。当另一个NameNode(standy => active)选主成功之后,会注意到上一个Active NameNode遗留下来的ActiveBreadCrumb节点,从而会回调ZKFailoverController的方法对旧的Active NameNode进行fencing。
① 首先ZKFC会尝试调用旧Active NameNode的HAServiceProtocol RPC接口的transitionToStandby方法,看能否将状态切换为Standby;
② 如果调用transitionToStandby方法切换状态失败,那么就需要执行Hadoop自带的隔离措施,Hadoop目前主要提供两种隔离措施:
sshfence:SSH to the Active NameNode and kill the process;
shellfence:run an arbitrary shell command to fence the Active NameNode;

只有在成功地执行完成fencing之后,选主成功的ActiveStandbyElector才会回调ZKFC的becomeActive方法将对应的NameNode切换为Active,开始对外提供服务。

HDFS读写数据的过程

读:

1、跟namenode通信查询元数据,找到文件块所在的datanode服务器

2、挑选一台datanode(就近原则,然后随机)服务器,请求建立socket流

3、datanode开始发送数据(从磁盘里面读取数据放入流,以packet为单位来做校验)

4、客户端以packet为单位接收,现在本地缓存,然后写入目标文件

写:

1、根namenode通信请求上传文件,namenode检查目标文件是否已存在,父目录是否存在

2、namenode返回是否可以上传

3、client请求第一个 block该传输到哪些datanode服务器上

4、namenode返回3个datanode服务器ABC

5、client请求3台dn中的一台A上传数据(本质上是一个RPC调用,建立pipeline),A收到请求会继续调用B,然后B调用C,将真个pipeline建立完成,逐级返回客户端

6、client开始往A上传第一个block(先从磁盘读取数据放到一个本地内存缓存),以packet为单位,A收到一个packet就会传给B,B传给C;A每传一个packet会放入一个应答队列等待应答

7、当一个block传输完成之后,client再次请求namenode上传第二个block的服务器。

三、小文件合并优化

3.4.1 Hive优化之小文件问题及其解决方案:

1、小文件是如何产生的:

  1. 数据源本身就包含大量的小文件。输入表本身就有很多小文件
  2. reduce数量越多,小文件也越多(reduce的个数和输出文件是对应的);插入的时候没有限制reduce个数, 资源也没有限制, 导致产生很多个ReduceTasks, 进而产生多个小文件.

2、小文件问题的影响:

HDFS中每个文件的元数据信息,包括位置大小分块信息等,都保存在NN内存中,在小文件数较多的情况下,会造成占用大量内存空间,导致NN性能下降;

 在HDFS中,每个小文件对象约占150byte,如果小文件过多会占用大量内存。这样NameNode内存容量严重制约了集群的扩展。

1个文件块,占用namenode多大内存150字节

1亿个小文件*150字节

1 个文件块 * 150字节

128G能存储多少文件块?   128 * 1024*1024*1024byte/150字节 = 9亿文件块

现在大数据平台文件总数超过30亿,单个NS文件数超过4亿的时候,读写性能会急剧下降,影响到所有读写该NS的任务性能;容易在文件存储端造成瓶颈,给HDFS带来压力,影响处理效率。

从Hive的角度看,小文件会开很多map,一个map开一个JVM去执行,所以这些任务的初始化,启动,执行会浪费大量的资源,严重影响性能。

在读取小文件多的目录时,MR会产生更多map数,造成GC频繁,浪费集群资源;
如果队列限制最大map数是20000,任务读取的分区文件数超过20000,需要加参数进行读取时小文件合并,或否则任务map数超过限制数直接被查杀。

3、小文件问题的解决方案: 

对于已有的小文件,我们可以通过以下几种方案解决:

采用har归档方式,将小文件归档,使用hadoop archive命令把小文件进行归档;
重建表,建表时减少reduce数量;

从小文件产生的途径就可以从源头上控制小文件数量,方法如下:

使用Sequencefile作为表存储格式,不要用textfile,在一定程度上可以减少小文件;

减少reduce的数量(可以使用参数进行控制);
少用动态分区,用时记得按distribute by分区;

(2)采用CombineTextInputFormat

//在驱动Driver类中添加代码如下:
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class);

//虚拟存储切片最大值设置20m
CombineTextInputFormat.setMaxInputSplitSize(job, 20971520); 

(3)有小文件场景开启JVM重用;如果没有小文件,不要开启JVM重用,因为会一直占用使用到的task卡槽,直到任务完成才释放。

JVM重用可以使得JVM实例在同一个job中重新使用N次,N的值可以在Hadoop的mapred-site.xml文件中进行配置。通常在10-20之间

<property>
    <name>mapreduce.job.jvm.numtasks</name>
    <value>10</value>
    <description>How many tasks to run per jvm,if set to -1 ,there is  no limit</description>
</property>    

通过参数进行调节,设置map/reduce端的相关参数,如下:   
1. 在Map输入的时候, 把小文件合并.
 
-- 每个Map最大输入大小,决定合并后的文件数
set mapred.max.split.size=256000000;
-- 一个节点上split的至少的大小 ,决定了多个data node上的文件是否需要合并
set mapred.min.split.size.per.node=100000000;
-- 一个交换机下split的至少的大小,决定了多个交换机上的文件是否需要合并
set mapred.min.split.size.per.rack=100000000;
-- 执行Map前进行小文件合并
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat; 
 
2. 在Reduce输出的时候, 把小文件合并.
 
-- 在map-only job后合并文件,默认true
set hive.merge.mapfiles = true;
-- 在map-reduce job后合并文件,默认false
set hive.merge.mapredfiles = true;
-- 合并后每个文件的大小,默认256000000   256*1000*1000  
set hive.merge.size.per.task = 256000000;
-- 平均文件大小,是决定是否执行合并操作的阈值,默认16000000//当输出文件的平均大小小于该值时,启动一个独立的MapReduce任务进行文件merge。  
set hive.merge.smallfiles.avgsize = 100000000; 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四月天03

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值