5.3 表设计与二级索引实战:订单表与画像表 本节摘要:用两张真实生产表把 5.1、5.2 的决策串成完整 Schema,并解决 HBase 没有二级索引带来的"一表难敌多查询"问题——双写冗余表、协处理器索引、索引表、搜索引擎四种方案的实现与取舍。 订单表:主体加时间的标准解 需求:日增 5000 万单,保存 2 年;查询以"按用户查订单列表(近 3 个月优先)"为绝对主力(95%),另有按订单号点查(4%)、按商户对账(1%)。 RowKey 推演(按 5.1 的清单法):主查询是"主体 + 时间",选反转散列 + 倒排时间: 三段各自的理由:反转用户号把首字节打散(落点均匀,3.1 节预分区的分界按反转后空间切);
本节摘要:用两张真实生产表把 5.1、5.2 的决策串成完整 Schema,并解决 HBase 没有二级索引带来的"一表难敌多查询"问题——双写冗余表、协处理器索引、索引表、搜索引擎四种方案的实现与取舍。
需求:日增 5000 万单,保存 2 年;查询以"按用户查订单列表(近 3 个月优先)"为绝对主力(95%),另有按订单号点查(4%)、按商户对账(1%)。
RowKey 推演(按 5.1 的清单法):主查询是"主体 + 时间",选反转散列 + 倒排时间:
rowkey = 反转(用户ID 二进制倒序) + (MAX_LONG - 下单时间戳毫秒) + 订单号后4位 长度约 8 + 8 + 4 = 20 字节
三段各自的理由:反转用户号把首字节打散(落点均匀,3.1 节预分区的分界按反转后空间切);倒排时间让最新订单排在扫描头部,"最近 3 个月"查询 LIMIT 100 通常扫几十行即止(5.1 节倒排时间戳);订单号后缀兜底唯一性。
按订单号点查怎么办?订单号不是行键前缀,直接查等于全表扫描。答案是双表:写订单的同时写一张 orders_by_oid 索引表,行键就是订单号,value 只存指向主表的完整行键:
主表: rev(u1001) + inv(ts) + 5678 → cf:status, cf:amount ... 索引表: ORD20240819005678 → f:rk = rev(u1001)+inv(ts)+5678
查询路径:索引表点查拿到主表行键 → 主表 get,两跳完成。写入端多一次 RPC,一致性由写入方保证(先写主表后写索引,失败则补偿重试)。这就是应用层维护的二级索引,最朴素也最可控。
对账查询:按商户 1% 频率、跑批型,不值得为之改行键——直接全表扫描(低峰、限流)或导出到分析引擎(第 6 章生态集成)。
参数(沿用 5.2 的决策):单列族 cf,VERSIONS=1,TTL=63072000(2 年),SNAPPY,ROW 布隆,块 64 KB。建表预分区分界按反转用户号空间均匀切 32 份。
需求:1 亿用户,每人 300–2000 个标签;查询以"取某人全部标签"(点查)为主,另有"某标签下有哪些人"(反查,周级运营批任务)。
正向表一张:行键 md5(用户ID) 前4字节 + 用户ID(哈希前缀模式,5.1 表格第一行场景),单列族 tag,每个标签一个列限定符(动态列的天然舞台,1.2 节),value 就是标签值或空:
a3f2... | u1001 | tag:age_seg=25_30 tag:city=hz tag:rfm=high ...
三千个标签也只是一行内的三千列,读取一次 get 全带回。VERSIONS=1、TTL 永久、SNAPPY。列限定符本身占存储(2.3 节 KeyValue 编码),超长标签名要编码缩短(如 age_seg → a1,映射表放业务侧)。
反查(标签 → 人群):正向表无法反查,建一张倒排表:行键 = 标签ID + 分桶号,列限定符 = 用户哈希,value 空。人群圈选用 scan 遍历该标签的所有分桶。倒排表通常由离线任务(Spark 批计算)重建而非实时双写——画像数据天生的最终一致性容忍度,让"离线倒排"比"实时双写"省一半复杂度。
| 方案 | 一致性 | 开发量 | 适用 |
|---|---|---|---|
| 应用双写(本文订单表) | 最终一致,可控 | 低 | 索引少、写入方集中 |
| 索引协处理器 | 较强(同 Region 事务界内) | 中 | 不想改写入方,第 6 章展开 |
| 离线重建(画像倒排) | 小时级延迟 | 低 | 反查容忍滞后 |
| 外部搜索引擎 | 准实时 | 高 | 多条件组合查询 |
选型的一条经验:索引数量与一致性要求成反比地推高复杂度。双写两个索引还能人工兜底,双写十个索引的补偿逻辑没人能维护——那种场景说明需求已经越出 HBase 的舒适区,考虑搜索引擎或宽表引擎。
Schema 定稿前过一遍:
locate_region,确认分布均匀(5.1 的热点实验流程);⚠️ 常见坑:双写索引表与主表用不同预分区策略,索引表自己先热点了。两张表的行键散列逻辑要一起设计——它们共享同一份写入流量。
💡 关键直觉:HBase 的 Schema 设计是"为查询清单做仲裁",每张好表都是对 95% 查询的偏袒。想一碗水端平的表,最后通常是全表扫描的慢表。
Schema 齐备,下一章看 HBase 的三个高级武器——协处理器、布隆过滤器、读缓存与生态集成——它们都是对前五章机制的"再加工"。