前置问题与边界

本篇回答一个核心问题:表如何扩展,Region 如何迁移,状态由谁推进。

Region 是 HBase 表按 row-key range 切分后的服务单位。它决定数据由哪个 RegionServer 接收,也决定客户端定位和负载均衡的粒度。split、merge 和 assignment 都是在改变 Region 与 RegionServer 的映射,不能把这些动作写成简单的 HDFS 文件移动。

HBase 2.6.6 使用 Procedure v2 和 AssignmentManager 推进 Region assignment。Region 持久状态与位置信息写入 hbase:metahbase:meta 自身的位置信息和可用性还涉及 ZooKeeper 或 registry 相关路径。ZooKeeper 不保存所有 HBase 元数据。

生命周期图

stateDiagram-v2
    [*] --> CLOSED
    CLOSED --> OPENING: assign
    OPENING --> OPEN: RegionServer opens region
    OPEN --> CLOSING: unassign or move
    CLOSING --> CLOSED: close completed
    OPEN --> SPLITTING: split requested
    SPLITTING --> SPLIT: daughters available
    OPEN --> MERGING: merge requested
    MERGING --> MERGED: merged region available
    SPLIT --> CLOSED
    MERGED --> CLOSED

状态图是概念模型。源码和 hbase:meta 中会有更细的 procedure 状态、过渡状态和失败重试记录。文章重点是分工:Master 推进控制面状态,RegionServer 打开或关闭 Region,hbase:meta 记录客户端路由所需信息。

关键对象和状态

对象 职责 不应混淆的边界
HMaster 调度 assignment、split、merge、balancer 不在普通读写数据路径上
AssignmentManager 推进 Region 分配状态 不是客户端缓存
Procedure v2 持久化控制面步骤 不是业务事务日志
RegionServer 打开、关闭并服务 Region 不决定全局表拓扑
hbase:meta 保存 Region 位置信息和状态 不是 ZooKeeper 的替代说法
ZooKeeper / registry 保存集群协调和入口位置 不保存所有表数据或所有 Region 元数据
HDFS 保存 HFile、WAL、目录 不提供 HBase Region 服务语义

hbase:meta 是客户端路由的关键表。Region 状态、start key、end key 和当前 server 等信息需要写入 meta,客户端才能定位普通表 Region。hbase:meta 本身也要被定位;这部分是集群引导和元数据可用性的特殊路径。

Split

Region 持续增长后,可以被 split 成两个 daughter Region。split 边界来自 row-key 空间中的某个 split key。split 不是把全部数据立刻复制成两份;HBase 会通过引用、文件视图和后续 compaction 逐步收敛文件布局。

split 的关键风险是热点未必消失。如果 row key 仍然单调递增,新写入可能继续集中到右侧 daughter Region。split 解决的是“一个 Region 过大或过热需要切开”,但 row key 设计决定切开之后压力能否分散。

Merge

Merge 把相邻或可合并的 Region 合成一个更大的 Region。它常用于 Region 过多、空 Region 过多或 split 过度后的治理。merge 同样需要 Master procedure、Region close/open、meta 更新和客户端缓存刷新配合完成。

merge 不应该被当成任意碎片整理命令。Region 合并会影响路由范围、负载分布和短期可用性,需要避开高峰,并结合 Region 大小、StoreFile 数量、compaction backlog 和表访问模式判断。

Assignment 与移动

Assignment 是把某个 Region 分配给某个 RegionServer 并打开服务的过程。move、balance、server crash recovery 都会触发 Region 关闭、重新分配和打开。客户端在这段时间可能收到 Region moved、not serving region 或连接异常,然后刷新缓存并重试。

HMaster 负责推进控制面,但普通 GetPut 不经过 HMaster 转发。客户端缓存 Region 位置后会直接访问 RegionServer。把 Master 写成“所有读写请求的入口”会误导故障定位。

最小实验

UNVERIFIED_RUNTIME:当前任务环境没有目标 HBase 运行条件。以下步骤只描述 HBase 2.6.6 目标环境中的复现路径,不包含伪造输出。

创建带预分区的表,避免依赖写入量触发自动 split:

1
hbase shell
1
create_namespace 'lab'
1
create 'lab:t08_region_lifecycle', 'cf', {SPLITS => ['user#3000', 'user#6000']}

查看 Region 列表:

1
list_regions 'lab:t08_region_lifecycle'

写入几个落在不同 range 的 row key:

1
put 'lab:t08_region_lifecycle', 'user#0001', 'cf:name', 'a'
1
put 'lab:t08_region_lifecycle', 'user#4000', 'cf:name', 'b'
1
put 'lab:t08_region_lifecycle', 'user#9000', 'cf:name', 'c'

手动 split 某个表或 Region。测试环境可以先对小表执行表级 split,生产环境应按实际 Region 名和负载窗口执行:

1
split 'lab:t08_region_lifecycle', 'user#8000'

再次查看 Region:

1
list_regions 'lab:t08_region_lifecycle'

查询 hbase:meta 观察表的 Region 记录。这里只观察行键范围和 server 字段,不把示例输出写成固定形态:

1
scan 'hbase:meta', {FILTER => "PrefixFilter('lab:t08_region_lifecycle')"}

清理实验表:

1
disable 'lab:t08_region_lifecycle'
1
drop 'lab:t08_region_lifecycle'
1
drop_namespace 'lab'

Java Public API 边界

Region 操作应通过 AdminRegionLocator 等 Public API。应用代码可以查询 Region 位置,但不应该写入 hbase:meta 或依赖内部 procedure 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.IOException;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.RegionLocator;

public final class HBaseRegionLocatorExample {
public static void main(String[] args) throws IOException {
Configuration conf = HBaseConfiguration.create();
TableName tableName = TableName.valueOf("lab:t08_region_lifecycle");
try (Connection connection = ConnectionFactory.createConnection(conf);
RegionLocator locator = connection.getRegionLocator(tableName)) {
List<HRegionLocation> regions = locator.getAllRegionLocations();
System.out.println(regions.size());
}
}
}

UNVERIFIED_RUNTIME:示例使用 HBase 2.6.x Public API,但本轮未在目标依赖组合下编译运行。

失败恢复路径

Assignment 失败时,Procedure v2 的持久化步骤使 Master 可以继续推进或重试。RegionServer 在打开 Region 前后失败,系统需要确认 Region 是否已经对外服务、meta 是否已更新、旧 server 是否仍持有该 Region。ServerCrashProcedure 会和 assignment 逻辑协作,让其他 RegionServer 接管 Region 并处理 WAL 恢复。

split 或 merge 中途失败时,HBase 需要在 parent、daughter、merged region、引用文件和 meta 记录之间维持一致。读写请求看到的是某一套可服务的 Region 映射,而不是半更新的 HDFS 目录列表。

工程迁移

HBase 机制 通用模式 迁移场景
Region 有序 key range 分片 Bigtable tablet、TiKV Region、分片数据库 range shard
Assignment 控制面推进服务归属 分区 leader 调度、Kafka partition reassignment
hbase:meta 路由目录 元数据表、placement map、service discovery registry
Procedure v2 可恢复控制面工作流 DDL job、rebalance job、分片迁移任务

模式公式是:serve(range) = assign(range, server) + publish(location) + refresh(client_cache)。听到“分片移动后请求打到旧节点”,先查路由发布和客户端缓存刷新,不要只查底层文件是否存在。

常见误解

误解 更准确的说法
split 会立刻重写所有 HFile split 可以通过引用和后续 compaction 收敛文件,不等于立即全量复制
Master 转发所有读写 普通读写由客户端直连 RegionServer,Master 管控制面
ZooKeeper 保存所有 HBase 元数据 Region 路由元数据主要在 hbase:meta,ZooKeeper/registry 管入口和协调状态
merge 只是节省目录数量 merge 改变 row-key range、负载分布和客户端路由
Region 越多越好 Region 过多会增加 Master、RegionServer、meta 和 compaction 压力

练习

  1. 创建预分区表,写入不同前缀 row key,观察 Region 分布。
  2. 对单调递增 row key 做 split,判断热点是否真正分散。
  3. 在测试集群移动 Region,观察客户端重试和 meta 变化。
  4. 合并两个低流量 Region 前,列出需要检查的 StoreFile、请求量和 compaction 指标。

系列导航

编号 文章
00 导读:row key 决定数据位置
01 数据模型:Cell、Column Family 与版本
02 集群架构:HMaster、RegionServer、ZooKeeper 与 hbase:meta
03 Schema 与 row key 设计
04 写入路径:WAL、MVCC、MemStore 与 flush
05 读取路径:BlockCache、Bloom Filter 与 HFile block
06 HFile 内部结构
07 Compaction、TTL、版本与 Delete
08 本篇:Region 生命周期:split、merge 与 assignment
09 Client 路由与重试
10 行级原子性与一致性边界
11 RegionServer 崩溃与 WAL 恢复
12 跨集群 Replication
13 Snapshot、Backup 与恢复
14 Get、Scan、Filter 与分页
15 BufferedMutator 与 Bulk Load
16 Coprocessor、Endpoint 与 Phoenix 边界
17 Kerberos、RPC 保护与 ACL
18 Metrics、hbtop、Compaction 与性能调优
19 HBase 3.0 与设计边界

参考资料