**大家想了解更多大数据相关内容请移驾我的课堂:
大数据相关课程**
剖析及实践企业级大数据
数据架构规划设计
大厂架构师知识梳理:剖析及实践数据建模
剖析及实践数据资产运营平台
Apache Hadoop 软件库是一个框架,它允许使用简单的编程模型,实现跨计算机集群的大型数据集的分布式处理。它最初的设计目的是为了检测和处理应用程序层的故障,从单个机器扩展到数千台机器(这些机器可以是廉价的),每个机器提供本地计算和存储,而不是依靠硬件提供高可靠性。 Hadoop 中有3个核心组件:
- 分布式文件系统:HDFS ------ 实现将文件分布式存储在很多的服务器上
- 分布式运算编程框架:MapReduce ------ 实现在很多机器上分布式并行运算
- 分布式资源调度平台:YARN ------ 帮用户调度大量的 MapReduce 程序,并合理分配运算资源
整个源头是 Google 发的三篇论文, Hadoop 的 MapReduce/HDFS/HBase 分别对应 Google 的 MapReduce/GFS/Big Table
HDFS
HDFS,是 Hadoop Distributed File System 的简称,是 Hadoop 抽象文件系统的一种实现。Hadoop 抽象文件系统可以与本地系统、Amazon S3 等集成,甚至可以通过 Web 协议(webhsfs)来操作。
传统的文件系统是单机的,不能横跨不同的机器;HDFS 的文件分布在集群机器上,例如客户端写入读取文件的直接操作都是分布在集群各个机器上的,没有单点性能压力,因此 HDFS 的设计本质上是为了大量的数据能横跨成百上千台机器,提供高吞吐量的服务,同时提供副本进行容错及高可靠性保证(计算机节点很容易出现硬件故障,而不管是 Google 公司的计算平台还是 Hadoop 计算平台都将硬件故障作为常态,通过软件设计来保证系统的可靠性)。
使用 HDFS 时,用户看到的是一个文件系统而不是很多文件系统,比如说要获取 /hdfs/tmp/file1 的数据,引用的是一个文件路径,但是实际的数据存放在很多不同的机器上,作为用户,不需要知道这些,就好比在单机上我们不关心文件分散在什么磁道什么扇区一样,HDFS 为用户管理这些数据。
设计特点
- 大数据文件:非常适合上 T 级别的大文件或者一堆大数据文件的存储,如果文件只有几个 G 甚至更小就不太适用;假设 HDFS 中块的大小为 64MB,备份数量为3,---般情况下,一条元数据记录占用 200B 的内存,那么对于 1GB 的大文件,将占用 1GB/64MB x 3 个文件块;对于1024个 1MB 的小文件,则占用 1024 x 3 个文件块,可以看到,存储同等大小的文件,单个文件越小,所需的元数据信息越大,占用的内存越大,因此 HDFS 适合存储超大文件
- 文件分块存储:HDFS 会将一个完整的大文件平均分块存储到不同计算器上,它的意义在于读取文件时可以同时从多个主机取不同区块的文件,多主机读取比单主机读取效率要高得多
- 流式数据访问:一次写入多次读写,这种模式跟传统文件不同,它不支持动态改变文件内容,而是要求让文件一次写入就不做变化,要变化也只能在文件末添加内容;通过流式的数据访问保证高吞吐量
- 数据靠拢:对数据进行计算时,采用将计算向数据靠拢的方式,即选择最近的数据进行计算,减少数据在网络中的传输延迟
- 廉价硬件:HDFS 可以应用在普通 PC 机上,这种机制能够让一些公司用几十台廉价的计算机就可以撑起一个大数据集群
- 高可靠性(硬件故障处理):HDFS 认为所有计算机都可能会出问题,为了防止某个主机失效读取不到该主机的块文件,它将同一个文件块副本分配到其它某几个主机上,如果其中一台主机失效,可以迅速找另一块副本取文件
- 高可用性:Hadoop 2.0 之前,NameNode 只有一个,存在单点问题(单点故障导致整个集群不可用,虽然 Hadoop1.0 有 SecondaryNameNode,CheckPointNode,BackupNode 这些来尽量满足 HA,但是单点问题依然存在),在 Hadoop 2.0 引入了新的 HA 机制,集群中会同时运行两个 Namenode,一个作为活动的 Namenode(Active),一个作为备份的 Namenode(Standby);备份的 Namenode 的命名空间与活动的 Namenode 是实时同步的,所以当活动的 Namenode 发生故障而停止服务时,备份 Namenode 可以立即切换为活动状态,而不影响 HDFS 集群服务
- 最终一致性:HDFS 是一个松散的一致性检查的模型,它主要是为了追加(append)操作而不是覆盖重写(overwrite)操作,因为覆盖重写的话可能在一次读的操作会读到与其他副本不一致的数据,而在追加操作中,其中一个副本的不一致也不会导致客户端读到不一致的数据;同时 HDFS 在追加操作时采用租用(Lease)机制,即将块的写操作授权给主块服务器(primary chunk server),另外的副本称为次块服务器(secondary chunk server),当多个客户端的并发写操作时,主块服务器缓存其写的顺序,之后联系次服务器进行追加操作
不适用的场景
- 低延时的数据访问:对延时要求在毫秒级别的应用,不适合采用 HDFS,HDFS 是为高吞吐数据传输设计的,因此 HBase 更适合低延时的数据访问
- 大量小文件:文件的元数据(如目录结构,文件 Block 的节点列表,Block-node Mapping)保存在 NameNode 的内存中, 整个文件系统的文件数量会受限于 NameNode 的内存大小; 经验而言,一个文件/目录/文件块一般占有150字节的元数据内存空间,如果有100万个文件,每个文件占用1个文件块,则需要大约 300M 的内存,因此上亿级别的文件数量在现有商用机器上难以支持
- 多方读写:HDFS 采用追加(append-only)的方式写入数据,不支持文件任意 offset 的修改(文件中的地址与内存中的地址表示不同,使用偏移量 File Offset 来表示),不支持多个写入器(writer)
架构
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 中获取文件的元数据信息,具体的数据传输不经过 NameNode,而是直接与具体的 DataNode 进行交互。
这里文件的元数据信息记录了文件系统中的文件名和目录名,以及它们之间的层级关系,同时也记录了每个文件目录的所有者及其权限,甚至还记录每个文件由哪些块组成,这些元数据信息记录在文件 fsimage 中,当系统初次启动时,NameNode 将读取 fsimage 中的信息并保存到内存中。
这些块的位置信息是由 NameNode 启动后从每个 DataNode 获取并保存在内存当中的,这样既减少了 NameNode 的启动时间,又减少了读取数据的查询时间,提高了整个系统的效率。
元数据
按类型分:
- 文件、目录自身的属性信息,例如文件名,目录名,修改信息等
- 与文件记录的信息的存储相关的信息,例如存储块信息,分块情况,副本个数等
- 记录 HDFS 的 Datanode 的信息,用于 DataNode 的管理
按形式分:
- 内存元数据,存储于内存上
- 元数据文件,存储于磁盘上,分为两类,用于持久化存储:
- fsimage 镜像文件:是元数据的一个持久化的检查点,包含 Hadoop 文件系统中的所有目录和文件元数据信息,但不包含文件块位置的信息;文件块位置信息只存储在内存中,是在 DataNode 加入集群的时候,NameNode 询问 DataNode 得到的,并且间断地更新
- edits 编辑日志:存放的是 Hadoop 文件系统的所有更改操作(文件创建,删除或修改)的日志,文件系统客户端执行的更改操作首先会被记录到 edits 文件中
fsimage 和 edits 文件都是经过序列化的,在 NameNode 启动的时候,它会将 fsimage 文件中的内容加载到内存中,之后再执行 edits 文件中的各项操作,使得内存中的元数据和实际的同步,存在内存中的元数据支持客户端的读操作,也是最完整的元数据。
当客户端对 HDFS 中的文件进行新增或者修改操作,操作记录首先被记入 edits 日志文件中,当客户端操作成功后,相应的元数据会更新到内存元数据中。
因为 fsimage 文件一般都很大(GB 级别的很常见),如果所有的更新操作都往 fsimage 文件中添加,这样会导致系统运行十分缓慢。
HDFS 这种设计实现着手于:一是内存中数据更新、查询快,极大缩短了操作响应时间;二是内存中元数据丢失风险颇高(断电等),因此辅佐元数据镜像文件(fsimage)+ 编辑日志文件(edits)的备份机制进行,确保元数据的安全。
SecondaryNameNode
从字面上来看,SecondaryNameNode 很容易被当作是 NameNode 的备份节点,其实不然,可以通过下图看 HDFS 中 SecondaryNameNode 的作用:
NameNode 管理着元数据信息,元数据信息会定期保存到 edits 和 fsimage 文件中,其中的 edits 保存操作日志信息。
在 HDFS 运行期间,新的操作日志不会立即与 fsimage 进行合并,也不会存到 NameNode 的内存中,而是会先写到 edits 中,当 edits 文件达到一定域值或间隔一段时间后触发 SecondaryNameNode 进行工作,这个时间点称为 checkpoint。
SecondaryNameNode 的角色就是定期地合并 edits 和 fsimage 文件,其合并步骤如下:
- 在进行合并之前,SecondaryNameNode 会通知 NameNode 停用当前的 editlog 文件, NameNode 会将新记录写入新的 editlog.new 文件中
- SecondaryNameNode 从 NameNode 请求并复制 fsimage 和 edits 文件
- SecondaryNameNode 把 fsimage 和 edits 文件合并成新的 fsimage 文件,并命名为 fsimage.ckpto
- NameNode 从 SecondaryNameNode 获取 fsimage.ckpt,并替换掉 fsimage,同时用 edits.new 文件替换旧的 edits 文件
- 更新 checkpoint 的时间
最终 fsimage 保存的是上一个 checkpoint 的元数据信息,而 edits 保存的是从上个 checkpoint 开始发生的 HDFS 元数据改变的信息。
DataNode
DataNode 是 HDFS 中的工作节点,也是从服务器,它负责存储数据块,也负责为客户端提供数据块的读写服务,同时也响应 NameNode 的相关指令,如完成数据块的复制、删除等。
另外, DataNode 会定期发送"心跳"信息给 NameNode,告知 NameNode 当前节点存储的文件块信息。当客户端给 NameNode 发送读写请求时,NameNode 告知客户端每个数据块所在的 DataNode 信息,然后客户端直接与 DataNode 进行通信,减少 NameNode 的系统开销。
当 DataNode 在执行块存储操作时,DataNode 还会与其他 DataNode 通信,复制这些块到其他 DataNode 上实现冗余。
机制
HDFS 的分块机制
HDFS 中数据块大小默认为 64MB,而一般扇区的大小为 512B(一般一个磁盘块由8个扇区组成,共4K,感谢 timoyyu 指正),HDFS 块之所以这么大,是为了最小化寻址开销,控制定位文件与传输文件所用的时间比例。
假设定位到 Block 所需的时间为 10ms,磁盘传输速度为 100M/s,如果要将定位到 Block 所用时间占传输时间的比例控制1%,则 Block 大小需要约 100M。
如果块足够大,从磁盘传输数据的时间会明显大于寻找块的地址的时间,因此,传输一个由多个块组成的大文件的时间取决于磁盘传输速率。
随着新一代磁盘驱动器传输速率的提升,寻址开销会更少,在更多情况下 HDFS 使用更大的块。
当然块的大小不是越大越好,因为 Hadoop 中一个 map 任务一次通常只处理一个块中的数据,如果块过大,会导致整体任务数量过小(在 MapReduce 任务中,Map 或者 Reduce 任务的个数小于集群机器数量),降低作业处理的速度。
HDFS 按块存储还有如下好处:
- 文件可以任意大,不会受到单个节点的磁盘容量的限制:理论上讲,HDFS 的存储容量是无限的;Block 的拆分使得单个文件大小可以大于整个磁盘的容量,构成文件的 Block 可以分布在整个集群,因此单个文件可以占据集群中所有机器的磁盘
- 简化文件子系统的设计:将系统的处理对象设置为块,可以简化存储管理,因为块大小固定,所以每个文件分成多少个块,每个 DataNode 能存多少个块,都很容易计算;同时系统中 NameNode 只负责管理文件的元数据,DataNode 只负责数据存储,分工明确,提高了系统的效率
- 有利于提高系统的可用性:HDFS 通过数据备份来提供数据的容错能力和高可用性,而按照块的存储方式非常适合数据备份;同时块以副本方式存在多个 DataNode 中,有利于负载均衡,当某个节点处于繁忙状态时,客户端还可以从其他节点获取这个块的副本
HDFS 的副本机制
HDFS 中数据块的副本数默认为3,也可以设置更多的副本数,这些副本分散存储在集群中,副本的分布位置直接影响 HDFS 的可靠性和性能。
一个大型的分布式文件系统都是需要跨多个机架的,如上面的架构示意图中,HDFS 涉及两个机架。
如果把所有副本都存放在不同的机架上,可以防止机架故障从而导致数据块不可用,同时在多个客户端访问文件系统时很容易实现负载均衡。
如果是写数据,各个数据块需要同步到不同机架上,会影响写数据的效率。
在 HDFS 默认3个副本情况下,会把第一个副本放到机架的一个节点上,第二副本放在同一个机架的另一个节点上,第三个副本放在不同的机架上。
这种策略减少了跨机架副本的个数,提高了数据块的写性能,也可以保证在一个机架出现故障时,仍然能正常运转。
HDFS 的读取机制
HDFS 通过 RPC(Remote Procedure Call,远程过程调用)调用 NameNode 获取文件块的位置信息,并且对每个块返回所在的 DataNode 的地址信息,然后再从 DataNode 获取数据块的副本。
HDFS 读文件的过程如图所示:
操作步骤如下:
- 客户端发起文件读取的请求
- NameNode 将文件对应的数据块信息及每个块的位置信息,包括每个块的所有副本的位置信息(即每个副本所在的 DataNode 的地址信息)都传送给客户端
- 客户端收到数据块信息后,直接和数据块所在的 DataNode 通信,并行地读取数据块
在客户端获得 NameNode 关于每个数据块的信息后,客户端会根据网络拓扑选择与它最近的 DataNode 来读取每个数据块。
当与 DataNode 通信失败时,它会选取另一个较近的 DataNode,同时会对出故障的 DataNode 做标记,避免与它重复通信,并发送 NameNode 故障节点的信息。
HDFS 的写入机制
当客户端发送写文件请求时,NameNode 负责通知 DataNode 创建文件,在创建之前会检查客户端是否有允许写入数据的权限。通过检测后,NameNode 会向 edits 文件写入一条创建文件的操作记录。
HDFS 中写文件的过程如图所示:
操作步骤如下:
- 客户端在向 NameNode 发送写请求之前,先将数据写入本地的临时文件中
- 待临时文件块达到系统设置的块大小时,开始向 NameNode 请求写文件
- NameNode 在此步骤中会检查集群中每个 DataNode 状态信息,获取空闲的节点,并在检查客户端权限后创建文件,返回客户端一个数据块及其对应 DataNode 的地址列表,列表中包含副本存放的地址
- 客户端在获取 DataNode 相关信息后,将临时文件中的数据块写入列表中的第一个 DataNode,同时第一个 DataNode 会将数据以副本的形式传送至第二个 DataNode,第二个节点也会将数据传送至第三个 DataNode
- DataNode 以数据包的形式从客户端接收数据,并以流水线的形式写入和备份到所有的 DataNode 中,每个 DataNode 收到数据后会向前一个节点发送确认信息;最终数据传输完毕,第一个 DataNode 会向客户端发送确认信息
- 当客户端收到每个 DataNode 的确认信息时,表示数据块已经持久化地存储在所有 DataNode 当中,接着客户端会向 NameNode 发送确认信息;如果在第4步中任何一个 DataNode 失败,客户端会告知 NameNode,将数据备份到新的 DataNode 中
基于 HDFS 的数据库
由结构化、非结构化、半结构化数据引入
结构化数据
指具有固定格式或有限长度的数据,如数据库,元数据等,可以通过固有键值获取相应信息,且数据的格式固定,如 RDBMS Data。
非结构化数据
指不定长或无固定格式的数据,如邮件、Word 文档、图片、声音、视频等等。
文件中的内容是没有固定格式指的是,比如一行有8个字段,之间用逗号分隔,而另一行有6个字段,用空格分隔等。
非结构化数据这类信息我们通常无法直接知道他的内容,数据库也只能将它保存在一个 BLOB 字段中,对以后检索非常麻烦。一般的做法是,建立一个包含三个字段的表(编号 number、内容描述 varchar(1024)、内容 blob)。引用通过编号,检索通过内容描述。
非结构化数据又一种叫法叫全文数据。不可以通过键值获取相应信息。
半结构化数据
它是结构化的数据,但是结构变化很大,如 XML,HTML 等,当根据需要可按结构化数据来处理,也可抽取出纯文本按非结构化数据来处理。可以通过灵活的键值调整获取相应信息,且数据的格式不固定,如 json,同一键值下存储的信息可能是数值型的,可能是文本型的,也可能是字典或者列表。
举一个半结构化的数据的例子,比如存储员工的简历。不像员工基本信息那样一致,每个员工的简历大不相同。有的员工的简历很简单,比如只包括教育情况;有的员工的简历却很复杂,比如包括工作情况、婚姻情况、出入境情况、户口迁移情况、党籍情况、技术技能等等,还有可能有一些我们没有预料的信息。
通常我们要完整的保存这些信息并不是很容易的,因为我们不会希望系统中的表的结构在系统的运行期间进行变更。
HBase
HBase 是 Hadoop Database,即 Hadoop 数据库,是一种非关系型数据库 NoSQL(它不具备关系型数据库的一些特点,例如,它不完全支持 SQL 的跨行事务,也不要求数据之间有严格的关系,同时它允许在同一列的不同行中存储不同类型的数据),通常被描述成稀疏的、分布式的、持久化的,由行键、列键和时间戳进行索引的多维有序映射数据库,主要用来存储非结构化和半结构化的数据。因为 HBase 基于 Hadoop 的 HDFS 完成分布式存储,以及 MapReduce 完成分布式并行计算,所以它的一些特点与 Hadoop 相同,依靠横向扩展,通过不断增加性价比高的商业服务器来增加计算和存储能力。
- HDFS 为 HBase 提供了高可靠性的底层存储支持
- Hadoop MapReduce 为 HBase 提供了高性能的计算能力
- Zookeeper 为 HBase 提供了稳定服务和 failover 机制
- Hive 为 HBase 提供了高层语言支持,使得在 HBase 上进行数据统计处理变的非常简单
- Sqoop 为 HBase 提供了方便的 RDBMS(关系型数据库)数据导入功能,使得传统数据库数据向 HBase 中迁移变的非常方便
此外,HBase 本身可以完全不考虑 HDFS 的,我们完全可以只把 HBase 当作是一个分布式高并发 K-V 存储系统,只不过它底层的文件系统是通过 HDFS 来支持。换做其他的分布式文件系统也是一样,不影响 HBase 的本质,甚至如果不考虑文件系统的分布式或稳定性等特性,完全可以用简单的本地文件系统,甚至内存文件系统来代替。
为什么需要 HBase
Hadoop HDFS 无法处理高速随机写入和读取,也无法在不重写文件的情况下对文件进行修改,用于实现离线的文件存储。HBase 正好解决了 HDFS 的缺点,因为它使用优化的方式(构建上层分布式内存)快速随机写入和读取,用于实现实时数据存储;此外,随着数据呈指数增长,关系数据库无法提供更好性能去处理海量的数据,HBase 提供可扩展性和分区,以实现高效的存储和检索。
设计特点
- 数据模型
- 松散的表 Schema,列的名字、数目、长度无需定义
- 数据类型唯一支持字节数组,任何可以转换为字节数组的内容都可以作为一个值存储(可以是字符串、数字、复杂对象、甚至可以是图像,只要它们可以呈现为字节)
- 支持多版本
- 访问方式
- 实时写入、更新、删除,支持批量、异步等方式
- 后台导入,直接生成存储格式的文件,十分高效
- 设置数据有效期,到期自动删除
- 指定主键(Row)的随机查询
- 有条件(为主键或列设定条件)的范围查询
- 支持协处理器
- 事务:只支持单行、同分区跨行的事务
- 索引:主键索引
- 数据强一致性、强持久性,牺牲了高可用性
- 程序语言支持:原生仅支持 JAVA,C、PHP 等非 JAVA 可以通过代理(Thrift)访问
- 性能与扩展
- 水平扩展,支持千台物理机级别的规模
- 扩容无需等待数据迁移,即扩即用
- 大表自动分裂,支持分区在线合并
- 扩展能力依赖表分区,Row 设计需要防热点
- 存储层采用 LSM 树,相比于 B-Tree(读写对等),写能力>读能力
- 安全与稳定
- 存储层默认3副本,数据可靠性高
- 支持表快照,方便冷备
- 系统内部采用 M-S 架构
- Master 支持热备,Master 故障影响 DDL,不影响 DML
- Slave 故障,影响可用性,部分数据区域的 DML 不可用,会自动恢复
- 支持双集群数据复制,M-M 灾备
- 支持用户认证与授权
- 数据导入导出
- 跨系统,支持导入、导出 CSV 格式的数据
- 同系统,支持 distcp 直接拷贝底层存储文件,快速导入
- 使用 Sqoop,在 HBase/MySQL/Oracle/Hive 等系统间相互迁移数据
较为重要的:
冷热数据分离
HBase 将新数据直接写入内存中,如果内存中存储的数据过多,就将内存的数据写入 HDFS
- 热数据是指刚产生的数据,先写内存,大概率的情况下,可以直接从内存中读取
- 冷数据是指先产生的数据,将内存中产生很久的数据写入 HDFS 中,被读取的概率较小
稀疏性
通常在传统的关系性数据库中,每一列的数据类型是事先定义好的,会占用固定的内存空间,在此情况下,属性值为空(NULL)的列也需要占用存储空间。
而在 HBase 中的数据都是以字符串形式存储的,为空的列并不占用存储空间,因此 HBase 的列存储解决了数据稀疏性的问题,在很大程度上节省了存储开销。所以 HBase 通常可以设计成稀疏矩阵,同时这种方式比较接近实际的应用场景。
强扩展性
HBase 的扩展是横向的,横向扩展是指在扩展时不需要提升服务器本身的性能,只需添加服务器到现有集群即可。
HBase 表根据 Region 大小进行分区,分别存在集群中不同的节点上,当添加新的节点时,集群就重新调整,在新的节点启动 HBase 服务器,动态地实现扩展。这里需要指出,HBase 的扩展是热扩展,即在不停止现有服务的前提下,可以随时添加或者减少节点。
使用场景对比
- 对于经常需要修改原有的数据的场景,使用 HBase 进行存储
- 对于需要经常进行全表扫描、进行大批量的查询的场景,选择 HDFS
- 对于性能要求不高且只需要支持单条数据查询或者小批量数据进行查询的场景,两者均可
架构
Client
- 与 Zookeeper 通信
- Client 与 HMaster 进行通信进行管理类操作
- Client 与 HRegionServer 进行数据读写类操作
Zookeeper
- 保证 HMaster 有一个活着
- HRegionServer、HMaster 地址存储
- 监控 HRegionServer 状态,将 HRegionServer 信息通知 HMaster
- 元数据存储
HMaster
- Master 节点
- 为 HRegionServer 分配 HRegion
- 负责 HRegionServer 的负载均衡
- 发现失效的 HRegionServer 并重新分配其上的 HRegion
- HDFS 上的垃圾文件回收
- 处理用户对表的增删改查操作
HRegionServer
- Slaver 节点
- HBase 中最核心的模块,主要负责响应用户 I/O 请求,向 HDFS 文件系统中读写数据
- 维护 HMaster 分配给它的 HRegion,处理对这些 HRegion 的 I/O 请求
- HRegionServer 管理一系列 HRegion 对象
HRegion
- 一个表最开始存储的时候,是一个 HRegion
- 一个 HRegion 中会有个多个 HStore,每个 HStore 用来存储一个列簇
- HRegion 会随着插入的数据越来越多,进行拆分,默认大小是 10G 一个
HStore
- HBase 存储的核心,由 MemStore 和 StoreFile 组成
- 每一个 Column Family 对应了一个 HStore
- 用户写入数据的流程为:Client 访问 ZK,ZK 返回 HRegionServer 地址 -> Client 访问 HRegionServer 写入数据 -> 数据存入 MemStore,一直到 MemStore 满 -> Flush 成 StoreFile
HLog
- 在分布式系统环境中,无法避免系统出错或者宕机,一旦 HRegionServer 意外退出,MemStore 中的内存数据就会丢失,引入 HLog 就是防止这种情况
数据存储
HBase 是面向列的存储和权限控制的,它里面的每个列是单独存储的,且支持基于列的独立检索。
进行数据的插入和更新时,行存储会相对容易。
而进行行存储时,查询操作需要读取所有的数据,列存储则只需要读取相关列,可以大幅降低系统 I/O 吞吐量。
数据模型
- 表(Table)
HBase 中的数据以表的形式存储。同一个表中的数据通常是相关的,使用表主要是可以把某些列组织起来一起访问。表名作为 HDFS 存储路径的一部分来使用,在 HDFS 中可以看到每个表名都作为独立的目录结构。
- 行(Row)
在 HBase 表里,每一行代表一个数据对象,每一行都以行键(Row Key)来进行唯一标识,行键可以是任意字符串。在 HBase 内部,行键是不可分割的字节数组,并且行键是按照字典排序由低到高存储在表中的。在 HBase 中可以针对行键建立索引,提高检索数据的速度。
- 列族(Colunm Family)
HBase 中的列族是一些列的集合,列族中所有列成员有着相同的前缀,列族的名字必须是可显示的字符串。列族支持动态扩展,用户可以很轻松地添加一个列族或列,无须预定义列的数量以及类型。所有列均以字符串形式存储,用户在使用时需要自行进行数据类型转换。
- 列标识(Column Qualifier)
列族中的数据通过列标识来进行定位,列标识也没有特定的数据类型,以二进制字节来存储。通常以 Column Family:Colunm Qualifier 来确定列族中的某列。
- 单元格(Cell)
每一个行键、列族、列标识共同确定一个单元格,单元格的内容没有特定的数据类型,以二进制字节来存储。每个单元格保存着同一份数据的多个版本,不同时间版本的数据按照时间先后顺序排序,最新的数据排在最前面。单元格可以用 <rowkey,column family:column="" qualifier,timestamp=""> 元组来进行访问。</rowkey,column>
- 时间戳(Timestamp)
在默认情况下,每一个单元格插入数据时都会用时间戳来进行版本标识。读取单元格数据时,如果时间戳没有被指定,则默认返回最新的数据;写入新的单元格数据时,如果没有设置时间戳,默认使用当前时间。每一个列族的单元数据的版本数量都被 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
MongoDB 是一种文档数据库,文档是处理信息的基本单位。一个文档可以很长、很复杂,可以无结构,与字处理文档类似。它常用于日志的采集和存储,小文件的分布式存储,类似互联网微博应用的数据存储。
适用场景
- 网站数据:非常适合实时的插入、更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。
- 缓存:由于性能很高,也适合作为信息基础设施的缓存层;在系统重启之后,由 Mongo 搭建的持久化缓存可以避免下层的数据源过载
- 大尺寸、低价值的数据:使用传统的关系数据库存储一些数据时可能会比较贵,在此之前,很多程序员往往会选择传统的文件进行存储
- 高伸缩性的场景:非常适合由数十或者数百台服务器组成的数据库
- 用于对象及 JSON 数据的存储:Mongo 的 BSON 数据格式非常适合文档格式化的存储及查询
不适用的场景
- 高度事物性的系统:例如银行或会计系统,传统的关系型数据库目前还是更适用于需要大量原子性复杂事务的应用程序
- 传统的商业智能应用:针对特定问题的 BI 数据库会对产生高度优化的查询方式,对于此类应用,数据仓库可能是更合适的选择
- 需要SQL的问题
MapReduce
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 组成:
- NameNode:记录了文件是如何被拆分成 Block 以及这些 Block 都存储到了那些 DateNode 节点
- NameNode:保存了文件系统运行的状态信息
- DataNode:存储被拆分的Blocks
- Secondary NameNode:帮助 NameNode 收集文件系统运行的状态信息
- JobTracker:当有任务提交到 Hadoop 集群的时候负责 Job 的运行,负责调度多个 TaskTracker
ap/Reduce 模型更通用,让 Map 和 Reduce 之间的界限更模糊,数据交换更灵活,磁盘的读写更少,以便更方便地描述复杂算法,取得更高的吞吐量。
框架
Hadoop 的集群主要由 NameNode,DataNode,Secondary NameNode,JobTracker,TaskTracker 组成:
[外链图片转存中...(img-3o6vBcPu-1704534366287)]
- NameNode:记录了文件是如何被拆分成 Block 以及这些 Block 都存储到了那些 DateNode 节点
- NameNode:保存了文件系统运行的状态信息
- DataNode:存储被拆分的Blocks
- Secondary NameNode:帮助 NameNode 收集文件系统运行的状态信息
- JobTracker:当有任务提交到 Hadoop 集群的时候负责 Job 的运行,负责调度多个 TaskTracker
- TaskTracker:负责某一个 Map 或者 Reduce 任务