5.2 列族与存储参数设计:纵向的物理决策 本节摘要:列族是 HBase 的物理存储单位,建表时的参数组合(VERSIONS、TTL、COMPRESSION、BLOCKSIZE、BLOOMFILTER)决定这张表的磁盘形态与读写代价。本节逐参数给出决策依据与修改代价,并把"什么时候值得建第二个列族"这个高频问题讲透。 列族数量:默认一个,两个封顶 先重复 2.2 节的机制:每个列族一个 Store,Flush 时所有列族一起刷,每个 Store 各产一个 HFile——列族数直接放大 HDFS 写压力与小文件数量。再加 3.3 节的读视角:BlockCache 以块为单位缓存,访问模式不同的列族混在一行里,会互相污染缓存。
本节摘要:列族是 HBase 的物理存储单位,建表时的参数组合(VERSIONS、TTL、COMPRESSION、BLOCKSIZE、BLOOMFILTER)决定这张表的磁盘形态与读写代价。本节逐参数给出决策依据与修改代价,并把"什么时候值得建第二个列族"这个高频问题讲透。
先重复 2.2 节的机制:每个列族一个 Store,Flush 时所有列族一起刷,每个 Store 各产一个 HFile——列族数直接放大 HDFS 写压力与小文件数量。再加 3.3 节的读视角:BlockCache 以块为单位缓存,访问模式不同的列族混在一行里,会互相污染缓存。
所以建第二个列族要过两道门:
cf 存高频读写的小字段(状态、金额),detail 存写一次读罕见的大字段(收货地址快照、发票明细)。分列族后扫 cf 不碰 detail 的存储,缓存也互不冲刷;detail 若普遍几百 KB,混在 cf 的 64 KB 块里会严重拖累读放大。反过来,字段访问模式一致就合用一个列族,把"语义区分"交给列限定符——列限定符不要钱(1.2 节:动态、免 DDL),列族很贵。极端反例是"一个字段一个列族",Flush 产物数量直接爆炸。
以订单表的完整建表语句为纲:
hbase:090:0> create 'orders', { hbase:091:1* NAME => 'cf', hbase:092:1* VERSIONS => '3', hbae:093:1* TTL => '15552000', -- 180 天 hbase:094:1* COMPRESSION => 'SNAPPY', hbase:095:1* BLOOMFILTER => 'ROW', hbase:096:1* BLOCKSIZE => '65536', hbase:097:1* BLOCKCACHE => true hbase:098:1* }, { hbase:099:1* NAME => 'detail', hbase:100:1* COMPRESSION => 'GZ', hbase:101:1* BLOCKSIZE => '262144', hbase:102:1* BLOOMFILTER => 'NONE' hbase:103:1* }
VERSIONS(默认 1)。保留几个历史版本。状态类字段保留 3 个便于审计"最近几次变更";不需要历史就留 1,读路径少做多版本合并(3.3 节)。配 MIN_VERSIONS 可与 TTL 组合:"过期但至少留 1 版"。
TTL(默认永久)。Cell 级过期,物理清除发生在 Major Compaction(2.3 节的伏笔)。按法规要求数据保留 180 天的场景,TTL 加定期 Major 是标准答案,比业务侧批量 delete 干净得多(墓碑本身也占地方)。
COMPRESSION(默认无)。SNAPPY 是均衡首选:压缩比约 2–3 倍,编解码快,CPU 换 IO 几乎稳赚。GZ 压缩比更高但慢,只配给"写一次读罕见"的冷列族(上例的 detail)。LZO/ZSTD 按集群可用性选。注意压缩作用于块(2.3 节),行键前缀重复的特性让 HBase 数据对压缩非常友好。
BLOCKSIZE(默认 64 KB)。读取块粒度,两难取舍:块大→索引少、顺序扫描快,但点查要解压更多无关数据,且缓存粒度粗;块小→点查快、缓存细,但索引膨胀、顺序扫描多付索引 IO。经验值:点查为主保持默认或 32 KB,顺序扫描为主(时序表)可到 128–256 KB。上例 detail 列族大块加 GZ,就是"冷数据大块吞"的配置。
BLOOMFILTER(默认 ROW)。行级布隆帮点查排除无关文件(原理与代价第 6 章展开)。行前缀布隆(ROWCOL 是列级,ROW_PREFIX 部分场景)按查询形态选;上例 detail 读罕见,索性 NONE 省内存。
IN_MEMORY(默认 false)。把列族优先留在缓存,维表、配置表这类小而热的表专用,慎用(挤占正常缓存)。
| 档位 | 参数 | 代价 |
|---|---|---|
| 可随时改 | TTL、VERSIONS、COMPRESSION | alter 即生效,后续 Compaction 逐步按新参数重写 |
| 改了要 Major | BLOOMFILTER、BLOCKSIZE | 只在新文件生效,要全面生效需 Major Compaction |
| 基本改不动 | 列族名、行键 | 只能建新表迁移数据 |
这就是"Schema 定稿即锁定性能"的含义。建表前拿生产量级的模拟数据跑一轮基准(第 4.3 节的方法),比上线后 alter 补救便宜太多。改 TTL 有一个经典陷阱要单独点名:收紧 TTL 不会立刻腾空间,旧数据要等 Major 清理;而放宽 TTL 倒是立刻生效。容量规划别按错方向估。
⚠️ 常见坑:alter 操作会先让表 disable 再 enable(旧版本行为,2.x 部分参数在线改),生产高峰改表 = 自断业务。所有 alter 走变更流程:低峰、先快照、灰度验证。
设计定稿后自查一遍输出:
hbase:104:0> describe 'orders' orders COLUMN FAMILIES DESCRIPTION {NAME => 'cf', VERSIONS => '3', TTL => '15552000', COMPRESSION => 'SNAPPY', BLOOMFILTER => 'ROW', BLOCKSIZE => '65536', ...} {NAME => 'detail', VERSIONS => '1', COMPRESSION => 'GZ', BLOCKSIZE => '262144', ...}
对照四个问题:列族是否 ≤2?热冷是否分族?版本与 TTL 是否与数据生命周期一致?压缩与块大小是否与读写形态匹配?全过,Schema 的纵向部分就稳了。
💡 关键直觉:HBase 的"表结构"其实是一组物理存储参数而非数据约束——没有类型、没有非空约束、没有外键,数据质量责任全部上移到写入方。约束越少,参数决策越重。
横向(5.1 行键)纵向(5.2 参数)都齐了,5.3 节把它们拼成两张真实生产表,并解决"一张表应付不了所有查询"的二级索引难题。