Coprocessor 把一部分逻辑放进 HBase 服务端进程执行。这个能力很强,也很危险:代码和 RegionServer 在同一个 JVM、同一套权限、同一条请求路径里运行。Endpoint 能做局部聚合和自定义 RPC,但它不是分布式 SQL 引擎;Phoenix 提供 SQL 层,但 Phoenix 和 HBase 的升级边界也不能混写。

本篇只抓一个核心问题:计算下推能做什么,何时会破坏隔离、升级和运维边界。

Coprocessor 路径图

flowchart TD
  Client[HBase Client] --> RPC[RegionServer RPC]
  RPC --> Observer[Observer hooks]
  Observer --> Region[Region]
  Region --> Store[Store/MemStore/HFile]
  Client --> Endpoint[Endpoint service]
  Endpoint --> Region
  Region --> WAL[WAL]

Observer 是事件钩子。它可以在 GetPutScan、flush、compaction、WAL 等事件前后执行代码。典型用途是权限检查、审计、轻量索引维护、请求改写。

Endpoint 是服务端 RPC 扩展。客户端显式调用 Endpoint,代码在承载相关 Region 的 RegionServer 上执行,返回聚合结果或自定义结果。典型用途是对一个表的多个 Region 做局部求和,再由客户端合并。

这两个能力都不改变 HBase 的核心存储模型。row key、Region、WAL、MemStore、HFile、BlockCache 仍然是读写路径的主体;Coprocessor 只是挂在路径上的扩展点。

关键对象与状态

对象 作用 风险
RegionObserver 拦截 Region 读写事件 影响主读写路径
MasterObserver 拦截表、namespace、assignment 等事件 影响控制面
WALObserver 拦截 WAL 相关事件 影响 durability 路径
CoprocessorService 暴露 Endpoint RPC 需要客户端显式调用
Coprocessor JAR 服务端执行代码 版本、依赖、权限都进入服务端

官方 Coprocessor 文档把它标成高级特性,并明确提醒:Coprocessor 直接运行在 RegionServer 上,能访问数据,错误代码可能破坏数据或稳定性;同时没有资源隔离。HBase Security Model 也说明,加载 coprocessor 等同授予服务器级访问,应当只用于受信任扩展。

状态变化可以按加载方式分成两类。

系统级 Coprocessor 通过 hbase-site.xml 加载。它随服务进程启动,作用于所有相关对象。卸载通常需要修改配置并重启。

表级 Coprocessor 通过表描述加载。它作用于单表,但加载本身是 schema change。生产环境必须用 ACL 限制谁能修改表描述,否则“能改 schema 的用户”就能把代码放进 RegionServer。

Observer 的适用边界

Observer 适合处理“请求经过这里时必须检查或补充”的逻辑。AccessController 就是典型例子:HBase ACL 是通过 coprocessor 实现的,读写请求进入 RegionServer 后先经过权限检查。

但 Observer 不适合承载重计算。一个 prePutpostGet 里做远程 HTTP、复杂 JSON 解析、大量内存分配,会直接拖慢 RegionServer 读写线程。没有资源隔离意味着一个坏 hook 可以把同 JVM 的正常 Region 一起拖住。

Observer 也不适合做跨行事务。它可以在一个 Put 经过时补写另一张表,但 HBase 不会因此提供跨行原子性。如果第二次写失败,主写入和副作用之间的补偿需要应用自己设计。

可迁移模式是“把强约束放在路径内,把重计算放在路径外”。权限检查、审计标记、幂等校验适合路径内;复杂派生数据、搜索索引重建、宽表回填适合异步作业或复制流。

Endpoint 不是查询引擎

Endpoint 的价值在于减少数据搬运。对一个跨很多 Region 的求和操作,客户端逐行 scan 会把大量 Cell 拉到客户端;Endpoint 可以让每个 RegionServer 在本地扫描并返回局部结果,客户端只合并小结果。

这并不等于 HBase 有了通用 SQL。Endpoint 没有统一优化器、没有成本模型、没有跨 Region join、没有快照隔离级别的查询计划,也不会自动维护二级索引。它是“服务端自定义 RPC”,不是“分布式关系查询层”。

HBase 2.x 的 Coprocessor 文档还提示一个升级风险:Table 上的同步 coprocessorService 方法已经 deprecated,建议使用 AsyncTablecoprocessorService。Endpoint 依赖 protobuf 和服务端扩展点,升级时必须重新核对 public API、依赖打包和运行时加载路径;具体大版本差异集中放到第 19 篇。

最小调用形状如下,API 名称按官方文档核对,当前本地无 HBase 依赖,标记为 UNVERIFIED_RUNTIME

1
2
3
4
5
6
Configuration conf = HBaseConfiguration.create();
try (AsyncConnection connection = ConnectionFactory.createAsyncConnection(conf).get()) {
AsyncTable<?> table = connection.getTable(TableName.valueOf("lab:cp"));
// Endpoint service request/response 由业务 proto 生成。
// 生产代码应限制 timeout、结果大小和异常处理。
}

这段代码不伪造业务 proto,也不把内部类当稳定 API。实际 Endpoint 示例应以项目自己的 .proto 和官方 2.6 API 为准。

Phoenix 的边界

Phoenix 在 HBase 之上提供 SQL 接口、二级索引和查询优化能力。它不是 HBase 内核的一部分,也不是把 HBase 变成关系数据库的开关。

Phoenix 需要和 HBase 版本匹配。它会依赖 HBase 的 client、server 端扩展和部分内部行为。HBase 升级时,Phoenix 兼容矩阵必须和 HBase release notes 一起检查;不能只看 HBase 服务端能启动就认为 SQL 层安全。

Phoenix 适合让已知模式的点查、范围查和有限聚合获得 SQL 入口。它不适合把 HBase 当 MySQL 替代品,尤其不适合依赖跨行事务、任意 join、强一致全局二级索引和高并发 OLTP 约束。

工程判断上,Phoenix 是上层访问模式,不是 HBase 存储语义本身。写 HBase 基础机制时应先解释 row key、Region 和 HFile,再解释 Phoenix 如何把 SQL 映射到这些对象。

最小实验

本地无 HBase 集群,实验为 UNVERIFIED_RUNTIME。目标是验证加载、调用和卸载三个状态,不给出固定输出。

创建实验表:

1
hbase shell

Shell 内创建表:

1
2
create_namespace 'lab'
create 'lab:cp', 'cf'

检查当前表描述:

1
describe 'lab:cp'

动态加载 coprocessor 前,必须确认 JAR 已在所有 RegionServer 可读的位置,通常是 HDFS 路径。加载动作是表 schema change,生产环境应先在测试集群验证。

1
2
3
4
disable 'lab:cp'
alter 'lab:cp', METHOD => 'table_att', 'Coprocessor' => 'hdfs:///apps/hbase/cp/example.jar|org.example.SafeObserver|1073741823'
enable 'lab:cp'
describe 'lab:cp'

观察点包括表描述里的 coprocessor 属性、RegionServer 日志的加载异常、读写请求延迟、Coprocessor Metrics UI 或 JMX 里的调用耗时。

失败恢复

Coprocessor 加载失败时,首要动作是恢复表或服务端配置,而不是重试业务写入。表级 coprocessor 可以通过删除表属性卸载;系统级 coprocessor 通常要修改 hbase-site.xml 并重启服务。

错误 coprocessor 可能导致 RegionServer abort。生产环境应把三类保护放在前面:JAR 白名单、最小权限、上线前 MiniCluster 或测试集群验证。HBase 提供 CoprocessorWhitelistMasterObserver 用于限制可加载 JAR。

Endpoint 失败时要按 RPC 失败处理。客户端必须设置 timeout,限制结果大小,并对每个 Region 的异常给出降级策略。把 Endpoint 当作普通函数调用会隐藏 Region 局部失败。

Phoenix 失败恢复要按上层系统处理。索引异常、统计信息过期、查询计划变化都可能表现为 HBase 读写变慢,但根因在 SQL 层。排查时应先分开 HBase 表读写健康度和 Phoenix 查询计划。

工程迁移

需求 推荐边界 原因
权限、审计、可见性标签 Observer 必须在服务端路径内执行
局部聚合、减少网络搬运 Endpoint 计算靠近 Region
复杂 ETL、索引重建 外部作业 避免阻塞 RegionServer
SQL 点查和有限范围查 Phoenix 让 SQL 映射到已设计好的 row key
跨行事务和任意 join 关系数据库或 NewSQL HBase 内核不提供这类边界

模式提炼:扩展点越靠近数据路径,越要把逻辑写小、权限收紧、故障回滚路径写清。路径内代码换来低延迟,也拿走了隔离。

常见误解

误解一:Coprocessor 是安全的插件系统。官方安全模型说得很清楚,加载 coprocessor 等同给服务端级访问,必须信任代码。

误解二:Endpoint 能把 HBase 变成 SQL 引擎。Endpoint 只是自定义服务端 RPC;SQL、优化器和索引语义属于 Phoenix 这类上层系统。

误解三:Observer 可以补齐外键和跨行事务。Observer 可以写副作用,不能把 HBase 的行级原子性扩展成跨行事务。

误解四:Phoenix 兼容 HBase 就等于没有升级风险。Phoenix 的 server 端扩展和 HBase API 版本必须一起核查。

练习

  1. 把一个“写入时同步维护二级索引”的需求拆成 Observer、异步作业、Phoenix 三种方案,比较失败补偿路径。
  2. 设计 Endpoint 求和实验,列出每个 Region 局部失败时客户端应如何返回。
  3. 检查生产集群是否限制表级 coprocessor 加载权限,并确认是否启用 JAR 白名单。

系列导航

参考资料

证据等级:Coprocessor、Endpoint 和安全边界为 VERIFIED_SOURCE;本地未加载 coprocessor,实验为 UNVERIFIED_RUNTIME