3、状态存储的旧时代:Hive ACID、HBase 作为状态存储的局限性分析
聊 Delta Lake 之前,咱们得先看看过去大家是怎么存「状态」的。说白了,状态存储就是让数据从「一次性处理」变成「可追溯、可更新、可查询」的过程。我入行那几年,Hive ACID 和 HBase 几乎是两个主流选择。但用久了你会发现,它们各有各的「拧巴」之处。
今天我就结合自己的踩坑经历,把这两个方案的局限性掰开揉碎了讲清楚。你想想看,如果不知道旧方案为什么疼,又怎么理解新方案的好呢?
3.1 Hive ACID:事务能力有了,但代价不小
Hive 从 3.0 开始支持 ACID 事务,听起来很美——终于能在 Hive 上做 UPDATE、DELETE 了。但实际用起来,嗯,有点「为了事务而事务」的味道。
3.1.1 底层实现:Delta 文件和 Compaction
Hive ACID 的核心机制是增量文件(delta files)。每次更新或删除,不会改原始数据文件,而是生成新的 delta 文件记录变更。读取时再把 base 文件和 delta 文件合并。
-- 创建支持 ACID 的表
CREATE TABLE user_orders (
order_id INT,
user_id INT,
amount DECIMAL(10,2),
status STRING
)
CLUSTERED BY (order_id) INTO 10 BUCKETS
STORED AS ORC
TBLPROPERTIES (
'transactional'='true',
'transactional_properties'='insert_only'
);
看着不复杂对吧?但这里有个坑:delta 文件会越积越多。我曾在项目里见过一张表跑了三个月,delta 文件堆了上千个,查询慢到令人发指。
3.1.2 核心局限:一张表看清
| 维度 | Hive ACID 的表现 | 实际影响 |
|---|---|---|
| 写入吞吐 | 受限于 bucket 数,并发写入容易冲突 | 高峰期写入延迟飙升 |
| 读取性能 | 需要合并 base + delta 文件 | 查询越频繁,性能越差 |
| 事务隔离 | 仅支持快照隔离级别 | 并发读写可能出现幻读 |
| Compaction 开销 | Major compaction 是重操作 | 资源消耗大,容易影响线上 |
| Schema 演进 | 支持有限,列类型变更麻烦 | 业务迭代时很痛苦 |
我个人习惯把 Hive ACID 看作「给离线批处理加了个补丁」。它解决了「能不能更新」的问题,但没解决好不好的问题。尤其是实时性要求高的场景,Hive ACID 基本扛不住。
3.2 HBase:实时读写强,但状态管理弱
再说 HBase。很多流计算场景(比如 Flink 的状态后端)会用 HBase 存中间结果。它确实能扛高并发、低延迟的读写。但作为「状态存储」,它有几个硬伤。
3.2.1 架构特点:LSM-Tree 与 Region 分裂
HBase 基于 HDFS,用 LSM-Tree 组织数据。写入先写 MemStore,再刷成 HFile。读取则合并 MemStore 和 HFile。Region 分裂是它扩展的方式。
// HBase 写入示例
Put put = new Put(Bytes.toBytes("row1"));
put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("status"), Bytes.toBytes("active"));
table.put(put);
// HBase 读取示例
Get get = new Get(Bytes.toBytes("row1"));
Result result = table.get(get);
byte[] value = result.getValue(Bytes.toBytes("cf"), Bytes.toBytes("status"));
单看读写,HBase 确实快。但问题出在状态管理的「一致性」和「事务」上。
3.2.2 核心局限:状态存储的「三座大山」
第一座:缺乏跨行事务。 HBase 只支持单行事务。如果你要更新多个 row key 对应的状态,要么自己实现两阶段提交,要么接受数据不一致。我在做 Flink 实时去重时,就遇到过因为 HBase 没有跨行事务,导致状态回滚后部分数据残留的问题。
第二座:Schema 设计僵化。 HBase 的列族虽然灵活,但 row key 设计一旦定下来,改起来就是大工程。我见过一个团队因为 row key 设计不合理,导致热点 Region 频繁分裂,集群稳定性直线下降。
第三座:历史版本管理弱。 HBase 虽然支持多版本,但版本数量有限,而且清理策略是 TTL 或最大版本数。你想做「时间旅行」查询?基本不可能。状态变化的历史轨迹,HBase 存不住。
一句话总结: HBase 适合做「当前值」的存储,但不适合做「状态变化」的存储。而真正的状态存储,既要能查当前值,也要能回溯历史。
3.3 两者的共同困境:批流分离
不管是 Hive ACID 还是 HBase,它们都解决不了同一个问题——批处理和流处理的数据割裂。
- Hive ACID 偏向批处理,实时写入能力弱。
- HBase 偏向实时读写,批量分析能力弱。
你想想看,一个实时计算任务,中间状态存在 HBase 里。但你要做离线分析时,还得把 HBase 数据导到 Hive 里。数据流转一圈,延迟、一致性、运维复杂度全上来了。
我在做某电商的实时数仓时,就面临这个尴尬:Flink 的实时去重状态在 HBase,离线报表又要从 Hive 读。两边数据对不上,排查了三天才发现是 HBase 的 TTL 清理策略和 Hive 的导入时间没对齐。这种问题,说白了就是架构层面的「先天不足」。
3.4 一张图看懂旧时代的局限
下面这张图,我把 Hive ACID 和 HBase 在状态存储场景下的核心问题画了出来。你看完应该能直观感受到——为什么我们需要一个新的范式。
我的建议: 如果你现在还在用 Hive ACID 或 HBase 做状态存储,不妨先梳理一下你的核心需求——是更看重实时性?还是更看重历史回溯?还是两者都要?想清楚这个,你就能理解为什么 Delta Lake 的「批流一体 + 时间旅行」会成为新范式了。