HBase 是构建在 HDFS 之上的分布式列式存储系统,脱胎于 Google Bigtable 论文,是 Hadoop 生态中处理海量结构化数据的核心组件。与关系型数据库不同,HBase 放弃了复杂的 SQL 查询和事务,换来了水平扩展能力和对百亿行数据的毫秒级随机读写。本文从数据模型出发,深入解析 HBase 的存储引擎、读写流程、RegionServer 架构,以及生产环境中最关键的 RowKey 设计与热点问题处理。
数据模型
核心概念
HBase 的数据模型与关系型数据库差异很大,理解这些概念是使用 HBase 的前提:
Table(表):HBase 的顶层组织单位,按 RowKey 字典序排列,自动水平切分为多个 Region。
RowKey(行键):每行数据的唯一标识,字节数组类型,按字典序排序。RowKey 的设计直接决定数据分布和查询性能,是 HBase 使用中最关键的设计决策。
Column Family(列族):列的逻辑分组,建表时静态定义,同一列族的数据存储在同一组 HFile 中。列族数量建议不超过 3 个,过多列族会导致 Compaction 和 MemStore 管理复杂度上升。
Column Qualifier(列限定符):列族内的列名,动态定义,无需提前声明,可以在写入时任意添加。完整的列名格式为 列族:列限定符,如 info:name。
Cell(单元格):由 {RowKey, Column Family, Column Qualifier, Timestamp} 四元组唯一确定的数据单元,存储一个字节数组值。
Timestamp(时间戳):每个 Cell 可以保存多个版本,通过时间戳区分。默认保留最新 3 个版本,读取时默认返回最新版本。
与关系型数据库对比
以用户信息表为例,关系型数据库和 HBase 的存储方式对比如下:
# 关系型数据库(MySQL)
# user_id | name | age | city | phone
# 1001 | Alice | 28 | Beijing | 13800000001
# HBase 数据模型
# RowKey | info:name | info:age | info:city | contact:phone
# 1001 | Alice | 28 | Beijing | 13800000001
# HBase 的每个 Cell 独立存储,可以有不同版本
# RowKey=1001, CF=info, CQ=city, ts=1713600000 -> "Beijing"
# RowKey=1001, CF=info, CQ=city, ts=1713500000 -> "Shanghai" (历史版本)
关键差异:
- HBase 没有固定 Schema,不同行可以有完全不同的列
- HBase 没有 JOIN,所有查询基于单行或范围扫描
- HBase 支持多版本,天然支持历史数据查询
- HBase 稀疏存储,空列不占用存储空间
Java API 基础操作
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "zk1,zk2,zk3");
try (Connection connection = ConnectionFactory.createConnection(conf);
Table table = connection.getTable(TableName.valueOf("user"))) {
// Put:写入一行数据
Put put = new Put(Bytes.toBytes("1001"));
put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes("Alice"));
put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"), Bytes.toBytes("28"));
put.addColumn(Bytes.toBytes("contact"), Bytes.toBytes("phone"), Bytes.toBytes("13800000001"));
table.put(put);
// Get:按 RowKey 精确查询
Get get = new Get(Bytes.toBytes("1001"));
get.addFamily(Bytes.toBytes("info")); // 只读取 info 列族
Result result = table.get(get);
byte[] name = result.getValue(Bytes.toBytes("info"), Bytes.toBytes("name"));
System.out.println(Bytes.toString(name)); // Alice
// Scan:范围扫描
Scan scan = new Scan();
scan.withStartRow(Bytes.toBytes("1000"));
scan.withStopRow(Bytes.toBytes("2000")); // [startRow, stopRow)
scan.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"));
scan.setCaching(500); // 每次 RPC 拉取 500 行,减少 RPC 次数
scan.setBatch(10); // 每行最多返回 10 个 Cell
try (ResultScanner scanner = table.getScanner(scan)) {
for (Result r : scanner) {
System.out.println(Bytes.toString(r.getRow()));
}
}
// Delete:删除(逻辑删除,写入 Tombstone 标记)
Delete delete = new Delete(Bytes.toBytes("1001"));
delete.addColumn(Bytes.toBytes("info"), Bytes.toBytes("age"));
table.delete(delete);
}
整体架构
核心组件
HBase 集群由以下核心组件构成:
HMaster:集群的管理节点,负责 Region 的分配与负载均衡、RegionServer 的故障检测与恢复、DDL 操作(建表、删表、修改表结构)。HMaster 不参与数据读写,不是性能瓶颈,但是单点,通常部署主备两个。
RegionServer:数据服务节点,负责实际的数据读写。每个 RegionServer 管理若干个 Region,处理客户端的 Get/Put/Scan/Delete 请求。RegionServer 是 HBase 的核心,也是性能调优的主要对象。
ZooKeeper:HBase 的协调服务,存储集群的元数据(hbase:meta 表的位置)、HMaster 选主、RegionServer 的存活状态监控。客户端通过 ZooKeeper 找到 hbase:meta 表所在的 RegionServer,再定位目标 Region。
HDFS:底层存储,HFile(HBase 的 SSTable 实现)和 WAL(Write-Ahead Log)都存储在 HDFS 上,提供数据的持久化和副本保障。
Region 与分区
Region 是 HBase 的基本数据分区单位,每个 Region 负责一段连续的 RowKey 范围 [startKey, endKey)。一张表初始只有一个 Region,随着数据增长,当 Region 大小超过阈值(默认 10 GB)时自动 Split 为两个子 Region,由 HMaster 将其分配到不同的 RegionServer 上。
客户端访问数据的寻址流程(三层寻址):
- 从 ZooKeeper 获取
hbase:meta表所在的 RegionServer 地址(缓存在客户端) - 访问
hbase:meta表,根据 RowKey 查找目标 Region 所在的 RegionServer(缓存在客户端) - 直接访问目标 RegionServer,读写数据
客户端会缓存 Region 位置信息,只有缓存失效(Region 发生 Split 或迁移)时才重新查询 hbase:meta。
存储引擎:LSM-Tree
HBase 的存储引擎基于 LSM-Tree(Log-Structured Merge-Tree),这是它能实现高吞吐写入的根本原因。LSM-Tree 的核心思路是:将随机写转换为顺序写,用读放大换取写放大的降低。
RegionServer 内部结构
每个 RegionServer 内部,数据流经以下组件:
WAL(Write-Ahead Log):写入数据前,先将操作追加写入 WAL(存储在 HDFS 上的顺序文件)。WAL 是故障恢复的保障:RegionServer 崩溃后,未持久化到 HFile 的数据可以从 WAL 重放恢复。每个 RegionServer 只有一个 WAL,所有 Region 共享(HBase 1.x 后支持多 WAL 提升并发)。
MemStore:每个 Column Family 对应一个 MemStore,是内存中的有序跳表(ConcurrentSkipListMap),按 {RowKey, CF, CQ, Timestamp} 排序。写入数据先写 WAL,再写 MemStore,写入完成即返回客户端成功。MemStore 达到阈值(默认 128 MB)时触发 Flush,将数据写入 HDFS 生成新的 HFile。
HFile:HBase 的持久化存储文件,是 SSTable(Sorted String Table)的实现,文件内数据按 Key 有序排列,支持高效的顺序扫描和二分查找。HFile 一旦写入就不再修改(Immutable),更新和删除通过写入新版本或 Tombstone 标记实现。
BlockCache:RegionServer 级别的读缓存,缓存从 HFile 读取的 Block(默认 64 KB)。默认使用 LRU 策略,也支持 BucketCache(堆外内存或 SSD)。
写入流程
一次 Put 操作的完整流程:
- 客户端查询
hbase:meta定位目标 RegionServer(通常已缓存) - RegionServer 将操作追加写入 WAL(顺序写,极快)
- 数据写入对应列族的 MemStore(内存操作)
- 返回客户端写入成功
- 当 MemStore 达到 128 MB(或 RegionServer 总 MemStore 达到堆内存 40%),触发 Flush:
- 将 MemStore 的数据快照写入 HDFS,生成新的 HFile
- 清空 MemStore,删除对应的 WAL 段
# HBase Shell 操作示例
# 创建表,指定列族和配置
create 'user',
{NAME => 'info', VERSIONS => 3, COMPRESSION => 'SNAPPY', BLOOMFILTER => 'ROW'},
{NAME => 'contact', VERSIONS => 1, COMPRESSION => 'SNAPPY', BLOOMFILTER => 'ROWCOL'}
# 写入数据
put 'user', '1001', 'info:name', 'Alice'
put 'user', '1001', 'info:age', '28'
put 'user', '1001', 'contact:phone', '13800000001'
# 读取数据
get 'user', '1001'
get 'user', '1001', {COLUMN => 'info:name', VERSIONS => 3} # 读取最近 3 个版本
# 范围扫描
scan 'user', {STARTROW => '1000', STOPROW => '2000', LIMIT => 100}
# 手动触发 Flush(生产环境谨慎使用)
flush 'user'
读取流程
一次 Get 操作需要合并多个数据源的结果:
- 查询 BlockCache(内存缓存),命中则直接返回
- 查询 MemStore(内存中未 Flush 的最新数据)
- 查询各个 HFile(可能有多个,按时间从新到旧查询)
- 合并所有来源的结果,按版本排序,返回最新版本(或指定版本数)
HFile 数量越多,读取需要查询的文件越多,读放大越严重。这是 Compaction 存在的根本原因。
Bloom Filter 优化读取:每个 HFile 内置 Bloom Filter,在读取时可以快速判断某个 RowKey(或 RowKey+Column)是否存在于该 HFile 中,不存在则跳过,大幅减少不必要的磁盘 IO。
Compaction:合并优化
随着 Flush 不断产生新的 HFile,单个 Region 的 HFile 数量会越来越多,导致读放大加剧。Compaction 负责将多个 HFile 合并为更大的 HFile,同时清理过期数据和 Tombstone 标记。
Minor Compaction:将少量较小的 HFile 合并为一个较大的 HFile。不清理过期版本和 Tombstone(除非合并了所有 HFile),速度快,I/O 开销小,频繁发生。
Major Compaction:将一个 Region 内某个列族的所有 HFile 合并为一个 HFile。彻底清理过期版本、Tombstone 标记,读性能最优。但 I/O 开销极大,会影响业务读写,默认每 7 天触发一次,生产环境通常关闭自动 Major Compaction,在业务低峰期手动执行。
# 关闭自动 Major Compaction(在 hbase-site.xml 中配置)
# hbase.hregion.majorcompaction = 0 (0 表示禁用自动触发)
# 手动在低峰期执行 Major Compaction
major_compact 'user'
major_compact 'user,1001,2000' # 只对指定 Region 执行
RowKey 设计
RowKey 设计是 HBase 使用中最重要的决策,直接影响数据分布、查询性能和热点问题。好的 RowKey 设计需要同时考虑:数据均匀分布(避免热点)、支持目标查询模式(精确查找或范围扫描)、尽量短小(RowKey 在每个 Cell 中都存储一份)。
热点问题
热点(Hotspot)是 HBase 生产环境最常见的问题:大量请求集中到少数几个 Region,导致这些 Region 所在的 RegionServer 过载,而其他 RegionServer 空闲。
顺序递增 RowKey 的热点问题:如果 RowKey 是递增的时间戳或自增 ID,所有新写入的数据都会落到最后一个 Region(最大 RowKey 所在的 Region),形成写热点。这是最常见的热点场景。
# 错误示例:RowKey 使用时间戳,所有写入集中到最新 Region
# RowKey: 1713600001, 1713600002, 1713600003, ...
# 所有写入都落到 Region [1713600000, +∞),其他 Region 空闲
# 正确做法:通过散列打散写入
RowKey 散列设计
方案一:Hash 前缀:取 RowKey 的 MD5/CRC32 前几位作为前缀,将数据均匀散列到各个 Region。代价是失去了范围扫描能力。
// 原始 RowKey:userId_timestamp
// 散列后:hash(userId)前2位 + userId_timestamp
String originalKey = userId + "_" + timestamp;
String hashPrefix = String.format("%02x", Math.abs(originalKey.hashCode()) % 256);
String rowKey = hashPrefix + "_" + originalKey;
// 结果:如 "3a_1001_1713600001",数据均匀分布到 256 个 Region
方案二:RowKey 反转:将递增部分(如时间戳、自增 ID)反转后作为 RowKey 前缀。时间戳 1713600001 反转为 1000063171,相邻时间的数据散列到不同 Region。
// 时间戳反转:最新数据排在前面(适合查最新数据的场景)
long reverseTs = Long.MAX_VALUE - System.currentTimeMillis();
String rowKey = userId + "_" + String.format("%019d", reverseTs);
// 查询某用户最新 N 条记录:scan startRow=userId+"_0", stopRow=userId+"_9"
方案三:Salting(加盐):在 RowKey 前加随机或轮询的前缀(如 0-N 的数字),将数据分散到 N 个桶。预分区时配合使用,确保每个桶对应一个 Region。
// Salting:按 userId 取模,分散到 N 个桶
int buckets = 32;
int salt = Math.abs(userId.hashCode()) % buckets;
String rowKey = String.format("%02d", salt) + "_" + userId + "_" + timestamp;
预分区(Pre-splitting)
新建表时如果不预分区,初始只有一个 Region,所有写入都集中在这个 Region,直到触发 Split 才开始分散。对于已知数据分布的场景,建表时应预先指定分区点:
# HBase Shell 预分区建表
# 按 Hash 前缀预分 16 个 Region
create 'user', 'info', 'contact',
SPLITS => ['10', '20', '30', '40', '50', '60', '70', '80', '90', 'a0', 'b0', 'c0', 'd0', 'e0', 'f0']
# 或者按数量均匀分区
create 'user', 'info', {NUMREGIONS => 16, SPLITALGO => 'HexStringSplit'}
常见查询模式的 RowKey 设计
场景一:查询某用户的历史订单(按时间倒序)
// RowKey = salt + userId + (Long.MAX_VALUE - orderTime)
// 同一用户的订单聚集在相邻 Region,扫描效率高
// 时间戳取反后,最新订单排在前面,scan 时自然按时间倒序
String rowKey = salt + "_" + userId + "_" + (Long.MAX_VALUE - orderTime);
// 查询用户 1001 的最新 10 条订单:
// scan startRow="xx_1001_0", stopRow="xx_1001_9", LIMIT=10
场景二:查询某类商品在某时间段的所有交易
// RowKey = categoryId + orderTime + orderId
// 同一品类的订单按时间聚集,支持时间范围扫描
// scan startRow="electronics_1713500000", stopRow="electronics_1713600000"
String rowKey = categoryId + "_" + orderTime + "_" + orderId;
场景三:消息系统(查询某会话的历史消息)
// RowKey = conversationId + (Long.MAX_VALUE - messageTime)
// 同一会话的消息聚集,时间倒序排列
String rowKey = conversationId + "_" + String.format("%019d", Long.MAX_VALUE - messageTime);
高级特性
过滤器(Filter)
HBase 的过滤器在 RegionServer 端执行,减少网络传输的数据量,比在客户端过滤效率更高:
// RowKey 前缀过滤器:只返回特定前缀的行
Filter prefixFilter = new PrefixFilter(Bytes.toBytes("1001_"));
// 列值过滤器:只返回 info:age > 25 的行
SingleColumnValueFilter ageFilter = new SingleColumnValueFilter(
Bytes.toBytes("info"),
Bytes.toBytes("age"),
CompareOperator.GREATER,
Bytes.toBytes("25")
);
ageFilter.setFilterIfMissing(true); // 没有 age 列的行也过滤掉
// 分页过滤器:服务端分页
Filter pageFilter = new PageFilter(100); // 每次返回最多 100 行
// 组合过滤器:AND/OR 逻辑
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
filterList.addFilter(prefixFilter);
filterList.addFilter(ageFilter);
Scan scan = new Scan();
scan.setFilter(filterList);
原子操作
HBase 支持行级原子操作,是实现分布式锁和 CAS 的基础:
// checkAndPut:CAS 操作,只有当指定列的值符合预期时才执行 Put
// 等价于:if (table[rowKey][cf:cq] == expectedValue) { put(newValue); }
boolean success = table.checkAndMutate(
Bytes.toBytes("1001"),
Bytes.toBytes("info"), Bytes.toBytes("status")
).ifEquals(Bytes.toBytes("active"))
.thenPut(new Put(Bytes.toBytes("1001"))
.addColumn(Bytes.toBytes("info"), Bytes.toBytes("status"), Bytes.toBytes("inactive")));
// incrementColumnValue:原子自增,适合计数器场景
long newCount = table.incrementColumnValue(
Bytes.toBytes("counter_1001"),
Bytes.toBytes("stats"),
Bytes.toBytes("page_view"),
1L // 每次加 1
);
协处理器(Coprocessor)
协处理器允许在 RegionServer 端执行自定义代码,类似于数据库的存储过程,减少客户端与服务端的数据传输:
- Observer:类似触发器,在 Get/Put/Delete/Scan 操作前后执行自定义逻辑(如二级索引维护、审计日志)
- Endpoint:类似存储过程,在 RegionServer 端执行聚合计算(如 COUNT、SUM),结果汇总后返回客户端,避免全量数据传输
生产实践
容量规划与参数调优
<!-- hbase-site.xml 核心参数 -->
<!-- RegionServer 堆内存建议 32-64 GB,不超过 64 GB(避免 GC 停顿过长)-->
<!-- export HBASE_HEAPSIZE=32768 -->
<!-- MemStore 占堆内存的比例上限,超过则阻塞写入触发 Flush -->
<property>
<name>hbase.regionserver.global.memstore.size</name>
<value>0.4</value>
</property>
<!-- 单个 MemStore 的 Flush 阈值 -->
<property>
<name>hbase.hregion.memstore.flush.size</name>
<value>134217728</value> <!-- 128 MB -->
</property>
<!-- BlockCache 占堆内存的比例 -->
<property>
<name>hfile.block.cache.size</name>
<value>0.4</value>
</property>
<!-- Region 大小上限,超过则触发 Split -->
<property>
<name>hbase.hregion.max.filesize</name>
<value>10737418240</value> <!-- 10 GB -->
</property>
<!-- 关闭自动 Major Compaction -->
<property>
<name>hbase.hregion.majorcompaction</name>
<value>0</value>
</property>
<!-- Minor Compaction:当 HFile 数量超过此值时触发 -->
<property>
<name>hbase.hstore.compactionThreshold</name>
<value>5</value>
</property>
常见问题排查
写入延迟飙高:通常是 MemStore 达到全局上限导致写入阻塞。排查:查看 RegionServer 日志中的 Blocking updates 字样;检查是否有 Region 的 HFile 数量过多(Minor Compaction 跟不上写入速度);考虑增大 RegionServer 堆内存或减小 MemStore 比例。
读取延迟飙高:BlockCache 命中率低或 HFile 数量过多。排查:通过 RegionServer Web UI(默认 16030 端口)查看 BlockCache 命中率;如果 HFile 数量过多,手动触发 Minor Compaction;检查是否有全表扫描(Scan 没有设置 startRow/stopRow)。
Region 热点:某个 RegionServer 的 CPU/网络显著高于其他节点。排查:HMaster Web UI(16010 端口)查看各 RegionServer 的 Region 数量和请求分布;如果是写热点,检查 RowKey 设计是否有顺序递增问题;如果是读热点,考虑对热点 Region 手动 Split 或迁移。
GC 停顿导致 RegionServer 超时:RegionServer 堆内存过大(>64 GB)时,Full GC 停顿可能超过 ZooKeeper 会话超时(默认 90 秒),导致 RegionServer 被 HMaster 认为宕机而触发 Region 迁移。处理:使用 G1GC 并调优 GC 参数;或将 BlockCache 迁移到堆外(BucketCache 模式),减小 JVM 堆大小。
与其他系统集成
HBase + Phoenix:Apache Phoenix 在 HBase 之上提供 SQL 接口,支持二级索引、JDBC 连接,适合需要 SQL 查询的场景。Phoenix 的查询最终转换为 HBase 的 Scan 操作,性能依赖 RowKey 设计和索引。
HBase + Spark:通过 hbase-spark 连接器,Spark 可以批量读写 HBase,适合离线分析场景。注意控制并发度,避免 Scan 请求压垮 RegionServer。
HBase + Flink:Flink 的 HBase Sink 支持流式写入,配合 BufferedMutator 批量提交,提升写入吞吐。HBase 也常作为 Flink 状态后端的外部存储,存储大规模维表数据用于实时 Join。
适用场景
HBase 并不是万能的,它的设计取舍(无 JOIN、无复杂事务、查询依赖 RowKey)决定了它只在特定场景下才是最优解。理解这些场景的共同特征,比记住一个列表更有价值。
适合的场景
用户行为日志与埋点数据
典型代表:电商用户的浏览、点击、加购、下单记录;APP 的操作日志;广告曝光与点击流水。特征是写入量极大(每秒百万级),数据按用户维度查询(给定 userId 查最近 N 条行为),时间窗口内的范围扫描。RowKey 设计为 userId + 反转时间戳,同一用户的行为聚集在相邻 Region,查询效率高。互联网公司的用户行为平台几乎都以 HBase 作为在线存储层。
消息系统的历史消息存储
典型代表:IM 聊天记录、站内信、通知消息。查询模式是"拉取某个会话的最近 N 条消息",天然适合 HBase 的范围扫描。多版本特性可以直接支持消息的已读/未读状态更新,无需额外的状态表。Facebook Messenger 早期就以 HBase 存储消息,这也是 HBase 最早的大规模生产案例之一。
时序数据(监控指标、IoT 数据)
典型代表:服务器 CPU/内存/网络指标、传感器采集数据、金融行情数据。数据特征:写入频繁(每秒数百万数据点)、按时间范围查询、数据量随时间线性增长。RowKey 设计为 metricName + timestamp 或 deviceId + timestamp,支持高效的时间范围扫描。OpenTSDB 就是构建在 HBase 之上的时序数据库,将多个指标点合并存储在同一行以减少 RowKey 开销。
用户画像与标签系统
典型代表:用户的人口属性、兴趣标签、行为特征、风控标签。数据特征:列数极多(每个标签一列,可能有数千列)且稀疏(不同用户有不同的标签集合),按 userId 点查,偶尔需要更新部分列。HBase 的稀疏存储(空列不占空间)和动态列(无需提前定义列名)天然适合这种宽表场景。关系型数据库在几千列的宽表上性能很差,而 HBase 处理起来毫无压力。
对象存储的元数据索引
典型代表:文件系统的目录/文件元数据(路径、大小、权限、修改时间)、图片/视频的属性信息。HDFS 的 NameNode 本身就面临元数据规模瓶颈,将元数据存入 HBase 可以支持更大规模的文件系统(如 Apache Ozone)。
增量数据的 Upsert 存储
典型代表:订单状态流水、商品库存变更记录。数据持续写入,同一 RowKey 的数据不断更新(HBase 的 Put 天然是 Upsert 语义),保留多版本历史。相比关系型数据库,HBase 的写入吞吐高出一到两个数量级,不需要加锁更新。
不适合的场景
需要复杂查询的场景:HBase 没有 SQL,没有 JOIN,没有聚合函数,没有二级索引(需要借助 Phoenix 或自建)。如果业务需要"按城市统计用户数"、"关联多张表做报表",HBase 不合适,应选 MySQL/PostgreSQL 或 Hive/Spark。
数据量较小的场景:HBase 集群至少需要 3 个 ZooKeeper + 1 个 HMaster + 3 个 RegionServer,运维成本高。数据量在千万行以内,MySQL 的性能完全够用且运维简单得多。HBase 的优势在百亿行以上才开始体现。
强事务场景:HBase 只支持单行原子操作(checkAndPut),跨行、跨表的事务需要借助外部协调(如 Omid/Tephra),复杂度高。需要 ACID 事务的场景应选关系型数据库。
随机读多写少的场景:HBase 的 LSM-Tree 架构对写友好,但读需要合并多个 HFile,存在读放大。如果业务是读多写少(如商品详情页),Redis 或关系型数据库是更好的选择。
选型对比
- vs MySQL/PostgreSQL:数据量 < 千万行、需要 JOIN/复杂查询、需要强事务 → 选关系型数据库;数据量 > 百亿行、写多读少、宽表稀疏 → 选 HBase
- vs Redis:数据需要持久化到磁盘、数据量超过内存容量、需要范围扫描 → 选 HBase;需要极低延迟(微秒级)、数据结构丰富(List/Set/Hash)→ 选 Redis
- vs Cassandra:已有 Hadoop 生态、需要与 Hive/Spark 深度集成 → 选 HBase;无 Hadoop 依赖、需要多数据中心复制、运维更简单 → 选 Cassandra
- vs Elasticsearch:需要全文检索、多字段任意组合查询 → 选 ES;需要高吞吐写入、按 RowKey 点查或范围扫描 → 选 HBase
- vs InfluxDB/Prometheus:纯时序场景、数据有自动过期需求、需要 PromQL/Flux 查询 → 选专用时序数据库;时序数据需要与其他业务数据共存、已有 HBase 基础设施 → 选 HBase(如 OpenTSDB)
总结
HBase 的核心价值在于:在 HDFS 之上提供了对海量数据的随机读写能力,填补了 Hadoop 生态中"批处理(MapReduce/Spark)能处理大数据,但无法随机访问"的空白。
理解 HBase 的关键是掌握三个核心机制:
- LSM-Tree 写入路径:WAL → MemStore → HFile,写入转顺序写,高吞吐的根本
- 读取合并:BlockCache + MemStore + 多个 HFile 的结果合并,Bloom Filter 和 Compaction 是优化读性能的手段
- RowKey 决定一切:数据分布、查询效率、热点问题,都由 RowKey 设计决定,是使用 HBase 最需要投入精力的地方
选型建议:HBase 适合写多读少、需要随机访问、数据量百亿行以上的场景,如用户行为日志、消息历史、时序监控数据。如果数据量在千万行以内,或需要复杂 SQL 查询,关系型数据库通常是更好的选择。如果主要是批量分析而非随机访问,Hive/Spark 直接读 HDFS 更合适。