HBase 深度解析

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 上。

客户端访问数据的寻址流程(三层寻址):

  1. 从 ZooKeeper 获取 hbase:meta 表所在的 RegionServer 地址(缓存在客户端)
  2. 访问 hbase:meta 表,根据 RowKey 查找目标 Region 所在的 RegionServer(缓存在客户端)
  3. 直接访问目标 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 操作的完整流程:

  1. 客户端查询 hbase:meta 定位目标 RegionServer(通常已缓存)
  2. RegionServer 将操作追加写入 WAL(顺序写,极快)
  3. 数据写入对应列族的 MemStore(内存操作)
  4. 返回客户端写入成功
  5. 当 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 操作需要合并多个数据源的结果:

  1. 查询 BlockCache(内存缓存),命中则直接返回
  2. 查询 MemStore(内存中未 Flush 的最新数据)
  3. 查询各个 HFile(可能有多个,按时间从新到旧查询)
  4. 合并所有来源的结果,按版本排序,返回最新版本(或指定版本数)

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 + timestampdeviceId + 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 更合适。