文章目录
- 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 特点
-
大
:一个表可以有数十亿行,上百万列
。
无模式
:每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一张表中不同的行可以有截然不同的列。
-
面向列族
:面向列(族)的存储和权限控制,列(族)独立检索。 -
稀疏
:空(null)列并不占用存储空间,表可以设计的非常稀疏。 -
数据多版本
:每个单元可以有多个版本,默认情况下版本号自动分配,是单元格插入时的时间戳。 -
数据类型单一
:Hbase中的数据都是字符串
,没有类型。 -
支持行级事务
。
1.2. Hbase 和 Hive
Hbase | Hive | |
类型 | 列式KV结构的NOSQL数据库 | 基于Hadoop的数据仓库 |
增删改查 | 支持随机读写,甚至可以动态 添加列,以及查询到Cell级别 | 导入和查询, 不支持随机读写 |
数据结构 | 支持半结构甚至非结构化数据, 得益与列族结构,只需要预先 定义列族即可 | 结构化数据,需要预先定义表格 |
ACID | 支持行级事务 | 不支持事务 |
应用场景 | 擅长随机读写,适合实时计算 | 适合离线计算 |
2. Hbase 数据模型
2.1. 逻辑结构
- Name Space
命名空间,类似于关系型数据库的database
或者schme
概念,每个命名空间下有多个表。HBase有两个自带的命名空间,分别是hbase
和default
,hbase中存放的是HBase内置的表,default表是用户默认使用的命名空间。 - Table
类似于关系型数据库的表概念。不同的是,HBase定义表时只需要声明列族即可
,不需要声明具体列。这意味着,往HBase写入数据时,字段可以动态、按需指定
。因此,和关系型数据库相比,HBase能够轻松应对字段变更的场景。 - Region
Table数据的一部分,相当于关系型数据库分表的概念。 - Row
HBase表中的每行数据都由一个 RowKey 和多个 Column
组成,数据是按照(RowKey的字典升序, column Qualifier升序, timestamp降序)
存储的,并且查询数据时只能根据RowKey进行检索,所以RowKey的设计十分重要。 - RowKey
类似关系性数据库的主键,不过Hbase的RowKey除了唯一之外,还具有顺序
(RowKey的字典升序)。
Hbase提供的查询API -get
,只能对 RowKey 进行查询。如果要使用条件查询,Hbase提供了 API -scan
,( setStartRow 与setEndRow来限定范围,setFilter来添加过滤器)。
Row Key设计原则 详见 [RowKey设计原则](#5.4. Row Key 和 Column Family 设计)
- Column Family
顾名思义,是Column的集合。与关系性数据库不同,Column Family列族是Hbase建表的基本单位,建表时,只需指明列族,而列限定符无需预先定义
,这对于动态增加列字段来说,开销相当低。
Colum Family设计,详见 [Column Family设计](#5.4. Row Key 和 Column Family 设计)
- Column
HBase中的每个列都由Column Family(列族
)和Column Qualifier(列限定符
)进行限定,例如info:name,info:age。
2.2. 物理存储结构
Hbase底层存储数据的结构称为 Store,一个Store 可能由多个StoreFile组成。在StoreFile中,数据以为Cell为单位进行存储,以 Row Key 为 Key,列信息为Value 的Map结构,并且以字节码
(String)形式存储。
- Row Key
Row Key唯一,且按照Row Key的字典顺序进行排序,作为Cell的Key,查询主要是依靠 Row Key 检索。 - Column Family
列族 - Column Qualifier
列限定符,真正描述一个列的元信息,可以在写入的时候动态指定。 - TimeStamp
用于标识数据的不同版本(version),每条数据写入时,系统会自动为其加上该字段,其值为写入HBase的时间。
这里要求所有 HRegionServer 的
时间一致
,否则可能无法启动集群,即使启动也状况频出。
- Type
记录该数据的操作类型,如 Put,Delete。 - Value
存储的真正数据,以上都是元数据。
3. Hbase 设计架构
3.1. Hbase基本架构
- Master
Master是所有Region Server的管理者
,其实现类为HMaster,主要作用如下:
对于表
的操作:create, delete, alter
对于RegionServer的操作:分配regions到每个RegionServer,监控每个RegionServer的状态,负载均衡和故障转移。 - Region Server
Region Server为Region 的管理者
,其实现类为HRegionServer,主要作用如下:
对于数据
的操作:get, put, delete;
对于Region的操作:splitRegion、compactRegion。 - Zookeeper
HBase通过Zookeeper来做master的高可用
、RegionServer监控、元数据入口
及集群配置的维护等工作。 - HDFS
HDFS为Hbase提供最终的底层数据存储服务,同时为HBase提供高可用
的支持。
3.2. RegionServer 架构
- StoreFile
保存实际数据的物理文件,StoreFile以Hfile的形式存储在HDFS
上。每个Store会有一个或多个StoreFile(HFile),数据在每个StoreFile中都是有序
的。 - MemStore
写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile,只有写入了StoreFile中,数据才算写入完成
。 - WAL
由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile
的文件中,然后再写入MemStore中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。 - BlockCache
读缓存,每次查询出的数据会缓存在BlockCache中,方便下次查询。
4. Hbase读写流程
4.1. Hbase 写流程
- Client先访问zookeeper,获取hbase:meta表位于哪个
Region Server
。 - 访问对应的Region Server,
获取hbase:meta表
,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache
,方便下次访问。 - 与写入数据的目标Region Server进行通讯;
- 将数据顺序写入(追加)到
WAL
; - 将数据写入对应的
MemStore
,数据会在MemStore进行排序; - 向客户端发送ack;
- 等达到MemStore的flush时机后,
将数据刷写到StoreFile
。
MemStore flush机制:
- MemStore 大小达到阈值
默认128M
,时间达到阈值默认1小时
,或者总MemSotre大小达到阈值时候,触发MemStore flush。- 首先hbase会先关闭掉当前这个已经达到阈值的内存空间,然后开启一个新memStore空间,用于继续写入工作。
Hbase2.0之前
:将旧的MemStore数据刷新到一个只读队列
中,尽可能晚的刷入HDFS,所以队列中可能存在多个 MemStore 数据,所以在写入HDFS之前还需要进行排序合并
。Hbase2.0之后
:将旧的MemStore数据刷新到一个只读队列
中,实时写入到HDFS,从而优化掉了 排序合并 的性能开销。
- 当 StoreFile 数量或大小达到阈值后,Hbase进行
Compaction
,将StoreFile合并。 - 当Storefile越来越大,Region也会越来越大,达到阈值后,会触发Split操作,
将Region一分为二
。
StoreFile Compaction,详见[StoreFile Compaction](#4.3. StoreFile Compaction)。
Region Split,详见[Regions Split](#4.3. Region Split)
4.2. Hbase 读流程
读流程:
Merge细节:
- Client先访问zookeeper,获取
hbase:meta
表位于哪个Region Server。 - 访问对应的Region Server,获取hbase:meta表,根据读请求的
namespace:table/rowkey
,查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。 - 与存储数据的目标Region Server进行通讯。
- 分别在MemStore和Store File(HFile)中查询目标数据,并将查到的所有数据进行
合并
。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)。 - 将查询到的新的数据块(
Block
,HFile数据存储单元,默认大小为64KB
)缓存到Block Cache
。 - 将合并后的最终结果返回给客户端。
4.3. StoreFile Compaction
由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的HFile中,因此查询时需要遍历所有的HFile。为了减少HFile的个数,以及清理掉过期和删除的数据
,会进行StoreFile Compaction。
- Minor(次要) Compaction:
当StoreFile达到阈值默认3个
时,会进行 Minor Compaction。此合并过程,仅将相邻StoreFile合并为一个并进行排序操作,并不会删除过期以及标记删除的数据,效率很高。 - Major(主要) Compaction:
阈值默认7天
,会触发 Major Compaction,将之前的Hfile和这个周期产生的Hfile做全局合并,并将那些过期的数据,或者已经标记删除的数据,全部都清除掉。对性能影响非常大,通常关闭闲时手动触发
。
4.3. Region Split
默认情况下,每个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设计
,具体如下:
- 预分区
预先建立Region分区,预分区可以减少 Hbase 在 region split 过程中的性能开销,但是对查询作用有限。- 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 设计
- 唯一原则
Row Key 是标识行数据的唯一标志,要保证不同行数据RowKey唯一。 - 长度原则
Rowkey 是一个二进制码流,长度建议不超过16字节。这是因为Row Key 太大增加HFile磁盘存储和MemStore内存存储的压力,最好保持在8 或者 16 字节,64位计算机性能最佳。 - 顺序原则
默认是Row Key 字典顺序升序排序,通常时间越近的数据查询越多,所以一般设计为Long.Max - timeStamp
,即时间降序排序。 - 散列原则
Row Key保证数据进入均衡的进入到各个Region中去,维护负载均衡
,避免某个 Region 发生热点现象。
- Column Family 设计
列族的设计主要是粒度的粗细,即多列族和少列族,各有优劣,以多列族
为例:
- 优点
减少读I/O数据量更多
。查询某一列族的某一列时就不需要全盘扫描,只需要扫描某一列族,同样一个表列族越多数据量越少,减少了读I/O数据量越多,但是这只是单个column
。 - 劣势
-
增加了写I/O效率
。写数据时列族越多产生的 Store 越多,那么 MomStore 需要 flush 的Store,以及 StoreFile 需要 compaction 的 HFile 也会越多,导致写IO次数越多。 - 虽然多列族可以
减少单个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可能会中断,导致线程安全问题。
解决方法:
- 建议使用同一个
HBaseConfiguration
实例来创建 HTable实例,共享ZooKeeper和socket实例。 - 是使用HTablepool创建HTable,维持一个线程安全的map里面存放的是tablename和其引用的映射。