HBase操作从Shell到Java API:连接管理与truncate实战
简介面向大数据课程学习者以“熟悉常用的HBase操作”实验为主线完整展示在Linux环境下使用HBase Shell命令与Java API操作分布式列式数据库的方法适合正在完成Hadoop/HBase课程实验或准备大数据开发入门的学习者参考。资源为单个docx文档约3.29MB内容包含实验环境配置Hadoop 2.7.1/HBase 1.1.2/JDK 1.7、Eclipse、Shell与Java代码对照步骤、运行截图与关键输出方便对照实践。已有6999人学习下载对同类学习者有较大参考价值。文档按实验任务组织覆盖列出所有表、打印指定表全部记录、创建表、插入/更新/删除数据以及基于行键、时间戳的查询与过滤操作。相较于零散教程这份报告将Shell命令与Java API并列呈现并提供可复制的代码段能帮助读者快速理解HBase在Hadoop生态中的角色节省自己整理环境与调试程序的时间。1. 先把 HBase 操作练成肌肉记忆再去谈架构做“实验三熟悉常用的 HBase 操作”时很多人的第一反应是“这不就是几行 Shell 命令”。但真正动手把同一件事用 Java API 重写一遍才会发现 Shell 只是表象背后是 Connection 生命周期、Cell 存储结构、列族与列限定符的动态追加规则。这个实验的价值在于让同一批操作既在 Shell 里跑一遍再用 Java API 实现一遍逼着你把两套接口对应上。我按 list、scan、put、delete、truncate 五个任务展开给出可直接运行的 Java 代码和对应 Shell 命令并补上平时写生产任务时踩过的坑比如连接不能频繁创建、truncate 会重建表结构、列族不能靠 put 自动创建。适合准备 HBase 面试题、做大数据课程设计或者刚搭好 HBase 想系统过一遍客户端 API 的读者。2. HBase 客户端连接Configuration、Connection 与 Admin 的生命周期管理2.1 Connection 是重量级对象不能每次现开现关HBase 客户端通过 RPC 与 HMaster、RegionServer 通信。Connection 内部持有连接池、Zookeeper 元数据缓存和线程池创建开销很大。实验代码里的 init() 在每个方法里调用一次close() 再关掉这在课程作业里能跑通但如果放进循环或给线上服务用会有大量无谓的连接建立和销毁严重时把 RegionServer 的连接数打满。我一般会用一个静态工具类持有 Connection进程退出时再关闭。Admin 和 Table 则是轻量级句柄可以从同一个 Connection 上反复获取所以生产代码里不会看到“方法内部新建连接”这种写法。2.2 伪分布环境下的配置项怎么填实验环境是 Windows 10 主机加 VMware 里的 Ubuntu Kylin 16.04 虚拟机Hadoop 版本 3.1.3JDK 1.8。这类伪分布安装里HBase 和 Hadoop 在同一台机器上所以代码里只需要设置 hbase.rootdirconfiguration HBaseConfiguration.create(); configuration.set(hbase.rootdir, hdfs://localhost:9000/hbase);HBaseConfiguration.create() 会先加载 classpath 下的 hbase-site.xml再用 set 覆盖。hbase.rootdir 告诉客户端 HBase 数据在 HDFS 上的根路径。如果连接的是多台机器组成的集群还要补充下表里的 quorum 配置否则客户端不知道去哪里找 meta 表。配置项实验环境值生产集群建议hbase.rootdirhdfs://localhost:9000/hbase集群 NameNode 对应 HDFS 路径hbase.zookeeper.quorumlocalhostzk1,zk2,zk3hbase.zookeeper.property.clientPort21812181hbase.client.operation.timeout不设置5000-10000便于快速失败注意 zookeeper.quorum 是让客户端连 Zookeeper而不是直接连 HMaster。实验代码里只设 rootdir 是因为 HBase 和 Zookeeper 都在本机默认配置能解析到 localhost。一旦换到远程集群忘记配 quorum 是最常见的连不上的原因报错日志里出现 Connection refused 时先查这一项。2.3 一个稳定的 init/close 写法public static Configuration configuration; public static Connection connection; public static Admin admin; public static void init() { configuration HBaseConfiguration.create(); configuration.set(hbase.rootdir, hdfs://localhost:9000/hbase); try { connection ConnectionFactory.createConnection(configuration); admin connection.getAdmin(); } catch (IOException e) { e.printStackTrace(); } } public static void close() { try { if (admin ! null) admin.close(); if (connection ! null) connection.close(); } catch (IOException e) { e.printStackTrace(); } }ConnectionFactory.createConnection 负责建立连接getAdmin 拿到管理操作句柄。这里有两个容易忽略的点Admin 接口继承自 Closeable不要直接实例化 HBaseAdmin那是旧版类早已弃用close 的顺序要先关 Admin 再关 Connection否则连接池可能还在被引用。在 HBase 2.x 客户端里HTableDescriptor 等类名已经变化但 init/close 这套框架保持稳定换版本时不需要重写连接逻辑。3. 四组高频操作对照Shell 命令与 Java API 的差异和雷区3.1 列出所有表list 与 admin.listTables()Shell 命令只有一行list它会输出当前实例里所有用户表的名字。Java 端对应实现public static void listTables() throws IOException { init(); SuppressWarnings(deprecation) HTableDescriptor[] hTableDescriptors admin.listTables(); for (HTableDescriptor hTableDescriptor : hTableDescriptors) { System.out.println(hTableDescriptor.getNameAsString()); } close(); }admin.listTables() 返回 HTableDescriptor 数组里面封装了表名、列族、属性等信息。getNameAsString() 直接拿表名。新版客户端建议改写成 admin.listTableDescriptors()返回 List 旧方法会有弃用警告。这里很容易踩的坑是在 HBase 2.x 里 list 默认会隐藏系统表如果你建的表在别的 namespace 下list 不一定能看到需要用 list_namespace_tables 或 Java 端指定 namespace。3.2 扫描整表scan 与 Result 解析Shell 命令scan student这条命令会把 student 表所有行、所有列的 Cell 按行键顺序打出来。Java 端对应实现要复杂一些因为每个 Cell 需要手动拆开public static void getData(String tableName) throws IOException { init(); Table table connection.getTable(TableName.valueOf(tableName)); Scan scan new Scan(); ResultScanner scanner table.getScanner(scan); for (Result result : scanner) { showCell(result); } scanner.close(); table.close(); close(); }showCell 负责格式化输出public static void showCell(Result result) { Cell[] cells result.rawCells(); for (Cell cell : cells) { System.out.println(RowName(行键): new String(CellUtil.cloneRow(cell))); System.out.println(Timestamp(时间戳): cell.getTimestamp()); System.out.println(Column Family(列簇): new String(CellUtil.cloneFamily(cell))); System.out.println(Column Name(列名): new String(CellUtil.cloneQualifier(cell))); System.out.println(Value(值): new String(CellUtil.cloneValue(cell))); } }Result.rawCells() 返回底层 Cell 数组Cell 是 HBase 存储的最小单元包含行键、列族、列限定符、时间戳和值。CellUtil.cloneFamily 这类方法返回新数组避免直接引用内部字节数组。这里有个很隐蔽的问题scan 默认会把每个 Cell 的所有历史版本都扫出来如果你只想读最新版本需要在 Scan 上设置 setMaxVersions(1)。实验里数据刚写入版本少看不出问题生产环境里一次 scan 可能拖回几十个版本读放大的开销会非常明显。3.3 put 插入列族必须提前存在列限定符可以后加Shell 命令put student,95001,Sname,LiYing put student,95001,course:math,80第一句往 95001 行的 Sname 列写入 LiYing第二句往 course 列族的 math 列写入 80。注意 course 这个列族必须在地表时创建否则会直接报错。HBase 只会在列族内动态增加列限定符不会动态增加列族这是列式存储和关系型表最大的区别之一。Java 端插入实现public static void insertRow(String tableName, String rowKey, String colFamily, String col, String val) throws IOException { init(); Table table connection.getTable(TableName.valueOf(tableName)); Put put new Put(rowKey.getBytes()); put.addColumn(colFamily.getBytes(), col.getBytes(), val.getBytes()); table.put(put); table.close(); close(); }Put 的构造参数是行键的字节数组addColumn 三个参数分别是列族、列限定符、值。时间戳不传时由 HBase 自动生成。实验代码里如果 col 传空字符串会生成一个空限定符的 Cell读起来很别扭。另外值得注意同一个 Put 对象可以多次调用 addColumn把这行的多个列一次性提交比循环调用 table.put 高效得多。原实验代码里 put 一条记录就做一次 RPC数据量小无所谓数据量大时性能差异是数量级的。3.4 delete 删除addFamily 和 addColumn 的语义差别Shell 删除数据常用两条命令delete student,95001,course:math deleteall student,95001Java 端 Delete 对象也区分删除粒度Delete delete new Delete(rowKey.getBytes()); // 删除整个列族 delete.addFamily(colFamily.getBytes()); // 删除指定列 delete.addColumn(colFamily.getBytes(), col.getBytes()); table.delete(delete);delete.addFamily 会删除该行下这个列族的所有版本delete.addColumn 只删除指定列的最新版本旧版本仍然可以通过带时间戳的 get 查到。很多人在面试里被问“HBase delete 之后数据还在吗”答案就是Column 级删除只标记最新版本的墓碑旧版本是否可见取决于查询时间戳。实验代码用 switch 让用户选择删列族还是删列这个交互没问题但要注意 delete 之后再做 scan结果可能和你预期不一样因为墓碑标记需要等到下一次 major compaction 才真正清理。3.5 高频操作对照速查表功能ShellJava API注意列出所有表listadmin.listTables()新版本用 listTableDescriptors扫描整表scan 表名table.getScanner(new Scan())记得关闭 ResultScanner插入/更新put 表,行,列族:列,值Put.addColumn table.put列族必须已存在删除整行deleteall 表,行new Delete(行键)不加列族或列就是删整行删除单列delete 表,行,列族:列delete.addColumn只删除最新版本这张表适合做翻译基准。实际写代码时如果某个 Shell 命令能跑通但 Java API 返回空结果先检查是不是表名写错、列族大小写不一致或者 Scan 没关 ResultScanner 导致连接池泄漏。4. 清空表 truncate 的完整复刻禁用表、删除表、重建表结构4.1 truncate 不是清空文件那么简单truncate student在 HBase 里的执行路径是先把表 disable让所有 Region 下线不再接收读写然后记录表原本的列族和属性接着删掉表最后用保存的 Schema 重新建立同名空表。这个过程和 Linux 的truncate -s 0完全不同它实际上是“删表重建”所以 Region 会重新分配表在 truncate 前后虽然是同名但底层的 Region 和 HFile 都已经换新。4.2 Java 复刻版本实验代码使用了一个有趣的思路先 scan 原表把所有 Cell 里出现的列族名收集到 List然后 disabledelete再按列族集合重建。原代码直接 add 到 List同一列族会出现多次重建时 HColumnDescriptor 重复添加会报错。更稳的写法是用 LinkedHashSet 去重public static void clearRows(String tableName) throws IOException { init(); TableName tablename TableName.valueOf(tableName); Table table connection.getTable(tablename); Scan scan new Scan(); ResultScanner scanner table.getScanner(scan); SetString colFamilies new LinkedHashSetString(); for (Result result : scanner) { for (Cell cell : result.rawCells()) { colFamilies.add(new String(CellUtil.cloneFamily(cell))); } } scanner.close(); table.close(); admin.disableTable(tablename); admin.deleteTable(tablename); HTableDescriptor descriptor new HTableDescriptor(tablename); for (String cf : colFamilies) { descriptor.addFamily(new HColumnDescriptor(cf)); } admin.createTable(descriptor); close(); }这段代码在“表里有数据”的前提下成立。如果表是空的scanner 扫不到任何 CellcolFamilies 就是空集合重建出来的表没有列族。所以生产环境里我一般不用扫描 Cell 的方式收集列族而是直接用原来的表描述符保存列族定义HTableDescriptor oldDescriptor admin.getTableDescriptor(tablename); HColumnDescriptor[] families oldDescriptor.getColumnFamilies();然后 disable、delete、再 createTable。实验代码用扫描是为了演示“从数据反推 Schema”这在分析外部导入的 HBase 数据时偶尔有用但作为清空表的实现不够健壮。如果你把这段代码拿到有预分区或自定义压缩算法的表上跑还会丢掉预分区信息所以真正的 truncate 语义改造要谨慎。4.3 新旧 API 差异和容错HBase 2.x 之后HTableDescriptor、HColumnDescriptor 被 TableDescriptor、ColumnFamilyDescriptor 取代实验代码里会有大量 Deprecated 警告。对应的替代写法是基于 TableDescriptorBuilder 构建TableDescriptor descriptor TableDescriptorBuilder.newBuilder(tablename) .setColumnFamily(ColumnFamilyDescriptorBuilder.of(cf)) .build();场景旧 API新 API获取表描述admin.getTableDescriptor(tablename)admin.getDescriptor(tablename)构建表描述new HTableDescriptor(tablename)TableDescriptorBuilder.newBuilder(tablename)添加列族new HColumnDescriptor(cf)ColumnFamilyDescriptorBuilder.of(cf)列出表admin.listTables()admin.listTableDescriptors()另外清空前最好先判断表是否存在否则 admin.disableTable 直接抛 TableNotFoundException。面试里常问“truncate 和 deleteall 的区别”答案不是“一个快一个慢”而是 deleteall 只删数据不删表truncate 会重建表结构并重置 Region 分布。理解了这一点Java 端复刻 truncate 时自然就会按 disable、delete、create 三步走。5. 从实验到生产批量写入、过滤器与连不上时的排查顺序5.1 把单条 put 换成批量写入实验里一条条 put生产上同一线程连续写一万条每条都走一次 RPC延迟会很难看。常用做法是先在内存里攒一个 List再一次性提交ListPut puts new ArrayList(); for (String rowKey : rowKeys) { Put put new Put(Bytes.toBytes(rowKey)); put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(name), Bytes.toBytes(value)); puts.add(put); } table.put(puts);如果写入量更大可以用 BufferedMutatorBufferedMutator mutator connection.getBufferedMutator(tablename); mutator.mutate(put); mutator.flush();BufferedMutator 内部按 write buffer 大小批量发送默认 2MB可以通过 BufferedMutatorParams 调整。flush 之后数据不一定立刻对读可见因为还要经过 MemStore flush 才落盘。这个异步行为经常让新人误以为数据丢了实际只是还没刷到 HFile。5.2 给 scan 加过滤器实验没写过滤器但“查询某列等于某个值的所有行”是 HBase 操作里绕不开的需求。常用做法是 SingleColumnValueFilterScan scan new Scan(); SingleColumnValueFilter filter new SingleColumnValueFilter( Bytes.toBytes(Sdept), Bytes.toBytes(), CompareOperator.EQUAL, Bytes.toBytes(CS) ); scan.setFilter(filter); ResultScanner scanner table.getScanner(scan);注意 SingleColumnValueFilter 会跳过那些没有指定列的行如果想保留这些行要设置 setFilterIfMissing(false)。HBase 的列族名区分大小写过滤条件里写错大小写不会报错只是查询结果为空排查时先打印一遍 Scan 的 toString 看过滤器条件是不是符合预期。5.3 连不上集群时先看三个参数实验环境如果一直连接不上按照我平时的排查顺序走一圈先确认 hbase.zookeeper.quorum 是否配置成 HBase 所在机器的 IP再确认 hbase-site.xml 是否真的打进了 classpathEclipse 项目里经常出现改了配置文件但没生效的情况然后看客户端日志里有没有 Connection refused有就说明 Zookeeper 地址不对没有就继续查 region server 是否启动完毕。把 hbase.client.operation.timeout 临时调小到 5000能让失败在几秒内暴露而不是卡住五分钟才抛 Retries exhausted。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →