前置问题与边界

本篇回答一个核心问题:Client 如何缓存 hbase:meta 路由,Region 移动后怎样重新定位。

HBase Client 不是把请求发给 Master 再由 Master 转发。普通 GetPutScan 会通过客户端定位找到 RegionServer,然后直接访问对应 Region。hbase:meta 是路由目录,ZooKeeper 或 registry 是引导与协调入口,HDFS 是底层文件系统;三者不能混成一个“元数据中心”。

HBase 2.6.6 中,Connection 是重对象,承载 RPC、线程、缓存和定位相关状态;TableAdminRegionLocator 是从 Connection 派生出来的轻量使用入口,使用后应该关闭。频繁创建 Connection 会增加资源和定位成本。

路由路径图

sequenceDiagram
    participant App as Application
    participant Conn as HBase Connection
    participant Meta as hbase:meta
    participant RS as RegionServer
    App->>Conn: Get(row)
    Conn->>Conn: check MetaCache
    alt cache hit
        Conn->>RS: RPC to cached region location
    else cache miss
        Conn->>Meta: locate region by row key
        Meta-->>Conn: region location
        Conn->>RS: RPC to RegionServer
    end
    alt moved or not serving
        RS-->>Conn: region error
        Conn->>Conn: reload or clear cache
        Conn->>Meta: locate again
        Conn->>RS: retry
    end
    RS-->>App: Result

正常路径依赖 MetaCache,异常路径刷新定位。把 HBase Client 写成“每次请求都查 hbase:meta”会夸大 meta 压力;把它写成“缓存永远可靠”又会忽略 Region 移动、split、merge 和失败恢复。

关键对象和状态

对象 生命周期建议 职责
Connection 进程级或服务级复用 管理 RPC、线程、配置、定位缓存
Table 短生命周期,使用后关闭 面向单表读写
Admin 短生命周期,使用后关闭 表、namespace、Region 操作
RegionLocator 短生命周期,使用后关闭 查询 Region 位置信息
MetaCache Connection 内部状态 缓存 table+row 到 Region location
hbase:meta 系统表 持久保存 Region 路由信息
RegionServer 数据面服务 接收对应 Region 的请求

Connection 复用是客户端工程实践的第一条边界。Web 服务中为每个 HTTP 请求创建一个 HBase Connection,会把连接初始化、线程、RPC 资源和 meta 定位都放大到请求粒度。更合理的做法是在应用生命周期内复用 Connection,每次操作按需获取并关闭 Table

定位与缓存

客户端根据 table name 和 row key 定位 Region。第一次定位可能需要访问 hbase:meta,得到 start key、end key 和 RegionServer 地址后写入 MetaCache。后续落在同一个 range 的请求可以直接使用缓存。

HBase 2.6.6 还在 Public API 中提供了 RegionLocator#getRegionLocationsPage,用于分页获取 Region location。这说明 Region 定位不是只能一次性拉完整表,也不是每个 row 都查一次 meta;客户端 API 明确暴露了按页处理大量 Region location 的能力。

缓存失效与重试

Region split、merge、move、RegionServer 崩溃和 balancer 都可能让旧缓存失效。旧 RegionServer 收到请求后可能返回 not serving region、region moved 或连接类错误。客户端捕获这类错误后,会清理或刷新对应缓存,再重新定位并重试。

重试不是无限保证成功。hbase.client.retries.number、operation timeout、RPC timeout、pause 策略和调用语义共同决定客户端能等多久。应用层还要处理幂等性。Get 可以安全重试;IncrementAppend、带条件的 mutation 需要结合服务端语义和返回状态判断,不能把所有失败都写成“客户端再发一次就没问题”。

Scan 的路由连续性

Scan 会从 start row 所在 Region 开始,拿到一段结果后继续跨 Region 前进。每跨到下一个 Region,客户端都需要新的 Region location。Region 在 scan 过程中移动时,scanner 可能失效,客户端需要重新打开 scanner 或按语义重试。

分页、cache、batch、limit 和 reversed scan 会在第 14 篇展开。本篇只保留路由事实:scan 是按 row-key range 跨 Region 推进,不是一次 RPC 把全表固定住。

最小实验

UNVERIFIED_RUNTIME:当前任务环境没有目标版本运行条件。以下步骤用于 HBase 2.6.6、Hadoop 3.4.3、JDK 17 环境,不包含伪造输出。

创建带预分区的表:

1
hbase shell
1
create_namespace 'lab'
1
create 'lab:t09_client_route', 'cf', {SPLITS => ['m']}

写入两个 range 的 row key:

1
put 'lab:t09_client_route', 'a#0001', 'cf:v', 'left'
1
put 'lab:t09_client_route', 'z#0001', 'cf:v', 'right'

查看 Region 列表:

1
list_regions 'lab:t09_client_route'

移动某个 Region 到另一台 RegionServer。单节点环境不适合该实验;多节点测试环境需要把 Region 名和目标 server 替换成实际值:

1
move 'ENCODED_REGION_NAME', 'TARGET_SERVER_NAME'

再次读取同一 row key:

1
get 'lab:t09_client_route', 'a#0001'

这组步骤只用于观察客户端重定位和 RegionServer 变化,不记录固定耗时或重试次数。不同配置、网络和 Region 状态会让表现不同。

清理实验表:

1
disable 'lab:t09_client_route'
1
drop 'lab:t09_client_route'
1
drop_namespace 'lab'

Java Public API 示例

下面示例展示 Connection 复用、Table 短生命周期和 RegionLocator 查询。它不访问内部 MetaCache 类,也不直接读写 hbase:meta

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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.Get;
import org.apache.hadoop.hbase.client.RegionLocator;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.util.Bytes;

public final class HBaseClientRouteExample {
public static void main(String[] args) throws IOException {
Configuration conf = HBaseConfiguration.create();
TableName tableName = TableName.valueOf("lab:t09_client_route");
try (Connection connection = ConnectionFactory.createConnection(conf)) {
try (Table table = connection.getTable(tableName)) {
Result result = table.get(new Get(Bytes.toBytes("a#0001")));
System.out.println(result.size());
}
try (RegionLocator locator = connection.getRegionLocator(tableName)) {
List<HRegionLocation> firstPage = locator.getRegionLocationsPage(null, 10);
System.out.println(firstPage.size());
}
}
}
}

UNVERIFIED_RUNTIME:本轮未在目标依赖下编译运行。HBase rel/2.6.6 的 RegionLocator Public API 中,getRegionLocationsPage 的签名是 getRegionLocationsPage(byte[] startKey, int limit);该方法是 optional default method,不能支持分页定位的实现可以抛出 UnsupportedOperationException

失败恢复路径

当 RegionServer 宕机时,客户端先看到连接错误、超时或 Region 不可服务。Master 侧的 ServerCrashProcedure 和 assignment 会让其他 RegionServer 接管 Region,并处理 WAL 恢复。客户端侧只负责按重试策略刷新路由并重新发送请求。

写请求的失败边界比读请求更复杂。客户端超时不一定说明服务端没有执行,也不一定说明已经执行。应用如果在超时后盲目重试非幂等操作,可能产生重复影响。HBase 的行级原子性和 CheckAndMutate 边界会在第 10 篇展开。

工程迁移

HBase 机制 通用模式 迁移场景
MetaCache 客户端路由缓存 RPC service discovery、partition map cache
cache reload 错误驱动失效 DNS 缓存刷新、Kafka metadata refresh
Connection 复用 重对象生命周期外提 DB connection pool、HTTP client 复用
RegionLocator paging 大路由表分页 shard map 分页、服务目录分页

模式公式是:request(key) = locate(key) -> cache(range) -> call(server) -> invalidate_on_error。听到“扩容后客户端仍访问旧节点”,先查缓存失效条件、错误分类和重试超时,而不是直接认为服务端路由没更新。

常见误解

误解 更准确的说法
所有请求都经 HMaster 转发 普通读写定位后直连 RegionServer
每次 Get 都查询 hbase:meta 客户端复用 MetaCache,缓存失效时才刷新
ZooKeeper 保存所有 Region 路由 hbase:meta 保存 Region 路由信息,ZooKeeper/registry 负责入口和协调状态
Connection 可以随请求创建 Connection 是重对象,应在应用生命周期内复用
重试可以掩盖所有失败 超时和非幂等 mutation 需要应用层处理语义边界

练习

  1. 在服务中把 Connection 生命周期从请求级改为应用级,列出资源使用变化的观测指标。
  2. 对预分区表读取两个 row-key range,观察首次定位和后续读的 meta 访问差异。
  3. 在测试集群移动 Region,记录客户端刷新缓存的触发条件。
  4. Increment 设计超时重试策略,写出哪些失败不能简单重放。

系列导航

编号 文章
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 与设计边界

参考资料