HBase读写流程 #
一、写入流程概述 #
HBase的写入流程经过多个组件协作,确保数据安全和高性能。
1.1 写入流程图 #
text
┌─────────────────────────────────────────────────────────────────────┐
│ HBase 写入流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ Client │ │
│ └────┬────┘ │
│ │ │
│ │ 1. 定位Region │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ZooKeeper → hbase:meta → RegionServer │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 2. 发送写请求 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ RegionServer │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 3. 写WAL (Write-Ahead Log) │ │ │
│ │ │ └── 顺序写入HDFS,保证持久性 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 4. 写MemStore │ │ │
│ │ │ └── 写入内存,有序存储 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 5. 返回ACK │ │ │
│ │ │ └── 确认写入成功 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
1.2 写入步骤详解 #
text
写入步骤
├── 1. Client定位Region
│ ├── 查询ZooKeeper获取meta表位置
│ ├── 查询meta表获取目标Region
│ └── 缓存Region位置信息
│
├── 2. 发送写请求
│ └── 直接向RegionServer发送Put请求
│
├── 3. 写WAL
│ ├── 顺序写入HDFS
│ ├── 确保数据持久化
│ └── 支持同步/异步模式
│
├── 4. 写MemStore
│ ├── 写入内存SkipList
│ ├── 数据有序存储
│ └── 触发Flush条件检查
│
└── 5. 返回确认
└── 向Client返回写入成功
二、写入详细流程 #
2.1 Region定位 #
text
┌─────────────────────────────────────────────────────────────────────┐
│ Region 定位 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 首次访问 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Client → ZooKeeper │ │
│ │ 获取meta表所在RegionServer │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Client → meta表RegionServer │ │
│ │ 根据RowKey查询目标Region │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Client 缓存Region位置 │ │
│ │ 后续访问直接使用缓存 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 2. 后续访问 │
│ └── 直接使用缓存的Region位置 │
│ │
│ 3. 缓存失效 │
│ ├── Region移动时失效 │
│ └── 访问失败时刷新 │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.2 WAL写入 #
text
┌─────────────────────────────────────────────────────────────────────┐
│ WAL 写入 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ WAL写入流程: │
│ │
│ 1. 序列化KeyValue │
│ └── 将Put操作序列化为WALEdit │
│ │
│ 2. 获取WAL序号 │
│ └── MVCC保证顺序性 │
│ │
│ 3. 写入WAL文件 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ WAL文件结构 │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ WAL Header │ │ │
│ │ │ - 文件格式版本 │ │ │
│ │ │ - 加密信息 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ WAL Entry 1 │ │ │
│ │ │ - Region信息 │ │ │
│ │ │ - WALEdit (KeyValue列表) │ │ │
│ │ │ - 时间戳 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ WAL Entry 2 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 4. 同步到磁盘 │
│ └── 根据WAL类型决定是否等待 │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.3 MemStore写入 #
text
┌─────────────────────────────────────────────────────────────────────┐
│ MemStore 写入 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ MemStore写入流程: │
│ │
│ 1. 获取行锁 │
│ └── 保证同一行的原子性 │
│ │
│ 2. 更新MVCC │
│ └── 分配写序号 │
│ │
│ 3. 写入Cell │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ MemStore (SkipList) │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ row3 │ cf:col → value3 (ts=1704067202) │ │ │
│ │ ├─────────────────────────────────────────────────────┤ │ │
│ │ │ row2 │ cf:col → value2 (ts=1704067201) │ │ │
│ │ ├─────────────────────────────────────────────────────┤ │ │
│ │ │ row1 │ cf:col → value1 (ts=1704067200) │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ 排序规则:RowKey → CF → CQ → Timestamp(倒序) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 4. 释放行锁 │
│ │
│ 5. 检查Flush条件 │
│ ├── MemStore大小达到阈值 │
│ └── RegionServer全局内存压力 │
│ │
└─────────────────────────────────────────────────────────────────────┘
2.4 Flush触发 #
text
Flush触发条件
├── MemStore级别
│ └── 单个MemStore达到128MB
│
├── Region级别
│ └── Region所有MemStore总和达到阈值
│
├── RegionServer级别
│ ├── 全局MemStore达到高水位(阻塞写入)
│ └── 全局MemStore达到低水位(强制Flush)
│
├── WAL级别
│ └── WAL文件数量过多
│
└── 手动触发
└── flush命令
三、读取流程概述 #
3.1 读取流程图 #
text
┌─────────────────────────────────────────────────────────────────────┐
│ HBase 读取流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ Client │ │
│ └────┬────┘ │
│ │ │
│ │ 1. 定位Region │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ZooKeeper → hbase:meta → RegionServer │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ 2. 发送读请求 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ RegionServer │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 3. 查询BlockCache │ │ │
│ │ │ └── 缓存命中直接返回 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 4. 查询MemStore │ │ │
│ │ │ └── 查找最新数据 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 5. 查询HFile │ │ │
│ │ │ ├── Bloom Filter过滤 │ │ │
│ │ │ ├── Block Index定位 │ │ │
│ │ │ └── 读取Data Block │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 6. 合并结果 │ │ │
│ │ │ └── 合并多版本,返回最新数据 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.2 读取步骤详解 #
text
读取步骤
├── 1. Client定位Region
│ └── 同写入流程
│
├── 2. 发送读请求
│ └── 向RegionServer发送Get/Scan请求
│
├── 3. 查询BlockCache
│ ├── 检查缓存是否命中
│ └── 命中则直接返回
│
├── 4. 查询MemStore
│ ├── 查找最新写入数据
│ └── 数据按RowKey排序
│
├── 5. 查询HFile
│ ├── Bloom Filter快速过滤
│ ├── Block Index定位数据块
│ └── 读取Data Block
│
└── 6. 合并结果
├── 合并多个数据源
└── 返回最新版本数据
四、读取详细流程 #
4.1 BlockCache查询 #
text
┌─────────────────────────────────────────────────────────────────────┐
│ BlockCache 查询 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ BlockCache结构: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ LruBlockCache │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Single Access (25%) │ │ │
│ │ │ - 单次访问的数据块 │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ Multi Access (50%) │ │ │
│ │ │ - 多次访问的数据块 │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ In-Memory Access (25%) │ │ │
│ │ │ - 常驻内存的数据块 │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 查询流程: │
│ 1. 计算Block Key │
│ 2. 查找缓存 │
│ 3. 命中则更新访问计数 │
│ 4. 未命中则从HFile读取 │
│ │
└─────────────────────────────────────────────────────────────────────┘
4.2 HFile查询 #
text
┌─────────────────────────────────────────────────────────────────────┐
│ HFile 查询 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 查询流程: │
│ │
│ 1. Bloom Filter检查 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 检查RowKey是否可能存在 │ │
│ │ ├── 可能存在 → 继续查询 │ │
│ │ └── 不存在 → 跳过该HFile │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 2. Block Index查找 │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Root Index │ │
│ │ ├── Data Index Block 1 [startKey1, offset1] │ │
│ │ ├── Data Index Block 2 [startKey2, offset2] │ │
│ │ └── Data Index Block 3 [startKey3, offset3] │ │
│ │ │ │
│ │ 二分查找定位目标Block │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 3. 读取Data Block │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 从HDFS读取Block │ │
│ │ ├── 解压缩 │ │
│ │ ├── 放入BlockCache │ │
│ │ └── 在Block内二分查找 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
4.3 结果合并 #
text
┌─────────────────────────────────────────────────────────────────────┐
│ 结果合并 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 数据来源(按时间戳倒序): │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ MemStore │ ts=1704067203 │ value=v3 │ │
│ │ StoreFile 1 │ ts=1704067202 │ value=v2 │ │
│ │ StoreFile 2 │ ts=1704067201 │ value=v1 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 合并规则: │
│ 1. 按时间戳倒序排列 │
│ 2. 返回最新版本 │
│ 3. 跳过已删除标记 │
│ 4. 应用TTL过滤 │
│ │
│ 返回结果:value=v3 (ts=1704067203) │
│ │
└─────────────────────────────────────────────────────────────────────┘
五、Scan操作 #
5.1 Scan流程 #
text
┌─────────────────────────────────────────────────────────────────────┐
│ Scan 流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Scan特点: │
│ ├── 范围查询 │
│ ├── 可能跨多个Region │
│ └── 使用迭代器模式 │
│ │
│ Scan流程: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 1. 定位起始Region │ │
│ │ └── 根据StartRow定位 │ │
│ │ │ │
│ │ 2. 创建RegionScanner │ │
│ │ └── 每个Region一个Scanner │ │
│ │ │ │
│ │ 3. 创建StoreScanner │ │
│ │ └── 每个Store一个Scanner │ │
│ │ │ │
│ │ 4. 创建StoreFileScanner │ │
│ │ └── 每个StoreFile一个Scanner │ │
│ │ │ │
│ │ 5. 迭代读取 │ │
│ │ ├── 按需从服务器获取数据 │ │
│ │ └── 缓存一定数量的行 │ │
│ │ │ │
│ │ 6. 跨Region继续 │ │
│ │ └── 当前Region读完,继续下一个Region │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
5.2 Scan配置 #
xml
<!-- hbase-site.xml -->
<!-- 客户端缓存行数 -->
<property>
<name>hbase.client.scanner.caching</name>
<value>100</value>
</property>
<!-- Scanner超时时间 -->
<property>
<name>hbase.client.scanner.timeout.period</name>
<value>60000</value>
</property>
<!-- Scanner Lease周期 -->
<property>
<name>hbase.regionserver.lease.period</name>
<value>60000</value>
</property>
六、数据一致性 #
6.1 MVCC机制 #
text
┌─────────────────────────────────────────────────────────────────────┐
│ MVCC 机制 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ MVCC(多版本并发控制)保证: │
│ ├── 读写不阻塞 │
│ ├── 读一致性 │
│ └── 写原子性 │
│ │
│ 工作原理: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 写操作 │ │
│ │ 1. 获取写序号 (writeNum = 5) │ │
│ │ 2. 写入MemStore │ │
│ │ 3. 更新读点 (readPoint = 5) │ │
│ │ │ │
│ │ 读操作 │ │
│ │ 1. 获取当前读点 (readPoint = 4) │ │
│ │ 2. 只读取 writeNum <= readPoint 的数据 │ │
│ │ 3. 不看到进行中的写入 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ 时间线示例: │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ T1: Write(w=1) ────────────────────────► Commit │ │
│ │ T2: Read(r=1) ──────► 看到w=1的数据 │ │
│ │ T3: Write(w=2) ──────────────────► Commit │ │
│ │ T4: Read(r=1) ──────► 看到w=1的数据(不看到w=2) │ │
│ │ T5: Read(r=2) ──────► 看到w=1,w=2的数据 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
6.2 行级原子性 #
text
行级原子性保证
├── 同一行多个操作的原子性
├── 使用行锁实现
└── 不支持跨行事务(除非使用协处理器)
6.3 一致性级别 #
text
一致性级别
├── 强一致性
│ └── 默认,读写同一Region
│
└── 时间线一致性
└── 可能读到旧数据(Region迁移场景)
七、性能优化 #
7.1 写入优化 #
text
写入优化策略
├── 批量写入
│ └── 使用批量Put减少网络开销
│
├── 预分区
│ └── 避免Region切分,均匀分布写入
│
├── 调整MemStore
│ ├── 增大MemStore大小
│ └── 调整Flush阈值
│
├── WAL优化
│ ├── 使用异步WAL
│ └── 调整WAL滚动策略
│
└── 压缩
└── 使用SNAPPY压缩减少IO
7.2 读取优化 #
text
读取优化策略
├── BlockCache调优
│ ├── 增大BlockCache大小
│ └── 使用BucketCache
│
├── Bloom Filter
│ └── 启用布隆过滤器减少IO
│
├── Scan优化
│ ├── 设置合理的缓存大小
│ ├── 指定列族和列
│ └── 使用过滤器
│
├── 避免全表扫描
│ └── 合理设计RowKey
│
└── Compaction
└── 定期Major Compaction优化查询
7.3 配置示例 #
xml
<!-- 写入优化 -->
<property>
<name>hbase.hregion.memstore.flush.size</name>
<value>268435456</value> <!-- 256MB -->
</property>
<!-- 读取优化 -->
<property>
<name>hfile.block.cache.size</name>
<value>0.4</value>
</property>
<!-- Scan优化 -->
<property>
<name>hbase.client.scanner.caching</name>
<value>1000</value>
</property>
八、总结 #
本节介绍了HBase的读写流程:
| 流程 | 关键步骤 |
|---|---|
| 写入 | 定位Region → 写WAL → 写MemStore → 返回ACK |
| 读取 | 定位Region → 查BlockCache → 查MemStore → 查HFile → 合并结果 |
| WAL | 顺序写入,保证数据持久化 |
| BlockCache | 读缓存,加速数据访问 |
| MVCC | 多版本并发控制,保证读写一致性 |
| Scan | 范围查询,跨Region迭代 |
下一步,让我们学习HBase的表操作!
最后更新:2026-03-27