HBase
Apache HBase 是基于 HDFS 的分布式列式 NoSQL 数据库,灵感来自 Google Bigtable,提供对数十亿行 × 数百万列数据的毫秒级随机读写能力。适合稀疏大表、时序数据、日志存储、消息归档等场景。
数据模型
HBase 以四维坐标定位一条数据:(RowKey, ColumnFamily:Qualifier, Timestamp) → Value
Table: user_behavior
┌──────────────┬──────────────────────────────┬──────────────────────┐
│ RowKey │ Column Family: info │ Column Family: act │
│ │ name │ city │ click │ buy │
├──────────────┼────────────┼─────────────────┼─────────┼────────────┤
│ user_001 │ Alice │ Beijing │ 12 │ 3 │
│ user_002 │ Bob │ Shanghai │ 8 │ │ ← 稀疏
└──────────────┴────────────┴─────────────────┴─────────┴────────────┘
| 概念 | 说明 |
|---|---|
| RowKey | 行键,全表唯一,字节序排序,是唯一索引 |
| Column Family | 列族,建表时定义,物理存储在同一文件 |
| Qualifier | 列名,动态添加,数量不限 |
| Timestamp | 版本号(通常为毫秒时间戳),支持多版本查询 |
| Cell | 一个具体的值(RowKey + CF + Qualifier + Timestamp) |
架构
HMaster(管理节点)
├── Region 分配 / 迁移
├── DDL(建表/删表)
└── 监控 RegionServer
RegionServer(数据节点,多个)
└── Region(一段连续 RowKey 范围)
└── Store(每个 Column Family 对应一个)
├── MemStore(写缓冲,内存)
└── HFile(SSTable,持久化到 HDFS)
ZooKeeper
└── 存储 HMaster 地址、RegionServer 心跳、Meta 表位置
写流程:WAL(预写日志)→ MemStore → MemStore 满 flush 为 HFile → 后台 Compaction 合并 HFile。
读流程:BlockCache → MemStore → HFile(布隆过滤器加速定位)。
快速上手(Java API)
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.6.0</version>
</dependency>Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "zk1,zk2,zk3");
conf.set("hbase.zookeeper.property.clientPort", "2181");
try (Connection connection = ConnectionFactory.createConnection(conf);
Table table = connection.getTable(TableName.valueOf("user_behavior"))) {
// PUT(写入)
Put put = new Put(Bytes.toBytes("user_001"));
put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes("Alice"));
put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("city"), Bytes.toBytes("Beijing"));
put.addColumn(Bytes.toBytes("act"), Bytes.toBytes("click"), Bytes.toBytes("12"));
table.put(put);
// GET(按 RowKey 点查)
Get get = new Get(Bytes.toBytes("user_001"));
get.addFamily(Bytes.toBytes("info")); // 只取 info 列族
Result result = table.get(get);
String name = Bytes.toString(result.getValue(
Bytes.toBytes("info"), Bytes.toBytes("name")));
// SCAN(范围扫描)
Scan scan = new Scan();
scan.withStartRow(Bytes.toBytes("user_001"));
scan.withStopRow(Bytes.toBytes("user_010")); // 左闭右开
scan.addColumn(Bytes.toBytes("act"), Bytes.toBytes("buy"));
scan.setCaching(100); // 每次 RPC 批量返回 100 行
try (ResultScanner scanner = table.getScanner(scan)) {
for (Result r : scanner) {
System.out.println(Bytes.toString(r.getRow()));
}
}
// DELETE
Delete delete = new Delete(Bytes.toBytes("user_001"));
delete.addColumn(Bytes.toBytes("act"), Bytes.toBytes("click"));
table.delete(delete);
}DDL(建表)
try (Admin admin = connection.getAdmin()) {
TableName tableName = TableName.valueOf("user_behavior");
ColumnFamilyDescriptor infoFamily = ColumnFamilyDescriptorBuilder
.newBuilder(Bytes.toBytes("info"))
.setMaxVersions(1)
.setCompressionType(Compression.Algorithm.SNAPPY) // 生产必开
.build();
ColumnFamilyDescriptor actFamily = ColumnFamilyDescriptorBuilder
.newBuilder(Bytes.toBytes("act"))
.setMaxVersions(3) // 保留 3 个历史版本
.setBloomFilterType(BloomType.ROW) // 加速 GET 查询
.build();
TableDescriptor tableDesc = TableDescriptorBuilder
.newBuilder(tableName)
.setColumnFamily(infoFamily)
.setColumnFamily(actFamily)
.build();
// 预分区(避免热点写入同一 Region)
byte[][] splitKeys = {
Bytes.toBytes("user_200"),
Bytes.toBytes("user_400"),
Bytes.toBytes("user_600"),
Bytes.toBytes("user_800"),
};
admin.createTable(tableDesc, splitKeys);
}RowKey 设计
RowKey 是 HBase 唯一的索引,设计好坏直接影响性能:
热点问题:顺序递增 RowKey(如时间戳前缀)导致写入集中在最后一个 Region。
| 方案 | 说明 | 示例 |
|---|---|---|
| 加盐(Salting) | 前缀加随机数 / 哈希前几位 | 3_user_001、7_user_001 |
| 翻转(Reversing) | 翻转单调部分 | 1000000003 → 3000000001 |
| 哈希前缀 | MD5/CRC32 取前几字节 | ab3f_user_001 |
| 组合键 | 业务维度 + 时间 | userId_reverseTimestamp |
时序数据常用模式:userId + (Long.MAX_VALUE - timestamp) → 按用户分片,同用户内按时间倒序,最新数据排在前面,Scan 无需 LIMIT 即可取最新 N 条。
过滤器
// 单列值过滤
Filter filter = new SingleColumnValueFilter(
Bytes.toBytes("info"), Bytes.toBytes("city"),
CompareOperator.EQUAL, Bytes.toBytes("Beijing"));
// 前缀过滤(RowKey 前缀匹配)
Filter prefixFilter = new PrefixFilter(Bytes.toBytes("user_"));
// 分页过滤(结合 lastRow 实现翻页)
Filter pageFilter = new PageFilter(100);
// 组合过滤
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
filterList.addFilter(prefixFilter);
filterList.addFilter(filter);
scan.setFilter(filterList);HBase vs 关系型数据库
| 维度 | HBase | MySQL |
|---|---|---|
| 数据模型 | 列式、稀疏、多版本 | 行式、关系型 |
| Schema | 列族固定,列动态 | 强 Schema |
| 索引 | 仅 RowKey | 主键 + 多个二级索引 |
| 事务 | 单行原子,无跨行事务 | 完整 ACID |
| 查询语言 | Java API / 有限 SQL(Phoenix) | SQL |
| 扩展性 | 水平扩展,PB 级 | 垂直扩展为主 |
| 适用场景 | 大规模稀疏数据、时序、日志 | 业务数据、复杂查询 |