hbase 总结

HBase

什么是HBase

hbase 是一个基于java、开源、NoSql、非关系型、面向列的、构建与hadoop 分布式文件系统(HDFS)上的、仿照谷歌的BigTable的论文开发的分布式数据库。

简介:

列式存储数据库

列式存储数据库以列为单位聚合数据,然后将列值顺序地存入磁盘,这种存储方法不同于行式存储的传统数据库,行式存储数据库连续地存储整行。图1-1形象地展示了列式存储和行式存储的不同物理结构。

列式存储的出现主要基于这样一种假设:对于特定的查询,不是所有的值都是必需的。尤其是在分析型数据库里,这种情形很常见,因此需要选择一种更为合适的存储模式。

在这种新型的设计中,减少I/O只是众多主要因素之一,它还有其他的优点:因为列的数据类型天生是相似的,即便逻辑上每一行之间有轻微的不同,但仍旧比按行存储的结构聚集在一起的数据更利于压缩,因为大多数的压缩算法只关注有限的压缩窗口。

像增量压缩或前缀压缩这类的专业算法,是基于列存储的类型定制的,因而大幅度提高了压缩比。更好的压缩比有利于在返回结果时降低带宽的消耗。

值得注意的是,从典型RDBMS的角度来看,HBase并不是一个列式存储的数据库,但是它利用了磁盘上的列存储格式,这也是RDBMS与HBase最大的相似之处,因为HBase以列式存储的格式在磁盘上存储数据。但它与传统的列式数据库有很大的不同:传统的列式数据库比较适合实时存取数据的场景,HBase比较适合键值对的数据存取,或者有序的数据存取。

hbase 总结_第1张图片

HBase 适用场景

  • 半结构化或非结构化数据
    对于数据结构字段不够确定或杂乱无章很难按一个概念去进行抽取的数据适合用HBase。以上面的例子为例,当业务发展需要存储author的email,phone,address信息时RDBMS需要停机维护,而HBase支持动态增加。
  • 记录非常稀疏
    RDBMS的行有多少列是固定的,为null的列浪费了存储空间。而如上文提到的,HBase为null的Column不会被存储,这样既节省了空间又提高了读性能。
  • 多版本
    如上文提到的根据Row key和Column key定位到的Value可以有任意数量的版本值,因此对于需要存储变动历史记录的数据,用HBase就非常方便了。比如上例中的author的Address是会变动的,业务上一般只需要最新的值,但有时可能需要查询到历史值。
  • 大数据量
    当数据量越来越大,RDBMS数据库撑不住了,就出现了读写分离策略,通过一个Master专门负责写操作,多个Slave负责读操作,服务器成本倍增。随着压力增加,Master撑不住了,这时就要分库了,把关联不大的数据分开部署,一些join查询不能用了,需要借助中间层。随着数据量的进一步增加,一个表的记录越来越大,查询就变得很慢,于是又得搞分表,比如按ID取模分成多个表以减少单个表的记录数。经历过这些事的人都知道过程是多么的折腾。采用HBase就简单了,只需要加机器即可,HBase会自动水平切分扩展,跟Hadoop的无缝集成保障了其数据可靠性(HDFS)和海量数据分析的高性能(MapReduce)。

Hbase特点

  • 海量存储: 数据存储量极大
  • 列式存储: hbase 是根据列族来存储数据的,劣质下面可以有非常多的列,在创建表的时候就必须指定列族。
  • 扩展性强: 集群节点进行扩展
  • 数据的多版本: 每个单元的数据可以有多个版本,默认情况下版本自动为单元格数据插入时间戳
  • 稀疏性: 在列族中,可以指定任意多的列,在列数据为空的情况下,是不会占用存储空间的
  • 无固定模式:

Hbase 和 hive 的区别

HBase hive
类型 列式存储 数据仓库
内部机制 数据库引擎 MapReduce
增删改查 都支持 只支持导入和查询
schema 只需要预先定义列族,不需要具体到列,列可以动态添加 需要预先定义表格
应用场景 实时 离线处理
特点 以K-V形式存储 类sql

Hive和Hbase是两种基于Hadoop的不同技术–Hive是一种类SQL的引擎,并且运行MapReduce任务,Hbase是一种在Hadoop之上的NoSQL 的Key/vale数据库。当然,这两种工具是可以同时使用的。就像用Google来搜索,用FaceBook进行社交一样,Hive可以用来进行统计查询,HBase可以用来进行实时查询,数据也可以从Hive写到Hbase,设置再从Hbase写回Hive。

HBase 系统架构(⭐⭐⭐⭐⭐)

hbase 总结_第2张图片

架构角色

  • Hmaster
    • 1.监控RegionServer
    • 2.处理RegionServer故障转移
    • 3.处理元数据的变更
    • 4.处理region的分配或转移
    • 5.在空闲时间进行数据的负载均衡
    • 6.通过Zookeeper发布自己的位置给客户端
  • RegionServer
    • 1.负责存储HBase的实际数据
    • 2.处理分配给它的Region
    • 3.刷新缓存到HDFS
    • 4.维护Hlog
    • 5.执行压缩
    • 6.负责处理Region分片
  • Write-Ahead logs
    • 当hbase读写数据的时候,数据会先写在Write-Ahead logfile的文件中,然后再写入内存中。再系统出现异常的情况下可以根据这个文件重新构建
  • Region
    • Hbase 表的分片,HBase表会根据RowKey值被切分成不同的region存储在RegionServer中,在一个RegionServer中可以有多个不同的region。
  • Store
    • 一个Store对应HBase表中的一个列族(列簇, Column Family)。
  • MemStore
    • 内存存储,用来保存当前的数据操作,所以当数据保存在WAL中之后,RegsionServer会在内存中存储键值对。
  • Hfile
    • 在磁盘上保存原始数据的实际的物理文件,是实际的存储文件。StoreFile是以Hfile的形式存储在HDFS的。

HBase客户端

HBase客户端(Client)提供了Shell命令行接口、原生Java API编程接口、Thrift/REST API编程接口以及MapReduce编程接口。HBase客户端支持所有常见的DML操作以及DDL操作,即数据的增删改查和表的日常维护等。其中Thrift/REST API主要用于支持非Java的上层业务需求,MapReduce接口主要用于批量数据导入以及批量数据读取。

HBase客户端访问数据行之前,首先需要通过元数据表定位目标数据所在RegionServer,之后才会发送请求到该RegionServer。同时这些元数据会被缓存在客户端本地,以方便之后的请求访问。如果集群RegionServer发生宕机或者执行了负载均衡等,从而导致数据分片发生迁移,客户端需要重新请求最新的元数据并缓存在本地。

ZooKeeper

  • 实现Master高可用:通常情况下系统中只有一个Master工作,一旦Active Master由于异常宕机,ZooKeeper会检测到该宕机事件,并通过一定机制选举出新的Master,保证系统正常运转。
  • 管理系统核心元数据:比如,管理当前系统中正常工作的RegionServer集合,保存系统元数据表hbase:meta所在的RegionServer地址等
  • 参与RegionServer宕机恢复:ZooKeeper通过心跳可以感知到RegionServer是否宕机,并在宕机后通知Master进行宕机处理。
  • 实现分布式表锁:HBase中对一张表进行各种管理操作(比如alter操作)需要先加表锁,防止其他用户对同一张表进行管理操作,造成表状态不一致。和其他RDBMS表不同,HBase中的表通常都是分布式存储,ZooKeeper可以通过特定机制实现分布式表锁。

Master

Master主要负责HBase系统的各种管理工作:

  • 处理用户的各种管理请求,包括建表、修改表、权限操作、切分表、合并数据分片以及Compaction等。
  • 管理集群中所有RegionServer,包括RegionServer中Region的负载均衡、RegionServer的宕机恢复以及Region的迁移等。
  • 清理过期日志以及文件,Master会每隔一段时间检查HDFS中HLog是否过期、HFile是否已经被删除,并在过期之后将其删除。

RegionServer

RegionServer主要用来响应用户的IO请求,是HBase中最核心的模块,由WAL(HLog)、BlockCache以及多个Region构成。

  • WAL(HLog):HLog在HBase中有两个核心作用——其一,用于实现数据的高可靠性,HBase数据随机写入时,并非直接写入HFile数据文件,而是先写入缓存,再异步刷新落盘。为了防止缓存数据丢失,数据写入缓存之前需要首先顺序写入HLog,这样,即使缓存数据丢失,仍然可以通过HLog日志恢复;其二,用于实现HBase集群间主从复制,通过回放主集群推送过来的HLog日志实现主从复制。

  • BlockCache :HBase系统中的读缓存。客户端从磁盘读取数据之后通常会将数据缓存到系统内存中,后续访问同一行数据可以直接从内存中获取而不需要访问磁盘。对于带有大量热点读的业务请求来说,缓存机制会带来极大的性能提升。

    BlockCache缓存对象是一系列Block块,一个Block默认为64K,由物理上相邻的多个KV数据组成。BlockCache同时利用了空间局部性和时间局部性原理,前者表示最近将读取的KV数据很可能与当前读取到的KV数据在地址上是邻近的,缓存单位是Block(块)而不是单个KV就可以实现空间局部性;后者表示一个KV数据正在被访问,那么近期它还可能再次被访问。当前BlockCache主要有两种实现——LRUBlockCache和BucketCache,前者实现相对简单,而后者在GC优化方面有明显的提升。

  • Region:数据表的一个分片,当数据表大小超过一定阈值就会“水平切分”,分裂为两个Region。Region是集群负载均衡的基本单位。通常一张表的Region会分布在整个集群的多台RegionServer上,一个RegionServer上会管理多个Region,当然,这些Region一般来自不同的数据表。

    一个Region由一个或者多个Store构成,Store的个数取决于表中列簇(column family)的个数,多少个列簇就有多少个Store。HBase中,每个列簇的数据都集中存放在一起形成一个存储单元Store,因此建议将具有相同IO特性的数据设置在同一个列簇中。

    每个Store由一个MemStore和一个或多个HFile组成。MemStore称为写缓存,用户写入数据时首先会写到MemStore,当MemStore写满之后(缓存数据超过阈值,默认128M)系统会异步地将数据flush成一个HFile文件。显然,随着数据不断写入,HFile文件会越来越多,当HFile文件数超过一定阈值之后系统将会执行Compact操作,将这些小文件通过一定策略合并成一个或多个大文件。

HDFS

HBase底层依赖HDFS组件存储实际数据,包括用户数据文件、HLog日志文件等最终都会写入HDFS落盘。HDFS是Hadoop生态圈内最成熟的组件之一,数据默认三副本存储策略可以有效保证数据的高可靠性。HBase内部封装了一个名为DFSClient的HDFS客户端组件,负责对HDFS的实际数据进行读写访问。

hbase 组件

hbase优点

  • 容量巨大:HBase的单表可以支持千亿行、百万列的数据规模,数据容量可以达到TB甚至PB级别。传统的关系型数据库,如Oracle和MySQL等,如果单表记录条数超过亿行,读写性能都会急剧下降,在HBase中并不会出现这样的问题。

  • 良好的可扩展性:HBase集群可以非常方便地实现集群容量扩展,主要包括数据存储节点扩展以及读写服务节点扩展。HBase底层数据存储依赖于HDFS系统,HDFS可以通过简单地增加DataNode实现扩展,HBase读写服务节点也一样,可以通过简单的增加RegionServer节点实现计算层的扩展。

  • 稀疏性:HBase支持大量稀疏存储,即允许大量列值为空,并不占用任何存储空间。这与传统数据库不同,传统数据库对于空值的处理要占用一定的存储空间,这会造成一定程度的存储空间浪费。因此可以使用HBase存储多至上百万列的数据,即使表中存在大量的空值,也不需要任何额外空间。

  • 高性能:HBase目前主要擅长于OLTP场景,数据写操作性能强劲,对于随机单点读以及小范围的扫描读,其性能也能够得到保证。对于大范围的扫描读可以使用MapReduce提供的API,以便实现更高效的并行扫描。

  • 多版本:HBase支持多版本特性,即一个KV可以同时保留多个版本,用户可以根据需要选择最新版本或者某个历史版本。

  • 支持过期:HBase支持TTL过期特性,用户只需要设置过期时间,超过TTL的数据就会被自动清理,不需要用户写程序手动删除。

  • Hadoop原生支持:HBase是Hadoop生态中的核心成员之一,很多生态组件都可以与其直接对接。HBase数据存储依赖于HDFS,这样的架构可以带来很多好处,比如用户可以直接绕过HBase系统操作HDFS文件,高效地完成数据扫描或者数据导入工作;再比如可以利用HDFS提供的多级存储特性(Archival Storage Feature),根据业务的重要程度将HBase进行分级存储,重要的业务放到SSD,不重要的业务放到HDD。或者用户可以设置归档时间,进而将最近的数据放在SSD,将归档数据文件放在HDD。另外,HBase对MapReduce的支持也已经有了很多案例,后续还会针对Spark做更多的工作。

HBase的缺点

  • HBase本身不支持很复杂的聚合运算(如Join、GroupBy等)。如果业务中需要使用聚合运算,可以在HBase之上架设Phoenix组件或者Spark组件,前者主要应用于小规模聚合的OLTP场景,后者应用于大规模聚合的OLAP场景。

  • HBase本身并没有实现二级索引功能,所以不支持二级索引查找。好在针对HBase实现的第三方二级索引方案非常丰富,比如目前比较普遍的使用Phoenix提供的二级索引功能。

  • HBase原生不支持全局跨行事务,只支持单行事务模型。同样,可以使用Phoenix提供的全局事务模型组件来弥补HBase的这个缺陷。

HBase 数据模型(⭐⭐⭐⭐⭐)

表排版

一个hbase 表由一个或多个列族(CF)组成,一个列族又包含多个列(我们称列为列限定符,CQ),每列存储相应的值。和传统数据库不一样。hbase 表是稀疏表,一些列可能根本不存值。在这种情况下也不会存储null 值。而且该列不会添加到表中。一旦一个rowkey及相应的列值生成了,将会被存储到表中。

在HBase 中,人们使用很多单词来描述不同的部分:行、列、键、单元格、值、行键、时间戳等。为了确保我们说的是同一个事情,我们将对HBase 的术语进行统一,一行由很多列组成,全部由相同的键引用。 一个特定的列和一个键称为单元格。一个单元格可以有很多版本,由不同时间戳的版本来区分。单元格可以叫做键值对。因此,一行由一个键引用,每行由一组单元格组成,其中每一个单元格又由指定的行名和特定列名确定。

有值的列才会被存储到底层的文件系统。另外,即使在创建表时需要定义列族,也不需要提前定义列名称。当列族插入数据时,列名称可以动态生成。因此,在不同的行中会有可能存在数百万动态创建的列名。

为了更快的查询,键和列会按照字母排序存储在表中,同时也存储在内存中

表存储

表存储有多个方面 。第一个方面是HBase如何将单独的一个值存储至表中。第二个面活及如何将所有这些单元格存储在一起,以形成 一个表。

从外到内,一个表是由一个或者多个region组成,一个region由一个或者多个列族组成 ,一个列族由一个 store 组成, 一个store 由唯一的 memstore 加上一个或者多个HFile组成, HFlie又是由block组成,而block是由cell组成。
hbase 总结_第3张图片

region

所有的行以及相关的列一起形成了一张表。但是,为了提供可扩展以及快速随机访问的功能,HBase不得不将数据分布在多个服务器中。为了达到这个目的,表被分割成多个region存储,每个region将会存储一个指定区间的数据。region将会被分配到RegionServer以服务于每个region的内容。当新的region被创建后,过了配置的那段时间,HBase的负载平衡器将会把数据移动到其他的RegionServer上,以确保HBase集群负载均衡。类似预分区,还有很多有效的关于region优化的策略。相关的知识点将会在下面几章陈述。

每个region都有一个起始键和一个结束键来定义它的边界。所有这些信息将随着文件保存在region中,也会保存在hbase:meta表中(对于HBase 0.96之前的版本则保存在.META.中)。通过这张表能够跟踪所有的region信息。当它们变得太大,region可以分裂。如果需要,region也可以合并。

列族

列族是HBase中独特的、其他的关系型数据库应用中没有的概念。对于同一个region,不同的列族会将数据存储在不同的文件中,而且它们的配置也可以不同。具有相同访问模式和相同格式的数据应该被划分到同一个列族中。关于格式的一个例子:对于每个用户画像信息,你除了要存储用户图片文件,还需要存储很多文本元数据信息,你可以将它们存储在不同的列族中:一个做了压缩的列族(所有文本信息存储的地方),另一个没有被压缩的列族(所有图片存储的地方)。关于访问模式的例子:如果一些信息大部分情况下被读取并且几乎从未被写入更新;另外-部分也是多数时候被写入却几乎未被读取,你就可以将它们分在不同的列族中。如果你想存储的列具有相似的存储格式和访问模式,就对它们进行重组并划分到同-个列族中。

对于一个给定的RegionServer,它的写入缓存区是由所有的列族共享,这些列族的配置适用该主机托管的所有region。过度使用列族将对缓存产生压力,这将会产生很多小文件,导致很多次的合并,反过来将会影响性能。当你对表的列族进行配置时,技术上并没有数目的限制。但是,综观过去的几年,我们工作中遇到的绝大部分的用户案例都是需要仅要求一个列族。有时有些需要两个列族,但是每次我们看到超过两个列族时,通常都是建议其减少列族的个数来改善效率。如果你的设计包括三个以上的列族,你可能需要认真考虑下是否真的需要这么多列族,大部分情况下它们可以重新分组。如果你没有对两个列族之间做一致性约束,而且数据也将在不同的时间存储到各自的列族中,你可以创建两个表,每个表都有一个列族,而不是给一个表创建两个列族。这个策略在决定region的大小的时候很有效。实际上,虽然保持两个列族大小相同更好。通过把它们分割成两个不同的表,这样使其更容易独自增长。

Store

我们发现一个store对应着一个列族。一个store对象由一个memstore和零个或更多的strore File(称为HFile)组成。store是存储所有写入表中的信息的实体,并且当数据需要从表中读取时也将被用到。

HFile

当内存写满必须要刷新到磁盘的时候,HFlie就会被创建。随着时间的推移,HFlie最终会被压缩成大的文件。它们是HBase用来存储表数据的文件格式。HFlie由不同种类的block块组成(如索引块和数据块)。HFlie存储在HDFS上,因此它们也能够获得Hadoop持久化和多副本的益处。

Block

HFile由block组成。这些block不应该和HDFS的block混淆。一个HDFS block可以包含很多HFile block。HFile block通常在8KB和1MB之间,但是默认大小是64KB,然而,如果一个表配置了压缩选项,HBase仍然会产生64KB 大小的block,然后压缩block。根据数据的大小以及压缩的格式,压缩后的block存储在磁盘大小也不一样。大的block将会创建数量较少的索引数据,这将有助于顺序表访问,而小一点的block将创建更多的索引值,有利于随机访问。

如果你将block size配置得很小,将会产生过多的HFlie block索引,这样给内存带来很大的压力,将会取得和预期相反的效果。同时,由于压缩的数据很小,压缩率也很低,数据容量将会增大。当你决定修改默认值的时候,需要考虑到所有的细节信息。在你做任何决定性的变化的时候,需要使用不同的配置并测试你的应用负载。然而,大部分情况下,建议使用默认值。
下面主要的块类型会在HFile文件涉及(因为它们大多是内部的细节实现,我们只会提供一个概述;如果想对具体的块类型了解更多,参考HBase的源代码):

  • 数据块
    一个数据块将包括压缩了或未被压缩的数据,但不是两者的组合。数据块包括删除和插入标记。
  • 索引块
    当查询指定的行时,HBase将会使用索引块来快速跳转到相应的HFlie地址。
  • 布隆过滤块
    这些数据块是用来存储数据块索引相关信息的。当查询指定key,使用布隆过滤块索引来跳过文件解析。
  • Trailer块
    Trailer块包含了文件的其他可变大小的部分的偏移量,它也包含HFile版本信息。

数据块采用逆序存储。这就意味着,数据块也按照逆序写入,而不是先在文件开头放入索引后将其他数据写入。先存储数据块然后存储数据块索引,Trailer数据块存储在最后。

单元格

HBase是面向列存储的数据库。这就意味着每一列将单独存储,而不是单独存储整个行。因为数据值能在不同的时间插入,因此在HDFS上最终变成不同的文件。

hbase 逻辑结构

HBase引入了列簇的概念,列簇下的列可以动态扩展;另外,HBase使用时间戳实现了数据的多版本支持。

hbase 总结_第4张图片

视图解说:

示例表中包含两行数据,两个rowKey分别为 com.cnn.wwwcom.example.www,按照字典序由小到大排列。每行数据有三个列簇,分别为anchorcontents以及people,其中列簇anchor下有两列,分别为cnnsi.com以及my.look.ca,其他两个列簇都仅有一列。可以看出 根据行 com.cnn.www 以及列 anchor:cnnsi.com 可以定位到数据CNN,对应的时间戳信息是t9,而同一行的另一列 contents:html 下有三个版本的数据,版本号分别为 t5,t6和t7。

多维度稀疏排序Map

Base是由一系列KV构成的。然而HBase这个Map系统却并不简单,有很多限定词——稀疏的、分布式的、持久性的、多维的以及排序的。接下来,我们先对这个Map进行解析,这对于之后理解HBase的工作原理非常重要。

HBase中Map的key是一个复合键,由rowkey、column family、qualifier、type以及timestamp组成,value即为cell的值。

例子:

上节逻辑视图中行"com.cnn.www"以及列"anchor:cnnsi.com"对应的数值"CNN"实际上在HBase中存储为如下KV结构:


{“com.cnn.www”,“anchor”,“cnnsi.com”,“put”,“t9”} -> “CNN”


同理,其他的KV还有:


{“com.cnn.www”,“anchor”,“my.look.ca”,“put”,“t8”} -> “CNN.com”
{“com.cnn.www”,“contents”,“html”,“put”,“t7”} -> “…”
{“com.cnn.www”,“contents”,“html”,“put”,“t6”} -> “…”
{“com.cnn.www”,“contents”,“html”,“put”,“t5”} -> “…”
{“com.example.www”,“people”,“author”,“put”,“t5”} -> “John Doe”


  • 多维

    这个特性比较容易理解。HBase中的Map与普通Map最大的不同在于,key是一个复合数据结构,由多维元素构成,包括rowkey、column family、qualifier、type以及timestamp。

  • 稀疏

    对于hbase来说,空值不需要任何填充。因为 hbase 的列在理论上是可以无限扩容的,对于百万列的表来说,通常都会存在大量的空值,如果使用填充null的策略,势必会造成大量空间的浪费。因此稀疏性是Hbase的列可以无限扩展的一个重要条件

  • 排序

    构成HBase的KV在同一个文件中都是有序的,但规则并不是仅仅按照rowkey排序,而是按照KV中的key进行排序——先比较rowkey,rowkey小的排在前面;如果rowkey相同,再比较column,即column family:qualifier,column小的排在前面;如果column还相同,再比较时间戳timestamp,即版本信息,timestamp大的排在前面。这样的多维元素排序规则对于提升HBase的读取性能至关重要

  • 分布式

    构成HBase的所有Map并不集中在某台机器上,而是分布在整个集群中。

hbase 物理结构

与大多数数据库系统不同,HBase中的数据是按照列簇存储的,即将数据按照列簇分别存储在不同的目录中。

  • 列簇anchor的所有数据存储在一起形成:

    hbase 总结_第5张图片

  • 列簇contents的所有数据存储在一起形成:

    hbase 总结_第6张图片

  • 列簇people的所有数据存储在一起形成:

hbase 总结_第7张图片

行式存储、列式存储、列簇式存储

  • 行式存储

    行式存储系统会将一行数据存储在一起,一行数据写完之后再接着写下一行,最典型的如MySQL这类关系型数据库,如图1-4所示。

    hbase 总结_第8张图片

    **行式存储在获取一行数据时是很高效的,但是如果某个查询只需要读取表中指定列对应的数据,那么行式存储会先取出一行行数据,再在每一行数据中截取待查找目标列。**这种处理方式在查找过程中引入了大量无用列信息,从而导致大量内存占用。因此,这类系统仅适合于处理OLTP类型的负载,对于OLAP这类分析型负载并不擅长。

  • 列式存储

    列式存储理论上会将一列数据存储在一起,不同列的数据分别集中存储,最典型的如Kudu、Parquet on HDFS等系统(文件格式),如图所示。

    hbase 总结_第9张图片

    **列式存储对于只查找某些列数据的请求非常高效,只需要连续读出所有待查目标列,然后遍历处理即可;但是反过来,列式存储对于获取一行的请求就不那么高效了,需要多次IO读多个列数据,最终合并得到一行数据。**另外,因为同一列的数据通常都具有相同的数据类型,因此列式存储具有天然的高压缩特性。

  • 列簇式存储

    从概念上来说,列簇式存储介于行式存储和列式存储之间,可以通过不同的设计思路在行式存储和列式存储两者之间相互切换。比如,一张表只设置一个列簇,这个列簇包含所有用户的列。HBase中一个列簇的数据是存储在一起的,因此这种设计模式就等同于行式存储。再比如,一张表设置大量列簇,每个列簇下仅有一列,很显然这种设计模式就等同于列式存储。上面两例当然是两种极端的情况,在当前体系中不建议设置太多列簇,但是这种架构为HBase将来演变成HTAP(Hybrid Transactional and Analytical Processing)系统提供了最核心的基础。

hbase 数据模型

Namespace(命名空间)

命名空间,类似于关系型数据库的DatabBase 概念,每个命名空间下有多个表。HBase有两个自带的命名空间,分别是hbase 和default,hbase 中存放的是HBase 内置的表,default 表是用户默认使用的命名空间。

Table(表)

表,一个表包含多行数据

Row(行)

一行数据包含一个唯一标识RowKey。多个column以及对应的值。在hbase中,一张表中所有row都按照rowKey的字典序由小到大排序

Column Family(列族)

column family在表创建的时候需要指定,用户不能随意增减。一个column family下可以设置任意多个qualifier,因此可以理解为HBase中的列可以动态增加,理论上甚至可以扩展到上百万列。

Column Qualifier(列)

与关系型数据库中的列不同,HBase中的column由column family(列簇)以及qualifier(列名)两部分组成,两者中间使用":"相连。比如contents:html,其中contents为列簇,html为列簇下具体的一列。

Cell(单元格)

单元格,由五元组(row,column,timestamp,type,value)组成的结构,其中type表示Put/Delete这样的操作类型,timestamp代表这个cell的版本。这个结构在数据库中实际是以KV结构存储的,其中(row,column,timestamp,type)是K,value字段对应KV结构的V。

Timestamp (时间戳/版本号)

时间戳,每个cell在写入HBase的时候都会默认分配一个时间戳作为该cell的版本,当然,用户也可以在写入的时候自带时间戳。HBase支持多版本特性,即同一rowkey、column下可以有多个value存在,这些value使用timestamp作为版本号,版本越大,表示数据越新。

RegionServer的核心模块(⭐⭐⭐⭐⭐)

RegionServer是HBase系统中最核心的组件,主要负责用户数据写入、读取等基础操作。RegionServer组件实际上是一个综合体系,包含多个各司其职的核心模块:HLog、MemStore、HFile以及BlockCache。

RegionServer内部结构

hbase 总结_第10张图片

一个RegionServer由一个(或多个)HLog、一个BlockCache以及多个Region组成。其中,HLog用来保证数据写入的可靠性;BlockCache可以将数据块缓存在内存中以提升数据读取性能;Region是HBase中数据表的一个数据分片,一个RegionServer上通常会负责多个Region的数据读写。一个Region由多个Store组成,每个Store存放对应列簇的数据,比如一个表中有两个列簇,这个表的所有Region就都会包含两个Store。每个Store包含一个MemStore和多个HFile,用户数据写入时会将对应列簇数据写入相应的MemStore,一旦写入数据的内存大小超过设定阈值,系统就会将MemStore中的数据落盘形成HFile文件。HFile存放在HDFS上,是一种定制化格式的数据存储文件,方便用户进行数据读取。

HLog(预写入日志)

当hbase读写数据的时候,数据会先写在Write-Ahead logfile的文件中,然后再写入内存中。再系统出现异常的情况下可以根据这个文件重新构建

HBase中系统故障恢复以及主从复制都基于HLog实现。默认情况下,所有写入操作(写入、更新以及删除)的数据都先以追加形式写入HLog,再写入MemStore。大多数情况下,HLog并不会被读取,但如果RegionServer在某些异常情况下发生宕机,此时已经写入MemStore中但尚未flush到磁盘的数据就会丢失,需要回放HLog补救丢失的数据。此外,HBase主从复制需要主集群将HLog日志发送给从集群,从集群在本地执行回放操作,完成集群之间的数据复制。

HLog文件结构

hbase 总结_第11张图片

说明如下:

  • 每个RegionServer拥有一个或多个HLog(默认只有1个,1.1版本可以开启MultiWAL功能,允许多个HLog)。每个HLog是多个Region共享的,图5-2中Region A、Region B和Region C共享一个HLog文件。

  • HLog中,日志单元WALEntry(图中小方框)表示一次行级更新的最小追加单元,它由HLogKey和WALEdit两部分组成,其中HLogKey由table name、region name以及sequenceid等字段构成。

HLog生命周期

HLog文件生成之后并不会永久存储在系统中,它的使命完成后,文件就会失效最终被删除。

在这里插入图片描述

HLog生命周期包含4个阶段:

  1. HLog构建:HBase的任何写入(更新、删除)操作都会先将记录追加写入到HLog文件中。

  2. HLog滚动:HBase后台启动一个线程,每隔一段时间(由参数’hbase.regionserver.logroll.period’决定,默认1小时)进行日志滚动。日志滚动会新建一个新的日志文件,接收新的日志数据。日志滚动机制主要是为了方便过期日志数据能够以文件的形式直接删除。

  3. HLog失效:写入数据一旦从MemStore中落盘,对应的日志数据就会失效。为了方便处理,HBase中日志失效删除总是以文件为单位执行。查看某个HLog文件是否失效只需确认该HLog文件中所有日志记录对应的数据是否已经完成落盘,如果日志中所有日志记录已经落盘,则可以认为该日志文件失效。一旦日志文件失效,就会从WALs文件夹移动到oldWALs文件夹。注意此时HLog并没有被系统删除。

  4. HLog删除:Master后台会启动一个线程,每隔一段时间(参数’hbase.master.cleaner.interval’,默认1分钟)检查一次文件夹oldWALs下的所有失效日志文件,确认是否可以删除,确认可以删除之后执行删除操作。确认条件主要有两个:

    1. 该HLog文件是否还在参与主从复制。对于使用HLog进行主从复制的业务,需要继续确认是否该HLog还在应用于主从复制。
    2. 该HLog文件是否已经在OldWALs目录中存在10分钟。为了更加灵活地管理HLog生命周期,系统提供了参数设置日志文件的TTL(参数’hbase.master.logcleaner.ttl’,默认10分钟),默认情况下oldWALs里面的HLog文件最多可以再保存10分钟。

MemStore

一张表会被水平切分成多个Region,每个Region负责自己区域的数据读写请求。水平切分意味着每个Region会包含所有的列簇数据,HBase将不同列簇的数据存储在不同的Store中,每个Store由一个MemStore和一系列HFile组成

  • 更新数据存储在 MemStore 中,HBase基于LSM树模型实现,所有的数据写入操作首先会顺序写入日志HLog,再写入MemStore,当MemStore中数据大小超过阈值之后再将这些数据批量写入磁盘,生成一个新的HFile文件。LSM树架构有如下几个非常明显的优势:

    • 这种写入方式将一次随机IO写入转换成一个顺序IO写入(HLog顺序写入)加上一次内存写入(MemStore写入),使得写入性能得到极大提升。大数据领域中对写入性能有较高要求的数据库系统几乎都会采用这种写入模型,比如分布式列式存储系统Kudu、时间序列存储系统Druid等
    • HFile中KeyValue数据需要按照Key排序,排序之后可以在文件级别根据有序的Key建立索引树,极大提升数据读取效率。然而HDFS本身只允许顺序读写,不能更新,因此需要数据在落盘生成HFile之前就完成排序工作,MemStore就是KeyValue数据排序的实际执行者。
    • MemStore作为一个缓存级的存储组件,总是缓存着最近写入的数据。对于很多业务来说,最新写入的数据被读取的概率会更大,最典型的比如时序数据,80%的请求都会落到最近一天的数据上。实际上对于某些场景,新写入的数据存储在MemStore对读取性能的提升至关重要。
    • 在数据写入HFile之前,可以在内存中对KeyValue数据进行很多更高级的优化。比如,如果业务数据保留版本仅设置为1,在业务更新比较频繁的场景下,MemStore中可能会存储某些数据的多个版本。这样,MemStore在将数据写入HFile之前实际上可以丢弃老版本数据,仅保留最新版本数据。

    总结:

    MemStore的主要作用:

    1. 更新数据存储在 MemStore 中,使用 LSM(Log-Structured Merge Tree)数据结构存储,在内存内进行排序整合。即保证写入数据有序(HFile中数据都按照RowKey进行排序),同时可以极大地提升HBase的写入性能。
    2. 作为内存缓存,读取数据时会优先检查 MemStore,根据局部性原理,新写入的数据被访问的概率更大
    3. 在持久化写入前可以做某些优化,例如:保留数据的版本设置为1,持久化只需写入最新版本。

HFile

MemStore中数据落盘之后会形成一个文件写入HDFS,这个文件称为HFile。

BlockCache

HBase实现了一种读缓存结构——BlockCache。客户端读取某个Block,首先会检查该Block是否存在于Block Cache,如果存在就直接加载出来,如果不存在则去HFile文件中加载,加载出来之后放到Block Cache中,后续同一请求或者邻近数据查找请求可以直接从内存中获取,以避免昂贵的IO操作。

BlockCache主要用来缓存Block。需要关注的是,Block是HBase中最小的数据读取单元,即数据从HFile中读取都是以Block为最小单元执行的。一个RegionServer只有一个BlockCache,在RegionServer启动时完成BlockCache的初始化工作。

写流程(⭐⭐⭐⭐⭐)

hbase 总结_第12张图片

  1. Client 先访问zookeeper,获取hbase:meta 表位于哪个Region Server。
  2. 访问对应的Region Server,获取hbase:meta 表,根据读请求的namespace:table/rowkey,
    查询出目标数据位于哪个Region Server 中的哪个Region 中。并将该table 的region 信息以
    及meta 表的位置信息缓存在客户端的meta cache,方便下次访问。
  3. 与目标Region Server 进行通讯;
  4. 将数据顺序写入(追加)到WAL;
  5. 将数据写入对应的MemStore,数据会在MemStore 进行排序;
  6. 向客户端发送ack;
  7. 等达到MemStore 的刷写时机后,将数据刷写到HFile。

详解

写流程的三个阶段

写入流程可以概括为三个阶段。

1)客户端处理阶段:客户端将用户的写入请求进行预处理,并根据集群元数据定位写入数据所在的RegionServer,将请求发送给对应的RegionServer。

2)Region写入阶段:RegionServer接收到写入请求之后将数据解析出来,首先写入WAL,再写入对应Region列簇的MemStore。

hbase 总结_第13张图片

3)MemStore Flush阶段:当Region中MemStore容量超过一定阈值,系统会异步执行flush操作,将内存中的数据写入文件,形成HFile。

用户写入请求在完成Region MemStore的写入之后就会返回成功。MemStore Flush是一个异步执行的过程。


  • 客户端处理阶段

    HBase客户端处理写入请求的核心流程基本上可以概括为三步。

    1. 用户提交put请求后,HBase客户端会将写入的数据添加到本地缓冲区中,符合一定条件就会通过AsyncProcess异步批量提交。HBase默认设置autoflush=true,表示put请求直接会提交给服务器进行处理;用户可以设置autoflush=false,这样,put请求会首先放到本地缓冲区,等到本地缓冲区大小超过一定阈值(默认为2M,可以通过配置文件配置)之后才会提交。很显然,后者使用批量提交请求,可以极大地提升写入吞吐量,但是因为没有保护机制,如果客户端崩溃,会导致部分已经提交的数据丢失

    2. 在提交之前,HBase会在元数据表hbase:meta中根据rowkey找到它们归属的RegionServer,这个定位的过程是通过HConnection的locateRegion方法完成的。如果是批量请求,还会把这些rowkey按照HRegionLocation分组,不同分组的请求意味着发送到不同的RegionServer,因此每个分组对应一次RPC请求。

      Client与ZooKeeper、RegionServer的交互过程

      hbase 总结_第14张图片

      客户端根据写入的表以及rowkey在元数据缓存中查找,如果能够查找出该rowkey所在的RegionServer以及Region,就可以直接发送写入请求(携带Region信息)到目标RegionServer。

      ·如果客户端缓存中没有查到对应的rowkey信息,需要首先到ZooKeeper上/hbase-root/meta-region-server节点查找HBase元数据表所在的RegionServer。向hbase:meta所在的RegionServer发送查询请求,在元数据表中查找rowkey所在的RegionServer以及Region信息。客户端接收到返回结果之后会将结果缓存到本地,以备下次使用。

      ·客户端根据rowkey相关元数据信息将写入请求发送给目标RegionServer,RegionServer接收到请求之后会解析出具体的Region信息,查到对应的Region对象,并将数据写入目标Region的MemStore中。

    3. HBase会为每个HRegionLocation构造一个远程RPC请求MultiServerCallable,并通过rpcCallerFactory.newCaller()执行调用。将请求经过Protobuf序列化后发送给对应的RegionServer。

  • Region写入阶段

    服务器端RegionServer接收到客户端的写入请求后,首先会反序列化为put对象,然后执行各种检查操作,比如检查Region是否是只读、MemStore大小是否超过blockingMemstoreSize等。检查完成之后,执行一系列核心操作

    hbase 总结_第15张图片

    1. Acquire locks:HBase中使用行锁保证对同一行数据的更新都是互斥操作,用以保证更新的原子性,要么更新成功,要么更新失败。
    2. Update LATEST_TIMESTAMP timestamps:更新所有待写入(更新)KeyValue的时间戳为当前系统时间。
    3. Build WAL edit:HBase使用WAL机制保证数据可靠性,即首先写日志再写缓存,即使发生宕机,也可以通过恢复HLog还原出原始数据。该步骤就是在内存中构建WALEdit对象,为了保证Region级别事务的写入原子性,一次写入操作中所有KeyValue会构建成一条WALEdit记录。
    4. Append WALEdit To WAL:将步骤3中构造在内存中的WALEdit记录顺序写入HLog中,此时不需要执行sync操作。当前版本的HBase使用了disruptor实现了高效的生产者消费者队列,来实现WAL的追加写入操作。
    5. Write back to MemStore:写入WAL之后再将数据写入MemStore。
    6. Release row locks:释放行锁。
    7. Sync wal:HLog真正sync到HDFS,在释放行锁之后执行sync操作是为了尽量减少持锁时间,提升写性能。如果sync失败,执行回滚操作将MemStore中已经写入的数据移除。
    8. 结束写事务:此时该线程的更新操作才会对其他读请求可见,更新才实际生效。

​ branch-1的写入流程设计为:先在第6步释放行锁,再在第7步Sync WAL,最后在第8步打开mvcc让其他事务可以看到最新结果。正是这样的设计,导致了第4章4.2节中提到的“CAS接口是Region级别串行的,吞吐受限”问题。这个问题已经在branch-2中解决。

  • MemStore Flush阶段

    随着数据的不断写入,MemStore中存储的数据会越来越多,系统为了将使用的内存保持在一个合理的水平,会将MemStore中的数据写入文件形成HFile。

Region写入流程

数据写入Region的流程可以抽象为两步:追加写入HLog,随机写入MemStore。

  • 追加写入HLog

    HLog保证成功写入MemStore中的数据不会因为进程异常退出或者机器宕机而丢失,但实际上并不完全如此,HBase定义了多个HLog持久化等级,使得用户在数据高可靠和写入性能之间进行权衡。

    • HLog持久化等级

      HBase可以通过设置HLog的持久化等级决定是否开启HLog机制以及HLog的落盘方式。HLog的持久化等级分为如下五个等级。

      • SKIP_WAL:只写缓存,不写HLog日志。因为只写内存,因此这种方式可以极大地提升写入性能,但是数据有丢失的风险。在实际应用过程中并不建议设置此等级,除非确认不要求数据的可靠性。
      • ASYNC_WAL:异步将数据写入HLog日志中。
      • SYNC_WAL:同步将数据写入日志文件中,需要注意的是,数据只是被写入文件系统中,并没有真正落盘。HDFS Flush策略详见HADOOP-6313。
      • FSYNC_WAL:同步将数据写入日志文件并强制落盘。这是最严格的日志写入等级,可以保证数据不会丢失,但是性能相对比较差。
      • USER_DEFAULT:如果用户没有指定持久化等级,默认HBase使用SYNC_WAL等级持久化数据。
    • HLog写入模型

      在HBase的演进过程中,HLog的写入模型几经改进,写入吞吐量得到极大提升。之前的版本中,HLog写入都需要经过三个阶段:首先将数据写入本地缓存,然后将本地缓存写入文件系统,最后执行sync操作同步到磁盘。

      很显然,三个阶段是可以流水线工作的,基于这样的设想,写入模型自然就想到“生产者-消费者”队列实现。然而之前版本中,生产者之间、消费者之间以及生产者与消费者之间的线程同步都是由HBase系统实现,使用了大量的锁,在写入并发量非常大的情况下会频繁出现恶性抢占锁的问题,写入性能较差。

      当前版本中,HBase使用LMAX Disruptor框架实现了无锁有界队列操作。基于Disruptor的HLog写入模型

      hbase 总结_第16张图片

      最左侧部分是Region处理HLog写入的两个前后操作:append和sync。当调用append后,WALEdit和HLogKey会被封装成FSWALEntry类,进而再封装成Ring

      BufferTruck类放入Disruptor无锁有界队列中。当调用sync后,会生成一个SyncFuture,再封装成RingBufferTruck类放入同一个队列中,然后工作线程会被阻塞,等待notify()来唤醒。

      最右侧部分是消费者线程,在Disruptor框架中有且仅有一个消费者线程工作。这个框架会从Disruptor队列中依次取出RingBufferTruck对象,然后根据如下选项来操作:

      • ·如果RingBufferTruck对象中封装的是FSWALEntry,就会执行文件append操作,将记录追加写入HDFS文件中。需要注意的是,此时数据有可能并没有实际落盘,而只是写入到文件缓存。
      • 如果RingBufferTruck对象是SyncFuture,会调用线程池的线程异步地批量刷盘,刷盘成功之后唤醒工作线程完成HLog的sync操作。
  • 随机写入MemStore

    KeyValue写入Region分为两步:首先追加写入HLog,再写入MemStore。MemStore使用数据结构ConcurrentSkipListMap来实际存储KeyValue,优点是能够非常友好地支持大规模并发写入,同时跳跃表本身是有序存储的,这有利于数据有序落盘,并且有利于提升MemStore中的KeyValue查找性能。

    KeyValue写入MemStore并不会每次都随机在堆上创建一个内存对象,然后再放到ConcurrentSkipListMap中,这会带来非常严重的内存碎片,进而可能频繁触发Full GC。HBase使用MemStore-Local Allocation Buffer(MSLAB)机制预先申请一个大的(2M)的Chunk内存,写入的KeyValue会进行一次封装,顺序拷贝这个Chunk中,这样,MemStore中的数据从内存flush到硬盘的时候,JVM内存留下来的就不再是小的无法使用的内存碎片,而是大的可用的内存片段。

    基于这样的设计思路,MemStore的写入流程可以表述为以下3步。

    1)检查当前可用的Chunk是否写满,如果写满,重新申请一个2M的Chunk。

    2)将当前KeyValue在内存中重新构建,在可用Chunk的指定offset处申请内存创建一个新的KeyValue对象。

    3)将新创建的KeyValue对象写入ConcurrentSkipListMap中。

读流程(⭐⭐⭐⭐⭐)

hbase 总结_第17张图片

1)Client 先访问zookeeper,获取hbase:meta 表位于哪个Region Server。
2)访问对应的Region Server,获取hbase:meta 表,根据读请求的namespace:table/rowkey,
查询出目标数据位于哪个Region Server 中的哪个Region 中。并将该table 的region 信息以
及meta 表的位置信息缓存在客户端的meta cache,方便下次访问。
3)与目标Region Server 进行通讯;
4)分别在Block Cache(读缓存),MemStore 和Store File(HFile)中查询目标数据,并将
查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不
同的类型(Put/Delete)。
5) 将从文件中查询到的数据块(Block,HFile 数据存储单元,默认大小为64KB)缓存到
Block Cache。
6)将合并后的最终结果返回给客户端。

MemStore Flush(⭐⭐⭐⭐⭐)

触发条件

  • MemStore级别限制:当Region中任意一个MemStore的大小达到了上限(hbase.hregion.memstore.flush.size,默认128MB),会触发MemStore刷新。
  • Region级别限制:当Region中所有MemStore的大小总和达到了上限(hbase.hregion.memstore.block.multiplier*hbase.hregion.memstore.flush.size),会触发MemStore刷新。
  • RegionServer级别限制:当RegionServer中MemStore的大小总和超过低水位阈值hbase.regionserver.global.memstore.size.lower.limit*hbase.regionserver.global.memstore.
  • size,RegionServer开始强制执行flush,先flush MemStore最大的Region,再flush次大的,依次执行。如果此时写入吞吐量依然很高,导致总MemStore大小超过高水位阈值hbase.regionserver.global.memstore.size,RegionServer会阻塞更新并强制执行flush,直至总MemStore大小下降到低水位阈值。
  • 当一个RegionServer中HLog数量达到上限(可通过参数hbase.regionserver.maxlogs配置)时,系统会选取最早的HLog对应的一个或多个Region进行flush。
  • HBase定期刷新MemStore:默认周期为1小时,确保MemStore不会长时间没有持久化。为避免所有的MemStore在同一时间都进行flush而导致的问题,定期的flush操作有一定时间的随机延时。
  • 手动执行flush:用户可以通过shell命令flush’tablename’或者flush’regionname’分别对一个表或者一个Region进行flush。

执行流程

  1. prepare阶段:遍历当前Region中的所有MemStore,将MemStore中当前数据集CellSkipListSet(内部实现采用ConcurrentSkipListMap)做一个快照snapshot,然后再新建一个CellSkipListSet接收新的数据写入。prepare阶段需要添加updateLock对写请求阻塞,结束之后会释放该锁。因为此阶段没有任何费时操作,因此持锁时间很短。

  2. flush阶段:遍历所有MemStore,将prepare阶段生成的snapshot持久化为临时文件,临时文件会统一放到目录.tmp下。这个过程因为涉及磁盘IO操作,因此相对比较耗时。

  3. commit阶段:遍历所有的MemStore,将flush阶段生成的临时文件移到指定的ColumnFamily目录下,针对HFile生成对应的storefile和Reader,把storefile添加到Store的storefiles列表中,最后再清空prepare阶段生成的snapshot。

StoreFile Compaction(合并)(⭐⭐⭐)

由于memstore 每次刷写都会生成一个新的HFile,且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的HFile 中,因此查询时需要遍历所有的HFile。为了减少HFile 的个数,以及清理掉过期和删除的数据,会进行StoreFile Compaction。

Compaction 分为两种,分别是Minor CompactionMajor Compaction

  • Minor Compaction:会将临近的若干个较小的HFile 合并成一个较大的HFile,但不会清理过期和删除的数据。
  • Major Compaction :会将一个Store 下的所有的HFile 合并成一个大HFile,并且会清理掉过期和删除的数据。

hbase 总结_第18张图片


Compaction基本工作原理

Compaction是从一个Region的一个Store中选择部分HFile文件进行合并。合并原理是,先从这些待合并的数据文件中依次读出KeyValue,再由小到大排序后写入一个新的文件。之后,这个新生成的文件就会取代之前已合并的所有文件对外提供服务。

Base根据合并规模将Compaction分为两类:Minor Compaction和Major Compaction。

  • Minor Compaction是指选取部分小的、相邻的HFile,将它们合并成一个更大的HFile。

  • Major Compaction是指将一个Store中所有的HFile合并成一个HFile,这个过程还会完全清理三类无意义数据:被删除的数据、TTL过期数据、版本号超过设定版本号的数据。

在HBase的体系架构下,Compaction有以下核心作用:

  • 合并小文件,减少文件数,稳定随机读延迟。

  • 提高数据的本地化率。

  • 清除无效数据,减少数据存储量。

Compaction触发时机

最常见的时机有如下三种:MemStore Flush、后台线程周期性检查以及手动触发。

  • MemStore Flush:应该说Compaction操作的源头来自flush操作,MemStore Flush会产生HFile文件,文件越来越多就需要compact执行合并。因此在每次执行完flush操作之后,都会对当前Store中的文件数进行判断,一旦Store中总文件数大于hbase.hstore.compactionThreshold,就会触发Compaction。需要说明的是,Compaction都是以Store为单位进行的,而在flush触发条件下,整个Region的所有Store都会执行compact检查,所以一个Region有可能会在短时间内执行多次Compaction。
  • ·后台线程周期性检查:RegionServer会在后台启动一个线程CompactionChecker,定期触发检查对应Store是否需要执行Compaction,检查周期为hbase.server.thread.wakefrequency*hbase.server.compactchecker.interval.multiplier。和flush不同的是,该线程优先检查Store中总文件数是否大于阈值hbase.hstore.compactionThreshold,一旦大于就会触发Compaction;如果不满足,接着检查是否满足Major Compaction条件。简单来说,如果当前Store中HFile的最早更新时间早于某个值mcTime,就会触发Major Compaction。mcTime是一个浮动值,浮动区间默认为[7-7×0.2,7+7×0.2],其中7为hbase.hregion.majorcompaction,0.2为hbase.hregion.majorcompaction.jitter,可见默认在7天左右就会执行一次Major Compaction。用户如果想禁用Major Compaction,需要将参数hbase.hregion.majorcompaction设为0。
  • 手动触发:一般来讲,手动触发Compaction大多是为了执行Major Compaction。使用手动触发Major Compaction的原因通常有三个——其一,因为很多业务担心自动Major Compaction影响读写性能,因此会选择低峰期手动触发;其二,用户在执行完alter操作之后希望立刻生效,手动触发Major Compaction;其三,HBase管理员发现硬盘容量不够时手动触发Major Compaction,删除大量过期数据。

负载均衡

Region合并

当一些region被不断的写数据,达到regionSplit的阀值时(默认10GB),就会触发region分裂,一旦集群中region很多,就会导致集群管理运维成本增加。可以使用在线合并功能将这些Region与相邻的Region合并,减少集群中空闲Region的个数。

Region合并的主要流程如下:

  1. 客户端发送merge请求给Master。
  2. Master将待合并的所有Region都move到同一个RegionServer上。
  3. Master发送merge请求给该RegionServer。
  4. RegionServer启动一个本地事务执行merge操作。
  5. merge操作将待合并的两个Region下线,并将两个Region的文件进行合并。
  6. 将这两个Region从hbase:meta中删除,并将新生成的Region添加到hbase:meta中。
  7. 将新生成的Region上线。

Region分裂

触发分裂策略

  • ConstantSizeRegionSplitPolicy

    一个Region中最大Store的大小超过阈值之后就会触发分裂。该策略最简单,但弊端相当大。阈值设置大,对大表友好,小表可能不会触发分裂,极端情况下可能只有一个region。阈值设置小,对小表友好,但一个大表可能在集群中产生大量的region。对于集群管理不是好事。

  • IncreasiongToUpperBoundRegionSplitPolicy

    一个Region中最大Store的大小超过阈值之后就会触发分裂。阈值不是固定的值,而是在一定情况下不断调整的,调整后的阈值大小和Region所属表在当前region server上的region个数有关系。
    调整后的阈值 = regions regions flushsize * 2
    阈值不会无限增大,maxRegionFileSize来做限制。能够自适应大小表,集群规模大的情况下,对大表很优秀,对小表会产生大量小region

  • SteppingSplitPolicy

    分裂阈值大小和待分裂Region所属表在当前Region Server上的region个数有关系。
    如果region个数为1,分裂之为flushsize * 2。
    否则为 maxRegionFileSize
    大表小表都不会产生大量的region

Region核心分裂流程

整个分裂事务过程分为三个阶段:prepare、execute和rollback。

  • prepare阶段

    在内存中初始化两个子region,具体是生成两个HRegionInfo对象,包含tableName、regionName、startkey、endkey等。同时会生成一个transaction journal,这个对象用来记录切分的进展。

  • execute阶段

    hbase 总结_第19张图片

  • rollback阶段

    如果execute阶段出现异常,则执行rollback操作。为了实现回滚,整个切分过程被分为很多子阶段,回滚程序会根据当前进展到哪个子阶段清理对应的垃圾数据,根据切分进展来做不同的回滚操作。

布隆过滤器

布隆过滤器由一个长度为N的01数组array组成。首先将数组array每个元素初始设为0。

对集合A中的每个元素w,做K次哈希,第i次哈希值对N取模得到一个index(i),即index(i)=HASH_i(w)%N,将array数组中的array[index(i)]置为1。最终array变成一个某些元素为1的01数组。

下面举个例子,如图2-9所示,A={x,y,z},N=18,K=3。

hbase 总结_第20张图片

初始化array=000000000000000000。

对元素x,HASH_0(x)%N=1,HASH_1(x)%N=5,HASH_2(x)%N=13。因此array=010001000000010000。

对元素y,HASH_0(y)%N=4,HASH_1(y)%N=11,HASH_2(y)%N=16。因此array=010011000001010010。

对元素z,HASH_0(z)%N=3,HASH_1(y)%N=5,HASH_2(y)%N=11。因此array=010111000001010010。

最终得到的布隆过滤器串为:010111000001010010。

此时,对于元素w,K次哈希值分别为:

HASH_0(w)%N=4

HASH_1(w)%N=13

HASH_2(w)%N=15

可以发现,布隆过滤器串中的第15位为0,因此可以确认w肯定不在集合A中。因为若w在A中,则第15位必定为1。

如果有另外一个元素t,K次哈希值分别为:

HASH_0(t)%N=5

HASH_1(t)%N=11

HASH_2(t)%N=13

我们发现布隆过滤器串中的第5、11、13位都为1,但是却没法肯定t一定在集合A中。

因此,布隆过滤器串对任意给定元素w,给出的存在性结果为两种:

·w可能存在于集合A中。

·w肯定不在集合A中。

当N取K*|A|/ln2时(其中|A|表示集合A元素个数),能保证最佳的误判率,所谓误判率也就是过滤器判定元素可能在集合中但实际不在集合中的占比。

举例来说,若集合有20个元素,K取3时,则设计一个N=3×20/ln2=87二进制串来保存布隆过滤器比较合适。

代码示例:

    <dependencies>
        
        <dependency>
            <groupId>com.google.guavagroupId>
            <artifactId>guavaartifactId>
            <version>31.1-jreversion>
        dependency>
    dependencies>

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

/**
 * 布隆过滤器
 *
 * @author Mr.Zhang
 * @date 2022/8/15 18:17
 */
public class BloomFilterTest {


    private static int size = 1000000;
    /**
     * 期望的误判率
     */
    private static double fpp = 0.01;
    /**
     * 布隆过滤器
     */
    private static BloomFilter<Integer>  bloomFilter =  null;

    
    private static int total = 1000000;

    public static void main(String[] args) {
        bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);

//        插入一百万样板数据
        for (int i = 0; i < total; i++) {
            bloomFilter.put(i);
        }
//        用另外十万测试数据,判断误判率
        int count = 0;
        for (int i = total; i < total + 100000; i++) {
            if (bloomFilter.mightContain(i)){
                count++;
                System.out.println(i+"误判了");
            }
        }
        System.out.println("总共的误判数"+count);


    }
}

协处理器(Coprocessor)

分类

HBase Coprocessor分为两种:Observer和Endpoint
hbase 总结_第21张图片

Observer

  • Observer Coprocessor类似于MySQL中的触发器。Observer Coprocessor提供钩子使用户代码在特定事件发生之前或者之后得到执行。
  • 在当前HBase系统中,提供了4种Observer接口:
    • RegionObserver,主要监听Region相关事件,比如get、put、scan、delete以及flush等。
    • ·RegionServerObserver,主要监听RegionServer相关事件,比如RegionServer启动、关闭,或者执行Region合并等事件。
    • WALObserver,主要监听WAL相关事件,比如WAL写入、滚动等。
    • MasterObserver,主要监听Master相关事件,比如建表、删表以及修改表结构等。

Endpoint

  • Endpoint Coprocessor类似于MySQL中的存储过程。Endpoint Coprocessor允许将用户代码下推到数据层执行。

Hbase写数据流程(⭐⭐⭐⭐⭐)

Client写入 -> 存入MemStore,一直到MemStore满 -> Flush成一个StoreFile,直至增长到一定阈值 -> 触发Compact合并操作 -> 多个StoreFile合并成一个StoreFile,同时进行版本合并和数据删除 -> 当StoreFiles Compact后,逐步形成越来越大的StoreFile -> 单个StoreFile大小超过一定阈值后(默认10G),触发Split操作,把当前Region Split成2个Region,Region会下线,新Split出的2个孩子Region会被HMaster分配到相应的HRegionServer 上,使得原先1个Region的压力得以分流到2个Region上
由此过程可知,HBase只是增加数据,没有更新和删除操作,用户的更新和删除都是逻辑层面的,在物理层面,更新只是追加操作,删除只是标记操作。
用户写操作只需要进入到内存即可立即返回,从而保证I/O高性能。

hbase 表的内部操作

HBase的可扩展性是基于其对数据的重组以合并成更大的文件,然后将表里面的数据分散到很多服务器上的能力。为了达到这个目标, HBase具有三大机制:合井、分裂、重平衡。这 个机制对用户是透明的。但是如果设计不好或者使用不当,将可能影响服务器的性能表现。因此,通过了解这些机制将有助于了解服务器的反应。

合并

HBase将所有接收到的操作保存到memstore内存区。当内存缓冲区满了之后,会将数据刷新到磁盘。随着时间的推移,这样的操作会不停地在HDFS上创建很多小文件,并且根据我们稍后会提到的具体标准,HBase将会选择相应的文件组合成更大的文件。这会在多个方面让HBase受益。首先,新的文件将由托管的RegionServer进行写入,保证数据存储在HDFS本地磁盘上。本地写操作将允许RegionServer在本地查询文件,而不是通过网络查询。其次,在用户查询数据的时候,将会减少查询文件的数量。提高HBase的查询效率,减少在HDFS上保持寻址所有小文件的压力。最后,它允许HBase对存储到这些文件的数据做一些清理工作。如果生存周期(TTL)导致这些文件失效,它们将不会被再次写到新的目标文件。同样适用在某些详细情况下的删除操作。

合并类型有两种

小合并

当HBase选择部分而不是所有的来进行合并的时候,这种合并被称为小合并。当前的region有三个或者三个以上的HFile,默认的配置将会触发合并。如果合并被触发,HBase将会基于合并规则选择一些文件进行合并。如果store中的所有文件都被选中,那么这个合并将会演变成大合并。

当HBase选择部分而不是所有的来进行合并的时候,这种合并被称为小合并。当前的region有三个或者三个以上的HFile,默认的配置将会触发合并。如果合并被触发,HBase将会基于合并规则选择一些文件进行合并。如果store中的所有文件都被选中,那么这个合并将会演变成大合并。小合并对数据进行一些清理操作,但不是所有的信息都会被清理。当你对一个单元格执行删除操作的时候,HBase将会存储一个标记,来表示所有比该单元格旧的同一单元格已经被删除。因此,相同key所对应的位于该时间徽之前的所有单元格应该被删除。当HBase在执行合并操作时发现删除标记,将会确保删除所有比该标记旧的,具有相同的key和列限定词的单元格。然而,由于一些单元格数据可能仍然存在于其他的文件中,而这些文件还未被选中进行合并,所以HBase不能删除这些标记,因为它仍然适用,并无法确定是否还有其他的单元格需要被删除。对于那些删除标记存在于未被选中合并的文件中情况也一样。如果是这种情况,那些由于删除标记而应该被移除的单元格将仍然保留到执行大合并为止。基于列族级定义的TTL的过期单元格将被删除,因为它们不依赖其他未被选中的文件的内容,除非该表已被配置成保持最少的版本数。

理解单元格的版本总数和合并的关系很重要。当决定需要保留数据的版本数后,最好就是将该数字视为在给定时间内的最小版本数。一个很好的例子就是只有单个列族的表,该表被配置为保留最大为3的版本数。只有两种情况下HBase将会删除多余的版本。第一种是刷新数据到磁盘的时候,第二种是执行大合并的时候。返回给客户端的单元格版本的个数,通常是根据表的配置而定,然而,当使用RAW=>true的时候,你可以获得所有版本的数据,下面深入探究下几种情况:

  • 执行四次put,并不刷到磁盘,紧接着执行scan,不论版本数,将会返回四个版本的数据。
  • 执行四次put,刷到磁盘,紧接着执行scan,将会返回三个版本的数据。
  • 执行四次put并刷到磁盘,再执行四次put,并刷到磁盘,紧接着执行scan,将会返回六个版本的数据。
  • 执行四次put并刷到磁盘,再执行四次put并刷新到磁盘,再进行大合并,紧接着执行scan,将会返回三个版本的数据。

大合并

当所有的文件被选中进行合并的时候,我们称为大合并。大合并和小合并工作原理类似,除了前者会将应用于所有相关单元格的delete market删除,此外同一个单元格所有多余的版本也将被丢弃。大合并可以对指定的region的列族级别或在region级别或者表级别进行手动触发。HBase也能够配置,以便于周期性地执行大合并。

自动每周一次的合并能够在任何情况下发生,这个时间取决于你的集群启动时间。这就意味着,当你几乎没有HBase的通信量时,自动合并可以突然发生,但是这也意味着,它可以精确的发生在峰值活动正在进行的时候。因为合并需要读取和重写所有的数据,大合并是I/O密集型操作,将会对你的集群反应时间和SLA产生很大影响。因此,强烈建议完全关闭自动合并,并且在当你知道在对集群影响最小的时候,你自己启动定时任务来触发合并操作。我们也建议不要同时合并所有的表。与将所有表的操作放在一周的同一天执行相反,可以将合并操作分散到整个周。最后,如果你的集群真的含有很多表和分区,建议开发一个程序检查每个分区的文件个数以及最旧文件的生命周期;在region级别上,只要超过你设定的文件数或最旧的文件的生命周期超过你预先配置的期限(即使只有一个文件)都可以触发一个合并操作。这样有助于保持region数据的locality值,同时降低你集群的IO。

分裂(自动分片)

分裂操作是合并的相反操作。当HBase将多个文件合并在一起的时候,如果在合并过程中没有太多的值被丢弃,它将创建一个更大的文件。输入的文件越大,就需要更多的时间去解析合并它们等。因此,HBase试图将它们保持在可配置的最大值之下。在HBase0.94这个版本以及更旧的版本中,默认值是1GB,然后在后面的版本中,这个值增加到10GB。当其中一个region的列族达到10GB之后,为了优化负载均衡,HBase将会对指定的region触发拆分机制,将region分裂成两个新region。因为region边界适用给定region的所有列族,所有的列族将会按照同样的方式进行分裂,即使它们远小于配置的最大值。当一个region分裂后,将会分割成两个新的较小的region,第一个region的start key为原来分区的start key,第二个region的end key是原来分区的end key。第一个region的end key以及第二个region的start key由HBase决定,它将会选出最优中点。HBase将会尽量选择中间点,然而,我们不想这个过程耗费太多的时间,所以它不会在一个HFile块内分裂。

关于拆分有些事情需要注意。首先HBase从不会在同一行的两列之间进行分裂。所有的列将会被保存在同一个region里面。因此,如果你有很多列或者非常大的列,单个行的值可能比配置的最大值都大,而HBase不能分裂它。你要避免这种整个region只服务于一行的情况。

同时,你还需要记住HBase将会拆分所有列族。即使你的第一个列族达到10GB的阙值,而第二个列族却只有少数几行或者几千字节,这两列族都会被拆分。分裂后的第二个列族将会在所有的region里面分布着微小的文件。这不是你想看到的状态,同时,你也想重新检查你的表设计来避免这样的事情。当你遇到这样的情况,并且.在你的两个列族之间也没有很强的一致性需求时,可以考虑将它们分裂成两个表。

最后,不要忘了分裂表是需要付出代价的。当一个region分裂并均衡后,region数据在本地的百分比将会降低,直到下次合并。这会影响读取性能,因为客户端会到达托管着region的RegionServer去获得数据,然而当region做完均衡之后,将需要通过网络从其他的RegionServer上获得数据以服务请求。同时,更多的region将会对master、HBase: meta表,以及region服务产生更大的压力。在HBase的世界,region分裂是合理并正常的,但是你还需要关注它们。

重平衡

Region分裂后,服务器有可能宕机,新的服务器可能加入到集群中,因此,在某种程度上,数据将不会很合理地分布在你所有的RegionServer上。为了帮助集群保持合理的分布数据,每5分钟(默认配置的调度时间)HBase Master将会运行一个负载均衡器来保证所有的RegionServer管理和服务着近乎相同数目的region。

HBa s e有几种不同的负载均衡算法。0.94版本之前,HBas e使用的都是SimpleLoadBalancer,但是从0.96版本之后开始使用StochasticLoadBalancer。尽管推荐使用默认配置的负载均衡器,但你也可以开发自己的负载均衡器并要求HBase使用它。
hbase 总结_第22张图片

当一个region被负载均衡器从一个服务器移动到另外一个新的服务器时,在几毫秒内该region将会不可用,同时丢弃它本地的数据,直到下一次做大合并操作的时候。

图2-7展示的是master如何将region从负载最重的服务器分配到负载压力小点的服务器。超负荷的服务器接受来自master的命令将region关闭并转移到目标服务器。
hbase 总结_第23张图片

RowKey设计原则(☆☆☆☆☆)

  • Rowkey长度原则

    Rowkey 是一个二进制码流,Rowkey 的长度被很多开发者建议说设计在10~100 个字节,不过建议是越短越好,不要超过16 个字节。

    原因如下:

    • 数据的持久化文件HFile 中是按照KeyValue 存储的,如果Rowkey 过长比如100 个字节,1000 万列数据光Rowkey 就要占用100*1000 万=10 亿个字节,将近1G 数据,这会极大影响HFile 的存储效率;
    • MemStore 将缓存部分数据到内存,如果Rowkey 字段过长内存的有效利用率会降低,系统将无法缓存更多的数据,这会降低检索效率。因此Rowkey 的字节长度越短越好。
    • 目前操作系统是都是64 位系统,内存8 字节对齐。控制在16 个字节,8 字节的整数倍利用操作系统的最佳特性。
    • Rowkey散列原则
      如果Rowkey是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将Rowkey的高位作为散列字段,由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个Regionserver 实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息将产生所有新数据都在一个 RegionServer 上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别RegionServer,降低查询效率。
    • Rowkey唯一原则
      必须在设计上保证其唯一性。

HRegionServer宕机如何处理?

  1. ZooKeeper会监控HRegionServer的上下线情况,当ZK发现某个HRegionServer宕机之后会通知HMaster进行失效备援;
  2. 该HRegionServer会停止对外提供服务,就是它所负责的region暂时停止对外提供服务;
  3. HMaster会将该HRegionServer所负责的region转移到其他HRegionServer上,并且会对HRegionServer上存在memstore中还未持久化到磁盘中的数据进行恢复;
  4. 这个恢复的工作是由WAL重播来完成

如何提高HBase客户端的读写性能

  1. 开启bloomfilter过滤器,开启bloomfilter比没开启要快3、4倍
  2. Hbase对于内存有特别的需求,在硬件允许的情况下配足够多的内存给它
  3. 通过修改hbase-env.sh中的 export HBASE_HEAPSIZE=3000 #这里默认为1000m
  4. 增大RPC数量
    通过修改hbase-site.xml中的hbase.regionserver.handler.count属性,可以适当的放大RPC数量,默认值为10有点小。

描述HBase中scan和get的功能以及实现的异同(☆☆☆☆☆)

HBase的查询实现只提供两种方式:

  • get
    按指定RowKey 获取唯一一条记录,get方法(org.apache.hadoop.hbase.client.Get) Get 的方法处理分两种 : 设置了ClosestRowBefore 和没有设置ClosestRowBefore的rowlock。主要是用来保证行的事务性,即每个get 是以一个row 来标记的。一个row中可以有很多family 和column。
  • scan
    按指定的条件获取一批记录,scan方法(org.apache.Hadoop.hbase.client.Scan)实现条件查询功能使用的就是scan 方式。
    • scan 可以通过setCaching 与setBatch 方法提高速度(以空间换时间);
    • scan 可以通过setStartRow 与setEndRow 来限定范围([start,end)start 是闭区间,end 是开区间)。范围越小,性能越高。
    • scan 可以通过setFilter 方法添加过滤器,这也是分页、多条件查询的基础。

HBase中scan对象的setCache和setBatch方法的使用?(☆☆☆☆☆)

  • setCaching
    设置的值为每次rpc的请求记录数,默认是1;cache大可以优化性能,但是太大了会花费很长的时间进行一次传输。
  • setBatch
    设置每次取的column size;有些row特别大,所以需要分开传给client,就是一次传一个row的几个column。
    batch和caching和hbase table column size共同决意了rpc的次数。

HBase寻址机制(☆☆☆☆☆)

Hbase在海量的表数据中,是如何找到用户所需要的表数据的呢?这里是通过索引的机制解决了这个问题。

hbase 总结_第24张图片

Client访问用户数据之前需要首先访问zookeeper集群,通过zookeeper集群首先确定-ROOT-表在的位置,然后在通过访问-ROOT- 表确定相应.META.表的位置,最后根据.META.中存储的相应元数据信息找到用户数据的位置去访问。通过 这种索引机制解决了复杂了寻址问题。

Hbase 优化方式(☆☆☆☆☆)

  • 减少调整
    减少调整这个如何理解呢?HBase中有几个内容会动态调整,如region(分区)、HFile,所以通过一些方法来减少这些会带来I/O开销的调整。

    • Region
      如果没有预建分区的话,那么随着region中条数的增加,region会进行分裂,这将增加I/O开销,所以解决方法就是根据你的RowKey设计来进行预建分区,减少region的动态分裂。
    • HFile
      HFile是数据底层存储文件,在每个memstore进行刷新时会生成一个HFile,当HFile增加到一定程度时,会将属于一个region的HFile进行合并,这个步骤会带来开销但不可避免,但是合并后HFile大小如果大于设定的值,那么HFile会重新分裂。为了减少这样的无谓的I/O开销,建议估计项目数据量大小,给HFile设定一个合适的值。
  • 减少启停
    数据库事务机制就是为了更好地实现批量写入,较少数据库的开启关闭带来的开销,那么HBase中也存在频繁开启关闭带来的问题。

    • 关闭Compaction,在闲时进行手动Compaction。
      因为HBase中存在Minor Compaction和Major Compaction,也就是对HFile进行合并,所谓合并就是I/O读写,大量的HFile进行肯定会带来I/O开销,甚至是I/O风暴,所以为了避免这种不受控制的意外发生,建议关闭自动Compaction,在闲时进行compaction。
    • 批量数据写入时采用BulkLoad。
      如果通过HBase-Shell或者JavaAPI的put来实现大量数据的写入,那么性能差是肯定并且还可能带来一些意想不到的问题,所以当需要写入大量离线数据时建议使用BulkLoad。
  • 减少数据量
    虽然我们是在进行大数据开发,但是如果可以通过某些方式在保证数据准确性同时减少数据量,何乐而不为呢?

    • 开启过滤,提高查询速度
      开启BloomFilter,BloomFilter是列族级别的过滤,在生成一个StoreFile同时会生成一个MetaBlock,用于查询时过滤数据
    • 使用压缩
      一般推荐使用Snappy和LZO压缩
  • 合理设计
    在一张HBase表格中RowKey和ColumnFamily的设计是非常重要,好的设计能够提高性能和保证数据的准确性

    • RowKey设计:应该具备以下几个属性
      • 散列性:散列性能够保证相同相似的rowkey聚合,相异的rowkey分散,有利于查询。
      • 简短性:rowkey作为key的一部分存储在HFile中,如果为了可读性将rowKey设计得过长,那么将会增加存储压力。
      • 唯一性:rowKey必须具备明显的区别性。
      • 业务性:举例来说:
        • 假如我的查询条件比较多,而且不是针对列的条件,那么rowKey的设计就应该支持多条件查询。
        • 如果我的查询要求是最近插入的数据优先,那么rowKey则可以采用叫上Long.Max-时间戳的方式,这样rowKey就是递减排列。
    • 列族的设计:列族的设计需要看应用场景
      • 优势:HBase中数据时按列进行存储的,那么查询某一列族的某一列时就不需要全盘扫描,只需要扫描某一列族,减少了读I/O;其实多列族设计对减少的作用不是很明显,适用于读多写少的场景
      • 劣势:降低了写的I/O性能。原因如下:数据写到store以后是先缓存在memstore中,同一个region中存在多个列族则存在多个store,每个store都一个memstore,当其实memstore进行flush时,属于同一个region的store中的memstore都会进行flush,增加I/O开销。

HBase在进行模型设计时重点在什么地方?一张表中定义多少个Column Family最合适?为什么?

Column Family的个数具体看表的数据,一般来说划分标准是根据数据访问频度,如一张表里有些列访问相对频繁,而另一些列访问很少,这时可以把这张表划分成两个列族,分开存储,提高访问效率

直接将时间戳作为行健,在写入单个region 时候会发生热点问题,为什么呢?

region中的rowkey是有序存储,若时间比较集中。就会存储到一个region中,这样一个region的数据变多,其它的region数据很少,加载数据就会很慢,直到region分裂,此问题才会得到缓解。

如何解决HBase中region太小和region太大带来的冲突?

Region过大会发生多次compaction,将数据读一遍并重写一遍到hdfs 上,占用io,region过小会造成多次split,region 会下线,影响访问服务,最佳的解决方法是调整hbase.hregion. max.filesize 为256m。

hbase 实时查询的原理

  1. 基于磁盘的顺序读取,顺序读取效率是非常高的
  2. 内存缓存

实时查询,可以认为是从内存中查询,一般响应时间在 1 秒内。HBase 的机制是数据先写入到内存中,当数据量达到一定的量(如 128M),再写入磁盘中, 在内存中,是不进行数据的更新或合并操作的,只增加数据,这使得用户的写操作只要进入内存中就可以立即返回,保证了 HBase I/O 的高性能。

HMaster宕机的时候,哪些操作还能正常工作

对表内数据的增删查改是可以正常进行的,因为hbase client 访问数据只需要通过 zookeeper 来找到 rowkey 的具体 region 位置即可. 但是对于创建表/删除表等的操作就无法进行了,因为这时候是需要HMaster介入, 并且region的拆分,合并,迁移等操作也都无法进行了

Hbase中的memstore是用来做什么的

hbase为了保证随机读取的性能,所以hfile里面的rowkey是有序的。当客户端的请求在到达regionserver之后,为了保证写入rowkey的有序性,所以不能将数据立刻写入到hfile中,而是将每个变更操作保存在内存中,也就是memstore中。memstore能够很方便的支持操作的随机插入,并保证所有的操作在内存中是有序的。当memstore达到一定的量之后,会将memstore里面的数据flush到hfile中,这样能充分利用hadoop写入大文件的性能优势,提高写入性能。

由于memstore是存放在内存中,如果regionserver因为某种原因死了,会导致内存中数据丢失。所有为了保证数据不丢失,hbase将更新操作在写入memstore之前会写入到一个write ahead log(WAL)中。WAL文件是追加、顺序写入的,WAL每个regionserver只有一个,同一个regionserver上所有region写入同一个的WAL文件。这样当某个regionserver失败时,可以通过WAL文件,将所有的操作顺序重新加载到memstore中。

如何提高HBase客户端的读写性能(☆☆☆☆☆)

① 开启bloomfilter过滤器,开启bloomfilter比没开启要快3、4倍

② Hbase对于内存有特别的需求,在硬件允许的情况下配足够多的内存给它

③ 通过修改hbase-env.sh中的 export HBASE_HEAPSIZE=3000 #这里默认为1000m

④ 增大RPC数量

通过修改hbase-site.xml中的hbase.regionserver.handler.count属性,可以适当的放大RPC数量,默认值为10有点小。

热点现象(数据倾斜)怎么产生的,以及解决方法有哪些

  • 热点现象:
    某个小的时段内,对HBase的读写请求集中到极少数的Region上,导致这些region所在的RegionServer处理请求量骤增,负载量明显偏大,而其他的RgionServer明显空闲。

  • 热点现象出现的原因:
    HBase中的行是按照rowkey的字典顺序排序的,这种设计优化了scan操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于scan。然而糟糕的rowkey设计是热点的源头。
    热点发生在大量的client直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。大量访问会使热点region所在的单个机器超出自身承受能力,引起性能下降甚至region不可用,这也会影响同一个RegionServer上的其他region,由于主机无法服务其他region的请求。

  • 热点现象解决办法:
    为了避免写热点,设计rowkey使得不同行在同一个region,但是在更多数据情况下,数据应该被写入集群的多个region,而不是一个。常见的方法有以下这些:

    • 加盐:在rowkey的前面增加随机数,使得它和之前的rowkey的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点。
    • 哈希:哈希可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据
    • 反转:第三种防止热点的方法时反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。反转rowkey的例子以手机号为rowkey,可以将手机号反转后的字符串作为rowkey,这样的就避免了以手机号那样比较固定开头导致热点问题
    • 时间戳反转:一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为rowkey的一部分对这个问题十分有用,可以用 Long.Max_Value - timestamp 追加到key的末尾,例如[key][reverse_timestamp],[key]的最新值可以通过scan [key]获得[key]的第一条记录,因为HBase中rowkey是有序的,第一条记录是最后录入的数据。
      比如需要保存一个用户的操作记录,按照操作时间倒序排序,在设计rowkey的时候,可以这样设计[userId反转] [Long.Max_Value - timestamp],在查询用户的所有操作记录数据的时候,直接指定反转后的userId,startRow是[userId反转][000000000000],stopRow是[userId反转][Long.Max_Value - timestamp]
      如果需要查询某段时间的操作记录,startRow是[user反转][Long.Max_Value - 起始时间],stopRow是[userId反转][Long.Max_Value - 结束时间]
    • HBase建表预分区:创建HBase表时,就预先根据可能的RowKey划分出多个region而不是默认的一个,从而可以将后续的读写操作负载均衡到不同的region上,避免热点现象。

什么是 NO-SQL

not only sql (不仅仅是sql)

HBase与LSM 树(⭐⭐⭐⭐)

  • 原理:
    数据会先写到内存中,为了防止内存数据丢失,写内存的同时需要持久化到磁盘,对应了HBase的MemStore和HLog;
    MemStore中的数据达到一定的阈值之后,需要将数据刷写到磁盘,即生成HFile(也是一颗小的B+树)文件;
    hbase中的minor(少量HFile小文件合并)major(一个region的所有HFile文件合并)执行compact操作,同时删除无效数据(过期及删除的数据),多棵小树在这个时机合并成大树,来增强读性能。
  • 针对LSM树读性能hbase的优化:
    Bloom-filter:就是个带随机概率的bitmap,可以快速的告诉你,某一个小的有序结构里有没有指定数据的。于是就可以不用二分查找,而只需简单的计算几次就能知道数据是否在某个小集合里啦。效率得到了提升,但付出的是空间代价。
    compact:小树合并为大树:因为小树性能有问题,所以要有个进程不断地将小树合并到大树上,这样大部分的老数据查询也可以直接使用log2N的方式找到,不需要再进行(N/m)*log2n的查询了。

你可能感兴趣的:(大数据,hbase)