HBase 与 Hive
Hadoop 生态系统不仅包含 MapReduce 和 HDFS,还发展出了丰富的数据存储和查询工具。HBase 提供实时读写的列式存储,Hive 提供类 SQL 的批处理查询接口,两者分别面向不同的应用场景。
HBase 列式存储
设计动机
关系数据库(RDBMS)在面对海量数据时存在根本性局限:单表难以存储数百 GB 数据,修改大表结构代价高昂,难以在线扩容。更重要的是,根据 CAP 定理,分布式系统无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),而 RDBMS 优先保证一致性,在扩展性上做出妥协。
HBase 是 Google BigTable 的开源实现,设计目标是在 HDFS 之上提供一个分布式列式存储系统,能够管理数十亿行、百万列级别的结构化数据,并提供实时的随机读写能力。
数据模型
HBase 的数据模型是一个多维的有序映射表,由以下要素定位一个数据单元:
| 要素 | 说明 | 特点 |
|---|---|---|
| RowKey | 行主键 | 按字典序排列,设计好坏直接影响查询性能 |
| Column Family | 列族 | 必须预先定义,是访问控制和存储管理的基本单位 |
| Column Qualifier | 列限定符 | 隶属于某个列族,可以动态添加 |
| Timestamp | 时间戳 | 支持多版本数据,默认自动递增 |
| Cell | 单元格 | 由上述四者唯一确定,存储实际的字节数据 |
RowKey CF:qualifier timestamp value
─────────────────────────────────────────────
row1 info:name t3 "Alice"
row1 info:name t2 "Bob" (历史版本)
row1 score:math t1 95
row2 info:name t1 "Charlie"
物理存储架构
graph TB
subgraph HBase Cluster
Master[HMaster] --> RS1[RegionServer 1]
Master --> RS2[RegionServer 2]
Master --> RS3[RegionServer 3]
subgraph RS1
R1[Region 1] --> Store1[Store - CF:info]
R1 --> Store2[Store - CF:score]
Store1 --> MS1[MemStore]
Store1 --> SF1[StoreFile / HFile]
end
end
RS1 --- HDFS[(HDFS)]
RS2 --- HDFS
RS3 --- HDFS
- Region:大表按 RowKey 范围分割为多个 Region,每个 Region 存储在一个 RegionServer 上
- MemStore:内存中的写缓冲区,数据写入时先到 MemStore
- StoreFile(HFile):MemStore 满后刷写到磁盘,形成不可变的 HFile 文件
- WAL(Write Ahead Log):写入前先记录日志,保证数据不丢失
数据读取时先查 MemStore,再查磁盘上的 StoreFile(每个 StoreFile 有类似 B 树的结构,允许快速查询)。多个小的 StoreFile 会定期合并(Compaction),Region 过大时会自动分裂(Split)。
数据记录的定位(二级索引)
HBase 使用二级索引结构定位数据记录:
- 第一层:ZooKeeper 中保存
hbase:meta表(旧版本称.META.)所在的 RegionServer 地址 - 第二层:
hbase:meta表保存所有用户数据表的 Region 位置信息(哪个 RowKey 范围在哪个 RegionServer 上)
版本说明:HBase 0.96 之前采用三级索引(ZooKeeper → -ROOT- Region → .META. → 用户数据),但由于 .META. 表极少需要分裂,-ROOT- 这一层实际意义不大,0.96 版本将其移除,简化为二级索引。
客户端查询时,先从 ZooKeeper 获取 hbase:meta 的位置,再查 hbase:meta 表找到目标 Region 所在的 RegionServer,最后直接访问该 RegionServer 读取数据。客户端会缓存 Region 位置信息,后续查询可跳过前两步。
Java API 示例
创建表:
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum", "zk-host");
HBaseAdmin admin = new HBaseAdmin(conf);
HTableDescriptor table = new HTableDescriptor("students");
table.addFamily(new HColumnDescriptor("info"));
table.addFamily(new HColumnDescriptor("score"));
admin.createTable(table);
插入数据:
HTable table = new HTable(conf, "students");
Put put = new Put(Bytes.toBytes("row1"));
put.add(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes("Alice"));
put.add(Bytes.toBytes("score"), Bytes.toBytes("math"), Bytes.toBytes("95"));
table.put(put);
查询数据:
Get get = new Get(Bytes.toBytes("row1"));
get.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"));
Result result = table.get(get);
byte[] value = result.getValue(Bytes.toBytes("info"), Bytes.toBytes("name"));
System.out.println(Bytes.toString(value)); // "Alice"
RowKey 设计原则
RowKey 的设计对 HBase 性能至关重要:
- 避免单调递增:如使用时间戳作为 RowKey,会导致写入热点集中在单个 Region
- 散列化:对原始 RowKey 取哈希或反转,使数据均匀分布
- 考虑查询模式:将经常一起访问的数据放在相邻的 RowKey 位置(空间局部性)
- 长度控制:RowKey 会存储在每个 KeyValue 中,过长会浪费存储空间
Hive 数据仓库
设计动机
HDFS 和 MapReduce 虽然强大,但直接编写 Java 程序进行数据分析门槛太高。数据分析师习惯使用 SQL 进行查询,Hive 的出现正是为了在 Hadoop 之上提供一个类 SQL 的查询接口。
Hive 最初由 Facebook 开发,用于处理每天数十 TB 的日志数据。它将 HQL(Hive SQL)查询自动编译为 MapReduce 作业执行,让用户无需编写 Java 代码即可完成大规模数据分析。
系统架构
graph LR
User[用户] --> CLI[CLI / JDBC / ODBC]
CLI --> Driver[Driver]
Driver --> Compiler[Compiler]
Compiler --> EE[Execution Engine]
EE --> HDFS[(HDFS)]
EE --> MR[MapReduce]
Driver --> MS[(Metastore)]
| 组件 | 功能 |
|---|---|
| HiveQL | 类 SQL 查询语言,提供数据定义和操作接口 |
| Driver | 驱动程序,协调各组件工作 |
| Compiler | 将 HQL 编译为 MapReduce 执行计划 |
| Execution Engine | 执行编译后的计划,调用 MapReduce 或 HDFS 操作 |
| Metastore | 元数据存储,保存表结构、分区信息、存储位置等 |
数据模型
Hive 的数据模型包含以下层次:
- Tables:类似关系数据库的表,列有类型(int、string、float 等),也支持复合类型(list、map、struct)
- Partitions:按某个列(如日期)将表数据划分为不同的目录,提高查询效率
- Buckets:在 Partition 内按哈希值进一步分桶,有利于抽样查询和 Join 优化
数据物理存储在 HDFS 的 /home/hive/warehouse 目录下,每个表对应一个子目录,Partition 和 Bucket 形成更深层的子目录。
表类型
Hive 支持三种表类型:
内部表(Managed Table):Hive 管理数据的生命周期,删除表时数据也被删除。
CREATE TABLE logs (ts BIGINT, msg STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE;
外部表(External Table):数据存储在 Hive 之外,删除表时只删除元数据,不影响实际数据。
分区表(Partitioned Table):按分区键组织数据,查询时可只扫描相关分区。
HQL 示例
-- 创建表
CREATE TABLE shakespeare (freq INT, word STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE;
-- 加载数据
LOAD DATA INPATH 'shakespeare_freq' INTO TABLE shakespeare;
-- 查询
SELECT * FROM shakespeare WHERE freq > 100
ORDER BY freq ASC LIMIT 10;
-- 聚合
INSERT OVERWRITE TABLE word_stats
SELECT word, SUM(freq) FROM shakespeare GROUP BY word;
-- Join
SELECT a.name, b.order_amount
FROM customers a JOIN orders b ON a.id = b.customer_id;
存储格式
Hive 支持多种存储格式,对查询性能影响显著:
| 格式 | 特点 | 适用场景 |
|---|---|---|
| TextFile | 纯文本,默认格式,不可分割压缩 | 小数据量、调试 |
| SequenceFile | 二进制键值对,可分割压缩 | 中间数据 |
| ORC | 列式存储,内置索引和压缩 | 大规模分析查询 |
| Parquet | 列式存储,跨平台兼容 | 与 Spark 等引擎配合 |
列式存储(ORC、Parquet)的优势在于:查询时只读取需要的列,减少 I/O;同一列数据类型相同,压缩率更高。
HBase vs Hive
| 维度 | HBase | Hive |
|---|---|---|
| 定位 | 实时读写的列式存储 | 批处理数据仓库 |
| 查询延迟 | 毫秒级 | 分钟到小时级 |
| 数据模型 | NoSQL,半结构化 | 类关系数据库,结构化 |
| 查询语言 | Java API / Shell | HQL(类 SQL) |
| 底层存储 | HDFS | HDFS |
| 典型场景 | 实时查询、随机读写 | 日志分析、报表统计 |
两者可以配合使用:HBase 存储实时数据,Hive 提供基于 HBase 表的 SQL 查询能力,实现实时存储与批量分析的统一。