4.1 Java API 详解:连接、CRUD 与批量


文档摘要

4.1 Java API 详解:连接、CRUD 与批量 本节摘要:Java 客户端的核心是两个对象的生命周期管理——Connection 重量级全局一份,Table 轻量按需创建。本节覆盖 CRUD、批量写、行级原子操作与 Scan 过滤器,全部示例可直接编译运行,并给出常见异常的成因对照。 环境与连接骨架 Maven 依赖(版本与服务器端对齐): 客户端第一课是对象的生命周期。Connection 背后是到 ZooKeeper 的会话、Meta 缓存(1.3 节)、RPC 线程池,创建一次要几百毫秒,必须全局单例、随进程存活;

4.1 Java API 详解:连接、CRUD 与批量

本节摘要:Java 客户端的核心是两个对象的生命周期管理——Connection 重量级全局一份,Table 轻量按需创建。本节覆盖 CRUD、批量写、行级原子操作与 Scan 过滤器,全部示例可直接编译运行,并给出常见异常的成因对照。

环境与连接骨架

Maven 依赖(版本与服务器端对齐):

<dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase-client</artifactId> <version>2.5.8</version> </dependency>

客户端第一课是对象的生命周期。Connection 背后是到 ZooKeeper 的会话、Meta 缓存(1.3 节)、RPC 线程池,创建一次要几百毫秒,必须全局单例、随进程存活Table 只是轻量句柄,获取几乎零成本但线程不安全,用完即关:

Configuration conf = HBaseConfiguration.create(); conf.set("hbase.zookeeper.quorum", "vm1,vm2,vm3"); conf.set("hbase.zookeeper.property.clientPort", "2181"); try (Connection conn = ConnectionFactory.createConnection(conf)) { // 全局一份 TableName orders = TableName.valueOf("orders"); try (Table table = conn.getTable(orders)) { // 按需获取 // ... 所有操作都在这里 } }

⚠️ 常见坑:把 ConnectionFactory.createConnection 写进请求处理函数,每来一个请求建一次连接——QPS 稍高就把 ZooKeeper 与客户端线程池打爆。另一个坑是多线程共享 Table 实例,出现诡异的位置错乱。记住口诀:连接一份,Table 一用一弃。

写入:Put 与批量

单条 Put 对应一次 RPC,逐条发送等于把网络往返当免费:

Put put = new Put(Bytes.toBytes("u1001-1724055123")); put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("status"), Bytes.toBytes("PAID")); put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("amount"), Bytes.toBytes("299.00")); put.addColumn(Bytes.toBytes("detail"), Bytes.toBytes("addr"), Bytes.toBytes("hangzhou")); put.setDurability(Durability.SYNC_WAL); // 2.1 节的持久性三档 table.put(put); // 同一行多列 一次网络往返

批量场景用 BufferedMutator(替代旧 API 的自动攒批 put):

try (BufferedMutator mutator = conn.getBufferedMutator(orders)) { List<Put> puts = new ArrayList<>(); for (Order o : orderList) { Put p = new Put(Bytes.toBytes(o.rowKey())); p.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("status"), Bytes.toBytes(o.status())); puts.add(p); } mutator.mutate(puts); // 客户端攒批 按 Region 分桶并行发 } // close 时自动 flush 未满批次;也可注册监听器收集失败回调

客户端会自动把批次按目标 Region 分桶、并行发往多台 RegionServer(见第 4 章 _index 的链路图)——这正是 3.1 节预分区的收益:行键散得开,并行度才上得去。

行级原子:同一行内的多列写入天然原子(它们是同一个 Region、同一个 MemStore 序列化单元)。跨行没有事务,需要条件更新用 checkAndPut

// 仅当 status 仍为 NEW 时才改为 PAID:乐观锁式 CAS boolean ok = table.checkAndPut( Bytes.toBytes("u1001-1724055123"), Bytes.toBytes("cf"), Bytes.toBytes("status"), Bytes.toBytes("NEW"), put);

单行 CAS 可用于简单状态机;更复杂的跨行一致性(如转账)不是 HBase 的主场,要么业务侧设计成单行,要么引入外部协调——这是选型时就要想清楚的事(呼应 1.1 节)。

读取:Get 与 Result

Get get = new Get(Bytes.toBytes("u1001-1724055123")); get.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("status")); // 只取需要的列 Result r = table.get(get); byte[] v = r.getValue(Bytes.toBytes("cf"), Bytes.toBytes("status")); System.out.println(Bytes.toString(v)); // PAID

Result 按 Cell 组织,rawCells() 可拿到全部坐标(含时间戳),对应 1.2 节的四维模型。读取端指定版本数的例子:

get.readVersions(3); // 取最近 3 个版本 对应 1.2 节 VERSIONS for (Cell c : r.rawCells()) { System.out.printf("ts=%d val=%s%n", c.getTimestamp(), Bytes.toString(c.getValueArray(), c.getValueOffset(), c.getValueLength())); }

扫描:Scan 与过滤器

Scan scan = new Scan() .withStartRow(Bytes.toBytes("u1001-")) // 前缀起点 .withStopRow(Bytes.toBytes("u1001~")) // '~' 拼尾 前缀匹配技巧 .addFamily(Bytes.toBytes("cf")) // 只读一个列族 少一层 Store .setCaching(100) // 客户端每次缓 100 行 .setBatch(50); // 每次 RPC 最多 50 列 try (ResultScanner scanner = table.getScanner(scan)) { for (Result r : scanner) { // 逐行处理 } } // try-with-resources 必须用:泄漏 scanner 会占住服务端游标

服务器端过滤(省网络、不省 IO 的那类,见 3.3 节):

SingleColumnValueFilter f = new SingleColumnValueFilter( Bytes.toBytes("cf"), Bytes.toBytes("status"), CompareOperator.EQUAL, Bytes.toBytes("PAID")); f.setFilterIfMissing(true); scan.setFilter(f);

能用行键区间表达的查询永远优先PrefixFilter 之类让服务端逐行判断,比 STARTROW/STOPROW 的纯区间推进慢一个量级——根因还是 1.2 节那句"一切查询皆行键算术"。

常见异常对照表

异常 成因 处置
org.apache.hadoop.hbase.DoNotRetryIOException 服务端明确拒绝(如表 disabled) 看消息修正调用
RetriesExhaustedException 重试耗尽(RegionServer 宕机/长 GC) 检查集群健康 再放流量
CallTimeoutException 单次 RPC 超时 调大 hbase.client.operation.timeout 或拆分大请求
NoServerForRegionException Meta 缓存全面失效 客户端会自动重查 突发则查 Meta 所在 RS

💡 关键直觉:客户端代码的问题八成出在对象生命周期(连接、Table、Scanner 三个 try-with-resources)与批处理姿势上,API 本身并不复杂。

本节要点回顾

  • Connection 单例、Table 轻量、Scanner 必关:三个生命周期规则规避大部分诡异故障;
  • BufferedMutator 攒批:客户端按 Region 分桶并行,预分区的并行红利在这里兑现;
  • 行内原子、跨行无事务:checkAndPut 做单行 CAS,复杂一致性要靠设计绕行;
  • Scan 三参数:startRow/stopRow 优先、caching 控客户端缓存、batch 控单次 RPC 列数;
  • 过滤器省网络不省 IO:条件下推到行键区间才是真优化。

Java 之外还有很多使用姿势。下一节看 Shell 的运维级用法与 Thrift/REST 多语言接口。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U