news 2026/10/21 6:31:56

HBase部署实战:从零安装到RowKey设计与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HBase部署实战:从零安装到RowKey设计与避坑指南

先交代一下背景。我之前维护过一个业务系统,单表数据量到千万级之后,MySQL 的慢查询基本就按不住了,几张大表的关联统计跑一次要几十秒,业务方抱怨不断。当时也考虑过分库分表,但团队评估下来改造链路太长,后来选了 HBase 作为历史明细数据的存储层,把线上的时序数据和订单流水逐步迁过去,问题才算真正解决。HBase 是一个构建在 HDFS 之上的分布式、面向列的 NoSQL 数据库,擅长海量数据的写入和按 RowKey 的毫秒级随机读,适合几亿甚至几十亿级别的结构化、半结构化数据场景。

这篇博文把我从零开始安装、配置、使用 HBase 的完整过程整理出来,包括版本选型、依赖环境搭建、核心配置逐项解释、Shell 和 Java API 的基本操作,以及我在部署和使用过程中真实踩过、排查过、修复过的几个坑。无论你是第一次接触 HBase 的新手,还是想搭一套测试环境做技术验证的开发同学,这篇文章应该能帮你少走不少弯路。

1. 先搞清楚 HBase 适合干什么,再决定要不要装

1.1 一张表把 HBase 和 MySQL 的定位差异说清楚

很多人第一次听到 HBase,下意识会把它当成“分布式的 MySQL”。这个理解不能说全错,但会误导你朝错误的方向使用它。它确实支持类似主键的 RowKey 随机读写,也支持多版本数据,但它没有真正意义上的跨行事务,也不支持 SQL 里的各种 JOIN 和复杂索引。它与 MySQL 在使用定位上有本质区别,我用一张表说明:

对比项MySQLHBase
数据规模单表千万级后开始吃力百亿级也能扛,取决于集群资源
存储方式行式存储,所有列一起落盘面向列族存储,空列不占空间
读写模式强事务,适合频繁更新、删改适合海量写入,更新本质是追加新版本
查询方式SQL + 索引,支持 JOIN按 RowKey get、按范围 Scan,不支持 JOIN
扩展方式分库分表,业务侵入大横向扩展 RegionServer,自动拆分 Region
事务能力ACID 完整事务仅单行级原子性,跨行需要额外设计
数据排序按主键聚簇索引,或二级索引按 RowKey 字典序自动排序
适用场景在线业务、交易系统、台账日志、流水、历史明细、时序数据

我自己实际使用下来的感受是:MySQL 负责“现在”的数据,HBase 负责“历史”的数据。比如订单表,近三个月的订单留在 MySQL 做实时业务,三个月前的订单归档到 HBase 做查询和统计分析,这样两边压力都小,各用所长。

1.2 存储层面的四个核心概念,懂了它你才看得懂日志

安装 HBase 之前,建议至少把下面四个概念理解透,因为后面所有的配置、日志、报错都和它们有关。

  • Region:HBase 数据水平分片的基本单位。一张表被按 RowKey 区间拆成多个 Region,每个 Region 交给一个 RegionServer 管理。数据量大了之后 Region 还会继续分裂,从 1 个变 2 个,2 个变 4 个。
  • WAL(Write-Ahead Log):写数据时的预写日志。数据到达 RegionServer,会先写 WAL 再写 MemStore,这样即使节点宕机,也能通过 WAL 恢复未落盘的数据。它对应的就是 hbase 的 wal 目录。
  • MemStore:每个 Region 内部的内存缓冲区,默认 128MB。写入的数据先在这里积攒,达到阈值后一次性刷写成磁盘上的 HFile 文件。它相当于一个“攒批”机制,把大量随机写变成顺序写。
  • HFile:数据最终落地的物理文件,存储在 HDFS 上。HFile 内部的数据是按 RowKey 有序排列的,底层还带布隆过滤器和时间范围索引,这是它能做快速查找的基础。

一句话总结它们的链路:客户端写数据 -> WAL -> MemStore -> HFile -> 定期 Compaction 合并小文件成大数据文件。整个读写链路里的很多问题排查,最后都会落到这四个概念上。

2. 安装前的版本选择和依赖搭建:这一关最容易半途而废

2.1 我实测过的一套稳定版本组合

HBase 安装最让人头疼的不是安装本身,而是版本兼容。JDK、Hadoop、Zookeeper、HBase 四个组件任何两个版本不匹配,都可能出现各种诡异报错。网上很多教程直接用“当前最新版”,这在实际操作中是大忌。我这边实际跑通、验证过的组合是:

  • JDK 1.8(1.8.0_202 及以上小版本都可以,不要用 JDK 11 去跑 HBase 2.x,兼容性会有额外问题)
  • Hadoop 3.3.4
  • Zookeeper 3.6.3(独立部署,三个节点)
  • HBase 2.4.17

HBase 目前主流的稳定分支是 2.x。HBase 3.x 虽然已经发布,但周边生态、客户端兼容性还不如 2.x 成熟,多数公司生产环境还在 2.4 系列。选 JDK 8 也是因为 HBase 2.x 对 JDK 8 的支持最成熟,CDH 等大数据套件的适配基准也基本是 JDK 8。如果你只是为了本地学习,JDK 8 + Hadoop 3.3.x + HBase 2.4.x 这一套组合非常稳,别自己凭空混搭版本。

需要特别提醒:安装前一定要去官网查 HBase 官方兼容矩阵,确认 Hadoop 版本匹配。否则后期会在 RPC 层报一堆奇怪的编解码错误,排查起来非常痛苦。

2.2 三台机器的基础环境操作清单

我这里用三台服务器做演示,主机名规划为 node01、node02、node03。node01 跑 HMaster 和 RegionServer,node02、node03 跑 RegionServer。如果是单机学习,你可以把下面的 hostname 都替换成自己的服务器 IP,也能跑起来。

在装 Hadoop 和 HBase 之前,有几个基础环境操作必须先做好,我自己在这上面踩过多次坑,这里原样列出来:

  1. 修改/etc/hosts,把三台机器的 IP 和主机名对应关系写上,并且把注释掉的 127.0.0.1 对应行处理好。否则节点之间通过主机名互相访问时会解析到 no such host。
  2. 配置 SSH 免密登录,至少保证 node01 可以免密登录到所有节点。因为 HBase 启动脚本会通过 SSH 去各节点执行远程启动命令,没有免密就会卡在输入密码那里。
  3. 三台机器的时间必须同步,时间差超过 30 秒以上,HBase 的 RegionServer 启动会有各种超时问题。我是直接配置了统一的 NTP 时间同步服务,简单场景你手动 date 命令校准也行。
  4. 关闭防火墙或放行端口。最简单的方式是先关闭防火墙再说,HBase 涉及 16010、16030、2181、8020 等一系列端口,生产环境再按需放行。

这些操作做完了,再去装 Hadoop。如果你只是测试 HBase,Hadoop 的最低要求是 HDFS 能启动。这里我直接用了 Hadoop 自带的分布式文件系统作为底层存储。

2.3 Zookeeper:用内置的还是单独搭

HBase 强依赖 Zookeeper 做分布式协调,比如 HMaster 选举、RegionServer 的注册与心跳、系统表 meta 的寻址。Zookeeper 挂了,整个 HBase 集群对外基本就不可用了。

HBase 二进制包自带了 Zookeeper,用最简单的方式启动时可以直接使用内置 ZK,也就是把hbase-env.sh里的HBASE_MANAGES_ZK保持为 true。这适合单机测试和快速体验。

但生产环境我强烈建议用独立部署的 Zookeeper 集群,原因有两个:一是 HBase 自带 ZK 和 HBase 进程捆绑,如果 RegionServer 负载很高,会拖累 ZK 心跳;二是 HBase 升级或重启时,如果连带 ZK 一起重启,会对整个集群稳定性造成额外影响。独立部署 ZK 之后,只需要在配置里把 quorum 指向它,然后把HBASE_MANAGES_ZK=false就行。

3. 下载配置启动:完整走一遍 HBase 部署流程

3.1 下载解压和目录规划

版本确定之后,下载就简单了。我习惯把大数据组件统一放在/opt/bigdata目录下,这样环境变量配置和维护都集中。下载命令以 HBase 2.4.17 为例:

cd /opt/bigdata wget https://archive.apache.org/dist/hbase/2.4.17/hbase-2.4.17-bin.tar.gz tar -zxvf hbase-2.4.17-bin.tar.gz mv hbase-2.4.17 hbase

解压后目录里有几个关键子目录和文件,新手要记一下位置:

  • conf/:所有配置文件所在目录
  • logs/:日志目录,排查问题第一个要找的地方就是这里
  • bin/:启动、停止脚本目录
  • lib/:HBase 依赖的 jar 包目录

3.2 hbase-site.xml 和 hbase-env.sh 配置逐项说明

所有核心配置都在conf/hbase-site.xml里。我先给出一个三节点生产环境的完整配置示例,然后逐项解释为什么这么配:

<configuration> <property> <name>hbase.rootdir</name> <value>hdfs://node01:8020/hbase</value> </property> <property> <name>hbase.cluster.distributed</name> <value>true</value> </property> <property> <name>hbase.zookeeper.quorum</name> <value>node01,node02,node03</value> </property> <property> <name>hbase.zookeeper.property.clientPort</name> <value>2181</value> </property> <property> <name>hbase.regionserver.handler.count</name> <value>50</value> </property> <property> <name>hbase.master.info.port</name> <value>16010</value> </property> </configuration>
  • hbase.rootdir:HBase 数据在 HDFS 上的根目录,必须是 HDFS 路径。注意目录要提前存在,而且权限要够,否则 Master 启动时会报错无法创建目录。
  • hbase.cluster.distributed设为true,就是告诉 HBase 以分布式模式运行,走 HDFS 和独立 ZK。如果设成 false,它会以 standalone 模式运行,数据落在本地文件系统,不是我们想要的。
  • hbase.zookeeper.quorum:Zookeeper 集群主机列表。

这里不需要填端口,只需要主机名,客户端连接时默认读 2181。

  • hbase.zookeeper.property.clientPort:指定 ZK 客户端端口,默认就是 2181,显式写出来方便自己以后检查。
  • hbase.regionserver.handler.count:每个 RegionServer 可以同时处理的 RPC 请求数量。默认值偏保守,读写并发比较高时可以加大到 50 到 100。但不是越大越好,太大反而堆积线程,要根据机器核数来看。
  • hbase.master.info.port:HBase Master 的 Web UI 端口。HBase 2.x 默认是 16010,通过浏览器访问http://node01:16010可以看到集群状态。

另外在conf/hbase-env.sh里必须设置两样东西:

export JAVA_HOME=/usr/local/java/jdk1.8.0_202 export HBASE_MANAGES_ZK=false

JAVA_HOME一定要显式写绝对路径。HBase 的启动脚本虽然会自动检测,但检测逻辑经常会在多版本 JDK 的环境下找错。HBASE_MANAGES_ZK=false表示使用独立 ZK,如果你用了内置 ZK,就不要这一行配置。

HBase 默认分配的堆内存偏保守,对于负责存储计算的进程来说不够。如果你的服务器内存足够(比如 32GB),建议把 Master 和 RegionServer 的堆内存调大:

export HBASE_HEAPSIZE=8G

这个方法会同时作用在 Master 和 RegionServer 上。对于大集群更精细的调法是把它们的堆内存拆开设置,测试环境可以先用这个统一配置。

3.3 启动集群并验证结果

所有配置完成后,启动顺序有讲究:先启动 HDFS,再启动 Zookeeper,最后启动 HBase。顺序颠倒容易出现 HBase 找不到 HDFS 元数据的情况。

按顺序执行:

# 1. 在 node01 上启动 HDFS start-dfs.sh # 2. 在三台节点上分别启动 Zookeeper(或使用 zkServer.sh start) zkServer.sh start # 3. 在 node01 上启动 HBase start-hbase.sh

启动完成后,验证集群是否正常的标准动作有三个:

# 1. 查看 Java 进程,确认 HMaster 和 HRegionServer 都已经启动 jps

在 node01 上应该看到 HMaster 和 HRegionServer 进程,在 node02、node03 上应该看到 HRegionServer 进程。如果只看到 HMaster 没有 HRegionServer,大概率配置有问题,常见原因:hbase.rootdir权限不足或 hosts 解析异常。

# 2. 进入 HBase Shell,检查集群状态 hbase shell status

正常情况下会显示类似如下的输出,说明三个 RegionServer 都正常:

1 active master, 0 backup masters, 3 servers, 0 dead, 33.0000 average load
# 3. 浏览器访问 http://node01:16010,能看到集群整体状态,以及每个 RegionServer 的 Region 数量、请求量、堆内存使用情况。

到这里,HBase 就装好了。看起来步骤不多,但这里每一步我几乎都踩过坑,后面单独拿出来讲。

4. Shell 命令和 Java API 是两套基本功,都得会

4.1 Shell 里最常用的建表、读写、删除命令

HBase Shell 是排查问题、快速操作数据最直接的工具。下面这些命令我几乎每天都在用,按操作场景分类给出来:

# 创建一张表,名为 student,包含两个列族 info 和 score create 'student', 'info', 'score' # 查看表结构 describe 'student' # 写入一条数据,RowKey 为 1001,向 info 列族写入 name 列的值 put 'student', '1001', 'info:name', 'zhangsan' # 读取指定 RowKey 的全部数据 get 'student', '1001' # 读取指定 RowKey 的单个单元格数据 get 'student', '1001', {COLUMN => 'info:name'} # 读取指定 RowKey 的多个版本(默认保留1个版本,修改配置后可保留多个) get 'student', '1001', {COLUMN => 'score:math', VERSIONS => 3} # 全表扫描(生产环境慎用,数据量大时很危险) scan 'student' # 带限制条件的扫描,只取前5条 scan 'student', {LIMIT => 5} # 按 RowKey 范围扫描 scan 'student', {STARTROW => '1001', STOPROW => '1005'} # 删除某个单元格的数据 delete 'student', '1001', 'info:name' # 删除整行的数据 deleteall 'student', '1001' # 清空表但不删除表结构 truncate 'student' # 禁用表和重新启用表(修改表结构前必须先 disable) disable 'student' enable 'student' # 删除表(必须先 disable) drop 'student'

刚上手时最容易犯的错误是直接对一张正在被读写的表执行drop。正确顺序一定是先disable,再drop。另外,scan全表在生产上要极其克制,因为它会扫描所有 Region,压力非常大。最常见的做法是给scan加STARTROW、STOPROW和LIMIT条件,把扫描范围框住。

4.2 Java API 的正确打开方式

程序访问 HBase 主要通过 Java API。下面这段代码是我实际项目里抽出来的模板,可以做最基本的建表、写入、查询和扫描:

import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.*; import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.util.Bytes; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class HBaseDemo { private static Connection connection; public static void main(String[] args) throws IOException { Configuration conf = HBaseConfiguration.create(); conf.set("hbase.zookeeper.quorum", "node01,node02,node03"); conf.set("hbase.zookeeper.property.clientPort", "2181"); // Connection 是重量级对象,一个进程只创建一个,全程复用 connection = ConnectionFactory.createConnection(conf); createTable("student", "info", "score"); putData("student", "1001", "info", "name", "zhangsan"); getData("student", "1001"); connection.close(); } public static void createTable(String tableName, String... columnFamilies) throws IOException { Admin admin = connection.getAdmin(); TableName tn = TableName.valueOf(tableName); if (admin.tableExists(tn)) { System.out.println("Table already exists"); return; } TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tn); for (String cf : columnFamilies) { builder.setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf)).build()); } admin.createTable(builder.build()); admin.close(); } public static void putData(String tableName, String rowKey, String cf, String column, String value) throws IOException { Table table = connection.getTable(TableName.valueOf(tableName)); Put put = new Put(Bytes.toBytes(rowKey)); put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(column), Bytes.toBytes(value)); table.put(put); table.close(); } public static void getData(String tableName, String rowKey) throws IOException { Table table = connection.getTable(TableName.valueOf(tableName)); Get get = new Get(Bytes.toBytes(rowKey)); Result result = table.get(get); Cell[] cells = result.rawCells(); for (Cell cell : cells) { System.out.println( "RowKey: " + Bytes.toString(CellUtil.cloneRow(cell)) + ", Column: " + Bytes.toString(CellUtil.cloneQualifier(cell)) + ", Value: " + Bytes.toString(CellUtil.cloneValue(cell)) ); } table.close(); } }

这段代码里有几个细节挺关键的。ConnectionFactory.createConnection()创建的是重量级对象,内部包含 RPC 连接池、Zookeeper 连接等资源,一个进程只需要一个,多个线程共享即可。千万不要在每次操作时 new 一个 Connection,否则连接数会爆掉,性能差到让你怀疑人生。Table对象相对轻量,但也不要极频繁地创建和销毁,一般每个线程保持一个。

4.3 批量写入和 Scan 的缓存设置

批量写入场景下,不要一条一条 put,要么用table.put(List<Put>),要么用BufferedMutator。我实测下来的经验是,批量 put 的性能相比单条 put 有几十倍的提升,因为减少了 RPC 次数。

BufferedMutator mutator = connection.getBufferedMutator(TableName.valueOf("student")); List<Put> puts = new ArrayList<>(); for (int i = 0; i < 10000; i++) { Put put = new Put(Bytes.toBytes("user_" + i)); put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes("user_" + i)); puts.add(put); if (puts.size() >= 1000) { mutator.mutate(puts); puts.clear(); } } if (puts.size() > 0) { mutator.mutate(puts); } mutator.flush(); mutator.close();

BufferedMutator会在客户端本地攒一批数据再提交,类似一个缓冲区。默认缓存大小可以配置,写入量大时建议调大到 16MB 以上。

Scan 方面,ResultScanner是分页从服务器端拉取数据的。默认下一批数据一次最多返回 100 行,如果是大批量导出场景,这个值太小会导致 RPC 次数过多。可以设置scan.setCaching(1000)把一次取回的行数调大,但也要注意 RegionServer 端的内存消耗,取值不要超过 5000,不然很容易把 RegionServer 的堆内存打满。

5. RowKey、预分区、列族数量:这三个设计点决定你后面用着爽不爽

5.1 RowKey 三条原则,反着做的都吃过亏

HBase 的查询效率几乎完全取决于 RowKey 的设计,因为只有 RowKey 才能走索引。它的排序规则是按字典序从小到大排列,这一点既是优点也是陷阱。我在日常运维中总结出三条必须遵守的原则:

第一,散列性。RowKey 不能是单调递增的连续值。比如用自增 ID、时间戳直接当 RowKey,物理上新数据永远写入最后一个 Region,热点就集中在一个 RegionServer 上,其他节点闲着。解决方案可以加盐(salt),比如user_id_hash % 100拼在 RowKey 前面;或者对原始主键做反向、哈希处理后作为前缀。

第二,长度越短越好。RowKey 是每条数据的索引,也是数据的一部分。HFile 无论是内存还是磁盘里,都会对 RowKey 做大量存储和比较。一个 100 字节的 RowKey 和一个 20 字节的 RowKey,在几十亿数据量下消耗的存储和 IO 差距是巨大的。建议 RowKey 设计在 10 到 50 字节以内,不要在 RowKey 里塞过长的业务描述字段。

第三,唯一性。RowKey 在同一张表里必须唯一,后者写入会覆盖前者数据。如果你需要同一个主键下保留多条历史版本,可以把时间戳、版本号拼进 RowKey,或者依赖 HBase 的 VERSIONS 机制,不过版本数一般不建议开太大。

5.2 默认建表只开一个 Region,写入热点几乎是必然的

新建一张表默认只有一个 Region,所有写入压力都会集中在这一个 Region 的 RegionServer 上。数据量变大后 Region 会分裂,但分裂过程中会有短暂的性能波动,而且分裂出的 Region 边界往往不均匀。生产环境的正确做法是预分区,在建表时直接指定 RowKey 的切分节点。

# 在 Shell 中按多个分割点建表,表会自动分成多个 Region create 'student', 'info', 'score', SPLITS => ['100000', '200000', '300000', '400000']

这样建出来的表会按 RowKey 100000、200000、300000、400000 自动划分成 5 个 Region,数据写入时就能分散到多个 RegionServer 上。

如果你不清楚具体切分点,也可以用十六进制字符串切分算法,它会均匀地把整个 RowKey 空间切开:

# 创建 15 个 Region,适合 RowKey 为十六进制字符串的场景 create 'student', 'info', 'score', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}

预分区到底要建多少 Region,没有一个固定值。可以根据集群节点数和预计数据量估一个范围,每个 Region 的理想数据量在 10GB 到 20GB 左右,这样可以减少 Region 后续分裂带来的开销。

5.3 列族和布隆过滤器怎么选

列族的数量直接影响读写性能。很多人习惯把一个业务模块一个列族,一张表建了三四个列族,这在 HBase 里是低效的。原因是每个列族的数据独立落在不同 HFile 里,刷写 MemStore 时如果一个列族数据量小、另一个数据量大,就会出现小文件较多的场景,最终 Compaction 和 Region 分裂时的 IO 都被放大。

实际经验是:一张表一个列族是最理想的状态,最多不要超过两到三个。列族的作用更多是区分不同生命周期或访问频率的数据,比如一个放详情数据,一个放统计汇总数据,它们的 TTL、压缩策略往往不同。

布隆过滤器则是偏读优化的重要手段。默认情况下,读取时会扫描这个 Region 涉及的所有 HFile 文件。开启布隆过滤器之后,它在一开始就能快速判断“这个 RowKey 不存在于该文件”,从而跳过大量无效文件的打开和数据块加载。对于随机读场景,开布隆过滤器的收益非常明显。

# 建表时指定布隆过滤器类型 create 'student', {NAME => 'info', BLOOMFILTER => 'ROW'}

如果你的查询模式是针对整个 RowKey,用ROW;如果经常查询某个 RowKey 下的特定列,用ROWCOL。用ROWCOL的开销会比ROW大一些,但过滤粒度更细。写入多、读取少的场景,你可以不开布隆过滤器,省一点写入时的内存和 CPU 开销。

6. 部署和日常使用中我踩过的三个坑

6.1 坑一:RegionServer 反复退出,日志全是连接超时

第一次部署时,我在 node01 执行完start-hbase.sh后,jps 明明看到了 HRegionServer 进程,但是过了几分钟再回头看,进程不见了。去看logs/hbase-hbase-regionserver-node01.log,里面大量出现TimeoutException和Connection refused,看起来像是 RegionServer 连不上其他节点。

我的排查链路是这样走的:

  1. 先检查 Zookeeper 是否正常。用echo srvr | nc node01 2181确认 ZK 能响应,没问题。
  2. 再检查 HDFS。执行hdfs dfsadmin -report,发现 DataNode 节点只有 node01 在线,node02、node03 的 DataNode 根本没通过检查。
  3. 去 node02 看 DataNode 日志,发现它一直报java.net.UnknownHostException: node02。排查 hosts 文件才发现,node02 的/etc/hosts里同时保留了127.0.0.1 node02和实际 IP 对应的 node02,导致它把自己解析到了回环地址。
  4. 把每个节点的 hosts 文件统一改成“实际 IP + 主机名”,去掉回环映射后,DataNode 正常注册,HBase 也随之稳定。

这个坑的本质不是 HBase 配置问题,而是 Hadoop 层没有跑通。遇到 RegionServer 反复退出,先检查底层 HDFS 节点健康状态,再看 ZK 和 hosts,不要一上来就去调 HBase 参数。

6.2 坑二:单条查询慢到离谱,从头排查一遍

另一个印象深刻的坑是:线上表用 get 按 RowKey 查询时,明明索引路径是准的,但查询响应平均 300 到 500 毫秒,个别请求甚至到秒级。这个表现完全不像分布式的随机读。

我的排查链路是这样的:

  1. 先看 HBase UI 上每个 RegionServer 的读请求量和延迟,发现所有请求几乎都打在一个 RegionServer 上。再往细看,那台机器的 Region 数量很少,但每个 Region 的 StoreFile 数量很多。
  2. 用hbase hfile -m -f hdfs://node01:8020/hbase/data/default/student/xxx/HFile 文件路径看 HFile 的索引和布隆过滤器情况,确认表在创建时没有开启布隆过滤器。
  3. 再去看表结构,确认表创建时才 2 个 Region(Share 大量分裂导致的),导致 RowKey 前缀的写入全部落在同一个 Region 区间。
  4. 最终处理方案:重设表结构,按业务 RowKey 散列后缀做预分区,开启BLOOMFILTER => 'ROW',然后对旧表执行一次 major_compact,把大量小的 StoreFile 合并成少量大文件。处理完再测,单条 get 拉回 1 到 5 毫秒区间。

这里是两个问题的叠加:RowKey 热点导致数据都在一个 Region,加上没有布隆过滤器,读取时要扫描的全文件就很多。如果当初建表就做了预分区并开启布隆过滤器,后面基本不会走到这一步。

6.3 坑三:数据写成功后立刻查不到

还有一次排查了很久的诡异问题:程序里 Put 返回成功,但立刻用 get 查同一 RowKey 却查不到。当时第一反应是数据一致性出问题了,一度怀疑 MemStore 还没刷盘导致读取链路有 bug。实际上问题出在客户端配置。

我的排查链路是这样的:

  1. 先在 HBase Shell 里手动 put 同一条数据,然后立刻 get,能查到。说明服务端没问题。
  2. 再去检查 Java 客户端代码,发现它连接的 ZK quorum 指向了另一个测试集群,虽然表名相同,但写入的是另一张表。
  3. 确认客户端hbase.zookeeper.quorum配置和服务端集群一致后,问题消失。

这类“数据丢失”问题其实大概率是客户端连错了集群或连错了表。排查顺序建议:Shell 验证服务端 -> 检查客户端配置 -> 检查表名和 RowKey 是否写错。不要一上来就怀疑 HBase 的写入链路,HBase 的 Put 返回成功代表 WAL 和 MemStore 都处理完了,在单行 PUT 场景下不会出现丢数据的现象。

最后分享一个小经验:在学习环境里跑 HBase 伪分布式时,HDFS 默认副本数是 3,三台虚拟机加起来可能磁盘没多大就满了。做个单机测试环境,可以把 HDFS 副本数改成 1,在hdfs-site.xml里设置dfs.replication=1,磁盘占用会少很多,性能反而更好。等到真正生产部署时,再按数据可靠性要求把副本数设定为 3。HBase 安装并不难,难点全在版本匹配、环境准备和设计习惯上。希望这篇记录能帮你把第一张表顺利建起来,少掉几次头发。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 6:31:56

Groq LPU推理架构解析:硬件级LLM优化与部署实践

1. 项目概述&#xff1a;这不是“又一个免费模型”&#xff0c;而是推理效率的临界点突破最近在几个技术群和开发者论坛里&#xff0c;几乎每天都有人贴出一张截图&#xff1a;Groq官网上赫然挂着两个新开放的开源大模型——Llama-3-70B-Instruct 和 Llama-3-8B-Instruct&#…

作者头像 李华
网站建设 2026/10/21 6:31:20

书匠策AI实测:从选题大纲到降重润色的课程论文全流程指南

课程论文最折磨人的阶段&#xff0c;往往不是坐在电脑前敲键盘的那两天&#xff0c;而是从选题到定稿之间那段“脑子里有想法但不知道从哪下手”的时间。我也是被逼到周五晚十一点才动笔的那类学生&#xff0c;后来花了一周时间把书匠策AI跑完了一整套课程论文流程&#xff0c;…

作者头像 李华
网站建设 2026/10/21 6:31:18

计算机视觉入门到落地:图像分类、目标检测与分割的工程实践复盘

最早接触计算机视觉那会儿&#xff0c;我和大多数人一样&#xff0c;先把经典机器学习课程刷了一遍&#xff0c;又照着"从入门到精通"的思路囤了一堆书。结果发现自己能手推公式&#xff0c;却连一张图片都处理不明白。真正让我从混沌走向清晰的&#xff0c;不是某一…

作者头像 李华
网站建设 2026/10/21 6:31:07

异构算力与国产数据库架构选型及调优实践

做信创架构这几年&#xff0c;前端业务接异构算力、后端数据落在国产数据库上的方案&#xff0c;我反复折腾了很多轮。异构算力加国产数据库&#xff0c;听起来像是两个独立问题&#xff0c;实际落到架构设计里会缠得很紧&#xff1a;一边要处理不同指令集、不同加速单元之间的…

作者头像 李华
网站建设 2026/10/21 6:30:38

Kafka Exactly Once实现原理:幂等生产者与事务机制深度解析

1. 消息投递语义的演进与Exactly Once的定位消息中间件领域里&#xff0c;投递语义从来都是绕不开的核心话题。做过几年后端开发的人大概都经历过这样的场景&#xff1a;业务方跑过来问“这条订单消息怎么处理了两次”&#xff0c;或者“为什么库存扣减少了一次”。这类问题的根…

作者头像 李华
网站建设 2026/10/21 6:30:31

旅游景点数据分析实战:从异构数据清洗到行为归因建模

简介&#xff1a;本资源是一份面向数据分析初学者与旅游行业从业者的实战型学习包&#xff0c;聚焦国庆旅游场景下的景点数据挖掘与可视化决策支持。通过真实去哪儿网平台的国庆景点数据&#xff08;Excel源文件&#xff09;与Python自动化分析脚本&#xff08;qunar_analyst.p…

作者头像 李华