-
-
- 原理解析
- HDFS
- 原理解析
-
Hadoop1.0是按64MB切,BlockSize=64MB; Hadoop2.0 BlockSize=128MB
优缺点:
- 超大文件支持,几百兆到几百PB;
- 流式数据访问,建立在一次写入多次读取的模式是最高效的思想上;
- 无需运行在昂贵的商业硬件,普通PC机即可(经济性的考虑)
- 小文件不适合,每个文件的索引目录及块大概占用150字节(因为1个文件三副本,在namenode上就有三条记录,小文件需要优化,合并成大文件)
- 多用户写入不适合,只支持一个用户写入,且在尾部;
- 不擅长实时计算(实时采用flink+kafka)
- 不擅长流式计算,MapReduce输入数据集是静态的;
- 不擅长DAG(有向图)计算,多个MR应用程序存在依赖关系,前一个MR的输出,作为后一个MR的输入;理论上也可以,但是过多的磁盘写入导致性能非常低下;(所以spark产生了,就是为了替代MR)
NameNode+DataNode架构模式(主从架构,也叫Master+Slave架构)
-
-
-
-
- NameNode
-
-
-
负责元数据管理,维护整个文件系统树及树内所有文件和索引目录;本地有fsimage(元数据镜像)和edit log(操作日志),文件系统树存在内存中,每次重启都从DataNode节点收集; 此外为了应对服务宕机而引起的元数据丢失,也要持久化到本地文件里;
- fsimage 文件,记录元数据信息的文件;(相当于某时刻的全量,checkpoints)
- edits文件,记录元数据信息改动的文件。只要元数据发生变化,这个edits文件就会有对应记录。(类似binlog,相当于增量)
- fsimage和edits log文件会定期做合并,这个周期默认是3600s。fsimage根据edits里改动记录进行元数据更新。(定期合并)
初始化元数据:hadoop namenode -format 这个指令实际的作用是创建了初始的fsimage文件和edits文件。
-
-
-
-
-
- NameNode高可用方案
-
-
-
-
方案如下:
此架构主要解决了两个问题,一是 NameNode 元数据同步问题,二是主备 NameNode 切换问题,由图可知,解决主、备 NameNode 元数据同步是通过 JournalNode 集群来完成的,而解决主、备 NameNode 切换可通过 ZooKeeper 来完成。
ZooKeeper 是一个独立的集群,在两个 NameNode 上还需要启动一个 failoverController(zkfc)进程,该进程作为 ZooKeeper 集群的客户端存在,通过 zkfc 可以实现与 ZooKeeper 集群的交互和状态监测。(ZKFC部署在namenode服务器上)
在hadoop2.0引入了HA机制。hadoop2.0的HA机制官方介绍了有2种方式,一种是NFS(Network File System,共享文件实现两个NN之间的数据同步)方式,另外一种是QJM(Quorum Journal Manager)方式。
从 Hadoop2.x 版本开始,HDFS 开始支持多个 NameNode,这样不但可以实现 HDFS 的高可用,而且还可以横行扩容 HDFS 的存储规模。在实际的企业应用中,使用最多的是双 NameNode 架构,也就是一个 NameNode 处于 Active(活跃) 状态,另一个 NameNode 处于 Standby(备用)状态,通过这种机制,实现 NameNode 的双机热备高可用功能。
在高可用的 NameNode 体系结构中,只有 Active 状态的 NameNode 是正常工作的,Standby 状态的 NameNode 处于随时待命状态,它时刻去同步 Active 状态 NameNode 的元数据。一旦 Active 状态的 NameNode 不能工作,可以通过手工或者自动切换方式将 Standby 状态的 NameNode 转变为 Active 状态,保持 NameNode 持续工作。Active NameNode和Standby NameNode之间通过NFS或者JN(JournalNode,QJM方式)来同步数据。这就是两个高可靠的 NameNode 的实现机制。
双 NameNode 架构中元数据一致性如何保证?
从 Hadoop2.x 版本后,HDFS 采用了一种全新的元数据共享机制,即通过 Quorum Journal Node(JournalNode)集群或者 network File System(NFS)进行数据共享。NFS 是操作系统层面的,而 JournalNode 是 Hadoop 层面的,成熟可靠、使用简单方便,所以,这里我们采用 JournalNode 集群进行元数据共享。(瓜子也是采用JournalNode)
由图可知,JournalNode 集群可以几乎实时的去 NameNode 上拉取元数据,然后保存元数据到 JournalNode 集群;同时,处于 standby 状态的 NameNode 也会实时的去 JournalNode 集群上同步 JNS 数据,通过这种方式,就实现了两个 NameNode 之间的数据同步。(注: HA架构下,没有SecondaryNameNode,而是两个NameNode,通过JournalNode来合并fsimage)
具体原理如下:
- Active NameNode会把最近的操作记录写到本地的一个edits文件中(edits file),并传输到NFS或者JournalNode中。
- Standby NameNode定期的检查,从NFS或者JN把最近的edit文件读过来,然后把edits文件和fsimage文件合并成一个新的fsimage,合并完成之后会通知active namenode获取这个新fsimage。(这个思想和hudi的basefile和detalfile一样)
- Active NameNode获得这个新的fsimage文件之后,替换原来旧的fsimage文件。这样,保持了Active NameNode和Standby NameNode的数据的实时同步,Standby NameNode可以随时切换成Active NameNode(譬如Active NameNode挂了)。而且还有一个原来hadoop1.0的secondarynamenode,checkpointnode,buckcupnode的功能:合并edits文件和fsimage文件,使fsimage文件一直保持更新。所以启动了hadoop2.0的HA机制之后,secondarynamenode,checkpointnode,buckcupnode这些都不需要了。
JournalNode 集群内部是如何实现的呢?
两个 NameNode 为了数据同步,会通过一组称作 JournalNodes 的独立进程进行相互通信。当 Active 状态的 NameNode 元数据有任何修改时,会告知大部分的 JournalNodes 进程。同时,Standby 状态的 NameNode 也会读取 JNs 中的变更信息,并且一直监控 EditLog (事务日志)的变化,并把变化应用于自己的命名空间。Standby 可以确保在集群出错时,元数据状态已经完全同步了。
JournalNode 集群的内部运行架构图:
由图可知,JN1、JN2、JN3 等是 JournalNode 集群的节点,QJM(Qurom Journal Manager)的基本原理是用 2N+1 台 JournalNode 存储 EditLog,每次写数据操作有 N/2+1 个节点返回成功,那么本次写操作才算成功,保证数据高可用。当然这个算法所能容忍的是最多有 N 台机器挂掉,如果多于 N 台挂掉,算法就会失效。
NameNode 主、备之间的切换可以通过手动或者自动方式来实现,作为线上大数据环境,都是通过自动方式来实现切换的,为保证自动切换,NameNode 使用 ZooKeeper 集群进行仲裁选举。基本的思路是 HDFS 集群中的两个 NameNode 都在 ZooKeeper 中注册,当 Active 状态的 NameNode 出故障时,ZooKeeper 能马上检测到这种情况,它会自动把 Standby 状态切换为 Active 状态。(即利用ZK实现master选举)
在Hadoop HA中,主要由以下几个组件构成:
- MasterHADaemon:与NameNode Master服务运行在同一个进程中,可接收外部RPC命令,以控制Master服务的启动和停止;(对NameNode进行运行控制)
- SharedStorage:共享存储系统,Active NameNode将信息写入共享存储系统,而Standby NameNode则读取该信息以保持与Active NameNode的同步,从而减少切换时间。常用的共享存储系统有zookeeper(被YARN HA采用)、NFS(被HDFS HA采用)、HDFS(被MapReduce HA采用)。(实际项目中采用了QJM)
- ZKFailoverController:基于Zookeeper实现的切换控制器,主要由两个核心组件构成:ActiveStandbyElector和HealthMonitor,其中,ActiveStandbyElector负责与zookeeper集群交互,通过尝试获取全局锁,以判断所管理的master进入active还是standby状态;HealthMonitor负责监控各个活动master的状态,以根据它们状态进行状态切换。(本质就是对Master进行错误监控,并在出现错误时切换Master)
- 健康检查,ZKFC 定期对本地的 NN 发起 health-check 的命令,如果 NN 正确返回,那么 NN 被认为是 OK 的,否则被认为是失效节点;
- session管理,当本地 NN 是健康的时候,ZKFC 将会在 ZK 中持有一个 session,如果本地 NN 又正好是 Active,那么 ZKFC 将持有一个短暂的节点(临时节点temp)作为锁,一旦本地 NN 失效了,那么这个节点就会被自动删除;
- 基础选举,如果本地 NN 是健康的,并且 ZKFC 发现没有其他 NN 持有这个独占锁,那么它将试图去获取该锁,一旦成功,那么它就开始执行 Failover,然后变成 Active 状态的 NN 节点;Failover 的过程分两步,首先对之前的 NameNode 执行隔离(如果需要的话),然后将本地 NameNode 切换到 Active 状态。
- Zookeeper集群:核心功能通过维护一把全局锁控制整个集群有且仅有一个active master(选举)。当然,如果ShardStorge采用了zookeeper,则还会记录一些其他状态和运行时信息。
实现高可用性时,可能会出现的问题:
- 脑裂(brain-split):脑裂是指在主备切换时,由于切换不彻底或其他原因,导致客户端和Slave误以为出现两个active master,最终使得整个集群处于混乱状态(所有的HA都可能出现脑裂)。解决脑裂问题,通常采用隔离(Fencing)机制,包括三个方面:
- 共享存储fencing:确保只有一个Master往共享存储中写数据。
- 客户端fencing:确保只有一个Master可以响应客户端的请求。
- Slave fencing:确保只有一个Master可以向Slave下发命令。
- Hadoop公共库中对外提供了两种fenching实现,分别是sshfence和shellfence(缺省实现),其中sshfence是指通过ssh登陆目标Master节点上,使用命令fuser将进程杀死(通过tcp端口号定位进程pid,该方法比jps命令更准确),shellfence是指执行一个用户事先定义的shell命令(脚本)完成隔离。
- 切换对外透明:为了保证整个切换是对外透明的,Hadoop应保证所有客户端和Slave能自动重定向到新的active master上,这通常是通过若干次尝试连接旧master不成功后,再重新尝试链接新master完成的,整个过程有一定延迟。在新版本的Hadoop RPC中,用户可自行设置RPC客户端尝试机制、尝试次数和尝试超时时间等参数。
HA解决的难度取决于Master自身记录信息的多少和信息可重构性,如果记录的信息非常庞大且不可动态重构,比如NameNode,则需要一个可靠性与性能均很高的共享存储系统,而如果Master保存有很多信息,但绝大多数可通过Slave动态重构,则HA解决方法则容易得多,典型代表是MapReduce和YARN。从另外一个角度看,由于计算框架对信息丢失不是非常敏感,比如一个已经完成的任务信息丢失,只需重算即可获取,使得计算框架的HA设计难度远低于存储类系统。(计算型任务的HA很简单,直接重新跑就可以,存储型设计到信息的迁移回复,就比较麻烦,HDFS如此,HBase的RegionServer也是如此)
集群部署规划示例:
部署前主机、软件功能、磁盘存储规划双 NameNode 的 Hadoop 集群环境涉及到的角色有 Namenode、datanode、resourcemanager(yarn)、nodemanager、historyserver、ZooKeeper、JournalNode 和 zkfc,这些角色可以单独运行在一台服务器上,也可以将某些角色合并在一起运行在一台机器上。
一般情况下,NameNode 服务要独立部署,这样两个 NameNode 就需要两台服务器,而 datanode 和 nodemanager 服务建议部署在一台服务器上,resourcemanager (yarn)服务跟 NameNode 类似,也建议独立部署在一台服务器上,而 historyserver 一般和 resourcemanager 服务放在一起。ZooKeeper 和 JournalNode 服务是基于集群架构的,因此至少需要 3 个集群节点,即需要 3 台服务器,不过 ZooKeeper 和 JournalNode 集群可以放在一起,共享 3 台服务器资源。最后,zkfc 是对 NameNode 进行资源仲裁的,所以它必须和 NameNode 服务运行在一起,这样 zkfc 就不需要占用独立的服务器了。
本着节约成本、优化资源、合理配置的原则,下面的部署通过 5 台独立的服务器来实现,操作系统均采用 Centos7.7 版本,每个服务器主机名、IP 地址以及功能角色如下表所示:
由表可知,namenodemaster 和 yarnserver 是作为 NameNode 的主、备两个节点,同时 yarnserver 还充当了 ResourceManager 和 JobHistoryServer 的角色。如果服务器资源充足,可以将 ResourceManager 和 JobHistoryServer 服务放在一台独立的机器上。
此外,slave001、slave002 和 slave003 三台主机上部署了 ZooKeeper 集群、JournalNode 集群,还充当了 DataNode 和 NodeManager 的角色。
在软件部署上,每个软件采用的版本如下表所示:
最后,还需要考虑磁盘存储规划,HDFS 文件系统的数据块都存储在本地的每个 datanode 节点上。因此,每个 datanode 节点要有大容量的磁盘(120T每台),磁盘类型可以是普通的机械硬盘,有条件的话 SSD 硬盘最好(datanode机械硬盘即可,olap才会用ssd,比如kudu+impala,clickhouse),单块硬盘推荐 4T 或者 8T,这些硬盘无需做 RAID,单盘使用即可,因为 HDFS 本身已经有了副本容错机制。(hdfs本身就配置了3副本)
NameNode 节点所在的主机要存储 HDFS 的元数据信息,这些元数据控制着整个 HDFS 集群中的存储与读写,一旦丢失,那么 HDFS 将丢失数据甚至无法使用,所以保证 NameNode 节点 HDFS 元数据安全性至关重要。在 NameNode 节点建议配置 4 块大小一样的磁盘,每两块做一个 raid1,共做两组 raid1,然后将元数据镜像存储在这两个 raid1 上。(dcm:虽然datanode是3副本,但是namenode上的数据,还是需要做raid1,就是完全复制的)
在gz实际项目中部分机器部署情况:
在实际项目中,我们还采用RBF解决了NameNode元数据太多的问题,具体参见相关章节。
-
-
-
-
- DataNode
-
-
-
负责块数据的管理,存储文件的节点,定时向NameNode同步存储块的列表;为了防止DataNode挂掉造成的数据丢失,对于文件块要有备份,一个文件块有三个副本(共3份)。(元数据管理在分布式构架中,是典型设计模式,分布式必有元数据管理,比如kafka,clickhouse,rabbitmq等等)
磁盘block一般为512Byte;文件系统block一般为n*512Byte,一般几K;HDFS的block默认为64M,hadoop2新版本为128M;但若果一个1M的文件实际上只占用1M,并不会占用128M。
文件块(block):最基本的存储单位。对于文件内容而言,一个文件的长度大小是size,那么从文件的0偏移开始,按照固定的大小,顺序对文件进行划分并编号,划分好的每一个块称一个Block。HDFS默认Block大小是128MB,以一个256MB文件,共有256/128=2个Block。几个关键参数如下:
dfs.blocksize(我看了一下hadoop3的配置,还是128M)
不同于普通文件系统的是,HDFS中,如果一个文件小于一个数据块的大小,并不占用整个数据块存储空间。
Replication:多副本。默认是三个。
HDFS的Block为什么这么大?(总时长= 定位时长 + 传输时长,找到平衡)
是为了最小化查找(seek)时间,控制定位文件与传输文件所用的时间比例。假设定位到Block所需的时间为10ms,磁盘传输速度为100M/s。如果要将定位到Block所用时间占传输时间的比例控制1%,则Block大小需要约100M。(本质是索引,稀疏索引,文件分块存储,在索引和数据传输中找到平衡点)
但是如果Block设置过大,在MapReduce任务中,Map或者Reduce任务的个数如果小于集群机器数量,会使得作业运行效率很低。(MAP任务数和数据块相关)
Block抽象的好处(逻辑抽象,物理介质可以是任意的;且可以分布在任意地方,不受单机限制)
- Block的拆分使得单个文件大小可以大于整个磁盘的容量,构成文件的Block可以分布在整个集群, 理论上,单个文件可以占据集群中所有机器的磁盘。
- Block的抽象也简化了存储系统,对于Block,无需关注其权限,所有者等内容(这些内容都在文件级别上进行控制)。
- Block作为容错和高可用机制中的副本单元,即以Block为单位进行复制。
注意:block虽然128M,但是实际占用空间为文件真实大小
-
-
-
-
- HDFS读写流程图
-
-
-
HDFS读过程
1.初始化FileSystem,然后客户端(client)用FileSystem的open()函数打开文件
2.FileSystem用RPC调用元数据节点,得到文件的数据块信息,对于每一个数据块,元数据节点返回保存数据块的数据节点的地址。
3.FileSystem返回FSDataInputStream给客户端,用来读取数据,客户端调用stream的read()函数开始读取数据。
4.DFSInputStream连接保存此文件第一个数据块的最近的数据节点,data从数据节点读到客户端(client)
5.当此数据块读取完毕时,DFSInputStream关闭和此数据节点的连接,然后连接此文件下一个数据块的最近的数据节点。
6.当客户端读取完毕数据的时候,调用FSDataInputStream的close函数。
7.在读取数据的过程中,如果客户端在与数据节点通信出现错误,则尝试连接包含此数据块的下一个数据节点。
8.失败的数据节点将被记录,以后不再连接。
(2)写过程
1.初始化FileSystem,客户端调用create()来创建文件
2.FileSystem用RPC调用元数据节点,在文件系统的命名空间中创建一个新的文件,元数据节点首先确定文件原来不存在,并且客户端有创建文件的权限,然后创建新文件。
3.FileSystem返回DFSOutputStream,客户端用于写数据,客户端开始写入数据。
4.DFSOutputStream将数据分成块,写入data queue。data queue由Data Streamer读取,并通知元数据节点分配数据节点,用来存储数据块(每块默认复制3块)。分配的数据节点放在一个pipeline里。Data Streamer将数据块写入pipeline中的第一个数据节点。第一个数据节点将数据块发送给第二个数据节点。第二个数据节点将数据发送给第三个数据节点。(写入到第一个节点,节点自己复制3份)
5.DFSOutputStream为发出去的数据块保存了ack queue,等待pipeline中的数据节点告知数据已经写入成功。
6.当客户端结束写入数据,则调用stream的close函数。此操作将所有的数据块写入pipeline中的数据节点,并等待ack queue返回成功(ack返回给客户端)。最后通知元数据节点写入完毕(通知NN)。
7.如果数据节点在写入的过程中失败,关闭pipeline,将ack queue中的数据块放入data queue的开始,当前的数据块在已经写入的数据节点中被元数据节点赋予新的标示,则错误节点重启后能够察觉其数据块是过时的,会被删除。失败的数据节点从pipeline中移除,另外的数据块则写入pipeline中的另外两个数据节点。元数据节点则被通知此数据块是复制块数不足,将来会再创建第三份备份。(逻辑就是:失败了,删除节点上的数据块,其他两个节点继续写入,通知元数据继续创建第三个备份)
补充:
- 发起一个写数据请求,并指定上传文件的路径,然后去找namenode。namenode首先会判断路径合法性,然后会判断此客户端是否有写权限。然后都满足,namenode会给客户端返回一个输出流。此外,namenode会为文件分配块存储信息。注意,namenode也是分配块的存储信息,但不做物理切块工作。
- 客户端拿到输出流以及块存储信息之后,就开始向datanode写数据。因为一个块数据,有三个副本,所以图里有三个datanode。packet可以简单理解为就是一块数据。pipeLine:[bl1,datanode01-datanode03-datanode-07],数据块的发送,先发给第一台datanode,然后再由第一台datanode发往第二台datanode,……。实际这里,用到了pipeLine 数据流管道的思想。
- 通过ack确认机制,向上游节点发送确认,这么做的目的是确保块数据复制的完整性。(所有副本写完,才会发ACK)
- 通过最上游节点,向客户端发送ack,如果块数据没有发送完,就继续发送下一块。如果所有块数据都已发完,就可以关流了。
- 所有块数据都写完后,关流。
- 管道流的目的是充分利用每台机器的带宽,避免网络瓶颈和高延时的连接,最小化推送所有数据的延时。 此外,利用通信的通信双工,能够提高传输效率。packet 是一个64kb大小的数据包
-
-
-
-
- 数据复制
-
-
-
由于 Hadoop 被设计运行在廉价的机器上,这意味着硬件是不可靠的,为了保证容错性,HDFS 提供了数据复制机制。HDFS 将每一个文件存储为一系列块,每个块由多个副本来保证容错,块的大小和复制因子可以自行配置(默认情况下,块大小是 128M,默认复制因子是 3)。
数据复制的实现原理
大型的 HDFS 实例在通常分布在多个机架的多台服务器上,不同机架上的两台服务器之间通过交换机进行通讯(故意存放到多个机架也是为了容灾)。在大多数情况下,同一机架中的服务器间的网络带宽大于不同机架中的服务器之间的带宽。因此 HDFS 采用机架感知副本放置策略,对于常见情况,当复制因子为 3 时,HDFS 的放置策略是:
在写入程序位于 datanode 上时,就优先将写入文件的一个副本放置在该 datanode 上,否则放在随机 datanode 上。之后在另一个远程机架上的任意一个节点上放置另一个副本,并在该机架上的另一个节点上放置最后一个副本。此策略可以减少机架间的写入流量,从而提高写入性能。
如果复制因子大于 3,则随机确定第 4 个和之后副本的放置位置,同时保持每个机架的副本数量低于上限,上限值通常为 (复制系数 - 1)/机架数量 + 2,需要注意的是不允许同一个 dataNode 上具有同一个块的多个副本。
-
-
-
-
- 副本的选择
-
-
-
为了最大限度地减少带宽消耗和读取延迟,HDFS 在执行读取请求时,优先读取距离读取器最近的副本。如果在与读取器节点相同的机架上存在副本,则优先选择该副本。如果 HDFS 群集跨越多个数据中心,则优先选择本地数据中心上的副本。
-
-
-
-
- 架构的稳定性
-
-
-
心跳机制和重新复制
每个 DataNode 定期向 NameNode 发送心跳消息,如果超过指定时间没有收到心跳消息,则将 DataNode 标记为死亡。NameNode 不会将任何新的 IO 请求转发给标记为死亡的 DataNode,也不会再使用这些 DataNode 上的数据。 由于数据不再可用,可能会导致某些块的复制因子小于其指定值,NameNode 会跟踪这些块,并在必要的时候进行重新复制。
数据的完整性
由于存储设备故障等原因,存储在 DataNode 上的数据块也会发生损坏。为了避免读取到已经损坏的数据而导致错误,HDFS 提供了数据完整性校验机制来保证数据的完整性,具体操作如下:
当客户端创建 HDFS 文件时,它会计算文件的每个块的 校验和,并将 校验和 存储在同一 HDFS 命名空间下的单独的隐藏文件中。当客户端检索文件内容时,它会验证从每个 DataNode 接收的数据是否与存储在关联校验和文件中的 校验和 匹配。如果匹配失败,则证明数据已经损坏,此时客户端会选择从其他 DataNode 获取该块的其他可用副本。
元数据的磁盘故障
FsImage 和 EditLog 是 HDFS 的核心数据,这些数据的意外丢失可能会导致整个 HDFS 服务不可用。为了避免这个问题,可以配置 NameNode 使其支持 FsImage 和 EditLog 多副本同步,这样 FsImage 或 EditLog 的任何改变都会引起每个副本 FsImage 和 EditLog 的同步更新。(NameNode HA)
支持快照
快照支持在特定时刻存储数据副本,在数据意外损坏时,可以通过回滚操作恢复到健康的数据状态。