HBase常用操作实验笔记:从Shell到Java API的读写实践

HBase常用操作实验笔记:从Shell到Java API的读写实践 简介这份实验报告资源面向正在学习大数据课程、需要掌握HBase操作的高校学生或自学者围绕“熟悉常用的HBase操作”主题系统覆盖了HBase在Hadoop体系中的角色、Shell命令用法以及Java API开发实践适合配合教材第五章进行实操训练。资源包为单个docx文档大小3.29MB内含完整实验报告包括实验环境Linux、Hadoop、HBase、JDK、Eclipse、实验目的、操作步骤和关键代码截图能直接用于对照练习或参考文档。报告以“列出所有表”“扫描指定表记录”等具体任务串联逐一给出Shell命令与对应Java实现并展示执行结果数据操作部分还涉及建表、增删改查等内容。已有6999人学习该资源适合需要完成同类实验或快速上手HBase基本操作的学习者借鉴。1. 拿常用 HBase 操作做一次端到端验证拿 HBase 里一张千万行的订单表做读写对比关系型数据库在分库分表之前整体延迟会随数据量增长明显抬升而 HBase 依靠 LSM 树和 Region 自动分裂能在海量数据上维持近似稳定的毫秒级随机读写。这个结论听起来简单但真正确认过它的人大多不是从架构图里学来的而是在终端里把 put、get、scan、delete 一个个敲完之后才建立的直觉。这篇实验笔记把 HBase 操作链路上最常用的一套动作整理成可照着执行的步骤——从 HBase 安装与配置、服务启动到命令行完成 DDL/DML再落到 Java API 的同一批操作最后聊几个会影响面试和线上排查的细节。适合正在做课程实验、准备 HBase 面试题或者想系统过一遍读写路径的工程师。2. 环境就绪从 HBase 安装与配置到最小可运行集群2.1 用 standalone 模式跑通最小环境HBase 的部署模式选择并不复杂。目标仅仅是跑通常用操作时standalone 模式完全够用只有一个 JVM 进程HMaster、内嵌 ZooKeeper 和 RegionServer 的工作线程都包含其中不依赖 Hadoop HDFS。它对实验结果没有实质影响因为 put/get/scan 走的是完整 RPC 链路唯一差别是 Region 的分配和调度逻辑更简单。如果后续想贴近生产布局再改成伪分布式把hbase.cluster.distributed设为 trueHMaster 与 RegionServer 就会分进程运行。无论哪种模式启动命令和排错方式几乎一致本实验按下述 standalone 配置展开。tar -xzf hbase-2.4.x-bin.tar.gz cd hbase-2.4.x/conf解压后优先确认两个文件。第一是 hbase-env.sh 中的JAVA_HOME很多机器上默认是空值或指向系统自带 JDK不显式设置后面启动会直接失败。第二是 hbase-site.xml本实验最少包含三个属性即可property namehbase.rootdir/name valuefile:///data/hbase/value /property property namehbase.cluster.distributed/name valuefalse/value /property property namehbase.zookeeper.property.dataDir/name value/data/zookeeper/value /propertyhbase.rootdir是 HBase 数据文件的持久化目录standalone 模式下直接指向本地路径伪分布式或完全分布式环境下这个值通常是hdfs://namenode:8020/hbase。hbase.cluster.distributed为 false 表示非分布式模式如果改成 true需要先准备好可用的远程文件系统或 HDFS否则主进程起不来。hbase.zookeeper.property.dataDir是内嵌 ZooKeeper 的数据目录不设置时默认写在安装目录的 tmp 下系统重启后容易被清理导致第二次启动异常。启动并做初步验证bin/start-hbase.sh jpsstandalone 模式下jps输出中出现 HMaster 进程即为成功伪分布式模式下还会看到 HRegionServer。接着输入bin/hbase shell能出现hbase:001:0提示符就说明元数据、ZooKeeper、Region 服务三层都已就绪。2.2 连接 Shell 并排查三类启动失败Shell 卡住或报错的排查顺序一般有三层。第一层看进程jps没有 HMaster问题一定出在启动脚本或 Java 环境有 HMaster 但 shell 连不上进入第二层。第二层看日志logs 目录下hbase-hbase-master-*.log中应出现Master has started如果日志尾部是 ZooKeeper 连接异常则进入第三层检查hbase.zookeeper.quorum的 host 和端口是否能被本机解析。默认值是 localhost:2181当环境变量里配置了 HOSTNAME 且 hosts 文件没有对应条目时shell 会不断重试而不是直接报错看起来像卡死。实验里最常见的失败原因是 ZooKeeper 数据目录的旧快照与当前版本状态不兼容。把hbase.zookeeper.property.dataDir指向的目录清空后重启通常能恢复。这与 HBase 原理和实践中的结论一致ZooKeeper 充当协调者协调者状态损坏所有客户端操作都会表现为超时或不可用。2.3 实验前先设计命名空间与列族在敲 DDL 之前先把表结构设计讲清楚。HBase 没有严格 schema但列族数量、每个列族的 VERSIONS 参数以及预分区数会直接决定后续读写的形态。本实验创建命名空间 dev 和表 orderscreate_namespace dev create dev:orders, {NAME info, VERSIONS 3}, {NAME detail, VERSIONS 1}第一条命令相当于关系型数据库里的建库dev:orders表示在 dev 命名空间下创建 orders 表。两个列族的差异在 VERSIONSinfo 列族保留 3 个历史版本detail 列族只保留 1 个。如果建表时遗漏 VERSIONSHBase 的默认值是 1也就是说相同行键和列的重复写入只会记下最后一次。这个默认值在 HBase 面试题里经常出现不少初学者误以为 HBase 天然保留所有历史版本。用describe dev:orders查看定义输出中能看到BLOOMFILTER ROW和DATA_BLOCK_ENCODING NONE。BLOOMFILTER 的 ROW 模式会在 Get 请求时先判断行是否可能存在减少无谓的磁盘读取实验阶段不用改它但理解它能解释为什么同一张表在 Get 和 Scan 路径上的性能差异巨大。提示环境搭建时常见做法是把 ZooKeeper 数据目录放到 /tmp 下系统一重启目录就被清理结果 restart 后 HMaster 起不来。提前建好 /data/zookeeper 并固定权限能避免这类最浪费时间的启动问题。3. 用 HBase Shell 覆盖常用操作建表、读写与删除的命令细节3.1 put 写入列族、列限定符与时间戳语义向 orders 表写入一行数据最小命令是put dev:orders, order_001, info:customer, zhangsan put dev:orders, order_001, info:amount, 299.00 put dev:orders, order_001, info:status, pending put dev:orders, order_001, detail:items, 3语法顺序依次是表名、行键、列族:列限定符、值。行键是 order_001列限定符是 customer、amount 这些具体列名。需要注意 HBase 没有内置数值类型299.00在存储层只是字节数组Java 客户端写入时需要自行序列化Shell 中不区分但你在实验报告中对类型做任何断言之前要清楚这一点。时间戳默认取服务端当前毫秒时间也可以写在 put 的第五个参数位置。实验阶段不建议手动指定因为一旦写入的时间戳大于 RegionServer 当前时钟这行数据会成为 Region 内长期排在最前面的数据后续 Scan 无论如何限定时间范围都跳不过去属于很难清理的脏数据。当 put 使用相同行键和相同列限定符时HBase 不会报错而是按版本机制保留新值。因为 info 列族 VERSIONS3连续写三次相同 Cell 再读取能看到三个时间戳不同的版本。这个行为直接对应 HBase 的 MVCC 与版本保留策略每次 put 产生一个新版本但最终只保留最近的 N 个。更贴近业务的写法是把一次订单的所有字段放进同一个 put 中用多行 put 每次只写一个字段会产生大量碎片 Cell在后续 compaction 时增加合并压力。Shell 里的 put 一次只能写一个 CellJava API 则可以在一个 Put 对象上叠加多个 column后面第 4 章会展示这个区别。3.2 get 读取限定列族与版本的三种写法回读刚才写入的数据get dev:orders, order_001 get dev:orders, order_001, {COLUMN info:status} get dev:orders, order_001, {COLUMN info, VERSIONS 3}第一条返回整行所有列族第二条通过 COLUMN 限定到单个单元格第三条在 info 列族下读取最近 3 个版本。实验里最容易混淆的是第三种它返回的是同一单元格的 3 个不同时间戳版本而不是 3 个不同列。版本与列是 HBase 读取路径上的两个不同维度Column 是横向过滤VERSIONS 是纵向深度。get 是单行随机读只会访问对应一个 Region不存在 Scan 的全表扫描开销。get 时如果 RowKey 不完整Shell 不会自动补前缀而是直接报错因为 HBase 的行访问必须精确命中行键。这个设计由 LSM 存储结构决定行键是数据分区和查询定位的唯一依据。3.3 scan 范围扫描STARTROW 与 ENDROW 的边界Scan 是实验里最容易踩坑的操作scan dev:orders, {STARTROW order_001, ENDROW order_010, LIMIT 5}STARTROW 是包含边界ENDROW 是排除边界因此上面命令查询的是 order_001 到 order_009最多返回 5 行。两个常见错误第一只给 LIMIT 不指定 STARTROW命令会从表的第一行开始扫描若 RowKey 不是按查询前缀设计结果基本没有参考价值第二ENDROW 写成 order_010预期却包含 order_010实际结果少一行。Shell 的 scan 默认不显示已删除的 Cell。想查看底层墓碑标记加 RAW 参数scan dev:orders, {RAW true, VERSIONS 10}RAW 模式会把所有旧版本和已删除的 Cell 一并拉出表中删除操作多时返回行数会明显膨胀。这个参数在 HBase 面试题里时常出现很多人只记住“显示原始数据”却没意识到它同时绕过了正常版本过滤排错时容易混淆数据量异常。3.4 alter、disable、dropDDL 操作的正确顺序建表后想调整列族可以直接在线执行alter dev:orders, {NAME detail, VERSIONS 3}这个操作不需要先禁用表HBase 支持动态修改存量表列族属性。真正不能在线执行的是删表drop 前必须先 disabledisable dev:orders drop dev:orders跳过 disable 直接 dropShell 会提示Table dev:orders is enabled, cannot drop。disable 的本质是把表状态置为 DISABLED 并让所有 Region 下线META 元数据中仍保留表定义drop 之后表定义和数据文件一并删除没有回收站。自动化脚本执行 drop 前务必确认状态误操作成本很高。实验中最常用的 DDL 命令总结如下操作命令示例可否在线执行后果建命名空间create_namespace dev是仅影响元数据建表create dev:orders, {NAME info}是表结构写入 META数据文件延迟生成改列族alter dev:orders, {NAME info, VERSIONS 5}是改动立即生效存量数据需等待合并禁用表disable dev:orders是Region 全部下线读写不可用删除整行deleteall dev:orders, order_001是逻辑删除墓碑标记保留至 Major Compaction删除表drop dev:orders否先 disable表定义与数据全部删除表格里最容易被忽视的是 deleteall。它在逻辑层面让整行消失但底层只是在相关 Cell 上写入墓碑标记空间不会立即释放。这解释了为什么 HBase 的删除操作在性能调优中不能视为零成本。4. 用 Java API 复刻常用操作Connection、Put 与 Scan 的正确写法4.1 引入 hbase-client 依赖并管理 Connection 生命周期命令行只是入口生产级操作基本都通过 Java API。以 Maven 工程为例dependency groupIdorg.apache.hbase/groupId artifactIdhbase-client/artifactId version2.4.x/version /dependency版本号不是越新越好而是要和服务端主版本保持一致。客户端与服务端主版本不一致时常见的表象不是编译错误而是 RPC 请求超时且日志里看不到显式协议异常。除 hbase-client 外hadoop-common 的版本也要匹配否则类加载阶段会出现多个冲突的 Configuration 实现。连接对象的获取方式是使用 ConnectionFactoryConfiguration conf HBaseConfiguration.create(); conf.set(hbase.zookeeper.quorum, localhost); conf.set(hbase.zookeeper.property.clientPort, 2181); try (Connection conn ConnectionFactory.createConnection(conf)) { // 读写逻辑 }Configuration 设置的两个 key 与 hbase-site.xml 中同名参数一一对应。hbase.zookeeper.quorum是 ZooKeeper 地址clientPort是端口。Connection 是重量级对象内部维护 RPC 连接池和元数据缓存实验中最常见的反模式是在每个方法里新建 Connection短时间大量连接会让 RegionServer 侧 ZK 会话数暴涨最终触发Too many open files。正确做法是全局复用同一个 ConnectionTable 实例则在需要时获取、用完关闭。4.2 单行读写Get 对象的构造与字节转换拿到 Connection 后单行读取的代码如下Table table conn.getTable(TableName.valueOf(dev:orders)); Get get new Get(Bytes.toBytes(order_001)); get.addColumn(Bytes.toBytes(info), Bytes.toBytes(status)); Result result table.get(get); String status Bytes.toString( result.getValue(Bytes.toBytes(info), Bytes.toBytes(status))); System.out.println(status); table.close();Get通过addColumn限定列族与列限定符显著减少返回数据量。Result.getValue返回 byte[]必须用 Bytes.toString 转成字符串直接输出对象引用只会得到[B1a2b3c4这类地址。Get 还支持版本参数get.setMaxVersions(3)会让单元格的多个历史版本一并返回遍历result.getMap()可以取得时间戳与对应值。写入操作的构造同样直接Put put new Put(Bytes.toBytes(order_002)); put.addColumn(Bytes.toBytes(info), Bytes.toBytes(customer), Bytes.toBytes(lisi)); put.addColumn(Bytes.toBytes(info), Bytes.toBytes(amount), Bytes.toBytes(199.00)); table.put(put);Put 构造函数的第一个参数是 RowKey之后每次 addColumn 增加一个待写入 Cell。当同一个 Put 覆盖多个列族时RegionServer 会按列族分别写不同 StoreFile行内原子性只保证在同一 Region 与同一列族内成立跨列族没有事务保证。这也是 HBase 原理与实践反复强调的边界HBase 提供行级原子性但不提供跨行事务。4.3 批量读取Scan 的边界、Limit 与资源释放批量读取的 Java 写法如下Scan scan new Scan(); scan.withStartRow(Bytes.toBytes(order_001), true); scan.withStopRow(Bytes.toBytes(order_010), false); scan.addFamily(Bytes.toBytes(info)); scan.setLimit(10); ResultScanner scanner table.getScanner(scan); for (Result r : scanner) { String rowKey Bytes.toString(r.getRow()); String customer Bytes.toString(r.getValue( Bytes.toBytes(info), Bytes.toBytes(customer))); System.out.println(rowKey - customer); } scanner.close();withStartRow的第二个参数表示是否包含边界withStopRow同理。setLimit限制的是返回 Cell 的数量不是行数也不是字节数当一行包含多个列时limit 可能把输出截断在前几个 Cell容易造成“少行”的错觉。生产环境中 Scan 不指定 startRow 就会从表第一个 Region 开始扫大表上会拖垮对应 RegionServer。scanner.close()不是可选项。不关闭 ResultScannerRegionServer 端会累积未释放的 Scanner 会话直到触发LeaseException。实验代码跑完即退出时不容易暴露常驻服务中连续扫几次就会出现明显的超时抖动。4.4 批量写入时 BufferedMutator 与 Put 列表的选择批量写入有两种常见实现。第一种是把多个 Put 放入 List 后调用一次 table.put适合一次性的批处理第二种使用 BufferedMutator适合持续写入BufferedMutator mutator conn.getBufferedMutator( TableName.valueOf(dev:orders)); try { Put p1 new Put(Bytes.toBytes(order_011)); p1.addColumn(Bytes.toBytes(info), Bytes.toBytes(status), Bytes.toBytes(shipped)); mutator.mutate(p1); mutator.flush(); } finally { mutator.close(); }BufferedMutator 默认在内部缓冲写入达到阈值或调用 flush 时真正发给 RegionServer。相比逐个 putRPC 次数大幅下降但延迟不再精确可控。若写入过程中出现 Region 分裂或 RegionServer 下线mutator 内部已缓冲的数据可能部分丢失业务侧必须设计重放机制。这也是 HBase 面试题里追问写入可靠性时的落脚点客户端缓冲区和 WAL 刷写时机如何配合。5. 进阶用版本读取与 Region 诊断验证实验效果5.1 用 VERSIONS 参数验证列族版本机制是否生效实验完成后最直接的验证是读回同一 Cell 的多个历史版本。回到 info 列族连续执行三次相同行键和列的 put然后执行get dev:orders, order_001, {COLUMN info:status, VERSIONS 3}如果返回 3 个时间戳不同的值说明 VERSIONS 配置生效若只返回一个用describe检查列族定义是否真的更新。常见原因是在 create 命令里把两个列族的参数写进同一个花括号导致后者覆盖前者。5.2 用 hbase hbck 检查 Region 状态一致性多版本数据写入后Shell 层面看到的是逻辑结果HMaster 上可能残留不一致的 Region 状态。检查元数据一致性hbase hbck -summary输出中显示Status: OK表示元数据一致如果提示存在不一致的 Region说明某些 Region 在 HMaster 注册状态与 RegionServer 实际状态不同。这个命令在验证阶段的更大价值是确认前面所有 disable、drop、deleteall 操作没有在 META 表里留下脏状态。5.3 一个容易被忽略的字节数组比较问题转到 Java API 后最容易踩的坑是 byte[] 值的比较。从 Result 取出的值均为 byte[]用 比较的是引用地址结果永远为 false直接调用 equals 也不会判断内容因为 byte[] 没有重写该方法。正确写法是boolean same Bytes.equals( result.getValue(Bytes.toBytes(info), Bytes.toBytes(status)), Bytes.toBytes(pending));把条件判断改为 Bytes.equals 后再跑一遍 get 逻辑分支判断行为会完全可预期。这个错误在 Shell 里不会出现一旦转到 Java API 几乎每个人都会遇到一次也是 HBase 面试题里考察候选人基本功的高频点。本文还有配套的精品资源点击获取