一篇用完整报错证据链讲透的复盘:HBase(hadoop2 发行版)自带的 27 个 hadoop-2.10.2 jar 不是"污染",删掉后 MR 作业确实跑通了,但数小时后 HMaster 在 flush 时抛 NoSuchMethodError 集体自杀;本文给出日志取证方法、安全回滚步骤和 6 条新增运维坑。
目录
- 一、事故经过:一次"成功"的优化
- 二、根因分析:一个方法签名引发的血案
- 三、为什么当时没发现?三个误判点
- 四、日志取证:怎么证明是 jar 的锅
- 五、安全回滚:从崩溃到恢复的完整步骤
- 六、★正确姿势:服务端保留、客户端绕开
- 七、新增的 6 条运维坑
- 八、写在最后
一、事故经过:一次"成功"的优化
背景:HBase 2.5.16(hadoop2 发行版 )跑在 Hadoop 3.3.6 之上。lib 目录里躺着 27 个 hadoop-*2.10.2*.jar,看着就和 3.3.6 的集群"不搭"。做千万行 ImportTsv 导入时,MR 作业起动即报 ClassCastException ...GetFileInfoRequestProto------于是做出了一个看起来很合理的决定:
bash
# 错误操作(请勿模仿):把 27 个 2.10.2 jar 移出 lib/
mkdir /home/hduser/hbase/lib/unused-hadoop2102
mv /home/hduser/hbase/lib/hadoop-*2.10.2*.jar /home/hduser/hbase/lib/unused-hadoop2102/
然后:重启 HBase,一切正常;MR 作业跑通了;导入完成了。看起来优化成功------直到当天 HMaster 在一次例行 flush 时抛出 NoSuchMethodError,直接 ABORTING,集群不可用。
关键报错(铁证日志):
ERROR [master/store-Flusher] regionserver.HStore: Failed flushing store ...
DroppedSnapshotException: ...
Caused by: java.lang.NoSuchMethodError:
org.apache.hadoop.hdfs.DFSInputStream.getReadStatistics()
Lorg/apache/hadoop/hdfs/DFSInputStream$ReadStatistics;
... ABORTING master hadoop1,16000
... KeeperErrorCode = NoNode for /hbase/master
二、根因分析:一个方法签名引发的血案
HBase 2.5.16 的 hadoop2 发行版是针对 Hadoop 2.10.2 编译的 。HdfsDataInputStream.getReadStatistics() 这个方法:
| 环境下 | 返回类型 |
|---|---|
| Hadoop 2.10.2(HBase 期望) | DFSInputStream$ReadStatistics(内部类路径) |
| Hadoop 3.3.6(实际加载) | 独立的 ReadStatistics 类 |
字节码里方法签名是硬编码的。移走 2.10.2 jar 后,HBase 加载到 3.3.6 的实现------方法名相同、签名不同,JVM 直接抛 NoSuchMethodError。这条代码路径在 WAL/flush 时才走到 ,所以启动、读写、MR 全都正常,只在 flush 触发时爆炸,且一旦爆炸 HMaster 主动 ABORTING 自杀,ZK 上的 master 节点随即消失。
一句话结论:这 27 个 jar 是 hadoop2 发行版的配套依赖,不是"污染";服务端必须和它一起用,MR 侧只能绕开它。
三、为什么当时没发现?三个误判点
- 验证只盯了 MR 侧:改动的动机是修 MR 报错,验证也只验证了 MR 跑通,没盯服务端日志------但这次改动同时影响"服务端 + MR 客户端"两个消费者。
- 故障是延迟暴露的:flush 不是每秒发生,从重启到爆炸隔了数小时到数十分钟不等。"改完重启、跑了一圈没事"≠安全。
- 被"日志干净"麻痹:翻 RegionServer 日志没看到报错------但 flush 路径在 Master 侧,而且没触发 flush 时本来就不会有报错。
方法论:一次改动影响多个消费者时,必须两端同时验证,且覆盖延迟触发路径(flush、compaction、重启)。
四、日志取证:怎么证明是 jar 的锅
4.1 锚定行首时间戳过滤(别用 awk 范围比较)
异常堆栈的续行没有时间戳 ,awk '$0 >= "时间戳"' 会把历史堆栈的续行也收进来,得出完全相反的结论。正确姿势:
bash
grep -E '^2026-10-10T(10:5[5-9]|11):' \
/home/hduser/hbase/logs/hbase-hduser-master-hadoop1.log \
| grep -cE 'NoSuchMethodError|ABORTING'
# 只统计以日期开头的行,计数归零才算真没有
4.2 确认服务端实际加载的 jar
hbase classpath 不是服务端判据! 它是客户端视角(走 shaded-client,不含 lib/*.jar)。正确判据是看 HMaster 进程实际打开了什么:
bash
lsof -p <HMaster的PID> | grep hadoop-common
# 应该看到 hadoop-common-2.10.2.jar ------ 有它,flush 才不会炸
五、安全回滚:从崩溃到恢复的完整步骤
bash
# 1. jar 放回(27 个,放完核对数量)
mv /home/hduser/hbase/lib/unused-hadoop2102/*.jar /home/hduser/hbase/lib/
ls /home/hduser/hbase/lib/hadoop-*2.10.2*.jar | wc -l # 应输出 27
# 2. 停 HBase(注意:stop-hbase.sh 先停 ZK,Master 可能卡在重连循环,
# 确认数据都在 HDFS 后可以 kill -9 兜底)
su - hduser
stop-hbase.sh
# 3. 从节点 RegionServer 可能残留(no pid file 被跳过),逐台补停
hbase-daemon.sh stop regionserver
# 4. 重启并盯 flush
start-hbase.sh
grep -c "Master has completed initialization" \
/home/hduser/hbase/logs/hbase-hduser-master-hadoop1.log # =1 即就绪
回滚后验证(三项全过) :手动触发 flush 恢复(日志出现 Finished flush of dataSize ~143.18 KB,正是先前触发自杀的那一步);重启后 NoSuchMethodError 计数为 0;Web UI 显示 1 active master + 2 servers,iot 表 1000 万行完好(HDFS 1.7G)。数据零损失。
六、★正确姿势:服务端保留、客户端绕开
| 问题 | 结论 |
|---|---|
| 能否移走 27 个 jar? | 不能,服务端必须保留 |
| MR 侧 ClassCastException 怎么解? | 手工拼 classpath 排除 2.10.2 直接 java 启动工具类,零文件改动 |
| 长期正解? | 换官方 hbase-2.5.16-hadoop3-bin 发行版(需停机窗口重建/迁移数据) |
七、新增的 6 条运维坑
- stop-hbase.sh 先停 ZK ,Master 卡在 ZK 重连循环 → 先
hbase-daemon.sh stop master,或确认数据在 HDFS 后 kill -9。 pgrep -f HMaster会匹配到命令本身 → 判存活用端口(16000/16020/2181)或ps -eo args | grep '[H]Master'。- stop 后 RegionServer 残留(no pid file 被脚本跳过)→ 逐台补停。
- 日志过滤误报 → 锚定行首时间戳(见 4.1)。
- 共用依赖改动只验一端 → 双端验证 + 延迟触发路径。
- Web UI 看不到数据(16010/16030/9870 都一样)→ 查数据走 Phoenix SQL 或图形客户端(系列五、六)。
八、写在最后
这次事故最值得记住的不是那个 NoSuchMethodError,而是**"验证通过"的假象是怎么造成的**:验证范围 ≠ 影响范围,观察窗口 < 触发延迟。以后再想清理任何"看着不顺眼"的依赖 jar 前,先问一句:这个 jar 的字节码,到底在服务端哪些路径上被调用?
下一篇预告:《系列五:Phoenix 5.3.2 集成------让 HBase 说 SQL,千万行 COUNT 从 88 秒到 25 秒》。hbase shell 的 scan 终究难写,Phoenix 用协处理器把查询下推到数据所在节点,附 SALT_BUCKETS 与 EXPLAIN 实战。