HBase海量数据的随机读写怎么破大数据系列第 8 篇HDFS 适合批量读但想要像数据库那样随机查一条数据HBase 来救场。从一个矛盾说起前面咱们聊了 HDFS它是个很好的分布式文件系统适合存大文件、批量读取。但有个场景它搞不定我想快速查一条数据。比如用户打开 App要查他的个人信息根据 user_id 查电商系统要根据订单号查订单详情物流系统要根据快递单号查物流轨迹这些场景的特点是数据量巨大几亿、几十亿条但每次只查其中一条或几条。你用 HDFS 试试HDFS 的设计是批量顺序读你要从几十亿条记录里找一条得扫描整个文件慢得要死。用 MySQL单机存不下这么多数据分库分表后跨库查询又麻烦。这时候就需要一个能支持海量数据随机读写的分布式数据库——HBase。HBase 是什么HBase 是 Apache 的分布式列式数据库基于 Google 的 Bigtable 论文实现。它的核心特点是海量数据存储PB 级别不是问题高并发随机读写毫秒级延迟查单条数据线性扩展加机器就能扩容强一致性读到的数据一定是最新的但注意HBase 不是关系型数据库不支持 SQL、不支持事务、不支持复杂查询。它只擅长一件事根据主键RowKey快速读写数据。HBase 的数据模型跟 MySQL 完全不一样如果你用 MySQL 的思维来理解 HBase会完全懵掉。咱们从头来MySQL 的模型行式存储┌─────────────────────────────────────────────────────────┐ │ MySQL 表行式存储 │ ├─────────────────────────────────────────────────────────┤ │ │ │ id │ name │ age │ city │ phone │ │ │ ───┼───────┼─────┼─────────┼─────────────┤ │ │ 1 │ 张三 │ 25 │ 北京 │ 13800138000 │ │ │ 2 │ 李四 │ 30 │ 上海 │ 13900139000 │ │ │ 3 │ 王五 │ 28 │ 广州 │ 13700137000 │ │ │ │ │ 特点按行存储一行数据存在一起 │ │ 查询SELECT * FROM users WHERE id 1 │ │ → 找到 id1 的那一行读出所有列 │ │ │ └─────────────────────────────────────────────────────────┘HBase 的模型列式存储┌─────────────────────────────────────────────────────────────────┐ │ HBase 数据模型列式存储 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ HBase 的表结构 │ │ │ │ RowKey │ Column Family: info │ Column Family: contact │ │ │──────────────────────────────│────────────────────────│ │ │ name │ age │ city │ phone │ email │ │ ───────┼───────┼───────┼──────────────┼──────────┼─────────────┤ │ user1 │ 张三 │ 25 │ 北京 │ 138... │ zhangqq.com│ │ user2 │ 李四 │ 30 │ 上海 │ 139... │ liqq.com │ │ user3 │ 王五 │ 28 │ 广州 │ 137... │ wangqq.com │ │ │ │ 关键概念 │ │ • RowKey主键唯一标识一行按字典序排序存储 │ │ • Column Family列族列的集合表定义时确定 │ │ • Column列属于某个列族可以动态添加 │ │ • Cell具体的数据单元由 {rowkey, column, version} 唯一标识 │ │ │ │ 特点 │ │ • 按列族存储不同列族的数据存在不同文件里 │ │ • 列可以动态添加不需要预先定义 │ │ • 每个 Cell 可以有多个版本时间戳 │ │ │ └─────────────────────────────────────────────────────────────────┘HBase 的核心概念1. RowKey行键RowKey 是 HBase 最重要的设计。所有数据按 RowKey 的字典序排序存储查询时必须指定 RowKey或 RowKey 范围。RowKey 设计得好不好直接决定了 HBase 的性能。2. Column Family列族列的集合表定义时就要确定。不同列族的数据存储在不同的 HFile 里可以独立设置压缩、缓存等属性。一个表的列族不要太多通常 1-3 个。列族太多会影响性能。3. Column Qualifier列标识符列族下的具体列可以动态添加。比如info:name、info:age、contact:phone。4. Cell数据单元由{rowkey, column family, column qualifier, timestamp}唯一标识。每个 Cell 可以存多个版本的数据通过 timestamp 区分。5. Namespace命名空间类似 MySQL 的 database用于逻辑隔离不同的表。HBase 的架构怎么做到随机读写┌─────────────────────────────────────────────────────────────────┐ │ HBase 架构简化版 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ HMaster大管家 │ │ │ │ │ │ │ │ • 管理表的创建、删除、修改 │ │ │ │ • 分配 Region 给 RegionServer │ │ │ │ • 监控 RegionServer 健康状态 │ │ │ │ • Region 分裂/合并的决策 │ │ │ │ │ │ │ │ 注意HMaster 不处理读写请求只负责管理 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ │ │ 管理指令 │ │ │ │ │ ┌───────────────────────────┼───────────────────────────┐ │ │ │ RegionServer 集群干活的 │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ │ │ RS 1 │ │ RS 2 │ │ RS 3 │ │ RS 4 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Region A│ │ Region C│ │ Region E│ │ Region G│ │ │ │ │ │ Region B│ │ Region D│ │ Region F│ │ Region H│ │ │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ │ │ │ │ 每个 RegionServer 管理多个 Region │ │ │ │ 每个 Region 是一段连续的 RowKey 范围 │ │ │ │ 客户端直接和 RegionServer 通信不经过 HMaster │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ ZooKeeper协调服务存储元数据选举 HMaster │ │ HDFS底层存储RegionServer 把数据文件存在 HDFS 上 │ │ │ └─────────────────────────────────────────────────────────────────┘Region数据分片HBase 表的数据按 RowKey 范围划分为多个Region每个 Region 负责一段连续的 RowKeyRowKey 范围 Region 1: [, user1000) → 存在 RegionServer 1 Region 2: [user1000, user2000) → 存在 RegionServer 2 Region 3: [user2000, user3000) → 存在 RegionServer 3 Region 4: [user3000, ) → 存在 RegionServer 4 当某个 Region 数据太多时会分裂成两个 Region 当 Region 太小时会合并客户端读写时先查 Meta 表存在 ZooKeeper 里找到 RowKey 属于哪个 Region然后直接找对应的 RegionServer。不需要经过 HMaster所以 HMaster 挂了不影响读写。LSM-TreeHBase 的存储引擎HBase 能做到高吞吐写入核心在于它的存储引擎——LSM-TreeLog-Structured Merge-Tree。传统数据库的写入方式B树MySQL 的 InnoDB 用 B树存储数据。写入时找到数据所在页如果页有空位直接插入如果页满了分裂页调整树结构更新索引问题随机写入时磁盘寻道开销大性能差。LSM-Tree 的写入方式先写内存再批量刷盘┌─────────────────────────────────────────────────────────────────┐ │ LSM-Tree 写入流程 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 写入请求 │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ MemStore内存│ ← 先写到内存里速度飞快 │ │ │ │ │ │ │ 数据按 RowKey │ │ │ │ 排序存储 │ │ │ └────────┬────────┘ │ │ │ MemStore 满了默认 128MB │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Flush 到磁盘 │ ← 变成 HFile不可变的排序文件 │ │ │ │ │ │ │ HFile 1 │ │ │ │ HFile 2 │ │ │ │ HFile 3 │ │ │ └─────────────────┘ │ │ │ │ │ │ HFile 太多了触发 Compaction合并 │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Compaction │ ← 多个小 HFile 合并成一个大 HFile │ │ │ 后台异步 │ 清理过期版本、删除标记 │ │ │ │ │ │ │ HFile_big │ ← 合并后的大文件 │ │ └─────────────────┘ │ │ │ │ 读取流程 │ │ 1. 先查 MemStore内存 │ │ 2. 再查 BlockCache读缓存 │ │ 3. 最后查 HFile磁盘 │ │ │ └─────────────────────────────────────────────────────────────────┘LSM-Tree 的核心思想写入只追加不修改数据先写到内存MemStore满了刷成不可变的 HFile批量顺序写刷盘时是顺序写磁盘性能高后台合并多个小 HFile 定期合并成大文件减少读时需要扫描的文件数读取时可能需要查多个 HFile MemStore所以 HBase 的读性能不如写性能。这也是 LSM-Tree 的 trade-off。RowKey 设计HBase 的灵魂RowKey 设计是 HBase 使用中最重要的环节设计不好性能差到怀疑人生。设计原则1. 散列性避免热点如果 RowKey 设计得太集中会导致某些 RegionServer 特别忙其他的很闲。反例RowKey 自增 ID如 0001, 0002, 0003... Region 1: [, 1000) ← 新数据全写这里热点 Region 2: [1000, 2000) Region 3: [2000, 3000) 新数据总是往最后一个 Region 写那个 RegionServer 被打爆解决方案加盐SaltingRowKey 前加随机前缀原始 RowKey: user12345 加盐后: a_user12345, b_user67890, c_user11111... 前缀随机数据分散到不同 Region哈希对 RowKey 取哈希值作为前缀RowKey MD5(user_id)[0:4] user_id反转把时间戳反转原始: 20240101120000时间戳 反转: 000002110104102 这样新数据不会集中在末尾2. 唯一性不能重复RowKey 是主键必须唯一。3. 长度短节省存储RowKey 会重复存储在每条记录里太长会浪费空间。建议控制在 16 字节以内。实际案例场景存储用户订单数据经常按用户 ID 查询方案 1简单RowKey user_id 问题如果某些用户订单特别多形成热点 方案 2推荐RowKey hash(user_id)[0:4] user_id order_time 优点 • hash 前缀保证散列性避免热点 • user_id 保证同一用户的数据相邻范围查询快 • order_time 保证同一用户的订单按时间排序 示例 RowKey a3f2 user12345 20240101120000 a3f2user1234520240101120000HBase 的适用场景适合的场景不适合的场景海量数据的随机读写PB 级复杂查询多表 Join、子查询高并发低延迟查询毫秒级事务处理不支持 ACID写密集型应用日志、时序数据小数据量 overhead 太大需要版本控制的数据需要 SQL 接口用 Phoenix稀疏矩阵存储列可以动态添加频繁更新的数据虽然有版本但不如数据库灵活典型应用场景用户画像根据 user_id 查用户的标签、行为数据消息/Feeds 流存储用户的时间线数据物联网时序数据存储设备传感器数据推荐系统存储用户-物品的交互数据HBase vs Cassandra两个列式数据库怎么选维度HBaseCassandra架构主从HMaster RegionServer无主P2P所有节点平等一致性强一致CP可调AP 默认可配成 CP依赖依赖 HDFS、ZooKeeper独立运行无外部依赖查询方式只能通过 RowKey 查支持二级索引、CQL类 SQL多数据中心原生不支持原生支持多数据中心复制运维复杂度较高多个组件较低适用场景与 Hadoop 生态集成独立部署、多地域、高可用简单选型已经在用 Hadoop 生态HDFS、YARN→HBase需要独立部署、跨数据中心 →Cassandra需要类 SQL 查询 → Cassandra 的 CQL 更友好小结今天咱们聊了 HBase定位海量数据的分布式列式数据库擅长随机读写数据模型RowKey Column Family Column跟关系型数据库完全不同架构HMaster 管理元数据RegionServer 处理读写Region 是数据分片单位LSM-Tree先写内存再批量刷盘写性能高读性能相对弱RowKey 设计最关键的设计环节要考虑散列性、唯一性、长度适用场景用户画像、时序数据、Feeds 流等海量随机读写场景HBase 的设计哲学是牺牲一部分功能SQL、事务、复杂查询换取海量数据下的高并发随机读写能力。在合适的场景下它是无可替代的。你用过 HBase 吗RowKey 是怎么设计的有没有遇到过热点问题欢迎聊聊