文章目录

  • 1. 概述
  • 1.1. Hbase 特点
  • 1.2. Hbase 和 Hive
  • 2. Hbase 数据模型
  • 2.1. 逻辑结构
  • 2.2. 物理存储结构
  • 3. Hbase 设计架构
  • 3.1. Hbase基本架构
  • 3.2. RegionServer 架构
  • 4. Hbase读写流程
  • 4.1. Hbase 写流程
  • 4.2. Hbase 读流程
  • 4.3. StoreFile Compaction
  • 4.3. Region Split
  • 5. Hbase 优化
  • 5.1. 减少调整
  • 5.2. 写数据优化
  • 5.3. 读数据优化
  • 5.4. Row Key 和 Column Family 设计
  • 3.5. 内存优化
  • 6. Hbase 知识点
  • 6.1. HTable API有没有线程安全问题?




1. 概述

HBase是一种分布式、可扩展、支持海量数据存储的KV类型NOSQL数据库

HDFS是TheGoogleFileSystem的开源实现,它适合海量数据一次写入多次读取的使用场景,广泛应用于大数据离线计算场景。但是,HDFS不支持数据并发写入以及随机读写,这使HDFS不擅长做实时计算。

HBase是BigTable的开源实现,是在HDFS的基础上,加入许多组件,以满足海量数据实时随机读写,适用于实时计算场景。

1.1. Hbase 特点

  1. :一个表可以有数十亿行,上百万列

无模式:每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有截然不同的列。

  1. 面向列族:面向列(族)的存储和权限控制,列(族)独立检索。
  2. 稀疏:空(null)列并不占用存储空间,表可以设计的非常稀疏。
  3. 数据多版本:每个单元可以有多个版本,默认情况下版本号自动分配,是单元格插入时的时间戳。
  4. 数据类型单一:Hbase中的数据都是字符串,没有类型。
  5. 支持行级事务

1.2. Hbase 和 Hive

Hbase

Hive

类型

列式KV结构的NOSQL数据库

基于Hadoop的数据仓库

增删改查

支持随机读写,甚至可以动态

添加列,以及查询到Cell级别

导入和查询, 不支持随机读写

数据结构

支持半结构甚至非结构化数据,

得益与列族结构,只需要预先

定义列族即可

结构化数据,需要预先定义表格

ACID

支持行级事务

不支持事务

应用场景

擅长随机读写,适合实时计算

适合离线计算

2. Hbase 数据模型

2.1. 逻辑结构

hbase hregion最大化压缩 hbase表压缩_hadoop

  1. Name Space
    命名空间,类似于关系型数据库的database 或者 schme概念,每个命名空间下有多个表。HBase有两个自带的命名空间,分别是hbasedefault,hbase中存放的是HBase内置的表,default表是用户默认使用的命名空间。
  2. Table
    类似于关系型数据库的表概念。不同的是,HBase定义表时只需要声明列族即可,不需要声明具体列。这意味着,往HBase写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase能够轻松应对字段变更的场景。
  3. Region
    Table数据的一部分,相当于关系型数据库分表的概念。
  4. Row
    HBase表中的每行数据都由一个 RowKey 和多个 Column 组成,数据是按照(RowKey的字典升序, column Qualifier升序, timestamp降序)存储的,并且查询数据时只能根据RowKey进行检索,所以RowKey的设计十分重要。
  5. RowKey
    类似关系性数据库的主键,不过Hbase的RowKey除了唯一之外,还具有顺序(RowKey的字典升序)。
    Hbase提供的查询API - get,只能对 RowKey 进行查询。如果要使用条件查询,Hbase提供了 API -scan,( setStartRow 与setEndRow来限定范围,setFilter来添加过滤器)。

Row Key设计原则 详见 [RowKey设计原则](#5.4. Row Key 和 Column Family 设计)

  1. Column Family
    顾名思义,是Column的集合。与关系性数据库不同,Column Family列族是Hbase建表的基本单位,建表时,只需指明列族,而列限定符无需预先定义,这对于动态增加列字段来说,开销相当低。

Colum Family设计,详见 [Column Family设计](#5.4. Row Key 和 Column Family 设计)

  1. Column
    HBase中的每个列都由Column Family(列族)和Column Qualifier(列限定符)进行限定,例如info:name,info:age。

2.2. 物理存储结构

hbase hregion最大化压缩 hbase表压缩_hbase_02

Hbase底层存储数据的结构称为 Store,一个Store 可能由多个StoreFile组成。在StoreFile中,数据以为Cell为单位进行存储,以 Row Key 为 Key,列信息为Value 的Map结构,并且以字节码(String)形式存储。

  1. Row Key
    Row Key唯一,且按照Row Key的字典顺序进行排序,作为Cell的Key,查询主要是依靠 Row Key 检索。
  2. Column Family
    列族
  3. Column Qualifier
    列限定符,真正描述一个列的元信息,可以在写入的时候动态指定。
  4. TimeStamp
    用于标识数据的不同版本(version),每条数据写入时,系统会自动为其加上该字段,其值为写入HBase的时间。

这里要求所有 HRegionServer 的时间一致,否则可能无法启动集群,即使启动也状况频出。

  1. Type
    记录该数据的操作类型,如 Put,Delete。
  2. Value
    存储的真正数据,以上都是元数据。

3. Hbase 设计架构

3.1. Hbase基本架构

hbase hregion最大化压缩 hbase表压缩_hbase hregion最大化压缩_03

  1. Master
    Master是所有Region Server的管理者,其实现类为HMaster,主要作用如下:
    对于的操作:create, delete, alter
    对于RegionServer的操作:分配regions到每个RegionServer,监控每个RegionServer的状态,负载均衡和故障转移。
  2. Region Server
    Region Server为Region 的管理者,其实现类为HRegionServer,主要作用如下:
    对于数据的操作:get, put, delete;
    对于Region的操作:splitRegion、compactRegion。
  3. Zookeeper
    HBase通过Zookeeper来做master的高可用、RegionServer监控、元数据入口及集群配置的维护等工作。
  4. HDFS
    HDFS为Hbase提供最终的底层数据存储服务,同时为HBase提供高可用的支持。

3.2. RegionServer 架构

hbase hregion最大化压缩 hbase表压缩_hadoop_04

  1. StoreFile
    保存实际数据的物理文件,StoreFile以Hfile的形式存储在HDFS上。每个Store会有一个或多个StoreFile(HFile),数据在每个StoreFile中都是有序的。
  2. MemStore
    写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile,只有写入了StoreFile中,数据才算写入完成
  3. WAL
    由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile的文件中,然后再写入MemStore中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。
  4. BlockCache
    读缓存,每次查询出的数据会缓存在BlockCache中,方便下次查询。

4. Hbase读写流程

4.1. Hbase 写流程

hbase hregion最大化压缩 hbase表压缩_hbase_05

  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的flush时机后,将数据刷写到StoreFile

MemStore flush机制:

  1. MemStore 大小达到阈值 默认128M,时间达到阈值 默认1小时,或者总MemSotre大小达到阈值时候,触发MemStore flush。
  2. 首先hbase会先关闭掉当前这个已经达到阈值的内存空间,然后开启一个新memStore空间,用于继续写入工作。
  3. Hbase2.0之前:将旧的MemStore数据刷新到一个 只读队列中,尽可能晚的刷入HDFS,所以队列中可能存在多个 MemStore 数据,所以在写入HDFS之前还需要进行排序合并
  4. Hbase2.0之后:将旧的MemStore数据刷新到一个 只读队列中,实时写入到HDFS,从而优化掉了 排序合并 的性能开销。
  1. 当 StoreFile 数量或大小达到阈值后,Hbase进行 Compaction,将StoreFile合并。
  2. 当Storefile越来越大,Region也会越来越大,达到阈值后,会触发Split操作,将Region一分为二
    StoreFile Compaction,详见[StoreFile Compaction](#4.3. StoreFile Compaction)。
    Region Split,详见[Regions Split](#4.3. Region Split)

4.2. Hbase 读流程

读流程:

hbase hregion最大化压缩 hbase表压缩_hadoop_06

Merge细节:

hbase hregion最大化压缩 hbase表压缩_hbase_07

  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. 分别在MemStore和Store File(HFile)中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)。
  5. 将查询到的新的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到Block Cache
  6. 将合并后的最终结果返回给客户端。

4.3. StoreFile Compaction

hbase hregion最大化压缩 hbase表压缩_hbase hregion最大化压缩_08

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

  1. Minor(次要) Compaction:
    当StoreFile达到阈值默认3个时,会进行 Minor Compaction。此合并过程,仅将相邻StoreFile合并为一个并进行排序操作,并不会删除过期以及标记删除的数据,效率很高。
  2. Major(主要) Compaction:
    阈值默认7天,会触发 Major Compaction,将之前的Hfile和这个周期产生的Hfile做全局合并,并将那些过期的数据,或者已经标记删除的数据,全部都清除掉。对性能影响非常大,通常关闭闲时手动触发

4.3. Region Split

hbase hregion最大化压缩 hbase表压缩_Server_09

默认情况下,每个Table起初只有一个Region,随着数据的不断写入,为了提高效率(分库分表思想),Region会自动进行拆分。对于 负载均衡的考虑,HMaster有可能会将某个Region转移给其他的Region Server。

Region Split 时机:

  • 0.94版本之前:
    当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize阈值(默认10G )。
  • 0.94版本之后:
    当1个region中的某个Store下所有StoreFile的总大小超过Min(RegionNum^3 * initialSize, RegionSplit阈值),该Region就会进行拆分。其中initialSize的默认值为MemStore刷写阈值的2倍,默认是256M。
  • 2.0版本之后:
    如果当前RegionServer上该表只有一个Region,按照MemStore刷写阈值的2倍分裂,否则按照RegionSplit阈值分裂。就是少Region的RegionServer更容易创建下一个Region,负载均衡。

5. Hbase 优化

5.1. 减少调整

HBase为了适应随机读写场景,会有几个内容动态调整,如region(分区)、HFile,所以通过一些方法来减少这些会带来I/O开销的调整。

  • Region
    如果没有预建分区的话,那么随着region中条数的增加,region会进行分裂,这将增加I/O开销,所以解决方法就是根据你的RowKey设计来进行预建分区以减少region的动态分裂

默认情况下,region有startRowKey和endRowKey范围,而数据往往写入最新的region,这就很有可能出现 数据热点问题(某个Region的RowKey范围包含大量数据,导致大量数据涌入某一个Region)。处理方案是 预分区RowKey设计,具体如下:

  1. 预分区
    预先建立Region分区,预分区可以减少 Hbase 在 region split 过程中的性能开销,但是对查询作用有限。
  2. Row Key 设计
  • 加盐处理(加随机数) :
    可以在rowkey前面动态添加随机数,从而保证数据可以均匀落在不同region中,缺点是 随机盐本身增加查询数据开销将相关性强的数据分散在不同的region中,导致查询的效率有一定降低。
  • 反转策略:
    比如说手机号反转 或者 时间戳的反转,缺点也是将相关性强的数据分散在不同的region中,导致查询的效率有一定降低。
  • hash处理:
    根据rowkey计算其hash值,并作为 rowkey 的前缀。优点是将rowkey相同的数据分布到一个region,不同的rowkey的数据散列分布在各个region。
  • HFile
    HFile是数据底层存储文件,在每个memstore进行刷新时会生成一个HFile,当HFile增加到一定程度时,会将属于一个region的HFile进行合并,这可以减少数据量以及后续查询开销,利大于弊。但是合并后HFile大小如果大于设定的值,那么 HFile会重新分裂。为了减少这样的无谓的I/O开销,建议估计项目数据量大小,给HFile设定一个合适的值。

5.2. 写数据优化

  • 关闭自动Compaction,在闲时进行手动Compaction因为HBase中存在Minor Compaction和Major Compaction,也就是对HFile进行合并,所谓合并就是I/O读写, 大量的HFile进行肯定会带来I/O开销,甚至是I/O风暴,所以为了避免这种不受控制的意外发生,建议关闭自动 Compaction,在闲时进行compaction。
  • Hbase存储海量数据优化(批量加载
    大量导入数据(上百亿起)到Hbase,Hbase写入数据,需要经过 写入WAL -> 写入 MomStore -> 排序 -> 写入HFile -> Minor/major Compactions 等过程,需要进行大量IO操作。当处理海量数据时,很可能由于资源问题写入失败,即使成功,一来时间肯定相当长,再者也会影响Hbase其他客户端的响应效率。
    此时,可以使用 MR 或者 Spark 进行 BulkLoad(批量加载),即在 HDFS 上预先生成 HFile,这样Hbase只要进行简单的IO操作,就可以实现数据的批量导入,写入性能可以提高好几倍

5.3. 读数据优化

  • 开启过滤,提高查询速度
    开启BloomFilter,BloomFilter是列族级别的过滤,在生成一个StoreFile同时会生成一个MetaBlock,用于查询时过滤数据,查询效率提高 3 ~ 4 倍。
  • 使用压缩
    减少数据大小,提高IO效率。一般使用 Snappy 或者 LZO(支持切片) 压缩。

5.4. Row Key 和 Column Family 设计

  • Row Key 设计
  1. 唯一原则
    Row Key 是标识行数据的唯一标志,要保证不同行数据RowKey唯一。
  2. 长度原则
    Rowkey 是一个二进制码流,长度建议不超过16字节。这是因为Row Key 太大增加HFile磁盘存储和MemStore内存存储的压力,最好保持在8 或者 16 字节,64位计算机性能最佳。
  3. 顺序原则
    默认是Row Key 字典顺序升序排序,通常时间越近的数据查询越多,所以一般设计为 Long.Max - timeStamp,即时间降序排序。
  4. 散列原则
    Row Key保证数据进入均衡的进入到各个Region中去,维护负载均衡,避免某个 Region 发生热点现象。
  • Column Family 设计
    列族的设计主要是粒度的粗细,即多列族和少列族,各有优劣,以多列族为例:
  • 优点
    减少读I/O数据量更多。查询某一列族的某一列时就不需要全盘扫描,只需要扫描某一列族,同样一个表列族越多数据量越少,减少了读I/O数据量越多,但是这只是单个column
  • 劣势
  1. 增加了写I/O效率。写数据时列族越多产生的 Store 越多,那么 MomStore 需要 flush 的Store,以及 StoreFile 需要 compaction 的 HFile 也会越多,导致写IO次数越多。
  2. 虽然多列族可以减少单个column读I/O数据量,但是往往查询一次查询可能不止一个字段,那么就需要跨列族查询,则查询的字段越多,效率越低。

Column Family的个数具体看表的数据,一般来说划分标准是根据数据访问频率,可以把这张表划分成两个列族,一个访问频率高的列族,另一个是访问频率低的列族。

3.5. 内存优化

HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,但是不建议分配非常大的堆内存,因为GC过程持续太久会导致RegionServer处于长期不可用状态(Stop-The-World即STW),一般16~36G内存就可以了,如果框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。

6. Hbase 知识点

6.1. HTable API有没有线程安全问题?

每一个HTable的实例化过程都要创建一个新的connection,Zookeeper连接数存在阈值,如果超过阈值,前面的connection可能会中断,导致线程安全问题。

解决方法:

  1. 建议使用同一个HBaseConfiguration实例来创建 HTable实例,共享ZooKeeper和socket实例。
  2. 是使用HTablepool创建HTable,维持一个线程安全的map里面存放的是tablename和其引用的映射。