☰
HDFS基础操作实战:块、副本、元数据与上传排错
2026/10/27 14:09:25 网站建设 项目流程

把 HDFS 当成一个能装几百 TB 的网络硬盘,是很多人第一次接触它时最容易犯的错。我刚开始做数据平台那会儿,也是这么理解的,结果第一次执行hdfs dfs -put把一份 4 GB 的日志推上去,回头用-du -s -h一看,占了 12 GB,当场懵了。后来才明白,问题不在命令写错,而在于我没有先搞清楚 HDFS 基础操作背后那套“块 + 副本 + 元数据”的模型。HDFS 基础操作这件事,看起来就是几十条 shell 命令,实际上它是你后面所有离线计算、数仓分层、数据治理的地基。命令记不住可以查,模型没建立起来,你会在副本数、块大小、权限、回收站这些地方反复吃亏。这篇内容适合三类人:刚上手大数据平台、需要把本地数据搬进集群的开发;需要排查“为什么文件传不上去、为什么删了还占空间”的运维;以及准备面试、想把 HDFS 读写链路讲清楚的同学。下面我按“思路—命令—链路—参数—报错—隐藏开关”的顺序,把这套基础操作拆开讲。

1. 动手之前先把 HDFS 的操作模型捋顺

1.1 为什么它的命令长得像 Linux,用起来又完全不像

HDFS 的命令行设计刻意模仿了 Linux 文件系统,ls、mkdir、rm、mv、cp、chmod、chown这些名字你全都认识,所以很多人第一反应是“这不就是个 Linux 吗”。但你真按 Linux 的习惯去用,很快就会碰壁。最典型的三条差异:第一,HDFS 不支持对已写入的文件做随机修改,除了在末尾追加(-appendToFile),你不能打开文件跳到第 100 字节改两个字符;第二,所谓“删除”在开启回收站之后只是把文件移动到/user/<用户名>/.Trash/current下,空间并不会立刻释放;第三,目录本身不占数据块,一个空目录在 NameNode 里就是一条元数据记录,所以你在 HDFS 上mkdir一万个空目录,DataNode 的磁盘一个字节都不会动。

这些差异不是设计缺陷,而是取舍。HDFS 的目标场景是一次写入、多次读取的超大文件顺序扫描,它把“并发写同一文件的某一段”“频繁随机更新”这些需求直接砍掉,换来了极高的吞吐和很低的元数据开销。理解了这一点,你就不会再纠结“为什么不能像 vim 一样改 HDFS 上的文件”,而是会自然地采用“读出来、改完、写回去”或者“生成新分区、切换分区”的做法。

我见过不少团队在 HDFS 上直接维护一份需要频繁更新的小配置文件,每次改都要删了重传,后来 NameNode 上堆了几十万个小文件,RPC 压力肉眼可见地涨。这类需求本来就不该由 HDFS 承担,换个存储介质会舒服得多。判断标准很简单:文件是不是一次写多次读、是不是足够大、是不是以顺序扫描为主。三条都满足,HDFS 就是好选择。

1.2 三个必须先建立的心智模型:块、副本、元数据

**块(Block)**是 HDFS 存储的最小单位。Hadoop 2.x 之后默认块大小是 128 MB,这个值写在hdfs-site.xml的dfs.blocksize里,单位是字节。一个 300 MB 的文件上传上去,会被切成 128 MB、128 MB、44 MB 三个块,第三个块叫“尾块”,不会补齐到 128 MB,磁盘上就只占 44 MB。这一点很重要,很多人以为不足一块也要占满一块,其实不是。

**副本(Replica)**是每个块的冗余份数,默认 3。上面那个 300 MB 的文件,三个块各存三份,实际磁盘开销就是 300 × 3 = 900 MB。你在-du -s里看到的“逻辑大小”和“实际占用”之所以对不上,根源就在这里。副本的作用是容错和并行读,代价是存储成本乘以副本数,这个账后面我会用具体数字算给你看。

**元数据(Metadata)**则是 NameNode 脑子里记的东西:目录树、文件到块的映射、块到 DataNode 的映射、权限、副本数、修改时间。这些信息不放在 DataNode 上,而是由 NameNode 维护,通过 EditLog 记录增量、FsImage 记录快照。你敲下hdfs dfs -put的那一瞬间,客户端先找 NameNode 要“我要写这个文件,给我几个位置”,NameNode 返回一批 DataNode 地址,客户端才真正把数据流式推给 DataNode。这个先后顺序决定了后面所有的报错形态:连不上 NameNode 是Call From ... failed on connection exception,连得上但 DataNode 有问题则是Bad connect ack或者All datanodes are bad。

1.3 配置入口先确认,别在错误的环境里折腾

在敲任何命令之前,我习惯先做三件事确认自己连的是哪个集群。第一,看core-site.xml里的fs.defaultFS,它决定了你不带前缀时hdfs dfs默认操作哪个文件系统;第二,看hdfs-site.xml里的dfs.replication和dfs.blocksize,这决定了默认副本和块大小;第三,用hdfs getconf -confKey直接把生效值打出来,避免改了配置文件但没分发、或者被某个-D参数覆盖。

这里有个非常容易被忽略的点:hdfs dfs支持任意 FS Scheme,你可以用它操作file:///开头的本地路径。这意味着hdfs dfs -ls file:///data是合法的,列的是本机目录。有些人排查问题时把本地路径当成了 HDFS 路径,来回折腾半天,其实就是路径没写对。养成习惯,HDFS 路径一律用绝对路径/xxx,本地路径要么用file:///前缀,要么用-put、-get这类明确区分源和目的的命令,能省掉大量误会。

2. 命令行:日常九成的基础操作都在这

2.1 hadoop fs 和 hdfs dfs 到底该用哪个

两个命令在绝大多数子命令上行为一致,区别在于入口。hadoop fs是更早的通用接口,它能操作所有注册了 FileSystem 实现的协议,hdfs://、file://、viewfs://都行;hdfs dfs是后来收窄的入口,主要面向 HDFS。在 Hadoop 3.x 里hadoop fs已经被标记为不推荐,日常统一用hdfs dfs就好,脚本里也建议统一,避免换版本之后行为不一致。

真正需要区分的是hdfs dfs和hdfs dfsadmin。前者是文件级操作,管的是“我的文件”;后者是集群级操作,管的是“整个集群的状态”,比如-report看容量、-safemode管安全模式、-setQuota设配额、-refreshNodes刷新节点列表。这两类命令权限要求完全不同,普通用户能跑hdfs dfs -ls,但跑hdfs dfsadmin -report通常需要超级用户或者配置了代理权限。很多人第一次执行dfsadmin报权限错误,就是因为没意识到这是两套体系。

2.2 上传四兄弟:put、copyFromLocal、moveFromLocal、appendToFile

这四个命令覆盖了绝大部分写入场景,但它们的行为差异很关键,我整理成一张表:

命令源文件是否保留是否支持多源典型用途
-put保留支持最常用,本地到 HDFS
-copyFromLocal保留支持与 put 基本等价,语义更明确
-moveFromLocal删除支持日志采集后清理本地,省一次删除
-appendToFile保留支持追加到已有文件末尾

-put和-copyFromLocal在实现上几乎就是同一个东西,选哪个看团队规范,我个人倾向-put,短。真正要留意的是同名覆盖:默认情况下目标文件已存在会直接报File exists,必须加-f才会覆盖。这个默认行为其实是保护机制,防止你手滑覆盖掉线上数据。我在生产脚本里从来不加-f,而是先把目标文件改名成带时间戳的备份,确认数据无误再清理,多一步,但睡得着觉。

-moveFromLocal在采集链路里特别好用。假设你有一个目录持续产生日志,采集脚本每小时跑一次,用-moveFromLocal把文件推上去,本地自动清空,省掉一次rm,也避免了“传完了但删除失败导致重复采集”的问题。不过要注意,它是先复制成功再删除源文件,如果传输中途失败,源文件还在,重跑不会丢数据,这个语义是对的。

-appendToFile是我用得比较谨慎的一个。HDFS 的追加是有限支持的,在开启dfs.support.append(3.x 默认 true)的情况下可以追加,但已经关闭的文件才能追加,正在被别的客户端写的文件不行。而且追加会产生新的块,如果一个文件被追加了几百次,就会变成几百个小块,NameNode 压力上来了。所以我的建议是:能批量合并就别用追加,追加只适合少量、低频的场景。

2.3 下载三兄弟:get、copyToLocal、getmerge

-get和-copyToLocal同样是等价关系,源在 HDFS、目的在本地。这里最容易踩的坑是目标路径语义:hdfs dfs -get /data/a.txt /tmp/会把文件放到/tmp/a.txt;但如果/tmp/a.txt已经是一个目录,它会把文件塞进/tmp/a.txt/a.txt,多套一层。这个行为和cp一致,但在脚本里非常容易出错,尤其是目标目录是动态生成的时候。我的做法是目标路径一律写全,包含文件名。

-getmerge是真正能省事的命令。它能把一个 HDFS 目录下的所有文件按字典序合并成一个本地文件,语法是hdfs dfs -getmerge /data/partitions/20240101 /tmp/merged.txt。做小批量数据导出、临时看结果的时候特别方便。但它有两个限制你得知道:一是全部在本地单进程拼接,文件大了会非常慢,几个 GB 以上就别用了;二是没有-skip-empty-file之外的过滤能力,目录里如果有_SUCCESS这类标记文件,也会被拼进去。所以生产上更稳的做法是用-cat配合重定向,或者干脆用计算引擎写单文件输出。

还有一个高频需求是“只看一眼内容”,这时候用-cat看小文件,用-tail看末尾 1 KB(-tail -f在 2.x 之后支持流式跟踪,调试正在写入的文件很有用),用-text看压缩文件或者 SequenceFile,它会自动识别并解压,比-cat看到一堆乱码强得多。

2.4 查看与统计:ls、du、df、count、stat 各管一摊

新手最常用的是-ls,但它给的信息很有限。加上-R可以递归,输出会很长,几十万文件直接刷屏,我不建议在大目录上用。想看目录总大小,用-du -s -h /path,-s是汇总,-h是人类可读;不加-s会列出每个子目录,适合定位“哪个子目录把空间吃掉了”。

-df -h /看的是整个文件系统的容量,注意它反映的是集群层面(更准确说是 NameNode 视角下所有 DataNode 的汇总),不是你个人目录的配额。如果你被设了配额,得用-count -q /path,输出会有四列数字:配额、已用、剩余、已用文件数配额等,第一次看容易懵,多试两次就熟了。

-stat是我个人最喜欢的排查工具,它能把一个文件的块大小、副本数、修改时间按格式串打出来。比如hdfs dfs -stat "%b %o %r %n %Y" /data/a.txt,%b是块大小、%o是块大小(字节)、%r是副本数、%n是文件名、%Y是修改时间戳。排查“为什么这个文件副本只有 2”时,一条命令就能确认,比翻 Web UI 快。

2.5 目录、权限与删除:几个不能想当然的地方

-mkdir -p一定要带-p,否则父目录不存在会直接失败。这个和 Linux 完全一致,但 HDFS 上新建用户目录时经常忘,报错信息是No such file or directory,看着像路径写错,其实是父目录没建。

权限方面,HDFS 用的是 POSIX 模型,-chmod、-chown、-chgrp都支持-R递归。默认 umask 是 022,所以新建目录是 755、新建文件是 644。这个默认值在多人协作的集群里往往不够用,需要按业务组划分目录并设置属组。这里提醒一句:-chown修改属主在 HDFS 上需要超级用户权限,普通用户即使是对自己创建的文件也改不了属主,只能改属组(前提是你同时属于目标组)。ACL(-setfacl)默认是关闭的,需要 NameNode 侧打开dfs.namenode.acls.enabled,没开的时候执行会报The ACL operation failed,别以为是命令写错了。

删除这一块,核心是分清三种写法:-rm删文件、-rm -r删目录、-rm -r -skipTrash跳过回收站直接删。第三种在清理临时数据时很常用,但它是真的不可恢复,我一般只在明确知道数据可重建时才用。还有一个坑:-rm -r /path/*这种通配符是由客户端 shell 展开还是由 HDFS 展开,取决于有没有引号,路径里有特殊字符时容易出错,稳妥做法是先-ls确认匹配结果,再执行删除。

3. 一条完整链路走通:从上传到校验

3.1 环境自检的三条命令

正式操作前,我会跑三条命令确认集群是健康的。第一条hdfs dfsadmin -safemode get,返回Safe mode is OFF说明 NameNode 已经退出安全模式,可以正常写;如果返回 ON,所有写操作都会抛SafeModeException,这时候等就行,别急着重启。第二条hdfs dfsadmin -report,看Live datanodes的数量和DFS Remaining剩余容量,如果 DN 数量比你预期少,说明有节点掉线,写三副本可能会卡在 pipeline 阶段。第三条hdfs fsck / -files -blocks,健康集群会返回Status: HEALTHY,如果有MISSING或者Under-replicated的块,先别往里灌数据,让运维处理。

这三条命令看着简单,但能挡掉八成“传不上去”的问题。我遇到过好几次新人反馈上传失败,一问都是安全模式没退出,或者某个 DN 磁盘满了导致副本写不下去。

3.2 上传、验证块位置与副本分布

假设我要把一个本地文件/home/data/app.log(300 MB)传到一个测试目录。完整操作是这样:

# 建目录 hdfs dfs -mkdir -p /demo/raw # 上传,目标写全路径 hdfs dfs -put /home/data/app.log /demo/raw/app.log # 看文件属性:块大小、副本数、修改时间 hdfs dfs -stat "%b %r %n %Y" /demo/raw/app.log # 看逻辑大小和实际占用 hdfs dfs -du -h /demo/raw/app.log

-du -h会输出两列数字,第一列是逻辑大小(300 M),第二列是实际占用(900 M,因为三副本)。很多人第一次看到这个会以为出 bug 了,其实就是副本的账。

想看每个块落在哪些 DataNode 上,用hdfs fsck /demo/raw/app.log -files -blocks -locations,输出里会列出每个块的 ID、长度和所在的 DataNode 地址。这个命令在排查“数据倾斜”“某个 DN 负载过高”时很有用,但注意它对大目录会很慢,只对具体文件用。

3.3 把存储开销算清楚,别等磁盘报警才反应过来

我用上面这个 300 MB 文件把账算一遍,你好在容量规划时直接套。块大小 128 MB,文件 300 MB,切出来是 128 + 128 + 44,共 3 个块。副本 3,所以每个块的物理占用是 128×3、128×3、44×3,合计 900 MB。如果这个文件是 100 GB,那就是 100 × 3 = 300 GB 的物理占用,再叠加 HDFS 自身的元数据开销。

元数据这块也得算。NameNode 内存里每个文件、每个块、每个副本都是一条对象记录,行业里常用的估算口径是每个对象几百字节量级。一个 100 GB 的文件只有 800 个块左右,元数据压力可以忽略;但如果你上传的是 100 万个 100 KB 的小文件,块数就是 100 万,副本 3 就是 300 万个副本对象,NameNode 内存会被迅速吃掉。这就是所谓“小文件问题”的量化来源,也是为什么后面我要专门讲合并。

计算的时候还有个容易忽略的系数:副本数不是随便设的,它受 DataNode 数量约束。你集群只有 2 台 DataNode,却把副本设成 3,那这个文件会永远处于“副本不足”状态,fsck一直报Under-replicated,NameNode 还会不停调度复制任务,白白消耗网络。所以副本数要满足“不超过 DN 数量”这个硬约束,一般小集群设 2,规模上来了再设 3。

3.4 修改副本数与块大小:哪些能改,哪些改不了

副本数是可以事后改的,命令是hdfs dfs -setrep -w 2 /demo/raw/app.log。-w表示等待复制完成,大文件加上跨机架复制,可能要等几分钟甚至更久;不加-w是提交后立刻返回,实际复制在后台跑。对目录加-R可以递归改整个目录树,这个操作在缩容或者调整冷热数据策略时很常用。

块大小就麻烦一些,只能对新建文件生效,已存在的文件改不了。想让某个文件用 64 MB 的块,得在上传时通过-D参数指定:

hdfs dfs -D dfs.blocksize=67108864 -put /home/data/small.log /demo/raw/small.log

注意这里-D的位置,必须放在子命令-put之前。放到后面,HDFS 会把它当成一个待上传的路径,报错信息还挺隐晦。这个坑我踩过一次,排查了快半小时才发现是参数顺序问题。同理,临时改副本数上传也是hdfs dfs -D dfs.replication=2 -put ...,同样要放前面。

3.5 Java API 版本:不写代码也得知道它在干什么

很多平台工具底层都是 Java API,你看懂了它,就能理解 shell 命令背后的行为。最小可运行的读写示例大概是这样:

Configuration conf = new Configuration(); conf.set("fs.defaultFS", "hdfs://nn-host:8020"); try (FileSystem fs = FileSystem.get(conf)) { Path dst = new Path("/demo/api/hello.txt"); // 第二个参数是 overwrite,默认 false try (FSDataOutputStream out = fs.create(dst, true)) { out.write("hello hdfs".getBytes(StandardCharsets.UTF_8)); } try (FSDataInputStream in = fs.open(dst)) { IOUtils.copyBytes(in, System.out, 4096, false); } }

这段代码里有三个值得说的点。第一,fs.create(path, true)的第二个参数是覆盖开关,写false或者不写,遇到同名文件直接抛FileAlreadyExistsException,和 shell 里不加-f是一个逻辑。第二,IOUtils.copyBytes的最后一个参数false表示不关闭输出流,如果写true,它会把System.out也关掉,后面再打印就报错。第三,客户端依赖的hadoop-client版本必须和集群版本对齐,版本差太多会在握手阶段报协议不匹配,错误信息里一般带Protocol字样,看到这个词先查版本。

在 Windows 上跑还会碰到一个经典问题:缺少本地库导致Could not locate executable null\bin\winutils.exe。这不影响功能,只是警告,但很多人被吓到,以为环境坏了。最省事的办法是直接在集群节点或者 Linux 开发机上跑,别在本地 Windows 上折腾。

3.6 Web UI 和 dfsadmin:最直观的验证手段

Hadoop 3.x 的 NameNode Web 端口默认 9870(2.x 是 50070),浏览器打开就能看到概览页,里面有容量、DN 数量、安全模式状态,还有一个 “Utilities → Browse the file system”,可以直接浏览目录、看文件的前几 KB 内容、下载文件、看每个文件的块分布。这个功能在验证“我到底传上去没有、块分布是否均匀”时特别直观,比敲命令快。

DataNode 的 Web 端口默认 9864,能看单个节点的存储使用、块数量、最近的操作日志。集群规模不大的时候,挨个点开几个 DN 页面看一眼,比dfsadmin -report更能发现“某台机器磁盘快满了”这种细节,因为-report里的平均值会掩盖掉个体差异。

4. 块、副本与权限:影响操作结果的三个隐藏参数

4.1 块大小为什么定在 128 MB,而不是越大越好

块大小这个参数的本质是在“寻址开销”和“并行粒度”之间找平衡。块太小,比如 16 MB,一个 1 TB 的文件会有 65536 个块,NameNode 要维护的元数据量暴增,同时 MapReduce 任务切分过细,任务启动开销超过实际计算时间;块太大,比如 1 GB,一个 1 TB 文件只有 1024 个块,任务并行度不够,某个节点挂了之后重算的数据量也更大。

128 MB 是多年实践下来的经验值,配合常见的千兆、万兆网络和机械盘、SSD 混合的存储介质比较均衡。到 Hadoop 3.x,这个默认值仍然是 128 MB,但社区里也有把大集群调到 256 MB 的做法,前提是文件普遍很大、计算框架支持大分片。我的建议是:没有明确理由就别改默认值,跟着集群规范走,团队里有人改了要记录在案,否则后面排查“为什么同一份数据两个目录块数不一样”会很费劲。

另外提醒一点,块大小是文件级的属性,写在 NameNode 的元数据里。你用-stat %o看到的字节数,就是这个文件实际生效的块大小,不同文件可以不一样,这完全合法,但会让容量估算变得复杂,所以生产上尽量统一。

4.2 副本数不是越多越安全

副本的本质是用存储换可用性。3 副本能容忍 2 台 DataNode 同时挂掉而数据不丢,2 副本只能容忍 1 台。但副本带来的成本是线性的:存储成本 ×3,写入时的网络流量 ×3(三副本 pipeline 写),读取时倒是有并行收益,但顺序扫描场景下收益不明显。

真正需要区分的是“热数据”和“冷数据”。热数据访问频繁,3 副本值得;冷数据一年都不读一次,放在低成本介质上、副本降到 2 甚至用纠删码,成本能降一大截。Hadoop 3.x 引入的纠删码(Erasure Coding)就是干这个的,它用校验块替代副本,存储开销能压到 1.5 倍左右,代价是读取时要解码、恢复时更耗 CPU。这属于进阶话题,但你在做容量规划时至少要知道这条路存在。

还有一个常被忽略的点:副本分布受机架感知影响。配了机架感知的集群,NameNode 会把三个副本按“同机架两个、另一机架一个”的策略放置,这样既能防单机故障,也能防整机架断电。如果没配机架感知,所有副本可能挤在同几个节点上,看起来是三副本,实际上一起挂。验证方法是用fsck -locations看块所在的 DN 地址,再对照机架拓扑,如果三个副本都在同一个网段,那大概率是机架感知没生效。

4.3 小文件治理:不是“合并一下”这么简单

小文件问题我在前面提过,这里展开讲具体做法。判断标准通常是“平均文件大小远小于块大小”,比如平均 1 MB,那 128 MB 的块就只用了不到 1%,资源浪费严重。治理手段有三类:

第一类是入库前合并,在数据采集环节就把小文件攒批,按时间窗口或者大小阈值合并成一个大文件,再-put上去。这是最根本的做法,成本最低。第二类是入库后合并,用计算任务把目录下的小文件读出来重写成一个或几个大文件,再删掉原目录。这个过程要小心,重写会改变文件路径,下游依赖路径的任务都要同步改,最好用分区切换的方式,别原地覆盖。第三类是用容器格式,比如把一批小文件写进一个 SequenceFile 或者 Parquet 文件里,逻辑上还是多个记录,物理上只有一个大文件,NameNode 压力直接降下来。

有个细节值得说:合并的时候要注意压缩格式。如果源文件是 gzip,它是不可切分的,合并后的大 gzip 文件在计算时只能由一个任务读,并行度反而下降。这种情况下要么改用可切分的压缩算法,要么在合并时就解压重写。

5. 常见报错与排查实录

5.1 连接类报错:先分清是 NameNode 还是 DataNode

最典型的报错是Call From xxx to nn-host:8020 failed on connection exception。看到这个,先分三步查:NameNode 进程在不在、8020 端口通不通、客户端配置的地址对不对。三步里最容易出问题的是第三步,尤其是从别的机器上跑客户端,fs.defaultFS还指着 localhost。我一般直接用telnet nn-host 8020确认端口,通不了就别在客户端上耗时间了。

另一类是UnknownHostException,说明主机名解析失败。HDFS 配置里习惯写主机名而不是 IP,如果客户端机器的 hosts 没配全,或者 DNS 有问题,就会卡在这一步。这种错误的迷惑性在于它看起来像代码问题,实际是环境问题。

还有一类是连得上但写不进去,报Bad connect ack with firstBadLink。这个通常是写 pipeline 里某个 DataNode 出问题了,比如磁盘满、进程假死、网络抖动。排查方法是看错误信息里提到的那个 DN 地址,登上去看df -h和 DN 日志。如果只是偶发,重试一次往往就好了;如果持续出现,那台机器要单独处理。

5.2 权限类报错:读懂那行 inode 信息

Permission denied: user=alice, access=WRITE, inode="/":hdfs:supergroup:drwxr-xr-x,这行报错信息其实把答案都写出来了:当前用户是 alice,要做的操作是写,被拒的 inode 是根目录/,它的属主是 hdfs、属组是 supergroup、权限是 755。755 意味着除了属主,其他人没有写权限,所以 alice 写不了根目录,这完全合理。

解决办法不是去改根目录权限(那很危险),而是让 alice 在自己的家目录下操作。家目录/user/alice一般由管理员预先创建并设置好属主,如果没有,就让管理员建一个。有些人图省事直接hdfs dfs -chmod 777 /,这是给自己挖坑,后面所有权限体系都失效了。

还有一种情况是自己建的文件自己改不了属主,报chown: Permission denied。前面说过,HDFS 里改属主需要超级用户,普通用户只能改属组,这是设计如此。要调整归属,找管理员或者用有代理权限的账号。

5.3 存储与状态类报错:安全模式、磁盘满、副本不足

SafeModeException: Cannot create file ... name system is in safe mode,这个前面提过,NameNode 启动时在等 DataNode 汇报块信息,达到阈值(默认 0.999)才退出。集群刚重启、或者大规模 DN 掉线后都会出现。正常情况等几十秒到几分钟就好,如果一直不退出,用hdfs dfsadmin -safemode get看状态,用-report看 DN 上线了几个,大概率是有 DN 起不来。

磁盘满的报错形式比较隐蔽,可能是All datanodes are bad,也可能是写一半卡住。这时候别在客户端上找原因,直接去 DN 上看磁盘使用率。HDFS 有预留空间机制,DataNode 磁盘用到一定比例就不再接受写入,所以“还有几百 G 空间但写不进去”是完全可能的。

副本不足不报错,但会一直有后台复制任务在跑,表现为网络流量异常。用hdfs fsck /path -files -blocks看Under-replicated blocks的数量,如果数量长期不降,通常是 DN 数量少于副本数,或者有节点被列入黑名单。

5.4 常见问题速查表

报错关键词大概率原因处理动作
connection exceptionNameNode 未启动 / 端口不通 / 地址配错查进程、telnet 端口、核对fs.defaultFS
UnknownHostException主机名解析失败补 hosts 或改用可达地址
Permission denied ... access=WRITE目标目录无写权限换到家目录或申请授权,别改根权限
File exists目标同名文件已存在确认后加-f,或先改名备份
SafeModeExceptionNameNode 在安全模式等阈值达成,检查 DN 存活
All datanodes are badDN 磁盘满 / 进程异常登 DN 看磁盘和日志
Bad connect ack写 pipeline 中某 DN 异常定位报错中的 DN 地址单独排查
Under-replicated blocks副本数大于 DN 数或节点离线调整副本数或恢复节点
Could not locate winutils.exeWindows 缺本地库忽略警告或在 Linux 上执行
Protocol版本不匹配客户端与集群版本不一致对齐hadoop-client版本

6. 权限、回收站与安全模式:几个容易被忽略的开关

6.1 回收站不开,删了就是真的删了

HDFS 的回收站默认是关闭的,fs.trash.interval默认 0,意味着-rm直接把元数据标记删除,块随后被清理,无法恢复。要开启,得在core-site.xml里设置fs.trash.interval(单位分钟,比如 1440 表示保留一天)和fs.trash.checkpoint.interval(检查点间隔,一般设成 interval 的十分之一)。

开启之后,-rm的文件会进到/user/<用户名>/.Trash/current/下,保留时间到了才真正清理。中间想恢复,用-mv从回收站移回原路径就行。想立刻清空,用-expunge,它会删除所有检查点,这个操作不可逆。

这里有个细节:回收站是按用户的,超级用户删的文件进超级用户的回收站,普通用户进自己的。而且回收站本身也占存储空间,在大集群上如果不控制保留时间,回收站能吃掉可观的容量。所以我的建议是开启但设短一点,比如 1440 分钟,足够覆盖误删当天发现的情况。

6.2 安全模式不是故障,是保护机制

安全模式的作用是 NameNode 启动时先收集所有 DataNode 汇报的块信息,确认副本达标率达到阈值(dfs.namenode.safemode.threshold-pct,默认 0.999)之后,才允许写操作。这个设计是为了避免在元数据还没恢复完整的时候就开始接受写入,导致块分布错乱。

理解这一点,你就不会在安全模式期间瞎折腾。想看状态用-safemode get,想主动进入用-enter(维护时用),想强制退出用-leave(谨慎,副本没达标就退出可能引发后续复制风暴),想等它自己退出用-wait。集群刚启动时-wait是最稳的做法,脚本里加一句,能避免一堆莫名其妙的写入失败。

6.3 配额与目录规划,做在前面比事后补救便宜

配额分两种:-setQuota限制目录下的文件数加目录数,-setSpaceQuota限制目录占用的逻辑空间大小。注意是逻辑空间,不乘副本数,所以配额设 1 TB,实际物理占用可能是 3 TB,规划磁盘时要按副本数放大。

我见过最常见的翻车是共享目录没设配额,某个任务疯狂写临时文件把整个集群磁盘打满,所有业务一起挂。所以共享的临时目录、个人工作目录都应该设配额,哪怕设得很宽松,也比完全不设强。设配额需要超级用户权限,找运维统一规划,别自己乱改。

7. 我踩过的几个坑,你大概率也会遇到

第一个坑是-D参数位置。前面提过一次,这里再强调:所有-D必须在子命令之前,hdfs dfs -D dfs.replication=2 -put a b是对的,hdfs dfs -put a b -D dfs.replication=2会把后半截当成路径。这个错误报出来是put: -D: No such file or directory之类的,看着像文件找不到,其实是参数解析问题。

第二个坑是通配符。hdfs dfs -rm -r /data/tmp/*里的*是由客户端 shell 展开的,如果你在脚本里用变量拼接路径,或者路径本身包含特殊字符,很容易删错东西。我的习惯是先在同一个位置跑一遍-ls,把输出贴到删除命令里,或者用-f配合明确路径。生产环境删数据,宁可多敲几行。

第三个坑是“删了但空间没释放”。原因通常有两个:回收站没清,或者文件被别的任务以-skipTrash之外的方式引用着(比如快照)。先-du -s看目录实际占用,再-expunge清回收站,如果还是没降,查一下是不是有快照或者正在写的临时文件。

第四个坑是本地路径和 HDFS 路径混淆。hdfs dfs -put /data/a.txt /data/b.txt,第一个参数是本地路径,第二个是 HDFS 路径,两条斜杠长得一模一样,非常容易写反。写反了不会报错,因为本地恰好有个/data/a.txt,结果就是把本地文件传到 HDFS 上的/data/b.txt,把线上的同名文件覆盖了。这类事故我见过不止一次,防御办法是本地路径统一用file:///前缀,或者干脆在脚本里把变量命名成LOCAL_SRC和HDFS_DST,肉眼可辨。

第五个坑是小文件上传后没管。前面算过账,100 万个 100 KB 的文件,块数 100 万,副本对象 300 万,NameNode 内存压力直接上来,而且后续每个计算任务都要开几百万个分片,调度开销比计算还大。所以上传前先评估平均文件大小,低于块大小十分之一的,就该考虑合并。

最后分享一个日常习惯:任何一次批量操作之前,先在一个测试目录上用同样的参数跑一遍,确认块数、副本数、权限都符合预期,再对生产目录执行。这个动作花不了一分钟,但能挡掉大部分因为参数写错导致的事故。HDFS 的基础操作本身不难,难的是在几百个文件、几十个目录、多个用户的环境里,每一次操作都清楚它在元数据和物理存储层面到底做了什么。把块、副本、元数据这三个概念和你的每一条命令对应上,很多看起来诡异的现象,其实都有非常直接的解释。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询