5.3 分布式NoSQL数据库HBase


文档摘要

5.3 分布式 NoSQL 数据库 HBase 本节摘要:HBase 在 HDFS 之上提供毫秒级随机读写。本节解释它的数据模型(RowKey、列族、单元格)、LSM 树为何先写内存与日志而非原地更新、Region 自动分裂与分配机制、读写路径各自怎么走,以及 RowKey 设计这一决定性工程问题。 为什么 HDFS 之上还需要 HBase 第 2 章结尾留过一个伏笔:HDFS 擅长顺序批量读,不擅长小随机读——寻址开销与 NameNode 问路延迟让"按用户 ID 查一条记录"的延迟以百毫秒起步。而在线服务(用户画像查询、订单状态、消息收件箱)要求个位数毫秒。

5.3 分布式 NoSQL 数据库 HBase

本节摘要:HBase 在 HDFS 之上提供毫秒级随机读写。本节解释它的数据模型(RowKey、列族、单元格)、LSM 树为何先写内存与日志而非原地更新、Region 自动分裂与分配机制、读写路径各自怎么走,以及 RowKey 设计这一决定性工程问题。

为什么 HDFS 之上还需要 HBase

第 2 章结尾留过一个伏笔:HDFS 擅长顺序批量读,不擅长小随机读——寻址开销与 NameNode 问路延迟让"按用户 ID 查一条记录"的延迟以百毫秒起步。而在线服务(用户画像查询、订单状态、消息收件箱)要求个位数毫秒。HBase 的定位就是给 Hadoop 生态补上这个出口:数据仍在 HDFS 上持久化,但通过内存索引与 LSM 结构把随机访问路径压到毫秒级。代价同样明确:只有按 RowKey 及其前缀/范围访问高效,没有二级索引、没有 SQL join——它不是替代关系库,是替代"关系库里那张撑不住的宽表"。

数据模型:稀疏宽表

HBase 逻辑上是一张按 RowKey 排序的稀疏多维 map:

表 user_behavior(列族 cf 与 profile 两个) RowKey 列族 cf 列族 profile u20260001 行为:click=1 行为:view=3 姓名=张三 城市=杭州 u20260002 行为:view=1 姓名=李四 u20260003 (该行 cf 族无数据) 姓名=王五 城市=深圳

要点:RowKey 是唯一寻址键,字节序字典排序,一切设计围绕它;列族在建表时定义、列名运行时随意加,同一列族的数据物理上存放在一起(正是 HDFS 文件按列族分目录);单元格(RowKey+列族+列+时间戳)多版本共存。稀疏意味着某行某列没数据就真的什么都不存,不像关系库留 NULL——几百万个用户、每人几百个可选属性的场景,稀疏模型存储效率极高。

# shell 里建表与写入 create 'user_behavior', 'cf', 'profile' put 'user_behavior', 'u20260001', 'profile:city', '杭州' put 'user_behavior', 'u20260001', 'cf:click', '1' # 读:按行键取整行 毫秒级 get 'user_behavior', 'u20260001' # 扫描区间 scan 'user_behavior', {STARTROW => 'u20260001', STOPROW => 'u20260002'}

LSM 树:为什么快,为什么有合并

HBase 底层(HFile 加 MemStore)是 LSM 树(Log-Structured Merge-Tree)结构。对比传统 B+ 树的"原地更新",LSM 把随机写转化为顺序写:

写路径。写入先追加到 WAL(Write-Ahead Log,防止内存数据丢失)——一次顺序磁盘追加;然后写进内存的 MemStore(跳表结构,保持按 RowKey 有序);写内存即返回,延迟微秒级。MemStore 满了(默认 128MB)刷成一个新的 HFile(有序文件,存 HDFS)。

读路径。先查 MemStore,再从新到旧查若干 HFile,合并结果。读放大由此产生:一个 RowKey 的最新版本可能散落在多个 HFile,都要翻一遍。compaction(合并) 是 LSM 的自愈机制:小的 HFile 定期合并成大的(minor),全部合并去掉过期版本(major),把读放大压回去。

维度 B+ 树(关系库) LSM(HBase)
原地更新 随机IO 追加 顺序IO
单次定位 多层合并 读放大
适用 读多写少均衡 写多 极高吞吐

写 MemStore 前先落 WAL,这个顺序保证 RegionServer 宕机后重放日志不丢数据——与 2.1 节 EditLog 的"先记流水再改内存"是同一条铁律的两次应用。

图 5-3 HBase 架构与读写路径

图 5-3 HBase 架构与读写路径

Region:横向扩展的基本单位

表按 RowKey 区间切成多个 Region,每个 Region 某时刻只由一个 RegionServer 服务——这是 HBase 负载均衡与容错的基本单位。Region 超过阈值(默认约 10GB)自动分裂为两个,HMaster 把新 Region 调度到空闲节点。RegionServer 宕机时,HMaster 检测(ZooKeeper 会话失效)后把它的 Region 连同 WAL 拆分重放到其他节点。注意数据面不经过 HMaster:客户端定位 Region 后直连 RegionServer,HMaster 挂了读写照常,只是分裂与迁移暂停——又一个控制面与数据面分离(第 1 章)的实例。

与 DataNode 共置部署是 HBase 性能的命门:RegionServer 读 HFile 时走 2.3 节的本地块与短路读,跨网络读会把毫秒预算烧光。这也解释了 4.3 节节点标签的建议——批处理 YARN 任务与 RegionServer 抢内存是 HBase 延迟抖动的头号元凶。

RowKey 设计:唯一的"索引权"

没有二级索引(社区方案都是外挂表或双写),RowKey 设计几乎决定一个 HBase 应用的成败。三条原则:

避免单调递增。时间戳做 RowKey 前缀,新写入全部落到最后一个 Region——写热点把单节点打满。解法是加盐(前缀加散列)或反转键序(手机号倒序存放,把同一尾号段打散)。

查询模式决定键序。按"用户+时间"查轨迹,RowKey 用 userId反转 + 倒序时间戳——用户内时间倒序天然有序,scan 一个用户全部轨迹是一段连续区间,一次 RPC 拿完。

长度权衡。RowKey 每个单元格都存一遍,键从 16 字节涨到 64 字节,存储与 BlockCache 效率同步恶化;太短则易冲突。10–100 字节是常见区间。

一个可量化的教训:某订单表用 订单创建时间+订单号 做 RowKey,上线后单 Region 写入 QPS 3 万封顶(其余 Region 空转);改为 订单号hash前2位+创建时间 后写热点消失,集群整体 QPS 上到 25 万。RowKey 设计错的代价不是慢一点,是横向扩展完全失效

本节要点回顾

  • HBase 补随机读写出口:数据仍在 HDFS,靠内存索引与 LSM 把随机访问压到毫秒,代价是无 join 无二级索引;
  • 写路径 WAL 先行:顺序追加日志再写 MemStore,满 128MB 刷 HFile,随机写转化为顺序写;
  • 读有放大:MemStore、BlockCache、由新到旧 HFile 合并,compaction 是自愈机制;
  • Region 是扩展与容错单位,HMaster 不在数据面,客户端直连 RegionServer;
  • 与 DataNode 共置并用标签隔离,是延迟稳定的前提;
  • RowKey 是唯一的索引权:防单调递增热点、按查询模式定键序、控制在百字节内。

下一节看数据如何进出集群:Sqoop 与 Flume 两条通道。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U