4、HBase 分布式数据库

版权声明: https://blog.csdn.net/KamRoseLee/article/details/83958378

一、HBase 定义

HBase 是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统。适合于存储大表数据(表的规模可以达到数十亿行以及数百万列),并且对大表数据的读、写访问可以达到实时级别;利用 Hadoop HDFS(Hadoop Distributed File System)作为其文件存储系统,提供实时读写的分布式数据库系统;利用 ZooKeeper 作为协同服务。

Hbase 作为一个分布式的大数据数据库系统,首先提供的就是高可靠性,由于在大数据系统中,我们需要保证整体数据的安全性和可靠性,所以 Hbase 也提供了自己所需要的相关的安全性保障,首先,Hbase 借力于外部组件,比如 HDFS, Hbase 将自己的文件写入到 HDFS 中,这就借助 HDFS 的数据安全性保障,保证了其数据的可靠性存储,并且主备集群之间的容灾能力可以增强 HBase 数据的高可用性, 主集群提供数据服务,备用集群提供数据备份,当主集群出现故障时,备集群可以提供数据服务。这里 Hbase 通过主备的形式来进行数据的安全性保障,实际上是借鉴了存储中主备容灾的相关思想,我们可以通过存储设备来实现应用级别的容灾保护,也可以通过 HBase 自己的进程来实现相关的容灾性能。HBase 集群容灾作为提高 HBase 集群系统高可用性的一个关键特性,为 HBase 提供了实时的异地数据容灾功能。它对外提供了基础的运维工具,包含灾备关系维护,重建,数据校验,数据同步进展查看等功能。为了实现数据的实时容灾,可以把本 HBase 集群中的数据备份到另一个集群。如上所说的是我们针对于应用和数据的保护, 那么进程的保护和 HDFS 很类似,组价内的进程底层次组件依赖于高层次组件来进行容灾保护,比如 Region 失效之后,需要 HMaster 来进行相关的迁移等操作, 那么高层次的组件依赖于 Zookeeper 来进行保护。这样就在进程上做了保护。高性能主要是面向于用户的实际读取操作,和其他相关的组件一样,Hbase 作为大数据的存储系统,一样是读多写少的这种应用场景,所以如何实现高效率的读取操作就是 Hbase 所需要做的很重要的一个工作。那么 Hbase 首先通过了分布式的集群保证了整体性能能够满足需求,其次还根据了 key-value 的形式保障了整体读取的高效性,另外 Hbase 还支持二级索引。

HBase    是一个 Key-Value    类型的分布式存储数据库。每张表的数据,是按照RowKey 的字典顺序排序的,因此,如果按照某个指定的 RowKey 去查询数据,或者指定某一个 RowKey 范围去扫描数据时,HBase 可以快速定位到需要读取的数据位置,从而可以高效地获取到所需要的数据。在实际应用中,很多场景是查询某一个列值为 XXX 的数据。HBase 提供了 Filter 特性去支持这样的查询,它的原理是:按照 RowKey 的顺序,去遍历所有可能的数据,再依次去匹配那一列的值,直到获取到所需要的数据。可以看出,可能仅仅为了获取一行数据,它却扫描了很多不必要的数据。因此,如果对于这样的查询请求非常频繁并且对查询性能要求较高,使用 Filter 无法满足这个需求。我们传统的时候,进行数据索引都是通过 key 值来进行的,那么我们可以根据自己所关注的相关列,对其创建一个二级索引表,这次我们在进行索引的时候,就可以直接根据二级索引表来查找匹配的数据,而不是根据 Rowkey 的值遍历数据进行查找。和传统的数据库不同的是,传统数据库是面向于行的存储,我们也可以称之为是面向于业务的,这个时候我们建立表格,都是预先定义好列的,然后向里面一行一行的添加数据信息。这样的话,拓展性会很差。不能完全适应大数据的相关处理。那么在 Hbase 中,我们采用了面向于列的存储,那么这样存储的话,底层是按照列的形式来维护数据,进行实际的存储操作,我们也可以进行属性的拓展。那么在实际的业务中,面向于行的存储多用于实际的业务操作,因为业务中,我们添加信息都是按照行存储的思维来进行的,一次性添加一行信息。面向于列的存储主要用于数据分析和数据挖掘,我们在进行分析的时候主要是关注某一个属性对于分析结果的影响,比如,年龄对于购买电子产品的一个相关关联性,这个时候我们的关注点就是年龄属性和是否会购买的结果,而对于其他的属性就不会更多的关注。面向于行的存储,一次可以完整的读取出所有的信息,但是在进行数据分析或者用户在进行条目读取时有特殊需求的情况下,数据就会多读取一部分。如果是面向于列的存储,对于关注点很明确的数据请求会很适合,但是如果需要完整的读取出某一个数据,就必须需要下发多次数据读取请求。

HBase 适合具有如下需求的应用:(1)海量数据(TB、PB)

海量数据的适应性主要取决于两个角度,首先第一个就是 Hbase 借力于 HDFS 组件,可以借助到 HDFS 的存储无限拓展能力。虽然 HDFS 可以无限拓展,但是 Hbase 本身能否承载这么多数据的表格加载也是一个需要考量的问题,所以 Hbase 本身也提供了对于海量数据的支持能力,尤其体现在性能方面,为了能够正常的使用和查询读取数据,那么在海量数据的情况下,Hbase 提供了非常优秀的基于 key-value 形式的索引,并且华为自身还做了对应特性的增强,添加了二级索引机制。

(2)高吞吐量

高吞吐量主要体现在数据的流式导入和导出等机制,HDFS 虽然给 Hbase 提供了海量数据的基础,但是 Hbase 本身也需要能够接收和处理这些数据,就好比于水管,总的水管容量再大,分枝水管过细,水流量也不会很高。所以 Hbase 在自身的基础上,就加强了对于海量数据的高吞吐量支持。

(4) 需要在海量数据中实现高效的随机读取实现方式为 key-value 和二级索引机制

扫描二维码关注公众号,回复: 4047042 查看本文章

(4)需要很好的性能伸缩能力这里主要体现的就是基于列存储的良好的拓展性。

(5)能够同时处理结构化和非结构化的数据(6)不需要完全拥有传统关系型数据库所具备的 ACID 特性

ACID 原则是数据库事务正常执行的四个特性,分别指原子性、一致性、独立性及持久性。

(1) 原子性(Atomicity):指一个事务要么全部执行,要么不执行.也就是说一个事务不可能只执行了一半就停止了.比如你从取款机取钱,这个事务可以分成两个步骤:1 划卡,2 出钱.不可能划了卡,而钱却没出来.这两步必须同时完成.要么就不完成。

(2) 一致性(Consistency):指事务的运行并不改变数据库中数据的一致性.例如,完整性约束了 a+b=10,一个事务改变了 a,那么 b 也应该随之改变。

(3) 独立性(Isolation):事务的独立性也有称作隔离性,是指两个以上的事务不会出现交错执行的状态.因为这样可能会导致数据不一致。

(4) 持久性(Durability):事务的持久性是指事务执行成功以后,该事务所对数据库所作的更改便是持久的保存在数据库之中,不会无缘无故的回滚。

二、数据结构

(1)结构化数据:具有固定的结构,属性划分,以及类型等信息。我们通常所理解的关系型数据库中所存储的数据信息,大多是结构化数据,如职工信息表,拥有 ID、Name、Phone、Address 等属性信息。通常直接存放在数据库表中。数据记录的每一个属性对应数据表的一个字段。

(2)非结构化数据:无法用统一的结构来表示。如文本文件、图像、视频、声音、网页等信息。数据记录较小时(如 KB 级别),可考虑直接存放到数据库表中(整条记录映射到某一个列中),这样也有利于整条记录的快速检索。数据较大时,通常考虑直接存放在文件系统中。数据库可用来存放相关数据的索引信息。(3)半结构化数据:具有一定的结构,但又有一定的灵活可变性。典型如 XML、 HTML 等数据。其实也是非结构化数据的一种。可以考虑直接转换成结构化数据进行存储。根据数据记录的大小和特点,选择合适的存储方式。这一点与非结构化数据的存储类似。

行存储和列存储概念(1)按行存储类型:数据按行存储在底层文件系统中。通常,每一行会被分配固定的空间。优点:有利于增加/修改整行记录等操作;有利于整行数据的读取操作。缺点:单列查询时,会读取一些不必要的数据。

(2)按列存储类型:数据以列为单位,存储在底层文件系统中。

优点:有利于面向单列数据的读取/统计等操作。缺点:整行读取时,可能需要多次 I/O 操作。

HBase 包含模块:(1)HMaster

在 HA 模式下,包含主用 Master 和备用 Master。

①主用 Master:负责 HBase 中 RegionServer 的管理,包括表的增删改查; RegionServer 的负载均衡,Region 分布调整;Region 分裂以及分裂后的 Region 分配;RegionServer 失效后的 Region 迁移等。

②备用 Master:当主用 Master 故障时,备用 Master 将取代主用 Master 对外提供服务。故障恢复后,原主用 Master 降为备用。

和 HDFS 不同的是,在 HDFS 中,NameNode 的备用节点是在工作状态的,并且会执行元数据持久化的操作,但是在 Hbase 中,备 Hmaster 是没有承担任何工作的,只处于是一个简单的热备状态。和以往的逐层进行管理的模型不同的是, HMaster 作为最高层的管理进程,除了负责 regionServer 的管理以外,还对底层的 Region 进行了管理,由于 Hbase 认为 RegionServer 并不安全,所以就将 Region 的管理放在了自身进行维护,这个主要原因是 RegionServer 作为 Region

的管理,是没有备份进程存在的,一旦 RegionServer 出现了故障,那么底层的 Region 就无法进行访问了。而 HMaster 将 Region 放在自身进行维护之后,一旦 RegionServer 出现了故障,那么就可以直接将 Region 进程进行迁移操作,并且将对应的读写权限交给其他的 RegionServer,这种情况只限进程出现故障的情况,如果是节点整体的故障,根据情况如果数据是存储在 HDFS 上的,那么根据 HDFS 的副本机制,数据还是可以正常找回的,如果是 Hbase 自身维护存储数据,那么同样 Region 也是会故障的。

(2)RegionServer

RegionServer 负责提供表数据读写等服务,是 HBase 的数据处理和计算单元。

RegionServer 一般与 HDFS 集群的 DataNode 部署在一起,实现数据的存储功能。就像如上所说,RegionServer 这里需要负责的是对 Region 的读写,这个很像是

DataNode,只有使用的权限,没有管理的权限。由于 RegionServer 本身在设计时就没有设计管理的权限,所以对于 RegionServer 来说,虽然称之为RegionServer,但是其实际上并没有服务管理的含义只有核心处理的功能,相当于每次用户提交了关于数据库的读写请求之后,RegionServer 只做了相关的处理操作。受理用户的请求,对用户的命令进行解析,并且转化为对应的读写操作和 Zookeeper 以及 Region 进行相关的任务操作执行,并且将最终执行返回的结果进行整合,并返回用户进行查看。

HBase 协作组件: ZooKeeper

ZooKeeper 为 HBase 集群中各进程提供分布式协作服务。各 RegionServer 将自己的信息注册到 Zookeeper 中,主用 Master 据此感知各个 RegionServer 的健康状态。

这里我们说 Zookeeper 提供的是分布式的协作服务,所谓说协作服务,主要体现在进程安全性和数据查询两个角度。所谓说进程安全性就是由 Zookeeper 保证整体进程的运行安全,这方面上 Zookeeper 主要做两件事,第一件事就是在 Hbase 进程启动之初,需要裁决主备 HMaster 进程,决定由哪一个进程来提供服务,另外一个进程进入到热备状态。第二件事就是做 HBASE 的 MetaRegion 进程的同步工作。由于整体 Hbase 维护的时候都是维护的一个整体命名空间。所以在实际上进行数据的读写操作时,如果按照动态子树的思想进行数据的查询工作,也就是指将元数据信息分布到各个节点进行维护,这样做就可能会导致读写的延迟增加,造成数据库修改的低效,毕竟在数据库中,对于用户业务或者是对延迟有要求的业务来说,延迟增加是一个致命的问题。所以为了保证整体查询的高效性,需要元数据被整体维护,而不是切分维护。所以我们选择将元数据存放在 Zookeeper 上,这是由于 Zookeeper 本身可以不仅提供高可靠性的数据安全保证,而且在进行元数据查询等操作的时候还可以提供一个高效的多进程并发的查询环境。

为什么不将元数据 Region 存放在 HMaster 上?

这个问题就涉及到了一个整个 Hadoop 设计时的核心问题,就是如何保证进程的安全性?在前面的文档中,我们也已经解释过,对于 Hadoop 来说,它本身是认为硬件永远不可靠,所以 Hadoop 保证自身安全性和稳定性是不会使用到任何的硬件技术的,所以它就需要通过自身机制来达到可靠性保护的目的。目前 Hadoop 主要使用的方法就是分层保护,一个组件内,底层的进程由高层进程来保护,高层进程由 Zookeep 来保护,Zookeeper 由自身机制提供高容错性和高可靠性保护。例如在 Hbase 中,RegionServer 安全性由 HMaster 保护,HMaster 由 Zookeeper

保护。那么元数据作为 Hbase 中重要的数据信息,为了能够提供一个高效的读写访问,Hbase 是将元数据文件加载进内存中进行读写的,但是这样做一旦出现问题就会导致数据丢失(数据库的持久化问题),如果我们将元数据加载进 HMaster 进程进行访问,主备进程本身其实可以有保护数据的条件,但是实际上由于备进程是处于不使用状态的,所以仅仅只有一个进程维护和管理元数据是存在安全隐患的,所以我们需要一个安全的并且存在数据多副本的机制来进行相关的保护,这个时候就会使用到 Zookeeper 了,Zookeeper 不仅可以提供数据的安全性保证而且进程是存在副本机制的。所以我们会把数据写入到 Zookeeper 中而不是HMaster 中进行保护。

HDFS

HDFS 为 HBase 提供高可靠的文件存储服务,HBase 的数据全部存储在 HDFS 中。实际上,我们可以发现在 Hbase 中,很多关于保护的相关操作都是由外部组件来实现的,Hbase 实现了一个非常良好的组件直接的协同交互,这样也可以保证相同的功能不会在组件之间产生冗余的情况。所以关于数据的保护,Hbase 对数据的保护其实都是由 HDFS 来实现的,具体就可以理解为是 HDFS 的多数据副本机制来实现的。

1.Region

将一个数据表按 Key 值范围横向划分为一个个的子表,实现分布式存储。这个子表,在 HBase 中被称作“Region”。每一个 Region 都关联一个 Key 值范围,即一个使用 StartKey 和 EndKey 描述的区间。事实上,每一个  Region  仅仅记录  StartKey  就可以了,因为它的EndKey 就是下一个 Region 的 StartKey。

数据库中实际记录的就是一个个的表格,在实际进行存储的时候,因为表的规模比较大,那么在进行存储的时候,如果整表进行维护那么对于数据的快速查找就会产生对应的问题。但是如果要是一条一条对数据进行维护,相应的维护开销又会很大,所以我们选择将一个完整的表划分为多个分区进行维护,按照几行几行的形式,那么每一个分区我们就称之为是一个 Region,按照 Region 进行分区我们可以保证对数据一个较好的维护。

Region 分为元数据 Region 以及用户 Region 两类。Meta Region 记录了每一个 User Region 的路由信息。读写 Region 数据的路由,包括如下几步:

(1) 找寻 Meta Region 地址。(2) 再由 Meta Region (3) 找寻 User Region 地址。

这里需要说明的是对于 Region 的操作我们实际上分为了两部分,一般来说,我们会将对数据的操作引擎和元数据放在一起,但是在 Hbase 中,我们没有这么做, 具体原因前面已经说明,Region 分为了数据操作和元数据操作两个部分,一个用户如果想要对 Region 进行读写,那么首先需要做的就是向 Zookeeper 查询元数据,之后就需要由 RegionServer 来执行具体的读写操作。用户 Region 就相当于是数据,那么路由其实指的是对数据存储的路径信息,和其他相关的属性信息, 由于 Zookeeper 维护 MetaRegion,所以元数据在整合起来之后,如何根据元数据的信息查找到数据的位置就需要详细的区分,这时候 MetaRegion中记录的地址信息就被称之为是路由信息,通过 MetaRegion 中的路由信息进行寻址就可以找到对应数据的存储位置。那么存储位置主要有以下几部分,机架号,节点号, 具体的存储逻辑位置三个大的部分。

RegionServer 是 HBase 的数据服务进程。负责处理用户数据的读写请求。 Region 被交由 RegionServer 管理。实际上,所有用户数据的读写请求,都是和 RegionServer 上的 Region 进行交互。

Region 可以在 RegionServer 之间发生转移。

RegionServer 这里只负责做关于 Region 的读写操作,但是具体的维护和管理操作是不交由 RegionServer 来进行维护的,这个思想就很类似于 MapReduce 中的 NM 节点,自身拥有对资源的所有权,但是管理权却不再自身,需要交由 Yarn 中的 RM 来进行统一集中的管理。RegionServer 作为对数据进行操作的核心驱动器,只负责了执行和监控任务的操作,其他的都不负责。RegionServer 一般都是和具体的 Region 部署在一个节点上的,所以从逻辑上来讲,本身 RegionServer 是对 Region 有管理权的,但是实际上在进行相关的操作的时候管理权却被 HMaster 回收了,这么做的主要原因是由于 RegionServer 是没有具体的安全保护机制的,属于单进程。所以一旦 RegionServer 出现问题就会导致整体数据元数据丢失。RegionServer 在将对 Region 的元数据管理权交给 zookeeper 之后,首先保证了元数据的整体安全维护,而且在实际上 RegionServer 出现故障之后,我们就可以直接将元数据中的路由信息改变为其他的节点,相当于是将数据的读写执行权交给了其他的节点,这样做就可以实现快速的故障迁移和高可靠性的安全保证。

2.HMaster

HMaster 进程负责管理所有的 RegionServer。(1)新 RegionServer 的注册。

(2)     RegionServer Failover 处理。

(3)负责建表/修改表/删除表以及一些集群操作。

就像之前所说的, 底层进程的安全性是交由上层进程实现的, 所以在这里 HMaster 的第一个作用就是需要保证底层进程的安全,说到安全性的保证,其实也算不上保证             RegionServer     的安全, HMaster        主要是创建和注册RegionServer    的信息,保证  RegionServer         的合法性,并且需要监控

RegionServer 的健康状况, 一旦 HMaster 检测到 RegionServer 出现了故障和问题,这个时候就需要进行故障的迁移,由于如上所说的,RegionServer 只涉及对 Region 的操作,对于实际的数据维护等操作都不运行,所以在RegionServer 进程出现故障之后 HMaster 在做 Failover 故障切换时无需对数据进行迁移的操作,只需要将 Zookeeper 中的元数据路由信息做一个更改即可,将对应的路由指针改向迁移后的目标 RegionServer 即可。这样就可以实现快速可靠的故障迁移操作。

HMaster 进程负责所有 Region 的转移操作。(1)新表创建时的 Region 分配。

(2)运行期间的负载均衡保障。

(3)     RegionServer Failover 后的 Region 接管。

HMaster 进程有主备角色。集群可以配置两个 HMaster 角色,集群启动时,这些 HMaster 角色通过竞争获得主 HMaster 角色。主 HMaster 只能有一个,备 HMaster 进程在集群运行期间处于休眠状态,不干涉任何集群事务。主备HMaster 的裁决交由 Zookeeper 决定。

HMaster 除了我们上面所说的对 RegionServer 的管理之外,对于 Region 的数据操作和维护也是由 HMaster 来做的,所以整体来讲,Region 的元数据是由Zookeeper 维护和管理,Region 的数据和操作是由 HMaster 来管理,作为RegionServer,只负责了读写等动作的执行。就像之前所说的,HMaster 负责了RegionServer 在故障情况下的 Region 迁移操作,那么 HMaster 首先就需要拥有对于 Region 的管理权,这里 HMaster 做的工作主要就是对于 Region 的创建、分配。包括节点在维护 Region 的时候,为了保证不会出现某些节点的压力过大的问题,HMaster 还需要做负载均衡的操作,保证整体集群的压力基本均衡,这个时候就需要对 Region 做对应的工作分配,将 RegionServer 维护的 Region 的压力和开销均衡到每一个节点。另外在 RegionServer 出现故障之后,如上我们需要做对应的故障迁移。

ColumnFamily 是 Region 的一个物理存储单元。同一个 Region 下面的多个ColumnFamily,位于不同的路径下面。ColumnFamily 信息是表级别的配置。也就是说,同一个表的多个 Region,都拥有相同的 ColumnFamily 信息(例如,都有两个 ColumnFamily,且不同 Region 的同一个 ColumnFamily 配置信息相同)。

ColumnFamily 列族,一个表在水平方向上由一个或多个 ColumnFamily 组成。一个 CF(ColumnFamily)可以由任意多个 Column 组成。Column 是 CF 下的一个标签,可以在写入数据时任意添加,因此 CF 支持动态扩展,无需预先定义 Column 的数量和类型。HBase 中表的列非常稀疏,不同行的列的个数和类型都可以不同。和传统的行存储相比,列存储更适合用于数据分析,而且在大数据的环境下,我们可能需要随时对列进行相关的操作,比如拓展和缩减。那么这个时候如果按照传统的行存储形式来进行相关的结构设计就会出现无法拓展的情况,因为行存储的一大典型特点就是需要在创建表的时候预先定义好列的结构,所以这个时候我们必须要做的就是对列进行模块化操作,所以列族在这个时候就出现了,通过列族的模块化设计,我们就可以实现对列的相关操作,保证整体的数据的拓展和属性维度的灵活调动,在列族下,我们还设计了列这一层的概念,相当于是多个列构成了一个列族,具体原因就和 Region 是一样的,对于大型的数据库,列维度也是很多的,这个时候如果针对每一个列都去进行维护,那么就和行维护一样产生很大的开销,所以我们可以把行存储和列存储做一个层次的对应比,行对应列,

Region 对应 ColumnFamily 的层次。那么在实际中,一个 Region 中会包含多个ColumnFamily,同样,多个 Region 也会拥有相同的 ColumeFamily。

4.key-value

KeyValue 具有特定的结构。Key 部分被用来快速的检索一条数据记录,Value 部分用来存储实际的用户数据信息。

KeyValue 作为承载用户数据的基本单元,需要保存一些对自身的描述信息,例如,时间戳,类型等等。那么,势必会有一定的结构化空间开销。

和传统的数据库或者是文件系统不同的是,Hbase 中的 key-value 是一起存储的,通过字段的形式一起以数据的类型存储到实际的存储空间中,key-value 分为了三部分,第一部分记录的是 key 值的长度和 value 值的长度,第二部分是 key 值的具体字段(行键值长度,行键值,列族长度,列族值,时间戳,key 类型),最后是实际的 value 数据。那么 key 值里面的行键值,列族值,以及时间戳就是我们事先数据查询的三个重要字段,我们又称之为三维有序存储。hbase 所谓的三维有序存储的三维是指:rowkey(行主键),column key(columnFamily+qualifier),timestamp(时间戳)三部分组成的三维有序存储。(1) rowkey,我们知道 rowkey 是行的主键,而且 hbase 只能用个 rowkey,

或者一个 rowkey 范围即 scan 来查找数据。所以 rowkey 的设计是至关重要的,关系到你应用层的查询效率。我们知道,rowkey 是以字典顺序排序的。而存储的字节码,字典排序,我们知道,如果是字母,那就是字母的顺序,比如,有两个rowkey,rowkey1:aaa222,rowkey2:bbb111,那么 rowkey1 是排在 rowkey2 前面的,因为按字典,a 排在 b 前面,如果 rowkey2 的第一位也是 a,那么就根据第二位来比较,如果还相同,则比较第三为,后面同样。这个理解了,我们在根据 rowkey 范围查询的时候,我们一般是知道 startRowkey,如果我们通过 scan 只传 startRowKey:d 开头的,那么查询的是所有比 d 大的都查了,而我们只需要 d 开头的数据,那就要通过 endRowKey 来限制。我们可以通过设定 endRowKey 为: d 开头,后面的根据你的 rowkey 组合来设定,一般是加比 startKey 大一位。比如说 rowkey 设计为:用户 ID-日期,那么查某个用户某天的数据,startKEY 为3231-20121212,endKey 为:3231+20121213,那么你查到的就是用户为 3231 在

20121212 这一天的数据。(2) columnkey

columnkey 是第二维,数据按 rowkey 字典排序后,如果 rowkey 相同,则是根据 columnkey 来排序的,也是按字典排序。

我们在设计 table 的时候要学会利用这一点。比如我们的收件箱。我们有时候需要按主题排序,那我们就可以把主题这设置为我们的 columnkey,即设计为columnFamily+主题.,这样的设计。

(3)     timestamp

timestamp 时间戳,是第三维,这是个按降序排序的,即最新的数据排在最前面。这个就没有什么说的了。

5.关于各个组件之间的逻辑关系说明

从模块进程来说,HBase 分为 HMaster 和 RegionServer。

RegionMaster 是为了维护 Region 的,其实也就是为了保证基本的表增删改查工作的。并且服务是维护在 DataNode 上的。我们在 HBase 上创建了一个表,那么这个表会按照键值的范围横向划分成一个个的子表,就成为 Region。根据键值的大小不同,我们所维护的 Region 的大小也不同,Region 分为 METAregion 和用户 region 两种需要注意的是,region 的创建和迁移等操作是由 HMaster 来维护的,region 的数据维护和读写操作是交由 regionserver 来执行的。这样做的话,一旦某一个 regionserver 失效,我们可以将该 region 进行转移。在其他的节点上进行维护。HMaster 主要做的工作就是创建和维护 RegionServer,并且给这些 Region Server 来分配 Region,并且在 RegionServer 出现问题的时候进行 Region 的迁移操作。对于 HBASE 来说,一个表按照横向进行划分,都由物理上的 column 进行存储,任意个 column 之间构成了 columnfamily,由于 columnfamily 的基本单位是表,所以基于表横向创建的多个 region 他们就会拥有相同的 columnfamily 信息。key 值划定了 region,之后由出现了 keyvalue,我们在底层存储时,数据都是按照 keyvalue 进行的组织和维护,用户可以通过 key 值进行快捷的访问和查找。

Region,CloumnFamily 和 key-value 的关系如下所示:

上图主要描述的是 Hbase 的数据构成方式,而实际上的 Region 进程构成图如下所示:

(1) Store:一个 Region 由一个或多个 Store 组成,每个 Store 对应图中的一个 Column Family。

(2) MemStore:一个 Store 包含一个 MemStore,MemStore 缓存客户端向 Region插入的数据。当 RegionServer 中的 MemStore 大小达到配置的容量上限时,RegionServer 会将 MemStore 中的数据“flush”到 HDFS 中。

(3) StoreFile:MemStore 的数据 flush 到 HDFS 后成为 StoreFile。随着数据的插入,一个 Store 会产生多个 StoreFile,当 StoreFile 的个数达到配置的最大值时,RegionServer 会将多个 StoreFile 合并为一个大的StoreFile。

(4) Hfile:HFile 定义了 StoreFile 在文件系统中的存储格式,它是当前 HBase系统中 StoreFile 的具体实现。

(5) Hlog:HLog 日志保证了当 RegionServer 故障的情况下用户写入的数据不丢失,RegionServer 的多个 Region 共享一个相同的 Hlog。

四、HBase 写流程

客户端发起请求通过 ZooKeeper 寻找到 meta 表所在 RegionServer meta 表中记载着各个 User Region 信息(rowkey 范围,所在 RegionServer),通过 meta 表寻找所要写入的 Region 所在 RegionServer 请求按 RegionServer 和 Region 打包发送到 Region 所在 RegionServer,由该 RegionServer 具体处理数据写入。

 

Region 写流程:

(1)首先,RegionServer 会请求需要写入数据的 Region 的读写锁(2)获取到了读写锁之后,RegionServer 会继续请求所需要修改的行的行锁。

(3)行锁获取成功之后,RegionServer 就会将数据写入到内存中,并且再写完成之后释放掉对应的行锁。

(4)行锁释放后,数据操作将会写入日志中(write ahead log 预写日志)

(5)全部修改完成之后,RegionServer 就会释放掉对应的 Region 的读写锁。在具体的读写请求中,首先第一步需要做到的就是查找元数据,通过元数据我们就可以寻找到相关的数据具体的存储节点和存储位置。所以首先第一步作为提供接口的进程,client 受理客户的请求,将读写请求转发给 zookeeper,然后进行相关的操作,在写操作中,又分为了新写和读改写两种操作,如果是新写,那么我们需要向 zookeeper 申请写空间,创建一个元数据,如果是读改写,那么就进行的是查询和改写操作,不需要新申请写空间。

在向 Zookeeper 查询到元数据之后,我们需要做的就是向 RegionServer 发送请求,通过 RegionServer 进行具体相关的操作。

RegionServer 会再转发请求之前首先获取对应位置的权限,权限主要分为读写锁,在进行写操作的时候,由于 Hbase 属于数据库,所以要获取到对应的读锁和写锁,读锁的获取是为了保证其他进程数据的更新,写锁是为了保障数据的 ACID 特性不被破坏。在获取到读写锁之后,针对当前写操作需要做的更改,进程还需要获取写操作对应操作的行锁,然后获取到之后,数据就会被先行写入到内存中进行缓存。写完成之后先释放的行锁,之后写操作日志,然后最终释放 Region 锁也就是对应的读写锁。先写内存的原因:HBase 提供了一个 MVCC(多版本并发控制)机制,来保障写数据阶段的数据可见性。先写 MemStore 再写 WAL,是为了一些特殊场景下,内存中的数据能够更及时的可见。如果写 WAL 失败的话,MemStore 中的数据会被回滚。写内存可以避免多 Region 情形下带来的过多的分散 IO 操作。数据在写入到 MemStore 之后,也会顺序写入到 HLog 中,以保证数据的安全。

Flush 刷新写操作:刷新操作会触发数据从内存中写入到对应的 HFile 中,如下三种场景,会触发一个 Region 的 Flush 操作:

(1)该 Region 的 MemStore 的总大小,达到了预设的 Flush Size 阈值。这种场景下的 Flush 操作,通常仅瞬间堵塞用户的写操作。但如果超出预设 Flush Size 阈值过多的话,也可能会引起一小段时间的堵塞。

(2)RegionServer 的总内存大小超出了预设的阈值大小。这种场景下,在总内存没有降低到预设的阈值以下之前,可能会较长时间堵塞。

(3)当 WALs 中文件数量达到阈值时。内存数据缓存高低水位技术:Cache 高低水位是指 Cache 中存储脏数据的最高限制值和最低限制值。如果 Cache 的高、低水位设置不合理,会影响 Cache 的读写性能。脏数据(DirtyRead)是指源系统中的数据不在给定的范围内或对于实际业务毫无意义,或是数据格式非法,以及在源系统中存在不规范的编码和含糊的业务逻辑。通俗的讲,当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。在存储中,对于脏数据的理解其实说成是驻留在 Cache 中的数据比较合适,例如在 TCQ 和 NCQ 的情况下,Cache 中的脏数据就比较多。将脏数据缓存在 Cache 中可使 IO 得到充分整合与调度,降低延迟,提升性能,但当写 Cache 中缓存的 IO 脏数据的总量达到一定上限时,就需要加快数据刷盘的速度,避免由于写 Cache 缓存了过多脏数据不能接收前端下发的新的写 IO 请求。默认情况下,系统设置的低水位值为 20,在运行实际的业务,特别是随机写业务的时候,可适当的提升低水位值,如设置为 40 或者 50。高水位需根据实际业务的下发情况进行调整,重点关注调整后的性能变化状况和 Cache 命中率状况,针对随机写业务建议设置在 80 左右。刷盘线程每隔 1s 启动一次高低水位设置的建议:高水位不宜设置太高。如果高水位设置的值太高页面缓冲就小,如果前端 I/O 流量突然增加,会产生 I/O 波动和时延增大的现象,从而影响写性能。低水位不宜设置太低。如果低水位设置的值太低,会导致后端将缓存数据频繁写入硬盘,降低写性能。高低水位的间距不宜太小。如果取值间距太小,不能很好的利用后端带宽。系统推荐的 Cache 高水位和低水位的取值分别为 80%和 20%。 Compaction 压缩操作:

Compaction 的主要目的,是为了减少同一个 Region 同一个 ColumnFamily 下面的小文件数目,从而提升读取的性能。

Compaction 分为 Minor、Major 两类:

Minor:小范围的 Compaction。有最少和最大文件数目限制。通常会选择一些连续时间范围的小文件进行合并。

Major:涉及该 Region 该 ColumnFamily 下面的所有的 HFile 文件。Major Compaction 过程中,会清理被删除的数据。

MinorCompaction 选取文件时,遵循一定的算法。

Split 分裂操作:

普通的 RegionSplit 操作,是指集群运行期间,某一个 Region 的数据大小超出了预设的阈值,则需要将该 Region 自动分裂成为两个子 Region。

分裂过程中,被分裂的 Region 会暂停读写服务。由于分裂过程中,父 Region 的数据文件并不会真正的分裂并重写到两个子 Region 中,而是仅仅通过在新 Region 中创建引用文件的方式,来实现快速的分裂。因此,Region 暂停服务的时间会比较短暂。

客户端侧所缓存的父 Region 的路由信息需要被更新。

这里所指的分裂,就相当于重新创建了两个新的逻辑 Region,这两个新的 Region 没有存储数据,存储的只是原有数据的映射,将旧的 Region 数据添加了一个映射到自身,数据本身还是存储在原先的 Region 中。当新的写请求下达的时候,数据就会写到新的 Region 中。

五、HBase 读流程

(1)客户端发起请求

(2)通过 ZooKeeper 寻找到 meta 表所在 RegionServer meta 表中记载着各个 UserRegion 信息(rowkey 范围,所在 RegionServer),通

过 rowkey 查找 meta 表,获取所要读取的 Region 所在 RegionServer

(3)请求发送到该 RegionServer,由其具体处理数据读取(4)数据读取返回到客户端。

Scanner 查询器:

Scanner 可以理解为一个栈,一个 store 里面有 menstore 和 hfile,当我们执行查询的时候,就会打开 menstore 的栈和各个 hfile 的栈,先从各个栈中 poll 出一条数据,然后做排序,next 返回排序后的第一个数据,然后该栈继续 poll 出一条数据,继续排序。在寻找到 rowkey 所对应的 RegionServer 和 Region 之后,需要打开一个查找器

Scanner,由其具体执行查找数据,Region 中会包含内存数据 MemStore,文件数据 Hfiles,那么在 openscanner 的时候就需要分别读取这两块数据,打开对应不同的 scanner 做查询操作。

OpenScanner 的过程,会为 MemStore,以及各个 HFile 创建所对应的 Scanner:

MemStore 对应的 Scanner 为 MemStoreScanner。HFile 对应的 Scanner 为StoreFileScanner。

BloomFilter 被用来优化一些随机读取的场景,即 Get 场景。它可以被用来快速的判断一条用户数据在一个大的数据集合(该数据集合的大部分数据都没法被加载到内存中)中是否存在。

BloomFilter 在判断一个数据是否存在时,拥有一定的误判率。但对于“用户数据 XXXX 不存在”的判断结果是可信的。

HBase 的 BloomFilter 的相关数据,被保存在 HFile 中。

数据在进行写入的时候,我们需要针对写入的数据进行反复的哈希计算,并且将对应的映射位改为 1,该位就是一个置位,没有什么实际意义,那么一旦有数据需要读取,我们可以针对需要读取的请求,进行数据哈希,之后和置位标志进行对比,如果为 1 表示存在,如果不为 1 表示不存在。

六、HBase 增强特性

1.二级索引

二级索引为 HBase 提供了按照某些列的值进行索引的能力。二级索引就是把要查找的列与 rowkey 关联成一个索引表。此时列成新的 rowkey,原 rowkey 成为 value。其实就是查询了 2 次。

在实际应用中,很多场景是查询某一个列值为 XXX 的数据。HBase 提供了 Filter 特性去支持这样的查询,它的原理是:按照 RowKey 的顺序,去遍历所有可能的数据,再依次去匹配那一列的值,直到获取到所需要的数据。可以看出,可能仅仅为了获取一行数据,它却扫描了很多不必要的数据。因此,如果对于这样的查询请求非常频繁并且对查询性能要求较高,使用 Filter 无法满足这个需求。

没有二级索引时,查找手机号“68XXX 的记录,必须按照 RowKey 做全表扫描,逐行匹配”“Mobile”字段,时延很大。有二级索引时,先查索引表,再定位到数据表中的位置,不用全表扫描,时延小。

2.HFS

在 Hadoop 生态系统中,无论是 HDFS,还是 HBase,均在面对海量文件的存储的时候,在某些场景下,都会存在一些很难解决的问题:

如果把海量小文件直接保存在 HDFS 中,会给 NameNode 带来极大的压力。由于 HBase 接口以及内部机制的原因,一些较大的文件也不适合直接保存到HBase 中。

HBase 文件存储模块(HBase FileStream,简称 HFS)是 HBase 的独立模块,它作为对 HBase 与 HDFS 接口的封装,应用在 FusionInsight HD 的上层应用,为上层应用提供文件的存储、读取、删除等功能。

HFS 的出现解决了需要在 HDFS 中存储海量小文件,同时也要存储一些大文件的混合的场景。简单来说,就是在 HBase 表中,需要存放大量的小文件(10MB 以下),同时又需要存放一些比较大的文件(10MB 以上)。

MOB 文件

在实际应用中,用户需要存储大大小小的数据,比如图像数据、文档。小于 10MB 的数据一般都可以存储在 HBase 上,对于小于 100KB 的数据,HBase 的读写性能是最优的。如果存放在 HBase 的数据大于 100KB 甚至到 10MB 时,插入同样个数的数据文件,其数据量很大,会导致频繁的 compaction 和 split,占用很多 CPU,磁盘 IO 频率很高,性能严重下降。

MOB 数据(即 100KB 到 10MB 大小的数据)直接以 HFile 的格式存储在文件系统上(例如 HDFS 文件系统),然后把这个文件的地址信息及大小信息作为 value 存储在普通 HBase 的 store 上,通过工具集中管理这些文件。这样就可以大大降低 HBase 的 compation 和 split 频率,提升性能。

图中 MOB 模块表示存储在 HRegion 上的 mobstore,mobstore 存储的是 key-value,key 即为 HBase 中对应的 key,value 对应的就是存储在文件系统上的引用地址以及数据偏移量。读取数据时,mobstore 会用自己的 scanner,先读取 mobstore 中的 key-value 数据对象,然后通过 value 中的地址及数据大小信息,从文件系统中读取真正的数据。

猜你喜欢

转载自blog.csdn.net/KamRoseLee/article/details/83958378