Apache Hadoop 软件库是一个框架,它允许使用简单的编程模型,实现跨计算机集群的大型数据集的分布式处理。它最初的设计目的是为了检测和处理应用程序层的故障,从单个机器扩展到数千台机器(这些机器可以是廉价的),每个机器提供本地计算和存储,而不是依靠硬件提供高可靠性。 Hadoop 中有3个核心组件:
- 分布式文件系统:HDFS —— 实现将文件分布式存储在很多的服务器上
- 分布式运算编程框架:MapReduce —— 实现在很多机器上分布式并行运算
- 分布式资源调度平台:YARN —— 帮用户调度大量的 MapReduce 程序,并合理分配运算资源
整个源头是 Google 发的三篇论文, Hadoop 的 MapReduce/HDFS/HBase 分别对应 Google 的 MapReduce/GFS/Big Table
HDFS,是 Hadoop Distributed File System 的简称,是 Hadoop 抽象文件系统的一种实现。Hadoop 抽象文件系统可以与本地系统、Amazon S3 等集成,甚至可以通过 Web 协议(webhsfs)来操作。
传统的文件系统是单机的,不能横跨不同的机器;HDFS 的文件分布在集群机器上,例如客户端写入读取文件的直接操作都是分布在集群各个机器上的,没有单点性能压力,因此 HDFS 的设计本质上是为了大量的数据能横跨成百上千台机器,提供高吞吐量的服务,同时提供副本进行容错及高可靠性保证(计算机节点很容易出现硬件故障,而不管是 Google 公司的计算平台还是 Hadoop 计算平台都将硬件故障作为常态,通过软件设计来保证系统的可靠性)。
使用 HDFS 时,用户看到的是一个文件系统而不是很多文件系统,比如说要获取 /hdfs/tmp/file1 的数据,引用的是一个文件路径,但是实际的数据存放在很多不同的机器上,作为用户,不需要知道这些,就好比在单机上我们不关心文件分散在什么磁道什么扇区一样,HDFS 为用户管理这些数据。
HDFS 主要由 3 个组件构成,分别是 NameNode、SecondaryNameNode 和 DataNode。
HDFS 是以 Master/Slave 模式运行的,其中,NameNode 和 SecondaryNameNode 运行在 Master 节点 上,而 DataNode 运行在 Slave 节点上,所以 HDFS 集群一般由一个 NameNode、一个 SecondaryNameNode 和许多 DataNode 组成,其架构如下图所示:
在 HDFS 中,文件是被分成块来进行存储的,一个文件可以包含许多个块,每个块存储在不同的 DataNode 中。当一个客户端请求读取一个文件时,它需要先从 NameNode 中获取文件的元数据信息,然后从对应的数据节点上并行地读取数据块。
下面介绍 HDFS 架构中 NameNode、SecondaryNameNode 和 DataNode 的功能:
NameNode 是主服务器,负责管理文件系统的命名空间以及客户端对文件的访问。当客户端请求数据时,仅仅从 NameNode 中获取文件的元数据信息,具体的数据传输不经过 NameNode,而是直接与具体的 DataNode 进行交互。
这里文件的元数据信息记录了文件系统中的文件名和目录名,以及它们之间的层级关系,同时也记录了每个文件目录的所有者及其权限,甚至还记录每个文件由哪些块组成,这些元数据信息记录在文件 fsimage 中,当系统初次启动时,NameNode 将读取 fsimage 中的信息并保存到内存中。
这些块的位置信息是由 NameNode 启动后从每个 DataNode 获取并保存在内存当中的,这样既减少了 NameNode 的启动时间,又减少了读取数据的查询时间,提高了整个系统的效率。
按类型分:
按形式分:
fsimage 和 edits 文件都是经过序列化的,在 NameNode 启动的时候,它会将 fsimage 文件中的内容加载到内存中,之后再执行 edits 文件中的各项操作,使得内存中的元数据和实际的同步,存在内存中的元数据支持客户端的读操作,也是最完整的元数据。
当客户端对 HDFS 中的文件进行新增或者修改操作,操作记录首先被记入 edits 日志文件中,当客户端操作成功后,相应的元数据会更新到内存元数据中。
因为 fsimage 文件一般都很大(GB 级别的很常见),如果所有的更新操作都往 fsimage 文件中添加,这样会导致系统运行十分缓慢。
HDFS 这种设计实现着手于:一是内存中数据更新、查询快,极大缩短了操作响应时间;二是内存中元数据丢失风险颇高(断电等),因此辅佐元数据镜像文件(fsimage)+ 编辑日志文件(edits)的备份机制进行,确保元数据的安全。
从字面上来看,SecondaryNameNode 很容易被当作是 NameNode 的备份节点,其实不然,可以通过下图看 HDFS 中 SecondaryNameNode 的作用:
NameNode 管理着元数据信息,元数据信息会定期保存到 edits 和 fsimage 文件中,其中的 edits 保存操作日志信息。
在 HDFS 运行期间,新的操作日志不会立即与 fsimage 进行合并,也不会存到 NameNode 的内存中,而是会先写到 edits 中,当 edits 文件达到一定域值或间隔一段时间后触发 SecondaryNameNode 进行工作,这个时间点称为 checkpoint。
SecondaryNameNode 的角色就是定期地合并 edits 和 fsimage 文件,其合并步骤如下:
最终 fsimage 保存的是上一个 checkpoint 的元数据信息,而 edits 保存的是从上个 checkpoint 开始发生的 HDFS 元数据改变的信息。
DataNode 是 HDFS 中的工作节点,也是从服务器,它负责存储数据块,也负责为客户端提供数据块的读写服务,同时也响应 NameNode 的相关指令,如完成数据块的复制、删除等。
另外, DataNode 会定期发送”心跳“信息给 NameNode,告知 NameNode 当前节点存储的文件块信息。当客户端给 NameNode 发送读写请求时,NameNode 告知客户端每个数据块所在的 DataNode 信息,然后客户端直接与 DataNode 进行通信,减少 NameNode 的系统开销。
当 DataNode 在执行块存储操作时,DataNode 还会与其他 DataNode 通信,复制这些块到其他 DataNode 上实现冗余。
HDFS 中数据块大小默认为 64MB,而一般扇区的大小为 512B(一般一个磁盘块由8个扇区组成,共4K,感谢 timoyyu 指正),HDFS 块之所以这么大,是为了最小化寻址开销,控制定位文件与传输文件所用的时间比例。
假设定位到 Block 所需的时间为 10ms,磁盘传输速度为 100M/s,如果要将定位到 Block 所用时间占传输时间的比例控制1%,则 Block 大小需要约 100M。
如果块足够大,从磁盘传输数据的时间会明显大于寻找块的地址的时间,因此,传输一个由多个块组成的大文件的时间取决于磁盘传输速率。
随着新一代磁盘驱动器传输速率的提升,寻址开销会更少,在更多情况下 HDFS 使用更大的块。
当然块的大小不是越大越好,因为 Hadoop 中一个 map 任务一次通常只处理一个块中的数据,如果块过大,会导致整体任务数量过小(在 MapReduce 任务中,Map 或者 Reduce 任务的个数小于集群机器数量),降低作业处理的速度。
HDFS 按块存储还有如下好处:
HDFS 中数据块的副本数默认为3,也可以设置更多的副本数,这些副本分散存储在集群中,副本的分布位置直接影响 HDFS 的可靠性和性能。
一个大型的分布式文件系统都是需要跨多个机架的,如上面的架构示意图中,HDFS 涉及两个机架。
如果把所有副本都存放在不同的机架上,可以防止机架故障从而导致数据块不可用,同时在多个客户端访问文件系统时很容易实现负载均衡。
如果是写数据,各个数据块需要同步到不同机架上,会影响写数据的效率。
在 HDFS 默认3个副本情况下,会把第一个副本放到机架的一个节点上,第二副本放在同一个机架的另一个节点上,第三个副本放在不同的机架上。
这种策略减少了跨机架副本的个数,提高了数据块的写性能,也可以保证在一个机架出现故障时,仍然能正常运转。
HDFS 通过 RPC(Remote Procedure Call,远程过程调用)调用 NameNode 获取文件块的位置信息,并且对每个块返回所在的 DataNode 的地址信息,然后再从 DataNode 获取数据块的副本。
HDFS 读文件的过程如图所示:
操作步骤如下:
在客户端获得 NameNode 关于每个数据块的信息后,客户端会根据网络拓扑选择与它最近的 DataNode 来读取每个数据块。
当与 DataNode 通信失败时,它会选取另一个较近的 DataNode,同时会对出故障的 DataNode 做标记,避免与它重复通信,并发送 NameNode 故障节点的信息。
当客户端发送写文件请求时,NameNode 负责通知 DataNode 创建文件,在创建之前会检查客户端是否有允许写入数据的权限。通过检测后,NameNode 会向 edits 文件写入一条创建文件的操作记录。
HDFS 中写文件的过程如图所示:
操作步骤如下:
指具有固定格式或有限长度的数据,如数据库,元数据等,可以通过固有键值获取相应信息,且数据的格式固定,如 RDBMS Data。
指不定长或无固定格式的数据,如邮件、Word 文档、图片、声音、视频等等。
文件中的内容是没有固定格式指的是,比如一行有8个字段,之间用逗号分隔,而另一行有6个字段,用空格分隔等。
非结构化数据这类信息我们通常无法直接知道他的内容,数据库也只能将它保存在一个 BLOB 字段中,对以后检索非常麻烦。一般的做法是,建立一个包含三个字段的表(编号 number、内容描述 varchar(1024)、内容 blob)。引用通过编号,检索通过内容描述。
非结构化数据又一种叫法叫全文数据。不可以通过键值获取相应信息。
它是结构化的数据,但是结构变化很大,如 XML,HTML 等,当根据需要可按结构化数据来处理,也可抽取出纯文本按非结构化数据来处理。可以通过灵活的键值调整获取相应信息,且数据的格式不固定,如 json,同一键值下存储的信息可能是数值型的,可能是文本型的,也可能是字典或者列表。
举一个半结构化的数据的例子,比如存储员工的简历。不像员工基本信息那样一致,每个员工的简历大不相同。有的员工的简历很简单,比如只包括教育情况;有的员工的简历却很复杂,比如包括工作情况、婚姻情况、出入境情况、户口迁移情况、党籍情况、技术技能等等,还有可能有一些我们没有预料的信息。
通常我们要完整的保存这些信息并不是很容易的,因为我们不会希望系统中的表的结构在系统的运行期间进行变更。
HBase 是 Hadoop Database,即 Hadoop 数据库,是一种非关系型数据库 NoSQL(它不具备关系型数据库的一些特点,例如,它不完全支持 SQL 的跨行事务,也不要求数据之间有严格的关系,同时它允许在同一列的不同行中存储不同类型的数据),通常被描述成稀疏的、分布式的、持久化的,由行键、列键和时间戳进行索引的多维有序映射数据库,主要用来存储非结构化和半结构化的数据。因为 HBase 基于 Hadoop 的 HDFS 完成分布式存储,以及 MapReduce 完成分布式并行计算,所以它的一些特点与 Hadoop 相同,依靠横向扩展,通过不断增加性价比高的商业服务器来增加计算和存储能力。
此外,HBase 本身可以完全不考虑 HDFS 的,我们完全可以只把 HBase 当作是一个分布式高并发 K-V 存储系统,只不过它底层的文件系统是通过 HDFS 来支持。换做其他的分布式文件系统也是一样,不影响 HBase 的本质,甚至如果不考虑文件系统的分布式或稳定性等特性,完全可以用简单的本地文件系统,甚至内存文件系统来代替。
Hadoop HDFS 无法处理高速随机写入和读取,也无法在不重写文件的情况下对文件进行修改,用于实现离线的文件存储。HBase 正好解决了 HDFS 的缺点,因为它使用优化的方式(构建上层分布式内存)快速随机写入和读取,用于实现实时数据存储;此外,随着数据呈指数增长,关系数据库无法提供更好性能去处理海量的数据,HBase 提供可扩展性和分区,以实现高效的存储和检索。
较为重要的:
冷热数据分离
HBase 将新数据直接写入内存中,如果内存中存储的数据过多,就将内存的数据写入 HDFS
稀疏性
通常在传统的关系性数据库中,每一列的数据类型是事先定义好的,会占用固定的内存空间,在此情况下,属性值为空(NULL)的列也需要占用存储空间。
而在 HBase 中的数据都是以字符串形式存储的,为空的列并不占用存储空间,因此 HBase 的列存储解决了数据稀疏性的问题,在很大程度上节省了存储开销。所以 HBase 通常可以设计成稀疏矩阵,同时这种方式比较接近实际的应用场景。
强扩展性
HBase 的扩展是横向的,横向扩展是指在扩展时不需要提升服务器本身的性能,只需添加服务器到现有集群即可。
HBase 表根据 Region 大小进行分区,分别存在集群中不同的节点上,当添加新的节点时,集群就重新调整,在新的节点启动 HBase 服务器,动态地实现扩展。这里需要指出,HBase 的扩展是热扩展,即在不停止现有服务的前提下,可以随时添加或者减少节点。
HBase 是面向列的存储和权限控制的,它里面的每个列是单独存储的,且支持基于列的独立检索。
进行数据的插入和更新时,行存储会相对容易。
而进行行存储时,查询操作需要读取所有的数据,列存储则只需要读取相关列,可以大幅降低系统 I/O 吞吐量。
HBase 中的数据以表的形式存储。同一个表中的数据通常是相关的,使用表主要是可以把某些列组织起来一起访问。表名作为 HDFS 存储路径的一部分来使用,在 HDFS 中可以看到每个表名都作为独立的目录结构。
在 HBase 表里,每一行代表一个数据对象,每一行都以行键(Row Key)来进行唯一标识,行键可以是任意字符串。在 HBase 内部,行键是不可分割的字节数组,并且行键是按照字典排序由低到高存储在表中的。在 HBase 中可以针对行键建立索引,提高检索数据的速度。
HBase 中的列族是一些列的集合,列族中所有列成员有着相同的前缀,列族的名字必须是可显示的字符串。列族支持动态扩展,用户可以很轻松地添加一个列族或列,无须预定义列的数量以及类型。所有列均以字符串形式存储,用户在使用时需要自行进行数据类型转换。
列族中的数据通过列标识来进行定位,列标识也没有特定的数据类型,以二进制字节来存储。通常以 Column Family:Colunm Qualifier 来确定列族中的某列。
每一个行键、列族、列标识共同确定一个单元格,单元格的内容没有特定的数据类型,以二进制字节来存储。每个单元格保存着同一份数据的多个版本,不同时间版本的数据按照时间先后顺序排序,最新的数据排在最前面。单元格可以用 <rowkey,column family:column=“” qualifier,timestamp=“”> 元组来进行访问。</rowkey,column>
在默认情况下,每一个单元格插入数据时都会用时间戳来进行版本标识。读取单元格数据时,如果时间戳没有被指定,则默认返回最新的数据;写入新的单元格数据时,如果没有设置时间戳,默认使用当前时间。每一个列族的单元数据的版本数量都被 HBase 单独维护,默认情况下 HBase 保留3个版本数据。
表是 HBase 中数据的逻辑组织方式,从用户视角来看,HBase 表的逻辑模型如表1所示。HBase 中的一个表有若干行,每行有多个列族,每个列族中包含多个列,而列中的值有多个版本。
表1展示的是 HBase 中的学生信息表 Student,有三行记录和两个列族,行键分别为0001、0002和0003,两个列族分别为 Stulnfo 和 Grades,每个列族中含有若干列,如列族 Stulnfo 包括 Name、Age、Sex 和 Class 四列,列族 Grades 包括 BigData、Computer 和 Math 三列。
在 HBase 中,列不是固定的表结构,在创建表时,不需要预先定义列名,可以在插入数据时临时创建。
从表1的逻辑模型来看,HBase 表与关系型数据库中的表结构之间似乎没有太大差异,只不过多了列族的概念。但实际上是有很大差别的,关系型数据库中表的结构需要预先定义,如列名及其数据类型和值域等内容。如果需要添加新列,则需要修改表结构,这会对已有的数据产生很大影响。
同时,关系型数据库中的表为每个列预留了存储空间,即表1中的空白 Cell 数据在关系型数据库中以“NULL”值占用存储空间。因此,对稀疏数据来说,关系型数据库表中就会产生很多“NULL”值,消耗大量的存储空间。
在 HBase 中,如表1中的空白 Cell 在物理上是不占用存储空间的,即不会存储空白的键值对。因此,若一个请求为获取 RowKey 为0001在 T2 时间的 Stulnfo:Class 值时,其结果为空。
与面向行存储的关系型数据库不同,HBase 是面向列存储的,且在实际的物理存储中,列族是分开存储的,即表1中的学生信息表将被存储为 Stulnfo 和 Grades 两个部分。
表2展示了 Stulnfo 这个列族的实际物理存储方式,列族 Grades 的存储与之类似。在表2中可以看到空白 Cell 是没有被存储下来的。
问题描述:
在某个时间段内,大量的读写请求全部集中在某个 Region 中,导致这台 RegionServer 的负载比较高,其他的 Region 和 RegionServer 比较空闲,那么这台 RegionServer 故障的概率就会增加,整体性能降低,效率比较差。
原因:
数据分配不均衡,如一张表只有一个 Region,或一张表有多个 Region,但是 Rowkey 是连续产生的。
Region有两个重要的属性:StartKey 和 EndKey。表示这个 Region 维护的 RowKey 的范围,当我们要读写数据时,如果 RowKey 落在某个 Start-End Key 范围内,那么就会定位到目标 Region 并且读写到相关的数据。
默认情况下,当我们通过 HBaseAdmin 来创建一张表时,刚开始的时候只有一个 Region,Start-End Key 无边界,所有的 RowKey 都写入到这个 Region 里,然后数据越来越多,Region 的 Size 越来越大,大到一定的阀值,HBase 就会将 Region 一分为二,成为2个 Region,这个过程称为分裂(Region-Split)。
如果我们就这样默认建表,表里不断的 Put 数据,一般情况我们的 RowKey 还是顺序增大的,这样存在的缺点比较明显:我们总是向最大的 StartKey 所在的 Region 写数据,因为我们的 RowKey 总是会比之前的大,并且 HBase 的(?)是按升序方式排序的。所以写操作总是被定位到无上界的那个 Region 中,之前分裂出来的 Region 不会被写数据,所以这样产生的结果是不利的。
如果在写比较频繁的场景下,数据增长太快,Split 的次数也会增多,由于 Split 是比较耗费资源的,且 Split 会使 Server 有一段时间的停顿,所以我们并不希望这种事情经常发生。
解决方式:
如何能做到呢?Rowkey 的散列或预分区就可以办的到。预分区一开始就预建好了一部分 Region,这些 Region 都维护着自己的 Start-End Key,我们将 RowKey 做一些处理,比如 RowKey%i,写数据能均衡的命中这些预建的 Region,就能解决上面的那些缺点,大大提供性能。
MongoDB 是一种文档数据库,文档是处理信息的基本单位。一个文档可以很长、很复杂,可以无结构,与字处理文档类似。它常用于日志的采集和存储,小文件的分布式存储,类似互联网微博应用的数据存储。
HDFS 可以为用户整体管理不同机器上的数据,任务下达时使用很多台机器处理,这就面临了如何分配工作的问题:如果一台机器挂了如何重新启动相应的任务、机器之间如何互相通信交换数据以完成复杂的计算等。这就是 MapReduce 的功能。MapReduce 的设计,采用了很简化的计算模型,只有 Map 和 Reduce 两个计算过程(中间用 Shuffle 串联)。使用这个模型,已经可以处理大数据领域很大一部分问题了。
将大的数据分析分成小块逐个分析,最后再将提取出来的数据汇总分析,最终获得我们想要的内容,举例说明:
考虑统计一个存储在 HDFS 上的巨大的文本文件,我们想要知道这个文本里各个词的出现频率。Map阶段,几百台机器同时读取这个文件的各个部分,分别把各自读到的部分分别统计出词频,产生类似(hello, 12100次),(world,15214次)等等这样的 Pair(这里把 Map 和 Combine 放在一起说以便简化);这几百台机器各自都产生了如上的集合,然后又有几百台机器启动 Reduce 处理。Reduce 阶段,Reducer 机器 A 将从 Mapper 机器收到所有以 A 开头的统计结果,机器 B 将收到 B 开头的词汇统计结果(实际上不会真的以字母开头做依据,而是用函数产生 Hash 值以避免数据串化。因为类似 X 开头的词比其他要少得多,会导致各个机器的工作量相差悬殊)。然后这些 Reducer 将再次汇总,(hello,12100)+(hello,12311)+(hello,345881)= (hello,370292)。每个 Reducer 都如上处理,就得到了整个文件的词频结果。
Map+Reduce 的简单模型虽然好用,但是很笨重。第二代的 Tez 和 Spark 除了引入内存 Cache 之类的新 Feature,本质上来说,是让 Map/Reduce 模型更通用,让 Map 和 Reduce 之间的界限更模糊,数据交换更灵活,磁盘的读写更少,以便更方便地描述复杂算法,取得更高的吞吐量。
Hadoop 的集群主要由 NameNode,DataNode,Secondary NameNode,JobTracker,TaskTracker 组成:
Hadoop 的集群主要由 NameNode,DataNode,Secondary NameNode,JobTracker,TaskTracker 组成:
[外链图片转存中…(img-3o6vBcPu-1704534366287)]