摘要
本文介绍了 HBase 数据库,包括其特点、组件、数据模型、数据写入与读取流程等基础知识,并分享了 HBase 实战经验总结,如性能指标、使用场景、与 HDFS 的关系、WAL 机制、数据删除方式、优化设计思考等内容,为大数据存储域的学习和实践提供了全面的参考。
1. HBase数据库简介
HBase 是一个面向列式存储的分布式数据库,其设计思想来源于 Google 的 BigTable 论文。HBase 底层存储基于 HDFS 实现,集群的管理基于 ZooKeeper 实现。HBase 良好的分布式架构设计为海量数据的快速存储、随机访问提供了可能,基于数据副本机制和分区机制可以轻松实现在线扩容、缩容和数据容灾,是大数据领域中 Key-Value 数据结构存储最常用的数据库方案。
1.1. HBase数据库特点
- 易扩展:Hbase 的扩展性主要体现在两个方面,一个是基于运算能力(RegionServer) 的扩展,通过增加 RegionSever 节点的数量,提升 Hbase 上层的处理能力;另一个是基于存储能力的扩展(HDFS),通过增加 DataNode 节点数量对存储层的进行扩容,提升 HBase 的数据存储能力。
- 海量存储:HBase 作为一个开源的分布式 Key-Value 数据库,其主要作用是面向 PB 级别数据的实时入库和快速随机访问。这主要源于上述易扩展的特点,使得 HBase 通过扩展来存储海量的数据。
- 列式存储:Hbase 是根据列族来存储数据的。列族下面可以有非常多的列。列式存储的最大好处就是,其数据在表中是按照某列存储的,这样在查询只需要少数几个字段时,能大大减少读取的数据量。
- 高可靠性:WAL 机制保证了数据写入时不会因集群异常而导致写入数据丢失,Replication 机制保证了在集群出现严重的问题时,数据不会发生丢失或损坏。而且 Hbase 底层使用 HDFS,HDFS 本身也有备份。
- 稀疏性:在 HBase的列族中,可以指定任意多的列,为空的列不占用存储空间,表可以设计得非常稀疏。
1.2. HBase数据库组件
HBase 可以将数据存储在本地文件系统,也可以存储在 HDFS 文件系统。在生产环境中,HBase 一般运行在 HDFS 上,以 HDFS 作为基础的存储设施。HBase 通过 HBase Client 提供的 Java API 来访问 HBase 数据库,以完成数据的写入和读取。HBase 集群主由HBase 的核心架构由五部分组成,分别是 HBase Client、HMaster、Region Server、ZooKeeper 以及 HDFS。它的架构组成如下图所示。

1.2.1. HBase Client
HBase Client 为用户提供了访问 HBase 的接口,可以通过元数据表来定位到目标数据的 RegionServer,另外 HBase Client 还维护了对应的 cache 来加速 Hbase 的访问,比如缓存元数据的信息。
1.2.2. HMaster
HMaster 是 HBase 集群的主节点,负责整个集群的管理工作,主要工作职责如下:
- 分配Region:负责启动的时候分配Region到具体的 RegionServer;
- 负载均衡:一方面负责将用户的数据均衡地分布在各个 Region Server 上,防止Region Server数据倾斜过载。另一方面负责将用户的请求均衡地分布在各个 Region Server 上,防止Region Server 请求过热;
- 维护数据:发现失效的 Region,并将失效的 Region 分配到正常的 RegionServer 上,并且在Region Sever 失效的时候,协调对应的HLog进行任务的拆分。
1.2.3. Region Server
Region Server 直接对接用户的读写请求,是真正的干活的节点,主要工作职责如下。
- 管理 HMaster 为其分配的 Region;
- 负责与底层的 HDFS 交互,存储数据到 HDFS;
- 负责 Region 变大以后的拆分以及 StoreFile 的合并工作。
与 HMaster 的协同:当某个 RegionServer 宕机之后,ZK 会通知 Master 进行失效备援。下线的 RegionServer 所负责的 Region 暂时停止对外提供服务,Master 会将该 RegionServer 所负责的 Region 转移到其他 RegionServer 上,并且会对所下线的 RegionServer 上存在 MemStore 中还未持久化到磁盘中的数据由 WAL 重播进行恢复。下面详细介绍下 Region Serve数据存储的基本结构,如下图所示。一个 Region Server 是包含多个 Region 的,这里仅展示一个。

- Region:每一个 Region 都有起始 RowKey 和结束 RowKey,代表了存储的Row的范围,保存着表中某段连续的数据。一开始每个表都只有一个 Region,随着数据量不断增加,当 Region 大小达到一个阀值时,Region 就会被 Regio Server 水平切分成两个新的 Region。当 Region 很多时,HMaster 会将 Region 保存到其他 Region Server 上。
- Store:一个 Region 由多个 Store 组成,每个 Store 都对应一个 Column Family, Store 包含 MemStore 和 StoreFile。
- MemStore:作为HBase的内存数据存储,数据的写操作会先写到 MemStore 中,当MemStore 中的数据增长到一个阈值(默认64M)后,Region Server 会启动 flasheatch 进程将 MemStore 中的数据写人 StoreFile 持久化存储,每次写入后都形成一个单独的 StoreFile。当客户端检索数据时,先在 MemStore中查找,如果MemStore 中不存在,则会在 StoreFile 中继续查找。
- StoreFile:MemStore 内存中的数据写到文件后就是StoreFile,StoreFile底层是以 HFile 的格式保存。HBase以Store的大小来判断是否需要切分Region。
当一个Region 中所有 StoreFile 的大小和数量都增长到超过一个阈值时,HMaster 会把当前Region分割为两个,并分配到其他 Region Server 上,实现负载均衡。
- HFile:HFile 和 StoreFile 是同一个文件,只不过站在 HDFS 的角度称这个文件为HFile,站在HBase的角度就称这个文件为StoreFile。
- HLog:负责记录着数据的操作日志,当HBase出现故障时可以进行日志重放、故障恢复。例如,磁盘掉电导致 MemStore中的数据没有持久化存储到 StoreFile,这时就可以通过HLog日志重放来恢复数据。
1.2.4. ZooKeeper
HBase 通过 ZooKeeper 来完成选举 HMaster、监控 Region Server、维护元数据集群配置等工作,主要工作职责如下:
- 选举HMaster:通ooKeeper来保证集中有1HMaster在运行,如果 HMaster 异常,则会通过选举机制产生新的 HMaster 来提供服务;
- 监控Region Server: 通过 ZooKeeper 来监控 Region Server 的状态,当Region Server 有异常的时候,通过回调的形式通知 HMaster 有关Region Server 上下线的信息;
- 维护元数据和集群配置:通过ooKeeper储B信息并对外提供访问接口。
1.2.5. HDFS
HDFS 为 HBase 提供底层数据存储服务,同时为 HBase提供高可用的支持, HBase 将 HLog 存储在 HDFS 上,当服务器发生异常宕机时,可以重放 HLog 来恢复数据。
1.3. HBase数据模型
HBase 是一个面向列式存储的分布式数据库。HBase 的数据模型与 BigTable 十分相似。在 HBase 表中,一条数据拥有一个全局唯一的键(RowKey)和任意数量的列(Column),一列或多列组成一个列族(Column Family),同一个列族中列的数据在物理上都存储在同一个 HFile 中,这样基于列存储的数据结构有利于数据缓存和查询。 HBase 中的表是疏松地存储的,因此用户可以动态地为数据定义各种不同的列。HBase中的数据按主键排序,同时,HBase 会将表按主键划分为多个 Region 存储在不同 Region Server 上,以完成数据的分布式存储和读取。
HBase 根据列成来存储数据,一个列族对应物理存储上的一个 HFile,列族包含多列列族在创建表的时候被指定。

1.3.1. Column Family
Column Family 即列族,HBase 基于列划分数据的物理存储,一个列族可以包含包意多列。一般同一类的列会放在一个列族中,每个列族都有一组存储属性:
- 是否应该缓存在内存中;
- 数据如何被压缩或行键如何编码等。
HBase 在创建表的时候就必须指定列族。HBase的列族不是越多越好,官方荐一个表的列族数量最好小于或者等于3,过多的列族不利于 HBase 数据的管理和索引。
1.3.2. RowKey
RowKey的概念与关系型数据库中的主键相似,HBase 使用 RowKey 来唯一标识某行的数据。访问 HBase 数据的方式有三种:
- 基于 RowKey的单行查询;
- 基于RowKey的范围查询;
- 全表扫描查询。
1.3.3. Region
HBase 将表中的数据基于 RowKey 的不同范围划分到不同Region上,每个Region都负责一定范围的数据存储和访问。
- 每个表一开始只有一个 Region,随着数据不断插入表,Region 不断增大,当增大到一个阀值的时候,Region 就会等分成两个新的 Region。当table中的行不断增多,就会有越来越多的 Region。
- 另外,Region 是 Hbase 中分布式存储和负载均衡的最小单元,不同的 Region 可以分布在不同的 HRegion Server上。但一个Hregion是不会拆分到多个server上的。
- 这样即使有一个包括上百亿条数据的表,由于数据被划分到不同的 Region上,每个 Region 都可以独立地进行写入和查询,HBase 写查询时候可以于多 Region 分布式并发操作,因此访问速度也不会有太大的降低。
1.3.4. TimeStamp
TimeStamp 是实现HBase多版本的关键。在HBase中,使用不同 TimeStamp 来标识相同RowKey对应的不同版本的数据。相同 RowKey的数据按照 TimeStamp 倒序排列。默认查询的是最新的版本,当然用户也可以指定 TimeStamp 的值来读取指定版本的数据。
1.4. HBase数据写入流程

步骤 | 说明 |
---|---|
1. 客户端请求 | 客户端通过 HTable 对象向 HBase 发送 Put 写操作 |
2. WAL 写入 | RegionServer 将写入操作先写入 WAL(预写日志,位于 HDFS)以保证故障恢复 |
3. MemStore | WAL 写完后,再写入 MemStore(在内存中按列簇组织的结构) |
4. Flush to HFile | 当 MemStore 达到阈值(如128MB),会触发 flush 操作,将数据写入磁盘,生成 HFile 存储在 HDFS 上 |
5. Compaction | 定期进行 HFile 合并(minor/major compaction),优化读取性能 |
1.4.1. Region Server 寻址
- HBase Client 访问 ZooKeeper;
- 获取写入 Region 所在的位置,即获取 hbase:meta 表位于哪个 Region Server;
- 访问对应的 Region Server;
- 获取 hbase:meta 表,并查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 Region 信息以及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问;
1.4.2. 写Hlog
- HBase Client 向 Region Server 发送写 Hlog 请求;
- Region Server 会通过顺序写入磁盘的方式,将 Hlog 存储在 HDFS 上;
1.4.3. 写MemStore并返回结果
- HBase Client 向 Region Server 发送写 MemStore 请求;
- 只有当写 Hlog 和写 MemStore 的请求都成功完成之后,并将反馈给 HBase Client,这时对于整个 HBase Client 写入流程已经完成。
1.4.4. MemStore刷盘
- HBase 会根据 MemStore 配置的刷盘策略定时将数据刷新到 StoreFile 中,完成数据持久化存储。
1.4.5. 为什么要把 WAL 加载到 MemStore中,再刷写成 HFile 呢?
WAL (Write-Ahead-Log) 预写日志是 HBase 的 RegionServer 在处理数据插入和删除过程中用来记录操作内容的一种日志。每次Put、Delete等一条记录时,首先将其数据写入到 RegionServer 对应的 HLog 文件中去。而WAL是保存在HDFS上的持久化文件,数据到达 Region 时先写入 WAL,然后被加载到 MemStore 中。这样就算Region宕机了,操作没来得及执行持久化,也可以再重启的时候从 WAL 加载操作并执行。
那么,我们从写入流程中可以看出,数据进入 HFile 之前就已经被持久化到 WAL了,而 WAL 就是在 HDFS 上的,MemStore 是在内存中的,增加 MemStore 并不能提高写入性能,为什么还要从 WAL 加载到 MemStore中,再刷写成 HFile 呢?
- 数据需要顺序写入,但 HDFS 是不支持对数据进行修改的;
- WAL 的持久化为了保证数据的安全性,是无序的;
- Memstore在内存中维持数据按照row key顺序排列,从而顺序写入磁盘;
所以 MemStore 的意义在于维持数据按照RowKey的字典序排列,而不是做一个缓存提高写入效率。
1.5. HBase读数据流程

步骤 | 说明 |
---|---|
1. 客户端请求 | 客户端通过 HTable 发送 Get 或 Scan |
2. MemStore 查找 | 首先查找 MemStore(最近写入但还未 flush 的数据) |
3. BlockCache 查找 | 若在 MemStore 未命中,再查找 BlockCache(读取热点缓存) |
4. HFile 查找 | 若仍未命中,则访问 HDFS 上的 HFile(可能多个) |
5. 数据合并 | 合并多个数据源的结果(如 MemStore + HFile + delete marker)后返回最终值 |
1.5.1. Region Server 寻址
HBase Client 请求 ZooKeeper 获取元数据表所在的 Region Server的地址。
1.5.2. Region 寻址
HBase Client 请求 RegionServer 获取需要访问的元数据,查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以 及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。
1.5.3. 数据读取
HBase Client 请求数据所在的 Region Server,获取所需要的数据。 Region 首先在 MemStore 中查找,若命中则返回;如果在MemStore 中找不到,则通过 BloomFilter 判断数据是否存在;如果存在,则在:StoreFile 中扫描并将结果返回客户端。
2. HBase实战经验总结
2.1. HBase在阿里云上的标准的服务器4核8G的性能指标?
对于 4 核 8GB 配置的服务器,HBase 的典型性能范围为:
- 写操作(QPS) :大约 5,000 到 20,000 QPS。
- 读操作(QPS) :大约 10,000 到 30,000 QPS。
- 混合负载(读写) :大约 10,000 到 40,000 请求每秒。
2.1.1. 基本性能估算
- CPU :4 核,适合一般的中小型应用(中小型应用 :10,000 ~ 100,000 月活用户,并发用户数 100~2,000),CPU 性能对 HBase 的性能影响主要体现在负载较重的查询操作,或者是 RegionServer 的数量较多时。
- 内存 :8 GB 内存一般适合中小规模的数据量。HBase 会将数据缓存到内存中,因此较多的内存有助于提升读操作性能,特别是扫描操作。常见的配置中,
hfile
和memstore
的大小会影响内存占用。 - 磁盘:磁盘性能是 HBase 的关键因素。若使用 SSD,将能提供更快的读取速度,特别是对于需要频繁读写大量数据的场景。
- 网络带宽:对于分布式系统,网络带宽也至关重要。集群中的数据同步和请求的处理需要较快的网络连接,特别是当集群规模扩展时。
2.1.2. 性能表现:
- 吞吐量:对于典型的读写操作,4 核 8 GB 内存的实例,每秒能够处理的读写请求数量通常会在几千到几万次之间,具体取决于数据模型、表的设计以及硬件性能。
- 延迟:HBase 的延迟通常较低,但在负载较高时,响应时间可能会增加。HBase 在读操作时特别依赖于内存缓存,且对于大规模数据,可能会遇到磁盘瓶颈。
- 写入速率:写入速率会受到 RegionServer 处理能力的影响,通常较低内存和 CPU 配置下的写入速率可能会在几千条到几万条每秒之间。
2.1.3. 具体性能调优建议:
- 调整内存配置 :可以通过调整
hbase.regionserver.global.memstore.upperLimit
、hbase.regionserver.global.memstore.lowerLimit
等参数,优化内存使用。 - 负载均衡:确保 RegionServer 分布均匀,避免单个节点成为瓶颈。
- 压缩:开启 HBase 的数据压缩功能,能有效减少磁盘占用,提高存储效率。
- 磁盘选择:建议使用 SSD 存储,尤其在读写操作频繁的场景下。
2.2. HBase/Mysql数据库使用场景?
这是一个非常经典的问题,关系到你在系统设计时如何选择合适的 存储系统 。HBase 和 MySQL 适用于完全不同的场景和需求,你可以通过以下几个维度来决定:
2.2.1. 🔍 HBase的使用场景
HBase 适合写多读少、海量数据、宽表、高并发、实时写入等场景。典型适用场景:
- 海量数据写入(如 TB ~ PB 级别):行为日志、埋点日志、设备数据。每秒几万到几十万条数据写入。
- 宽表/稀疏表模型:每个用户或对象都有大量不同字段(列),列非常多。每条记录可能只有一部分列有值(典型 NoSQL 特点)。
- 大数据实时处理、风控等场景:如:贷前风控实时查库,撞库校验。实时写入 + 实时读取(通过行键精确查找)。
- 物联网 IoT:每秒千万级设备数据上传,写入吞吐量要求高。
- 反欺诈与行为画像:用户行为流水、模型特征库、用户风险画像。
2.2.2. 🔍 MySQL 使用场景
MySQL 更适合业务数据管理、小中型系统、强一致性交易等。典型适用场景:
- 业务系统数据库:如电商订单系统、会员系统、后台管理系统。表结构固定、关系清晰、事务强一致性需求高。
- 小中规模数据:单表数据不超过千万级。数据增长速度可控。
- 需要复杂查询的业务系统:多表 JOIN、子查询、聚合分析、窗口函数。
- 强事务一致性要求:银行转账、支付流水、用户余额等需要强 ACID 保证。
- 数据分析平台前置库:先存入结构化数据,后由报表工具读取(如 BI 系统)。
2.2.3. 🚨 如果你用错了会怎样?
- 用 HBase 实现订单系统?
-
- 查询字段有限,缺少事务,数据模型混乱,非常难维护。
- 用 MySQL 存每秒百万条日志?
-
- 性能瓶颈明显,写入缓慢,磁盘和连接资源不堪重负。
2.2.4. ✅ HBase/MySQL使用场景总结
比较维度 | HBase | MySQL |
---|---|---|
类型 | 分布式列式存储(NoSQL) | 关系型数据库(RDBMS) |
数据模型 | 列族 + 行键,Schema 灵活 | 表结构固定,强 Schema |
查询能力 | 基于行键和列键,不支持复杂查询 | 支持 SQL、多表关联、JOIN、子查询等 |
扩展能力 | 横向扩展强,可支持数十亿级数据 | 横向扩展弱,适合中小规模数据 |
写入性能 | 非常高,写吞吐量大 | 中等,事务一致性强但写入有限 |
读取性能 | 依赖缓存和预读,随机读较慢,顺序读快 | 读取性能好,尤其适合 OLTP 场景 |
一致性与事务 | 最终一致性,支持批量写入 | 强一致性,ACID 事务支持完整 |
典型应用场景 | 日志、行为埋点、风控、IoT、反欺诈、推荐系统 | 核心业务系统、电商订单、金融系统、用户数据管理 |
运维复杂度 | 高,依赖 Hadoop/HDFS | 成熟工具多,使用门槛低 |
2.2.5. 🧠 实际项目中的组合用法(推荐)
大多数实际项目中会:
- 用 MySQL 存储主业务数据(订单、用户、账户)
- 用 HBase 存储大数据、日志、行为流、特征库等
例如在金融风控中:
- 用户借款行为数据存在 HBase(快速撞库查询 + 特征服务)
- 用户的账户状态和交易记录存在 MySQL(强一致性)
2.3. Hbase/StartRock数据库使用场景
这是一个非常有代表性的问题,涉及两个不同类型数据库的使用场景选择:
- HBase :Hadoop 生态下的分布式 NoSQL 数据库,适合海量数据的高并发读写。用 HBase :当你需要实时写入、大量行级数据、快速按主键查找时。
- StarRocks :新一代 MPP(Massively Parallel Processing)架构的分布式列式数据库,主打高性能分析型查询(OLAP)。用 StarRocks :当你需要高速 OLAP 分析、复杂 SQL 查询、报表展示时。
2.3.1. 🔍 HBase 适用场景
HBase 适合实时写入 + 精准主键查询,支持海量数据横向扩展。典型适用:
- 实时风控特征库、撞库服务(通过 rowkey 快速命中)
- IoT 设备数据流(高频写入 + 行键读取)
- 用户行为埋点、日志流入
- 推荐系统用户画像(写入频繁,读取通过主键定位)
- 风控平台中用户策略数据、命中记录、反欺诈行为流水
2.3.2. 🔍 StarRocks 适用场景
StarRocks 是用于高性能 分析型查询(OLAP) 的数据库,替代 ClickHouse、Kylin、Doris 等方案。典型适用:
- 报表系统、实时大屏、多维分析平台(BI)
- 实时指标聚合(如:近 5 分钟内新增用户数)
- 离线数据导入后分析(例如从 HBase 或 Kafka ETL 到 StarRocks)
- 跨维度聚合与分析(带 JOIN、GROUP BY、SUM、AVG、窗口函数等)
- 代替传统的 Hive + Presto/Impala 做数仓中间层
2.3.3. 🧠 HBase/StarRocks场景分析
需求/场景 | 推荐使用 | 原因 |
---|---|---|
每秒数万次写入、按主键快速查找 | HBase | 写性能极高,行键定位读取,适合实时写入日志和行为数据 |
秒级聚合查询、近实时指标分析 | StarRocks | 聚合查询性能极佳,支持多维分析 |
仅根据 rowkey 查找记录 | HBase | 单行快速查找,毫秒级响应 |
需要 GROUP BY、JOIN、窗口分析 | StarRocks | 完整支持 SQL,适合 BI 和多维报表 |
数据源为 Kafka、HDFS,想支持分析 | StarRocks | 支持流式导入 + 实时分析 |
反欺诈平台中存储用户特征 & 状态 | HBase | 每个用户数据字段灵活变动、写入频繁 |
建大数据分析平台,替代 Hive 报表 | StarRocks | 查询性能远超 Hive、Presto |
用户标签存储/查询/实时更新 | HBase | 支持稀疏列、高频更新 |
用户行为分析、渠道投放效果评估 | StarRocks | 支持复杂分析、聚合,适合 BI、数据分析师 |
2.3.4. ✅ HBase/StarRocks 使用场景
维度 | HBase | StarRocks |
---|---|---|
数据库类型 | 分布式 NoSQL(列族存储,KV 模型) | 分布式列式数据库(MPP架构,分析型 OLAP) |
数据模型 | Schema 灵活,行键+列族 | 严格结构化,表模型与 SQL 兼容 |
存储引擎 | 基于 HDFS,写多读少场景优化 | 高性能列存,支持高吞吐的聚合与复杂分析 |
写入能力 | 非常强(秒级百万级 TPS),低延迟写入 | 一般(通过批量导入),适合周期性同步、ETL 写入 |
查询能力 | 仅支持行键查询,不支持复杂 SQL 查询 | 强大 OLAP 查询,支持 JOIN、聚合、窗口函数等复杂分析 |
查询延迟 | 毫秒级(KV查找),复杂查询不适合 | 子秒级 ~ 秒级响应,支持多维分析 |
使用场景 | 行级存储、大量写入、实时访问、行为日志、IoT | 报表分析、多维聚合、数据可视化、大屏展示、BI 系统 |
事务一致性 | 最终一致性,支持幂等写 | 支持导入事务和写入原子性 |
接入方式 | Java API、Phoenix(SQL)等 | 标准 SQL(MySQL 协议兼容),可接 BI 工具 |
2.3.5. 📌 实际系统推荐组合使用(非常常见)
许多大数据平台或风控平台会 同时使用 HBase + StarRocks,各司其职:
模块 | 存储 | 说明 |
---|---|---|
实时风控查库 | HBase | 每个用户实时特征写入、撞库查找 |
风控数据分析 | StarRocks | 对策略命中情况、用户行为进行聚合分析 |
用户行为日志 | HBase/Kafka | 行为写入 Kafka -> HBase |
运营报表系统 | StarRocks | 生成日报、月报、用户增长等分析 |
2.4. HBase数据库与HDFS是什么关系?
HBase 和 HDFS 的关系是非常紧密的:HBase 是建立在 HDFS(Hadoop Distributed File System)之上的一个分布式 NoSQL 数据库系统。HDFS 是底层的分布式文件存储系统,而HBase 是基于 HDFS 构建的分布式数据库,专门用于支持高并发、高吞吐的读写。 类比理解:
系统 | 类比角色 | 说明 |
---|---|---|
HDFS | "硬盘" | 提供分布式存储能力(存文件) |
HBase | "数据库引擎" | 提供行列式结构的读写访问 |
HBase 自己并不存储数据文件,而是将数据 以 HFile 文件形式存储在 HDFS 上,并由 RegionServer 管理这些文件的访问。
2.4.1. 🔍 HBase与HDFS联系
HDFS 是存储层
- HDFS 提供了可靠的、可扩展的分布式文件存储。
- 它将大文件切分为 block(默认 128MB),分布式存储在多个 DataNode 上。
HBase 是计算/访问层(NoSQL 数据库)
- HBase 以表格的形式组织数据,底层每一行/列的数据都以 HFile 存储在 HDFS 上。
- 所有写入数据(WAL、MemStore flush 成 HFile)最终都会存储到 HDFS。
写入流程中 HDFS 的作用
-
当你通过 HBase 写入一条数据时,流程大概如下:
客户端 → RegionServer → MemStore(内存) → WAL(写前日志,存在 HDFS) → Flush 成 HFile → 存入 HDFS
2.4.2. 📌 HBase与HDFS对比
特性 | HBase | HDFS |
---|---|---|
本质 | 分布式数据库(行列式存储) | 分布式文件系统 |
用途 | 存储、检索结构化数据 | 存储任意格式文件 |
数据访问方式 | 行键查询、列族、NoSQL API | 通过路径读取完整文件 |
依赖性 | 依赖 HDFS 存储数据文件、WAL、HFile 等 | 不依赖 HBase |
示例使用场景 | 用户画像、行为日志、风控特征查询 | 存储原始日志文件、大文件、ETL中间结果等 |
2.4.3. 🚀 HBase与HDFS如何协作?
HBase 作为一个数据库系统:
- 使用 HDFS 存储所有的数据文件(HFile)和日志(WAL)
- 利用 HDFS 的副本机制、容错机制保证数据安全性
- 利用 HDFS 的大文件读写性能,提升吞吐量
HBase 是"数据库",HDFS 是它的"硬盘"。HBase 不存储文件,它把数据存成文件交给 HDFS 来管理。
2.5. Hbase里面的写入WAL(Write-Ahead Log)预写日志 和Rocket MQ中消息写入是不是一样的原理?
2.5.1. HBase 写入 WAL 的原理
HBase 的写入流程大致如下:
- 客户端写入数据(Put 请求)
- 写入 WAL(Write-Ahead Log) :预写日志是一个追加文件,主要用于故障恢复。
- 数据写入 MemStore(内存中的数据结构)
- 异步刷新:当 MemStore 达到阈值时,刷写到磁盘形成 HFile(即 SSTable)
WAL 的作用:
- 主要是保证数据的持久化和恢复能力;
- 如果 RegionServer 崩溃,可以通过 WAL 进行数据恢复;
- 是一种典型的"先写日志,后写内存"方式。
2.5.2. RocketMQ 消息写入原理
RocketMQ 写入流程:
- Producer 发送消息
- 消息写入 CommitLog(顺序写入磁盘文件)
- CommitLog 是 RocketMQ 的核心数据结构,相当于消息队列的存储引擎
- 后续通过 ConsumeQueue、IndexFile 等进行消费和索引构建
RocketMQ 的 CommitLog 特点:
- 实现了高吞吐量的顺序写;
- 保证了消息持久性;
- 本质上也是"先写磁盘,后处理"的模式。
2.5.3. HBase WAL vs RocketMQ CommitLog(对比)
对比项 | HBase WAL | RocketMQ CommitLog |
---|---|---|
作用 | 数据恢复 | 消息持久化 + 消费顺序 |
写入方式 | 先写 WAL,再写 MemStore | 直接写入磁盘(顺序) |
是否消费数据 | 不直接消费 | 是消费者的消费源头 |
是否构建索引 | 否 | 是(ConsumeQueue / IndexFile) |
是否支持回放 | 是(用于恢复) | 是(消费者消费) |
落盘机制 | 基于 FS 的文件 | MappedByteBuffer 直接写文件 |
2.6. 定期进行HFile合并(minor/major compaction)的原理是什么?
HBase 数据写入架构中的一个关键部分,其设计初衷是为了优化 查询性能 和 磁盘使用效率。下面详细介绍其原理:
2.6.1. 🔧 为什么需要 HFile 合并?
HBase 写入流程是先写入内存(MemStore)和 WAL,然后内存刷新为 HFile 文件。
长期运行下来,会产生大量小文件(HFile),导致:
- 查询性能下降:一次查询可能需要扫描多个 HFile。
- 存储碎片严重:重复Key/老旧数据无法及时清理。
- 影响 TTL / 删除标记(Tombstone)清除时机。
这就是合并(Compaction)机制存在的原因。
2.6.2. 📦 Compaction 类型
2.6.2.1. Minor Compaction(小合并)
- 触发条件:MemStore 刷新为多个小 HFile,数量达到阈值(默认 3)。
- 处理方式:将多个小 HFile 合并成一个大 HFile,不清除过期数据和 delete 标记。
- 目的:减少文件数量,提高扫描效率。不会对系统造成太大压力,频率高。
2.6.2.2. Major Compaction(大合并)
- 触发条件:定期调度(默认 7 天),或手动触发。表/Region 数据量达到一定级别。
- 处理方式:将所有 HFile 合并成一个新文件。清除 TTL 过期数据。应用 delete marker,彻底删除标记数据。
- 代价大:IO 开销高,耗时长,但对系统"净化"彻底。
2.6.3. 🔄 Compaction 原理简图
arduino
写入流程:
WAL + MemStore
↓(MemStore flush)
多个小 HFile(s1.hfile, s2.hfile, ...)
↓(Minor Compaction)
较大 HFile(merged1.hfile)
↓(Major Compaction)
一个完整且干净的 HFile(cleaned.hfile)
2.6.4. 📌 Compaction 过程中的优化点
- 并发控制:RegionServer 限制同时进行的 compaction 数量。
- Throttling(限速) :防止影响在线读写。
- Compaction 策略类可自定义 :如
RatioBasedCompactionPolicy
。
2.6.5. 💡 为什么要合并 HFile总结
目的 | Minor Compaction | Major Compaction |
---|---|---|
减少小文件数量 | ✅ | ✅ |
提升查询效率 | ✅ | ✅ |
删除过期数据 / 删除标记数据 | ❌ | ✅ |
系统资源消耗 | 低 | 高 |
触发频率 | 高 | 低 |
2.7. MemStore 是内存数据? 默认大小是128M ?
是的,MemStore 是 HBase 中用于临时存储写入数据的内存组件 ,它的确是内存中的数据结构,属于 HBase 写路径的一部分。
2.7.1. ✅ MemStore是什么?
当写入数据到 HBase 时,流程如下:
- 写入数据首先会记录到 WAL(预写日志) ,用于数据恢复。
- 然后数据会被写入到内存中的 MemStore。
- 当 MemStore 的大小达到阈值,就会被刷写(Flush)到磁盘上的 HFile 文件中,也就是持久化。
2.7.2. 📌 MemStore默认大小是多少?
- 默认情况下,单个 MemStore 的大小大约是 128MB(128 * 1024 * 1024 字节) 。
- 实际大小可以通过参数配置:
arduino
hbase.hregion.memstore.flush.size
默认值:134217728
(128MB)这意味着如果某个列族的 MemStore 达到这个阈值,就会触发 flush,将数据写入磁盘(HFile)。
2.7.3. 🧠 注意事项:
- MemStore 是每个列族(ColumnFamily)单独管理的,每个列族都有自己的 MemStore。
- 如果多个 MemStore 总体占用超过阈值(比如整个 RegionServer 超过
hbase.regionserver.global.memstore.size
),也会触发 flush。 - 合理配置 MemStore 大小对于避免频繁 flush 和 GC 非常关键。
2.8. Hbase的数据删除?
在 HBase 中, "删除"并不是立即物理删除数据 ,而是通过写入一个特殊的标记(称为 Delete Marker )来实现的。这个过程称为 逻辑删除。
阶段 | 说明 |
---|---|
写入时 | 写入 Delete Marker |
查询时 | 遇到 Delete Marker,会隐藏原始数据 |
合并时(Major Compaction) | 真正删除数据和 Delete Marker |
下面是 HBase 删除机制的完整原理和流程:
2.8.1. 🔧 HBase 删除数据的几种方式
2.8.1.1. 删除一列(Delete a column)
go
Delete delete = new Delete(rowKey);
delete.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col"));
table.delete(delete);
删除指定 rowKey
下某列(特定版本)。
2.8.1.2. 删除列族中的所有列(Delete all columns in a family)
arduino
delete.addFamily(Bytes.toBytes("cf"));
删除指定 row 下的整个列族。
2.8.1.3. 删除整行(Delete a row)
sql
Delete delete = new Delete(rowKey);
table.delete(delete);
删除 rowKey 对应的整行数据。
2.8.2. 🔍 删除原理
2.8.2.1. ✅ 写入 Delete Marker(删除标记)
- 删除操作会生成一个
Delete
类型的 特殊 Cell,这就是 Delete Marker。 - Delete Marker 是和正常数据一样,写入到 WAL → MemStore → HFile。
2.8.2.2. ✅ 查询时跳过被标记为删除的数据
- 查询数据时,HBase 会根据 Delete Marker 决定是否跳过某个 Cell。
- Delete Marker 有作用范围(比如:删除某列、列族或整行)。
2.8.2.3. ✅ 真正删除发生在 Major Compaction(大合并)
- 当触发 major compaction 时,系统会将 HFile 重新合并,此时:
-
- Delete Marker 会被识别。
- 被标记为删除的数据 和 Delete Marker 都会被永久清除。
- 也就是说,删除是"延迟"的,直到一次 major compaction 之后才真正释放磁盘空间。
2.8.2.4. 🕒 何时触发 Compaction?
- Minor Compaction:多个小的 HFile 合并成大的 HFile,不会清理删除数据。
- Major Compaction:会合并所有 HFile,并真正删除已经打上标记的数据。
- 可以手动触发:
shell
hbase> major_compact 'your_table_name'
2.8.2.5. 🧠 补充说明
特性 | 说明 |
---|---|
Delete 是写操作 | 删除不是直接删掉数据,而是增加一条"删除指令" |
删除会占用磁盘 | 直到 major compaction 清理 |
查询性能可能受影响 | 查询时要判断是否有 Delete Marker |
TTL 也会自动删除数据 | 超过时间的数据也会被标记,等待 compact 清理 |
2.9. 一般的中小型应用 是多大的用户量?
中小型应用的用户量并没有一个绝对标准,但在行业实践中,大致可以按如下维度来划分:
2.9.1. 根据日活跃用户(DAU)/月活跃用户(MAU)来划分:
应用规模 | 日活用户(DAU) | 月活用户(MAU) | 特点 |
---|---|---|---|
小型应用 | 100 ~ 1,000 | 1,000 ~ 10,000 | 通常是内部使用、地方级、垂直小众应用 |
中型应用 | 1,000 ~ 100,000 | 10,000 ~ 1,000,000 | 中型企业、行业类应用、电商/教育/工具类 App |
大型应用 | 100,000 ~ 1,000,000+ | 1,000,000 ~ 100,000,000+ | 全国性平台、大型B2C电商、社交/内容类 |
2.9.2. 按并发用户量划分:
应用规模 | QPS(请求数/秒) | 并发用户数 | 特点 |
---|---|---|---|
小型应用 | < 100 | < 100 | 单机部署足够,轻量数据库 |
中型应用 | 100 ~ 5,000 | 100 ~ 5,000 | 通常会用到 Redis 缓存、读写分离、限流等机制 |
大型应用 | > 5,000 | > 5,000 | 分布式系统,微服务架构,多活部署,高可用要求高 |
2.9.3. 举例说明:
类型 | 应用场景 | 估计用户量级 | 备注 |
---|---|---|---|
OA 系统 | 企业内部使用 | 100 ~ 10,000 | 小中型企业 |
CRM 系统 | 销售支持平台 | 1,000 ~ 100,000 | 客户越多并发越高 |
地方政务系统 | 局部区域使用 | 10,000 ~ 200,000 | 日志系统、报表系统等 |
电商/商城系统 | 中型垂类平台 | 10,000 ~ 1,000,000 | 秒杀高峰并发高 |
教育/视频系统 | 中型网课平台 | 10,000 ~ 500,000 | 在线人数高波动 |
银行/支付系统 | 核心应用 | > 1,000,000 | 高并发、高可用、金融级别 |
2.9.4. ✅ 你可以这样粗略估算:
并发用户数 ≈ 总用户数 × 活跃率 × 峰值系数。比如:
- 10万注册用户
- 月活 20%,活跃用户 2万
- 峰值系数 10%(高峰期有10%的活跃用户同时使用)
则并发用户 ≈ 2万 × 10% = 2,000
3. HBase+SpringBoot是实战示例
在 Spring Boot中操作 HBase 进行 CRUD(增删改查) ,通常可以通过以下几种方式:
3.1. 🧱 基础依赖(Maven)
首先需要添加 HBase 客户端依赖:
xml
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.4.17</version> <!-- 注意版本与服务器一致 -->
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-common</artifactId>
<version>2.4.17</version>
</dependency>
3.2. 🔧 配置 HBase 连接
创建 HBase 配置类:
kotlin
@Configuration
public class HBaseConfig {
@Value("${hbase.zookeeper.quorum}")
private String zkQuorum;
@Value("${hbase.zookeeper.property.clientPort}")
private String zkPort;
@Bean
public org.apache.hadoop.conf.Configuration configuration() {
org.apache.hadoop.conf.Configuration config = HBaseConfiguration.create();
config.set("hbase.zookeeper.quorum", zkQuorum);
config.set("hbase.zookeeper.property.clientPort", zkPort);
return config;
}
@Bean
public Connection hbaseConnection(org.apache.hadoop.conf.Configuration configuration) throws IOException {
return ConnectionFactory.createConnection(configuration);
}
@PreDestroy
public void closeConnection(@Autowired Connection connection) throws IOException {
if (connection != null && !connection.isClosed()) {
connection.close();
}
}
}
3.3. 📦 创建 Service 工具类
scss
@Service
public class HBaseService {
@Autowired
private Connection connection;
/**
* 创建表
*/
public void createTable(String tableName, String... columnFamilies) throws IOException {
Admin admin = connection.getAdmin();
TableName tn = TableName.valueOf(tableName);
if (!admin.tableExists(tn)) {
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tn);
for (String cf : columnFamilies) {
builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(cf));
}
admin.createTable(builder.build());
}
admin.close();
}
/**
* 插入数据
*/
public void putData(String tableName, String rowKey, String cf, String qualifier, String value) throws IOException {
Table table = connection.getTable(TableName.valueOf(tableName));
Put put = new Put(Bytes.toBytes(rowKey));
put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(qualifier), Bytes.toBytes(value));
table.put(put);
table.close();
}
/**
* 查询数据
*/
public String getData(String tableName, String rowKey, String cf, String qualifier) throws IOException {
Table table = connection.getTable(TableName.valueOf(tableName));
Get get = new Get(Bytes.toBytes(rowKey));
get.addColumn(Bytes.toBytes(cf), Bytes.toBytes(qualifier));
Result result = table.get(get);
byte[] value = result.getValue(Bytes.toBytes(cf), Bytes.toBytes(qualifier));
table.close();
return value == null ? null : Bytes.toString(value);
}
/**
* 删除数据
*/
public void deleteData(String tableName, String rowKey, String cf, String qualifier) throws IOException {
Table table = connection.getTable(TableName.valueOf(tableName));
Delete delete = new Delete(Bytes.toBytes(rowKey));
delete.addColumns(Bytes.toBytes(cf), Bytes.toBytes(qualifier));
table.delete(delete);
table.close();
}
}
3.4. ✅ 示例调用
kotlin
@RestController
@RequestMapping("/hbase")
public class HBaseController {
@Autowired
private HBaseService hBaseService;
@PostMapping("/put")
public String put() throws IOException {
hBaseService.putData("test_table", "row1", "info", "name", "张三");
return "ok";
}
@GetMapping("/get")
public String get() throws IOException {
return hBaseService.getData("test_table", "row1", "info", "name");
}
@DeleteMapping("/delete")
public String delete() throws IOException {
hBaseService.deleteData("test_table", "row1", "info", "name");
return "deleted";
}
}
3.5. 📌 补充说明
操作类型 | 说明 |
---|---|
Put | 写入(支持批量 put) |
Get | 查询(指定列族、列名) |
Delete | 删除(单列、整行) |
Scan | 扫描(用于分页/模糊搜索) |
4. HBase优化设计思考
4.1. HAMaster高可用设计
在HBase中HMaster负责监控HRegionServer的生命周期,均衡RegionServer的负载,如果HMaster挂掉了,那么整个HBase集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以HBase支持对HMaster的高可用配置。
ruby
关闭HBase集群(如果没有开启则跳过此步)
[xjl@hadoop102 hbase]$ bin/stop-hbase.sh
在conf目录下创建backup-masters文件
[xjl@hadoop102 hbase]$ touch conf/backup-masters
在backup-masters文件中配置高可用HMaster节点
[xjl@hadoop102 hbase]$ echo hadoop103 > conf/backup-masters
将整个conf目录scp到其他节点
[xjl@hadoop102 hbase]$ scp -r conf/ hadoop103:/opt/module/hbase/
[xjl@hadoop102 hbase]$ scp -r conf/ hadoop104:/opt/module/hbase/
打开页面测试查看
4.2. HBase的预分区设计
每一个region维护着StartRow与EndRow,如果加入的数据符合某个Region维护的RowKey范围,则该数据交给这个Region维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高HBase性能。
ini
# 手动设定预分区
Hbase> create 'staff1','info','partition1',SPLITS => ['1000','2000','3000','4000']
# 生成16进制序列预分区
create 'staff2','info','partition2',{NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
# 按照文件中设置的规则预分区
创建splits.txt文件内容如下:
aaaa
bbbb
cccc
dddd
然后执行:
create 'staff3','partition3',SPLITS_FILE => 'splits.txt'
# 使用JavaAPI创建预分区
//自定义算法,产生一系列hash散列值存储在二维数组中
byte[][] splitKeys = 某个散列值函数
//创建HbaseAdmin实例
HBaseAdmin hAdmin = new HBaseAdmin(HbaseConfiguration.create());
//创建HTableDescriptor实例
HTableDescriptor tableDesc = new HTableDescriptor(tableName);
//通过HTableDescriptor实例和散列值二维数组创建带有预分区的Hbase表
hAdmin.createTable(tableDesc, splitKeys);
4.3. HBase的RowKey设计(随机性,防止数据倾斜)
一条数据的唯一标识就是RowKey,那么这条数据存储于哪个分区,取决于RowKey处于哪个一个预分区的区间内,设计RowKey的主要目的,就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。接下来我们就谈一谈RowKey常用的设计方案。
shell
# 生成随机数、hash、散列值
比如:
原本rowKey为1001的,SHA1后变成:dd01903921ea24941c26a48f2cec24e0bb0e8cc7
原本rowKey为3001的,SHA1后变成:49042c54de64a1e9bf0b33e00245660ef92dc7bd
原本rowKey为5001的,SHA1后变成:7b61dec07e02c188790670af43e717f0f46e8913
在做此操作之前,一般我们会选择从数据集中抽取样本,来决定什么样的rowKey来Hash后作为每个分区的临界值。
# 字符串反转
20170524000001转成10000042507102
20170524000002转成20000042507102
这样也可以在一定程度上散列逐步put进来的数据。
# 字符串拼接
20170524000001_a12e
20170524000001_93i7
4.4. HBase的内存优化
HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,一般会分配整个可用内存的70%给HBase的Java堆。但是不建议分配非常大的堆内存,因为GC过程持续太久会导致RegionServer处于长期不可用状态,一般16~48G内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。
HBase基础优化
arduino
1.允许在HDFS的文件中追加内容
hdfs-site.xml、hbase-site.xml
属性:dfs.support.append
解释:开启HDFS追加同步,可以优秀的配合HBase的数据同步和持久化。默认值为true。
2.优化DataNode允许的最大文件打开数
hdfs-site.xml
属性:dfs.datanode.max.transfer.threads
解释:HBase一般都会同一时间操作大量的文件,根据集群的数量和规模以及数据动作,设置为4096或者更高。默认值:4096
3.优化延迟高的数据操作的等待时间
hdfs-site.xml
属性:dfs.image.transfer.timeout
解释:如果对于某一次数据操作来讲,延迟非常高,socket需要等待更长的时间,建议把该值设置为更大的值(默认60000毫秒),以确保socket不会被timeout掉。
4.优化数据的写入效率
mapred-site.xml
属性:
mapreduce.map.output.compress
mapreduce.map.output.compress.codec
解释:开启这两个数据可以大大提高文件的写入效率,减少写入时间。第一个属性值修改为true,第二个属性值修改为:org.apache.hadoop.io.compress.GzipCodec或者其他压缩方式。
5.设置RPC监听数量
hbase-site.xml
属性:Hbase.regionserver.handler.count
解释:默认值为30,用于指定RPC监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。
6.优化HStore文件大小
hbase-site.xml
属性:hbase.hregion.max.filesize
解释:默认值10737418240(10GB),如果需要运行HBase的MR任务,可以减小此值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。该值的意思就是,如果HFile的大小达到这个数值,则这个region会被切分为两个Hfile。
7.优化HBase客户端缓存
hbase-site.xml
属性:hbase.client.write.buffer
解释:用于指定Hbase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少RPC次数的目的。
8.指定scan.next扫描HBase所获取的行数
hbase-site.xml
属性:hbase.client.scanner.caching
解释:用于指定scan.next方法获取的默认行数,值越大,消耗内存越大。
9.flush、compact、split机制
当MemStore达到阈值,将Memstore中的数据Flush进Storefile;compact机制则是把flush出来的小文件合并成大的Storefile文件。split则是当Region达到阈值,会把过大的Region一分为二。
涉及属性:
即:128M就是Memstore的默认阈值
hbase.hregion.memstore.flush.size:134217728
即:这个参数的作用是当单个HRegion内所有的Memstore大小总和超过指定值时,flush该HRegion的所有memstore。RegionServer的flush是通过将请求添加一个队列,模拟生产消费模型来异步处理的。那这里就有一个问题,当队列来不及消费,产生大量积压请求时,可能会导致内存陡增,最坏的情况是触发OOM。
hbase.regionserver.global.memstore.upperLimit:0.4
hbase.regionserver.global.memstore.lowerLimit:0.38
即:当MemStore使用内存总量达到hbase.regionserver.global.memstore.upperLimit指定值时,将会有多个MemStores flush到文件中,MemStore flush 顺序是按照大小降序执行的,直到刷新到MemStore使用内存略小于lowerLimit
博文参考