引言
什么是大数据?多大量算大?说白了:当数据规模大到在获取、存储、管理、分析方面大大超出了单节点服务器能力范围。大数据具有海量的数据规模、快速的数据流转、多样的数据类型和价值密度低四大特征。大数据的初心是:用多台廉价的服务器并行处理,来替代昂贵的单台高性能服务器,以达到节约成本的目的。
很多外行眼里:大数据,自带AI处理能力,能分析,能预测。实则不然,大数据,只是拥有了处理海量数据的能力,那数据从哪来?怎么治理规整?怎么用?基于大规模数据,咱是要落地数仓?还是用它来训练ai模型?这些都在于使用者。它就好比一口大锅。有了锅,不代表饭做好了。蒸米饭,还是蒸馒头,取决于厨子。
大数据知识广,且深。因此咱们需要把它梳理一下:
协调工作 | zookeeper | ||
作业调度 | yarn | ||
运算引擎 | spark | hive | |
mapreduce | |||
数据库/湖 | 基于hdfs文件的数据库 hbase | 数据湖hudi | 数据湖iceberg |
底层存储 | HDFS 离线 | ||
kafka 实时,临时存储(默认存7天数据) | |||
实时运算引擎 | flink,sparkstreaming | ||
基础语言 | scala | python | |
java |
HDFS
下面,咱们从hdfs说起:HDFS(Hadoop Distributed File System )是Hadoop分布式文件系统。
特点
主要特点:两高一大
- 支持超大文件
- 高可用
- 高吞吐量
- 一次写入多次读取
HDFS不适合的场景:低延迟数据访问;大量的小文件;多用户写入文件、修改文件。
为什么不支持随机写呢?
1:hadoop设计的初衷是大规模数据的计算和olap分析, 应用场景区别与数据库,所以在HDFS设计时候就侧重在一次写入多次读取
2: 假设现在我们在HDFS中加入了随机写.那么为了正确性我们需要保障的东西有哪些?
- 鉴于hadoop设计之初是想在廉价硬件上执行大规模的数据计算,HDFS是设计的必须支持CAP理论之分区容错性(partition tolerance)来抵消不可靠硬件随时会宕机的风险,那么在修改的时候,我们也是需要去namenode查询重新得到需要改写的文件的HDFS数据块的其他备份来修改以保证数据备份的一致性,中间过程增加了文件io和不同数据块备份间的网络io开销,并且在修改的过程中,还需要考虑修改完一个block之后,然后在修改备份block时候的各种异常回退的操作.
- 如果上一条的操作效率跟HDFS写入类似的话,那么在修改数据块数据时,如何保证不被其他试图修改同一个块的用户修改而被迫加锁的性能的损耗,将是一个新增的性能消耗点.这没有删除一个数据,然后重新写入的效率高
- 如果现在数据a在修改过程中(三个备份分别在datanode1,datanode2,datanode3),datanode1上的数据a已经修改成功,datanode2和datanode3还在同步的过程中,如果此时一个读请求由于网络路径比较短选择在datanode3上读取数据a, 此时你是要加锁不让读呢还是提供一个脏读数据呢?如果是第一种情况加速不让读提供强一致性分布式事务,不仅大大降低HDFS性能而且违背hadoop设计的初衷. 如果提供一个脏读数据,为什么不直接删除数据a然后重新插入修改后数据a,让用户有个正确的读取操作呢?
综上,首先hadoop初衷不是为了修改数据而是侧重查询,其次就是确实是可以实现HDFS修改,但是同时也要付出系统复杂性和性能的代价.这是一个trade-off的选择
hdfs构成
NameNode保存着HDFS的名字空间,对于任何对文件系统元数据产生修改的操作;主要负责三个功能,分别是1)管理元数据 2)维护目录树 3)响应客户请求
DataNode将HDFS数据以文件的形式存储在本地文件系统中,它并不知道有关HDFS文件的信息。
block块:数据块是HDFS的文件存储处理单元,默认128M数据。
为什么HDFS中块(block)不能设置太大,也不能设置太小?
1. 如果块设置过大,
一方面,从磁盘传输数据的时间会明显大于寻址时间,导致程序在处理这块数据时,变得非常慢;
另一方面,mapreduce中的map任务通常一次只处理一个块中的数据,如果块过大运行速度也会很慢。
2. 如果块设置过小,
一方面存放大量小文件会占用NameNode中大量内存来存储元数据,而NameNode的内存是有限的,不可取;
另一方面文件块过小,寻址时间增大,导致程序一直在找block的开始位置。
因而,块适当设置大一些,减少寻址时间,那么传输一个由多个块组成的文件的时间主要取决于磁盘的传输速率。
HDFS中块(block)的大小为什么设置为128M?
1. HDFS中平均寻址时间大概为10ms;
2. 经过前人的大量测试发现,寻址时间为传输时间的1%时,为最佳状态;
所以最佳传输时间为10ms/0.01=1000ms=1s
3. 目前磁盘的传输速率普遍为100MB/s;
计算出最佳block大小:100MB/s x 1s = 100MB
所以我们设定block大小为128MB。
ps:实际在工业生产中,磁盘传输速率为200MB/s时,一般设定block大小为256MB
磁盘传输速率为400MB/s时,一般设定block大小为512MB
读写删除流程
写流程
- client 客户端发送上传请求,通过 RPC 与 namenode 建立通信,namenode检查该用户是否有上传权限,以及上传的文件是否在hdfs对应的目录下重名,如果这两者有任意一个不满足,则直接报错,如果两者都满足,则返回给客户端一个可以上传的信息。
- client根据文件的大小进行切分,默认128M一块,切分完成之后给namenode发送请求第一个block块上传到哪些服务器上。
- namenode收到请求之后,根据网络拓扑和机架感知以及副本机制进行文件分配,返回可用的DataNode的地址。
- 客户端收到地址之后与服务器地址列表中的一个节点如A进行通信,本质上就是RPC 调用,建立pipeline,A收到请求后会继续调用B,B再调用C,将整个pipeline建立完成,逐级返回client。
- client开始向A上发送第一个block(先从磁盘读取数据然后放到本地内存缓存),以packet(数据包,64kb)为单位,A收到一个packet就会发送给B,然后B发送给C,A每传完一个packet就会放入一个应答队列等待应答。
- 数据被分割成一个个的packet数据包在pipeline上依次传输,在pipeline反向传输中,逐个发送ack(命令正确应答),最终由pipeline中第一个DataNode节点A将pipelineack 发送给Client。
- 当一个block传输完成之后, Client再次请求NameNode上传第二个block,namenode 重新选择三台DataNode给client。
读流程
- client向namenode发送RPC请求,请求文件block的位置。
- namenode收到请求之后会检查用户权限以及是否有这个文件,如果都符合,则会视情况返回部分或全部的 block 列表,对于每个block,NameNode都会返回含有该block副本的DataNode 地址;这些返回的DN地址,会按照集群拓扑结构得出DataNode与客户端的距离,然后进行排序,排序两个规则:网络拓扑结构中距离Client近的排靠前;心跳机制中超时汇报的DN状态为STALE,这样的排靠后。
- Client选取排序靠前的DataNode来读取block,如果客户端本身就是DataNode,那么将从本地直接获取数据
- 底层上本质是建立Socket Stream,重复的调用父类DataInputStream 的read方法,直到这个块上的数据读取完毕
- 当读完列表的block后,若文件读取还没有结束,客户端会继续向NameNode 获取下一批的block列表。
- 读取完一个 block 都会进行checksum验证,如果读取DataNode时出现错误,客户端会通知NameNode,然后再从下一个拥有该block副本的DataNode继续读。
- read方法是并行的读取block信息,不是一块一块的读取
- 最终读取来所有的block会合并成一个完整的最终文件。
删除流程
- 客户端向namenode发起RPC调用,请求删除文件。namenode检查合法性。
- namenode查询文件相关元信息,向存储文件数据块的datanode发出删除请求。
- datanode删除相关数据块。返回结果。
- namenode返回结果给客户端。
注:当用户或应用程序删除某个文件时,这个文件并没有立刻从HDFS中删除。实际上,HDFS会将这个文件重命名转移到/trash目录。文件在/trash中保存的时间是可配置的,当超过这个时间时,Namenode就会将该文件从名字空间中删除。删除文件会使得该文件相关的数据块被释放。
如果用户想恢复被删除的文件,他可以浏览/trash目录找回该文件。/trash目录仅仅保存被删除文件的最后副本。目前的默认策略是删除/trash中保留时间超过6小时的文件。将来,这个策略可以通过一个被良好定义的接口配置。当一个文件的副本系数被减小后,Namenode会选择过剩的副本删除。下次心跳检测时会将该信息传递给Datanode。Datanode遂即移除相应的数据块,集群中的空闲空间加大。
Datanode启动和心跳
Datanode启动
- 向namenode节点发送远程调用versionRequest()请求,进行版本检查,这里的版本检查只涉及构建版本号,保证他们之间的版本一致。
- 发送远程调用register(),向namenode注册。register()的工作是检查确认该datanode是namenode管理集群的成员。
- Datanode将它管理的block信息通过blockReport()方法上报到namenode节点,帮助namenode节点建立hdfs文件数据块到datanode的映射关系。这一步操作完成之后,datanode节点可以正式提供服务。
Datanode心跳
datanode需要每隔一段时间向namenode发送心跳,如果namenode长时间接收不心跳,会认为该datanode节点已经失效。
NameNode元数据管理
namenode的职责:响应客户端请求、管理元数据。
元数据如果存储在namenode节点的磁盘中,因为经常需要进行随机访问,还有响应客户请求,必然是效率过低。所以元数据metadata要存储在内存中!方便随机存储!因此,元数据需要存放在内存中。但如果只存在内存中,一旦断点,元数据丢失,整个集群就无法工作了!因此解决办法是元数据存储在内存中,但在磁盘中有备份,这个备份就是fsImage,存放在namenode节点对应的磁盘中。(元数据->内存;edits.log,fsImage->磁盘;但三者都在Namenode中)
新的问题:当在内存中的元数据更新时,如果同时更新fsImage,就会导致效率过低,相当于第一种;但如果不更新,就会发生一致性问题,一旦namenode节点断点,就会产生数据丢失。
解决办法:引入edits.log文件(只进行追加操作,效率很高)。每当元数据有更新或者添加元数据时,修改内存中的元数据并追加到edits.log中。这样,一旦namenode节点断电,可以通过fsImage和edits.log的合并,合成元数据。
(datanode数据改变的记录在元数据中,元数据的改变记录在edits.log中)
但是,如果长时间添加数据到edit.log中,会导致该文件数据过大,效率降低,而且一旦断电,恢复元数据需要的时间过长。因此,需要定期进行fsImage和edits.log的合并。
如果这个操作有namenode节点完成,又会效率过低。因此,引入一个新的节点Standby NameNode,专门用于fsImage和edits.log的合并。
合并操作发生在Standby NameNode中。
Hadoop2.0之后容灾机制
Hadoop2.0的HA 机制有两个NameNode,一个是Active状态,另一个是Standby状态。两者的状态可以切换,但同时最多只有1个是Active状态。只有Active Namenode提供对外的服务。Active NameNode和Standby NameNode之间通过JournalNode来同步数据。
- Active和Standby两个NameNode之间的数据交互流程为:
- NameNode在启动后,会先加载FSImage文件和共享目录上的EditLog Segment文件;
- Standby NameNode会启动EditLogTailer线程和StandbyCheckpointer线程,正式进入Standby模式;
- Active NameNode把EditLog提交到JournalNode集群;
- Standby NameNode上的EditLogTailer 线程定时从JournalNode集群上同步EditLog;
- Standby NameNode上的StandbyCheckpointer线程定时进行Checkpoint,并将Checkpoint之后的FSImage文件上传到Active NameNode。
QJM方式有明显的优点,一是本身就有fencing的功能,二是通过多个Journal节点增强了系统的健壮性,所以建议在生产环境中采用QJM的方式。JournalNode消耗的资源很少,不需要额外的机器专门来启动JournalNode,可以从Hadoop集群中选几台机器同时作为JournalNode。
主备NameNode切换
Active NameNode和Standby NameNode可以随时切换,可以人工和自动。人工切换是通过执行HA管理命令来改变NameNode的状态,从Standby到Active,或从Active到Standby。自动切换则在Active NameNode挂掉的时候,Standby NameNode自动切换成Active状态。
主备NameNode的自动切换需要配置Zookeeper。Active NameNode和Standby NameNode把他们的状态实时记录到Zookeeper中,Zookeeper监视他们的状态变化。当Zookeeper发现Active NameNode挂掉后,会自动把Standby NameNode切换成Active NameNode。
安全模式
安全模式是Hadoop的一种保护机制,用以保证集群中block块的安全性。Namenode启动后会进入一个称为安全模式的特殊状态。处于安全模式的Namenode是不会进行数据块的复制的。Namenode从所有的 Datanode接收心跳信号和块状态报告。块状态报告包括了某个Datanode所有的数据块列表。每个数据块都有一个指定的最小副本数。当Namenode检测确认某个数据块的副本数目达到这个最小值,那么该数据块就会被认为是副本安全的;在一定百分比(这个参数可配置)的数据块被Namenode检测确认是安全之后(加上一个额外的30秒等待时间),Namenode将退出安全模式状态。接下来它会确定还有哪些数据块的副本没有达到指定数目,并将这些数据块复制到其他Datanode上。
在安全模式状态下,文件系统只接受读数据的请求,而不接受删除、修改等请求。整个系统达到安全标准时,hdfs自动离开安全模式。
常见问题
小文件过多
HDFS不适合存储小文件的原因:每个文件都会产生元信息,当小文件多了之后元信息也就多了,对namenode会造成压力。
解决方案参见:链接
快丢失
某个datanode节点挂了,集群进入乐安全模式,手动退出安全模式导致,尽管手动退出安全模式之后hdfs还会逐步恢复副本,但是,此时用户有了删除权限,就可能误删正在恢复的块,导致块彻底丢失。
解决步骤
- 定位丢失的块
- 输出损坏的块及其所属的文件
hdfs fsck -list-corruptfileblocks
- 输出文件及其对应的块信息
hdfs fsck / | egrep -v '^\.+$' | grep -v eplica
- 情况1:BLOCK部分副本损坏
方案一:hadoop会在6个小时候自动检测并修复
方案二:手工重启hdfs服务后会自动修复
方案三:手工修复(推荐使用)hdfs dfs -get而
后-put就可以解决。
- 情况2:BLOCK全副本均损坏
若文件不重要:
# 退出安全模式 hdfs dfsadmin -safemode leave
#删除损坏(丢失)的BLOCK hdfs fsck /path -delete
若文件重要,且数据源可重新接入,或者它是程序生成的,则需要重新生成
Rebalance
HDFS数据并不总是均匀存储在datanode中的,如新增加一个datanode节点到集群中。当有新的数据要存储时,namenode会考虑多种因数后再选择datanode来存储数据。
因数如下:
- 把一个副本存放到client所在的节点中。
- 不能把所有副本放在同一个机架上,这样可以避免机架崩溃导致所有副本丢失。
- 把其中一个副本存到同一个机架中的节点中,这样可以减少跨机架网络I/O。
- HDFS数据要均匀分布到集群中的 datanode节点中。
考虑到上述因素,HDFS提供了一个分析数据块的存放和datanode数据再平衡的工具rebalancer。