HBase

https://javapub.blog.csdn.net/article/details/146604071

一、数据存储格式

数据存储示例

(Row Key, Column Family:Column Qualifier, Timestamp) → Cell Value

xml 复制代码
表名: user_table

┌──────────┬───────────────────────────┬────────────────────┐
│  Row Key │     Column Family: info   │ Column Family: add │
├──────────┼───────┬───────┬──────────┼─────────┬──────────┤
│          │ name  │ age   │ gender   │ city    │ country  │
├──────────┼───────┼───────┼──────────┼─────────┼──────────┤
│ user1    │ Tom   │ 25    │ male     │ Beijing │ China    │
│ user2    │ Jerry │ 30    │ female   │ Shanghai│ China    │
└──────────┴───────┴───────┴──────────┴─────────┴──────────┘

实际存储格式

xml 复制代码
┌─────────────────────────────────────────────────────────┐
│                      Table                               │
│  ┌─────────────────────────────────────────────────┐   │
│  │              Region (按Row Key范围划分)            │   │
│  │  ┌─────────────────────────────────────────────┐ │   │
│  │  │         Store (每个列族一个Store)             │ │   │
│  │  │  ┌───────────────────────────────────────┐  │ │   │
│  │  │  │         MemStore (内存)               │  │ │   │
│  │  │  └───────────────────────────────────────┘  │ │   │
│  │  │  ┌───────────────────────────────────────┐  │ │   │
│  │  │  │         HFile (磁盘)                  │  │ │   │
│  │  │  │  按Row Key排序,同一行的数据相邻存储     │  │ │   │
│  │  │  └───────────────────────────────────────┘  │ │   │
│  │  └─────────────────────────────────────────────┘ │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

行键是数据的主索引,每个行键对应一行数据,同一行中的所有数据在物理上通常是连续存储的,这使得基于行键的范围查询具有极高的效率 。列族是列的逻辑分组,同一列族内的列在物理存储上会被存储在一起,便于进行列级别的压缩和优化。

按列族划分物理存储

每个列族对应一个独立的Store

每个Store包含自己的MemStore和HFile

不同列族的数据物理上完全分离

HFile内部按行存储

HFile中的数据按Row Key字典序排序

同一行的所有列(无论属于哪个列族)的数据在物理上是相邻的

数据结构类似:RowKey → {ColumnFamily1: [列1, 列2...], ColumnFamily2: [列1, 列2...]}

HBase采用了稀疏矩阵 的存储方式,这是其与传统关系型数据库的重要区别之一。在传统数据库中,每一行必须包含所有预定义的列,即使某些列的值为空也会占用存储空间。而HBase中的空白列不占用任何存储空间,这种设计使得HBase特别适合存储那些列数量巨大但每行实际使用的列却很少的稀疏数据

HBase中的数据按照行键字典序进行排序存储 ,这种设计对读写性能有着深远的影响。首先,有序存储使得范围扫描(Range Scan)操作变得极为高效 ,因为相邻的行键数据在物理上是连续存储的,可以充分利用顺序I/O 的优势。其次,有序存储配合布隆过滤器(Bloom Filter)可以快速定位可能包含目标数据的HFile,避免无效的磁盘I/O操作

这种结构的优势

  • 可以轻松添加新的列,不影响现有数据
  • 不同行可以有不同的列(非结构化、半结构化数据)
  • 按列族物理存储,适合针对特定列族的查询
  • 支持数据多版本(每个Cell可以有多个时间戳的值)

二、HBase数据操作

数据操作

get和scan的主要区别:

  • get:类似于关系数据库的主键查询,直接定位到特定行,性能较好
  • scan:类似于关系数据库的表扫描,可以扫描整表或指定范围的数据,可以添加过滤条件

数据建模最佳实践

根据实际查询需求设计行键,考虑数据分布的均衡性

行键设计原则

  • 热点数据分散:避免所有请求都访问同一个区域
  • 相关数据聚集:便于范围查询
  • 长度适中:不要太长,建议在100字节以内

列族使用原则

  • 保持列族数量少:建议2-3个
  • 相关数据聚合:经常一起访问的数据放在同一列族
  • 访问频率分离:不同访问频率的数据放在不同列族

三、HBase原理

写入流程详解
  1. 当客户端发起写入请求时,首先需要通过ZooKeeper获取集群的元数据信息,包括根表(-ROOT-)和元表(.META.)的位置。获得元数据后,客户端可以直接连接到存储目标数据Range的RegionServer,无需每次都通过Master中转,这种设计大大减少了Master的负载和请求延迟。

  2. 写入请求到达RegionServer后,首先会被写入WAL(Write-Ahead Log)。WAL采用HDFS的顺序追加写入模式,这种写入方式具有极高的吞吐量和优秀的磁盘I/O性能。写入WAL的目的是为了在节点故障时能够恢复尚未持久化的数据,确保数据不丢失。WAL记录了操作的详细信息,包括所属Region、写入的键值对等。

  3. 完成WAL写入后,数据会被插入到MemStore 中。MemStore是RegionServer内存中的一块区域,用于缓存写入的数据。与传统数据库的事务日志不同,MemStore中的数据已经经过排序组织 ,这为后续的刷写操作做好了准备。MemStore采用LSM树(Log-Structured Merge-tree)的思想,在内存中维护一个有序的数据结构,当达到配置的阈值(默认128MB)时,会触发刷写(Flush)操作将数据写入磁盘生成HFile文件。

  4. 为了防止数据丢失,HBase还会定期将MemStore中的数据checkpoint到WAL中,表示这些数据已经安全持久化。如果RegionServer发生故障,其他节点可以通过重放WAL中未刷写的数据来恢复丢失的写入。这种先写日志后写缓存的设计,虽然增加了一次磁盘I/O,但WAL的顺序写入特性使得这个开销相对较小,同时换来了数据的高可靠性。

读取流程详解
  1. 当RegionServer接收到读取请求时,首先会在BlockCache 中查找目标数据块。BlockCache是RegionServer级别的读缓存,采用LRU(Least Recently Used)算法管理热点数据 。数据块是HBase读取的基本单位,每次从HFile读取数据时,整个数据块都会被缓存到BlockCache中,以便后续访问同一数据块的请求可以直接从内存获取。合理配置BlockCache大小可以显著提升读取性能,特别是对于读多写少的应用场景。

  2. 如果在BlockCache中未命中,数据会继续在MemStore中查找。MemStore中存储的是尚未刷写到磁盘的最近写入,这部分数据具有很高的访问时效性。由于MemStore中的数据已经过排序,可以使用二分查找快速定位目标行

  3. 如果数据既不在BlockCache也不在MemStore中,则需要扫描HFile来获取数据。HBase采用多级索引和布隆过滤器来加速这个过程。布隆过滤器是一种空间效率极高的概率数据结构,可以快速判断某个键是否可能存在于HFile中。如果布隆过滤器显示键不存在,则直接跳过该HFile,避免不必要的磁盘I/O。

  4. 读取HFile时,HBase会首先加载HFile的索引块,快速定位到目标数据块所在的起始位置,然后加载相应的数据块进行解析。这种基于索引的随机读取模式,大大减少了需要扫描的数据量,提高了读取效率。

  5. 对于需要扫描多个HFile的读取请求,HBase会并行读取多个文件,然后合并结果返回给客户端。合并过程中会使用时间戳确定数据的最新版本,对于标记删除的数据也会在合并时进行过滤。

合并与 compaction 机制

随着数据不断写入,HBase会在磁盘上生成越来越多的HFile文件。如果任由文件数量增长,读取操作需要扫描大量的HFile,性能会严重下降。为了解决这个问题,HBase引入了compaction机制,定期合并小文件生成大文件

  • Minor Compaction(小合并):Minor Compaction会选取少数几个较小的HFile进行合并,这个过程只会重写数据,不会删除过期版本或标记删除的数据。Minor Compaction的触发条件包括:HFile数量超过配置阈值、某个Store的HFile总大小超过阈值等。Minor Compaction的执行代价相对较小,通常可以快速完成。

  • Major Compaction(大合并) :Major Compaction会将一个Store中的所有HFile合并成一个文件。在这个过程中,过期的数据版本和标记删除的数据会被真正删除,释放占用的磁盘空间。Major Compaction还会重新排序数据,按照行键顺序组织,可能有利于后续的范围扫描。由于Major Compaction需要读取和重写大量数据,代价较高,通常会安排在集群负载较低的时段(如深夜)进行。

Compaction是HBase性能调优的重要关注点。过于频繁的compaction会消耗大量I/O资源影响正常读写,而compaction不及时又会导致文件数量过多影响读取性能。因此需要根据实际负载特征和I/O能力进行合理配置。

高性能读写原理
  1. LSM树存储结构

HBase采用LSM树(Log-Structured Merge-tree)作为其核心存储结构,这是其实现高性能写入的关键技术。LSM树的核心思想是将随机写入转化为顺序写入,通过内存缓冲区吸收写入压力,定期批量刷写到磁盘生成有序文件

传统的B树类存储结构在进行写入时,需要先定位到数据应该插入的位置,然后在该位置进行原地修改操作。这种随机写入模式会产生大量的磁盘寻道操作,在机械硬盘上性能较差。而LSM树采用追加写(Append-only)的方式,新数据首先追加到内存中的有序结构(类似跳表或红黑树),当内存结构达到阈值时一次性刷写到磁盘成为一个新的HFile。

这种设计带来的好处是显而易见的:

  • 首先,所有对磁盘的写入都是顺序的,可以充分利用磁盘的顺序带宽优势
  • 其次,写入操作不需要预先读取数据块,避免了读-修改-写的操作模式;
  • 最后,多个小的写入可以被合并成一次大的磁盘写入,进一步提高效率。

LSM树的写入放大率(Write Amplification)通常在10-30倍之间,即写入1MB数据实际可能需要10-30MB的磁盘写入量,但这换取的是远超随机写入的吞吐量。

  1. 顺序I/O与磁盘利用率优化

HBase的写入流程充分利用了顺序I/O的性能优势。当MemStore刷写到磁盘生成HFile时,数据的写入是完全顺序的。HDFS的追加写特性确保了数据块被连续写入,而不是分散在磁盘的不同位置。对于大规模数据写入场景,顺序写入的性能可以是随机写入的数十甚至数百倍。

这种顺序写入策略在批量导入场景下效果尤为显著。相比单条记录逐条写入的方式,批量写入可以积攒足够的数据后再触发刷写,每次刷写都能达到接近磁盘带宽上限的吞吐量。HBase提供了多种批量写入API,合理使用这些API可以大幅提升导入性能。

  1. 多层缓存体系

HBase构建了完善的多层缓存体系,从内存到磁盘逐级加速数据访问。MemStore作为写缓存,不仅提供了写入缓冲能力,还在内存中维护了有序的数据结构,为后续刷写做好准备。BlockCache作为读缓存,采用LRU策略自动管理热点数据,保证经常访问的数据始终驻留在内存中。

对于顺序扫描场景,HBase还提供了扫描缓存(Scanner Cache)机制,可以在一次RPC请求中批量返回多行数据,减少网络往返次数。

  1. 无锁设计与并发优化

HBase在并发控制方面采用了多种高效技术,尽量减少锁竞争对性能的影响。在写入环节,RegionServer允许多个客户端并发写入不同的Region ,由于不同Region的数据在物理上隔离,并发写入不会产生冲突。对于同一Region内的写入,HBase采用了无锁设计,通过CAS(Compare-and-Swap)等原子操作保证并发安全

MemStore的更新操作采用了分段锁(Striped Locking)技术,将一个MemStore划分为多个segment,每个segment独立加锁,这样多个线程可以并发写入不同的segment,大大提高了并发写入能力。这种设计在多核CPU上优势尤为明显,可以充分利用多核的并行处理能力。

在读取方面,HBase支持多个并发Scanner同时扫描同一Region,每个Scanner维护独立的遍历状态,互不干扰。这种设计使得复杂的查询可以分解为多个并行子任务,充分利用集群的并行处理能力。

  1. 列式存储与数据局部性

虽然HBase经常被归类为列式存储数据库,但其设计与传统的纯列式存储有所不同。HBase的列式特性主要体现在同一列族的列数据在物理上是连续存储的 ,而不是按行存储。这种设计使得读取特定列族时只需要扫描相关的文件,避免读取无关列的数据。

列式存储带来的另一个好处是同一列数据的类型相同或相似,有利于实现更高效的压缩算法。HBase支持多种压缩算法,包括Gzip、LZO、Snappy等,列式存储使得压缩效率更高,压缩后的数据占用更少的磁盘空间,减少了I/O数据量,间接提升了读写性能。

  1. 布隆过滤器与索引优化

布隆过滤器是HBase提升读取性能的重要数据结构。对于HBase的每个HFile,会为其构建一个布隆过滤器,记录该文件中包含的所有行键。

HFile采用多级索引结构,包括根索引、中间索引和叶子索引。索引块在HFile打开时会被加载到内存中,支持O(1)或O(log n)时间复杂度的索引查找。对于范围扫描场景,HBase还可以利用相邻HFile的元数据快速跳过不包含目标范围的HFile。

  1. 网络通信优化

HBase客户端与RegionServer之间的通信采用了多种优化技术。RPC(远程过程调用)层面采用了高效的序列化框架,可以快速将Java对象序列化为字节流进行传输。客户端会缓存Region位置信息,避免每次请求都查询元数据,只有在发生Region迁移导致缓存失效时才会重新获取。

对于批量操作,HBase提供了Scanner的batchSize和caching参数控制每次返回的数据量。较大的batch和caching值可以减少网络往返次数,提高扫描吞吐量,但会增加内存占用和响应延迟。合理的参数配置需要根据实际网络带宽、内存资源和延迟要求进行权衡。

在集群内部,RegionServer之间的数据迁移也经过了优化。采用底层的HDFS数据传输机制,可以直接在DataNode之间传输数据块,Master节点只负责协调控制,不参与实际的数据传输,这种设计避免了Master成为性能瓶颈。

总结

写入快的原因

WAL采用顺序追加写入

MemStore内存缓冲减少磁盘I/O次数

数据先写内存,后台异步刷写磁盘

多个小写入合并成一次大顺序写入

读取快的原因

BlockCache缓存热点数据

布隆过滤器快速排除不包含数据的HFile

多级索引精确定位数据块

顺序扫描利用顺序I/O

相关推荐
阿乐艾官1 天前
【HBase列式存储数据库】
android·数据库·hbase
Francek Chen2 天前
【大数据存储与管理】分布式数据库HBase:04 HBase的实现原理
大数据·数据库·hadoop·分布式·hbase
代码的奴隶(艾伦·耶格尔)3 天前
Hbase安装与使用
大数据·数据库·hbase
Francek Chen5 天前
【大数据存储与管理】分布式数据库HBase:03 HBase数据模型
大数据·数据库·hadoop·分布式·hdfs·hbase
EAIReport6 天前
MongoDB、Redis、HBase 三大NoSQL数据库:核心区别与选型指南
redis·mongodb·hbase
yatum_20146 天前
集群节点时钟同步(NTP)配置手册
linux·分布式·hbase
升职佳兴7 天前
CentOS 9 下 HBase 2.4.9 分布式集群安装与配置实战
分布式·centos·hbase
代码的奴隶(艾伦·耶格尔)7 天前
Hbase的使用
java·前端·hbase
代码的奴隶(艾伦·耶格尔)8 天前
Hbase GUI 可视化软件
大数据·数据库·hbase