第一次把 Hadoop 完全分布式集群在自己手上从零搭起来,绝大多数人都会经历同一个过程:照着教程一步步抄,命令一条条敲,最后jps一敲,该有的进程一个不少,心里刚松一口气,结果一上传文件就发现副本数不对,或者作业提交上去一直卡在 ACCEPTED 不动。问题不在于你抄漏了哪一行配置,而在于完全分布式的本质是"多台机器协同",只要有任何一处跨机器的前提没打通,整个集群就会以一种看似正常、实则半死不掉的状态运行。
这篇内容讲的就是用 Hadoop 搭一套真正意义上的完全分布式集群:三台机器、独立角色分工、HDFS 和 YARN 都能正常对外提供服务、能实际跑通一个作业。适合已经在本机跑通过伪分布式、准备往多机环境迈一步的人,也适合搭过一次但没搭利索、想搞清楚每个参数背后到底在干什么的人。我不会只给你一套配置模板,而是把每个关键动作的动机、报错时的排查路径、以及那些文档里不会写的坑一起讲透——集群搭建这活儿,真正花时间的从来不是敲命令,而是判断"现在到底哪一环断了"。
1. "完全分布式"这四个字到底比伪分布式多了什么
很多人对完全分布式的理解停留在"把伪分布式复制到三台机器上",这个理解会让你在配置阶段做出大量无用功,也会让你在出问题时完全找不到方向。先把概念捋清楚,后面所有的配置选择才有依据。
1.1 伪分布式跑得通,一到三台机器就集体趴窝的原因
伪分布式(Pseudo-Distributed)的本质是:所有守护进程跑在同一台机器、同一个 JVM 之外的不同进程里,但彼此通过 localhost 通信。它把"分布式"的复杂度压到了最低——网络是本地回环、主机名永远是 localhost、时间一定同步、文件系统权限模型单一。你在这种环境里配错一个fs.defaultFS的主机名,也大概率不会立刻暴露,因为解析 localhost 永远成功。
完全分布式把这几层"默认正确"的前提全部抽走了。第一层是名字解析:机器之间靠主机名互相找,hosts 里少一行,UnknownHostException立刻就来。第二层是信任关系:启动脚本start-dfs.sh是通过 SSH 去从节点拉起进程的,没有免密,脚本会在第一个从节点卡住等密码,或者直接抛权限错误。第三层是状态一致性:NameNode 格式化时生成一个集群 ID(clusterID),DataNode 启动时要拿自己的 clusterID 去比对,对不上就直接退出,日志里一行Incompatible clusterIDs,但jps里你可能只看到少了个 DataNode,第一反应往往是"配置文件写错了"。第四层是资源边界:单机上你不需要考虑每台机器给 YARN 留多少内存,多机环境下 NodeManager 默认按物理内存的一大比例上报,容器一多就互相挤,出现莫名其妙的 container killed。
所以真正需要你重新理解的不是"多了几个配置文件",而是:完全分布式是一个由多个独立操作系统进程通过约定好的网络协议拼起来的系统,任何一个环节的假设失效,系统就会降级而不是崩溃。降级比崩溃更难查,这是搭建阶段最需要建立的认知。
1.2 角色切分:谁该装在哪台机器上
Hadoop 的核心角色不多,但每个角色的放置位置会直接影响你后续的操作便利度和容错能力。常见的角色清单是这样的:HDFS 侧有 NameNode(元数据主节点)、SecondaryNameNode(元数据检查点辅助)、DataNode(数据存储);YARN 侧有 ResourceManager(资源主节点)、NodeManager(节点资源代理);此外还有 MapReduce 的 JobHistoryServer(历史作业服务)。
对于三台机器的学习/测试集群,最省事也最合理的分配是:
| 机器 | 主机名示例 | 承担的角色 |
|---|---|---|
| 机器一 | node01 | NameNode、ResourceManager、JobHistoryServer |
| 机器二 | node02 | SecondaryNameNode、DataNode、NodeManager |
| 机器三 | node03 | DataNode、NodeManager |
关键点在于 SecondaryNameNode不要和 NameNode 放在同一台机器上。它的作用是定期把 NameNode 的 edits 日志合并进 fsimage,合并过程本身消耗内存和 IO,放在一起等于让主节点在关键时刻跟自己抢资源,也失去了"辅助节点独立存活"的意义。同理,ResourceManager 和 NameNode 可以同机(它们管理的资源维度不同,小集群压力不大),但如果你后面打算做高可用实验,最好一开始就在心里留好位置。
DataNode 和 NodeManager 通常成对出现,因为 HDFS 的块本地性依赖"数据在哪个节点,计算就去哪个节点",两者分离会让本地性优化失效。这一点在测试集群里感受不明显,但它是 Hadoop 设计思想的核心之一,值得记住。
1.3 三节点最小拓扑与硬件配比参考
三台虚拟机是搭建完全分布式的最低门槛:少于三台,dfs.replication=3的默认副本数就凑不齐,HDFS 会一直处于"副本不足"的告警状态,虽然不影响功能,但每次dfsadmin -report都跳一堆 under-replicated blocks,很容易误判成故障。
配置上给一个务实的参考:每台 2 核 CPU、内存 4GB 起步、磁盘 50GB。内存是关键瓶颈——NameNode 本身不重,但 DataNode 加 NodeManager 的 JVM 堆叠起来,再跑一个 MapReduce 作业的 container,2GB 内存会非常紧张。如果宿主机资源有限,可以把三台压到各 2GB,但必须相应调小 YARN 的可用内存参数(后面会讲怎么算),否则作业会因为申请不到容器而一直排队。
网络方面,虚拟机用 NAT 或仅主机模式都行,重点是三台机器要能互相 ping 通、IP 固定不漂移。千万不要用 DHCP 自动分配,IP 一变,hosts 映射全废,你会花半小时怀疑人生。另外建议给每台机器加一块独立的数据盘挂载点(比如/data),把 HDFS 数据目录和临时目录放进去,跟系统盘分开——这不是洁癖,是因为格式化 NameNode、清数据目录这类操作在测试环境里非常频繁,一旦手滑rm -rf到系统盘上的目录,重装系统的成本远高于加一块盘。
2. 装系统之后的底座工程:主机名、hosts、时间与防火墙
这一段内容在很多教程里被压缩成三行命令,但它恰恰是后续 80% 莫名报错的根源。底座没打牢,配置写得再对也是白搭。
2.1 主机名和 hosts 映射是后面所有配置的地基
Hadoop 的配置文件里到处是主机名:fs.defaultFS写的是hdfs://node01:9000,yarn.resourcemanager.hostname写的是node01,workers文件里写的也是主机名。这些字符串最终都要被解析成 IP,解析不了就直接抛异常。
操作顺序是:先用hostnamectl set-hostname node01改主机名,改完reboot或者重新登录让提示符更新;然后编辑/etc/hosts,把三台的 IP 和主机名都写进去。三台机器上的 hosts 文件内容必须完全一致,每台都要包含全部三行,不能只写自己那台加个 localhost。原因很简单:node02 上的 DataNode 要向 node01 的 NameNode 注册,注册时返回的可能是主机名而不是 IP,node02 必须能把node01解析出来。
一个常见的坑是 hosts 里同时存在127.0.0.1 node01和192.168.10.101 node01两条记录。这时解析结果取决于顺序,可能走到回环地址上去,表现为"本机进程都正常,但从节点连不上主节点"。hosts 文件里只保留真实 IP 那一行,把127.0.0.1后面的主机名去掉,只留 localhost 那两条标准记录。
改完立刻验证,别等到配完 Hadoop 再回头查:
# 三台机器分别执行,三行都要能通 ping -c 2 node01 ping -c 2 node02 ping -c 2 node03 # 再确认解析结果和预期 IP 一致 getent hosts node012.2 时间同步这件小事怎么把集群拖垮
Hadoop 本身不像某些分布式数据库那样强依赖时钟,但时间漂移会以很隐蔽的方式咬你:NameNode 和 DataNode 的日志时间对不上,你按照时间戳去关联两台机器的事件时完全串不起来;YARN 的容器超时判断、作业的重试间隔也会受影响;如果后面接 Kerberos 认证,时间偏差超过几分钟直接认证失败。
测试环境里最省事的做法是选一台机器做内网时间源,或者干脆让三台都指向同一个可用的时间服务。如果环境完全离线,可以用chronyd让 node01 以本地时钟作为基准,另外两台指向 node01:
# node01 上 /etc/chrony.conf 增加 # allow 192.168.10.0/24 # local stratum 10 # node02、node03 上 /etc/chrony.conf # server node01 iburst systemctl enable --now chronyd chronyc sources -v验证方式是date看三台输出是否基本一致。这里要强调的是:时间同步不需要精确到毫秒级,但绝对不能相差几分钟以上,而且一旦发现不同步,最好在三台都调整完之后再启动集群,别在集群运行中猛然改时间,那会让日志混乱到无法阅读。
2.3 防火墙与 SELinux:要么关,要么精确放行
学习环境里最务实的做法是直接关闭防火墙和 SELinux,理由是 Hadoop 用到的端口非常多(NameNode 的 9000/9870、DataNode 的 9864/9866/9867、ResourceManager 的 8030/8031/8032/8088、NodeManager 的若干随机端口、JobHistory 的 10020/19888),逐个放行的工作量远大于收益。
systemctl stop firewalld systemctl disable firewalld # SELinux 临时关闭 setenforce 0 # 永久关闭,改 /etc/selinux/config 中的 SELINUX=disabled,重启生效如果环境不允许关闭防火墙,那就必须把上面那批端口在三台机器之间互相放行,而且要放行的是内网网段而不是单台 IP,因为 NodeManager 的通信端口有不少是动态分配的。这一点在生产环境里是硬要求,测试环境里可以偷懒,但你要知道自己在偷懒。
3. SSH 免密互信:成败往往卡在这十分钟
配置免密这件事,搜索出来的答案五花八门:有人说只配主节点到从节点,有人说要两两互配。到底配几组,取决于你的启动脚本怎么跑。
3.1 到底要配几组信任关系
start-dfs.sh的执行逻辑是:在 NameNode 所在机器上运行,然后通过 SSH 分别登录到 SecondaryNameNode 和每一个 DataNode 上执行启动命令。start-yarn.sh类似,在 ResourceManager 所在机器上运行,SSH 到每个 NodeManager。在我们的角色分配里,NameNode 和 ResourceManager 都是 node01,所以最小必要集是 node01 单向免密到 node02、node03,以及 node01 自己(免密登录本机)。
那我为什么建议直接配成三台两两互信?因为测试集群里你经常会手动跨机器操作:在 node02 上想看看 node03 的日志,或者某个节点的进程挂了要从别的机器 ssh 上去重启。两两互信省掉大量输入密码的摩擦,而成本只是多执行几条ssh-copy-id。
操作上每台机器先生成密钥对:
ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa注意-P ''表示空密码,如果不加这个参数,命令会交互式问你密码,脚本化的时候容易卡住。然后每台机器把公钥分发给三台(包括自己):
ssh-copy-id node01 ssh-copy-id node02 ssh-copy-id node033.2 首次连接的 yes 交互是怎么把脚本打断的
即使公钥已经分发完,第一次ssh node02时依然会弹出一个指纹确认提示,要求输入 yes。这是 SSH 的 known_hosts 机制在起作用:它要确认你连接的主机是不是真的。手动操作时这不算问题,但start-dfs.sh是一串自动化脚本,任何一次交互提示都会让脚本挂起,表现就是"命令敲下去,光标一直闪,什么都不输出"。
规避办法有两个:一是提前手动 ssh 一遍每台机器,把指纹确认掉;二是在~/.ssh/config里加一行StrictHostKeyChecking no,但这个做法会降低安全性,测试环境可以用,生产环境不建议。我更推荐第一种,因为顺手还能验证连通性。
另一个更隐蔽的问题是 known_hosts 冲突:如果你重装过某台虚拟机,IP 没变但主机密钥变了,再 ssh 过去会报Host key verification failed并直接拒绝连接。这时候需要ssh-keygen -R node02删掉旧记录再重连。这个坑在反复重建虚拟机的过程中会反复出现,记住这条命令能省很多时间。
3.3 用一条命令验证互信是否真的通了
别用单独一条ssh node02来验证,它可能命中缓存让你误判。用循环一次把所有链路都跑一遍:
for h in node01 node02 node03; do echo "==== $h ====" ssh $h "hostname; whoami" done预期输出是每台机器自己的主机名和当前用户,全程没有任何密码或指纹提示。如果某一行卡住,就单独针对那台机器排查:ssh -v node0x看卡在密钥交换还是认证阶段。这一步必须三台机器都做一遍,因为ssh-copy-id只是把公钥追加到目标机器的authorized_keys,如果目标机器的.ssh目录权限不对(比如被 umask 弄成了 777),SSH 会静默拒绝使用公钥,退回密码认证。权限要求是:.ssh目录 700,authorized_keys600。
4. 四个 XML 加三个附属文件:Hadoop 集群的配置核心
Hadoop 的配置看起来很唬人,其实真正决定集群能不能跑起来的就那么十来个参数。剩下几百个参数是调优用的,前期完全不用碰。我的建议是:先让集群以最小配置跑起来,再逐项优化,而不是一上来抄一份几千行的全网最全配置。
配置文件都在$HADOOP_HOME/etc/hadoop/下。开始之前先把解压目录的属主改成运行 Hadoop 的用户(比如 hadoop 用户),否则后面 NameNode 无法在数据目录里创建文件。
4.1 core-site.xml:先把文件系统入口钉死
这个文件只需要关注两三行,但它是集群的"门牌号"。核心参数:
| 参数 | 建议值 | 作用与说明 |
|---|---|---|
fs.defaultFS | hdfs://node01:9000 | 默认文件系统的 URI,所有hadoop fs命令不带前缀时走这里 |
hadoop.tmp.dir | /opt/module/hadoop-3.3.6/data/tmp | 各类临时文件的根目录,务必放在数据盘 |
io.file.buffer.size | 131072 | 读写缓冲区大小,128KB 是个平衡点 |
fs.defaultFS里的端口 9000 是 NameNode 的 RPC 端口,3.x 版本默认值其实是 8020,但 9000 在大量资料里被使用,两者都可以,关键是配置里写的端口和实际启动的端口一致。如果你在 core-site 里写了 9000,但 HDFS 实际监听的是 8020,客户端连接会直接被拒绝,报Call From xxx to node01:9000 failed on connection exception。
hadoop.tmp.dir的重要性被严重低估。这个目录是很多其他目录的默认父目录,如果它被放在/tmp下,而系统配了定期清理/tmp的任务,某天早上你重启集群就会发现 NameNode 起不来了——元数据没了。所以第一件事就是把它显式指定到一个稳定位置,并且保证三台机器上路径一致、目录存在、属主正确。
4.2 hdfs-site.xml:副本数、元数据目录与 Web 端口
HDFS 侧的配置稍微多一点,但每一项都有明确目的:
| 参数 | 建议值 | 说明 |
|---|---|---|
dfs.replication | 3 | 副本数,不能超过 DataNode 数量 |
dfs.namenode.name.dir | file:///data/hadoop/name | 元数据存放目录,可配多个实现冗余 |
dfs.datanode.data.dir | file:///data/hadoop/data | 数据块存放目录,建议多磁盘多个路径 |
dfs.namenode.secondary.http-address | node02:9868 | 辅助节点 Web 端口,指定到 node02 |
dfs.namenode.http-address | node01:9870 | NameNode Web UI |
dfs.blocksize | 134217728 | 128MB,与默认一致,写出来便于理解 |
dfs.namenode.handler.count | 20~30 | NameNode 处理 RPC 的线程数 |
几个容易踩的点。第一,dfs.namenode.name.dir和dfs.datanode.data.dir要用file://前缀,虽然某些版本不写也能识别,但写了更明确。第二,这两个目录必须在格式化之前就创建好并赋予正确属主,NameNode 启动时会去检查,目录不存在或者没权限,它会直接退出,日志里是Directory does not exist或Permission denied,但jps里只是少一个进程,很迷惑。第三,dfs.replication=3的前提是至少三台 DataNode 在线,如果你只有两台机器,把它改成 2,否则每次上传文件都会看到副本不足的提示。
dfs.namenode.handler.count这个参数在搭建阶段不用调,但值得知道它的算法:一般取20 * log(集群节点数)。三台机器按公式算大概是 20 出头,默认值 10 其实也够用。写在这里是为了说明一件事——Hadoop 的参数大多有推导逻辑,不是拍脑袋定的,理解逻辑比记住数值有用。
4.3 yarn-site.xml:资源调度器的关键几项
YARN 的配置直接决定了作业能不能跑起来,也是最容易配出"集群活着但作业不动"的地方。
| 参数 | 建议值 | 说明 |
|---|---|---|
yarn.nodemanager.aux-services | mapreduce_shuffle | 必配,否则 Reduce 阶段拿不到数据 |
yarn.resourcemanager.hostname | node01 | ResourceManager 所在主机 |
yarn.nodemanager.resource.memory-mb | 2048 | 单节点可分配给容器的物理内存 |
yarn.scheduler.maximum-allocation-mb | 2048 | 单个容器可申请的最大内存 |
yarn.nodemanager.vmem-check-enabled | false | 关闭虚拟内存检查,测试环境强烈建议 |
yarn.log-aggregation-enable | true | 开启日志聚合,方便集中查看 |
yarn.nodemanager.remote-app-log-dir | /tmp/logs | 聚合日志的 HDFS 路径 |
yarn.nodemanager.aux-services配错或不配,症状特别有迷惑性:Map 阶段正常跑完,Reduce 阶段卡在 0% 或者直接报Shuffle failed。因为 Shuffle 是 Reduce 从各 Map 节点拉取数据的阶段,走的就是这个 aux-service 提供的服务。很多人会去怀疑网络或者磁盘,其实是这一行忘了写。
yarn.nodemanager.resource.memory-mb的取值要有计算依据。假设机器 4GB 内存,操作系统和 Hadoop 各守护进程大约占用 1.5GB,剩下 2.5GB 可以给容器用,取 2048 比较稳妥。如果你直接按物理内存填 4096,NodeManager 会真的往上报 4GB,然后一旦调度满,机器就开始 swap,整个节点响应慢到无法操作,严重的会被系统 OOM killer 干掉进程。这个参数宁小勿大,你可以在跑通之后逐步往上调,直到找到机器的真实上限。
yarn.nodemanager.vmem-check-enabled=false是我建议所有测试集群都加上的一项。默认情况下 YARN 会检查容器的虚拟内存使用,而 JVM 的虚拟内存占用往往远大于实际堆内存(尤其是配了大-Xmx但实际用得少的时候),结果就是容器明明还活着,却因为"超出虚拟内存限制"被 kill,日志里是Container [pid=xxx] is running beyond virtual memory limits。关掉这个检查能用实际内存来约束,虽然不够严格,但测试环境里能避免大量无谓的排查时间。
4.4 mapred-site.xml、workers 与 hadoop-env.sh
mapred-site.xml决定 MapReduce 作业跑在哪个框架上,以及作业运行时的环境变量。3.x 版本里,这几项缺失会导致作业提交后立刻失败,报Could not find or load main class之类的错误:
<property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> <property> <name>yarn.app.mapreduce.am.env</name> <value>HADOOP_MAPRED_HOME=/opt/module/hadoop-3.3.6</value> </property> <property> <name>mapreduce.map.env</name> <value>HADOOP_MAPRED_HOME=/opt/module/hadoop-3.3.6</value> </property> <property> <name>mapreduce.reduce.env</name> <value>HADOOP_MAPRED_HOME=/opt/module/hadoop-3.3.6</value> </property> <property> <name>mapreduce.jobhistory.address</name> <value>node01:10020</value> </property> <property> <name>mapreduce.jobhistory.webapp.address</name> <value>node01:19888</value> </property>HADOOP_MAPRED_HOME这三个环境变量是 Hadoop 3.x 特有的坑。2.x 时代不需要配,3.x 把 MapReduce 的 jar 拆得更细,容器启动时找不到 MapReduce 相关的类路径,就会报类找不到。这三个变量告诉容器去哪个目录找 MapReduce 的依赖。路径要写成绝对路径,不能写$HADOOP_HOME,因为容器里的环境变量不一定有 HADOOP_HOME。
workers文件(2.x 里叫slaves)内容很简单,每行一个主机名,列出所有需要启动 DataNode 和 NodeManager 的机器:
node01 node02 node03注意这里要包含 NameNode 所在的 node01,因为 node01 同时也是个 DataNode。漏写自己的主机名,会导致start-dfs.sh启动后 node01 上没有 DataNode,虽然集群看起来是通的,但副本分布和数据本地性都会异常。
hadoop-env.sh里必须显式声明JAVA_HOME。Hadoop 3.x 对这个特别敏感,即使系统环境变量里有 JAVA_HOME,脚本也可能读不到,报JAVA_HOME is not set and could not be found。写法是:
export JAVA_HOME=/opt/module/jdk1.8.0_212JDK 版本必须选 8。虽然 3.3 版本官方支持 JDK 11,但实际使用中 MapReduce 和部分依赖在 11 上会出现反射相关的警告甚至错误,测试环境没必要给自己找麻烦。JDK 装在/opt/module/这类固定路径下,不要放在用户家目录,因为运行 Hadoop 的用户可能变化,路径带用户名的配置很容易失效。
4.5 关键参数对照表与分发脚本
配置都改完之后,一定要把这套文件分发到另外两台机器上,而且分发的时机和内容要一致。手动 scp 是最稳的:
# 在 node01 上执行,把 etc/hadoop 目录同步过去 for h in node02 node03; do scp -r $HADOOP_HOME/etc/hadoop/* $h:$HADOOP_HOME/etc/hadoop/ done如果你经常改配置,可以写一个简单的同步脚本xsync,用 rsync 做增量同步。这里有个细节:scp -r source/*和scp -r source行为不同,前者是把目录里的内容铺到目标目录,后者是连目录本身一起拷。用错了会出现hadoop/hadoop/这种嵌套目录,配置覆盖不到真正的配置文件上,症状是"我明明改了配置,怎么重启还是老样子"。
分发完记得核对关键文件的校验值,或者至少cat一遍确认内容对得上。我在早期搭集群时有过一次经历:三台机器的hdfs-site.xml里dfs.replication一个是 3 一个是 2,导致上传文件后副本分布乱七八糟,查了快一个小时才想到去比对配置文件。
5. 格式化与启动:顺序错一步就重装
配置检查无误之后,就进入最紧张的环节。这里的每一步都有"做错了需要重来"的性质,所以动手之前建议先把数据目录清干净、把流程在心里过一遍。
5.1 namenode -format 只能做一次的原因
格式化的命令是:
hdfs namenode -format它的实际动作是在dfs.namenode.name.dir指定的目录里创建current子目录,生成初始的fsimage文件,并写入一个随机生成的clusterID到VERSION文件里。DataNode 首次启动时会向 NameNode 注册,并把自己目录里的VERSION文件写上同一个 clusterID。
问题的关键就在这个 clusterID 上。如果你在 DataNode 已经启动过一次并写入了 clusterID 之后,再次执行格式化,NameNode 会重新生成一个全新的 clusterID,而 DataNode 目录里还留着旧的。此时 DataNode 启动时比对发现不一致,直接报Incompatible clusterIDs并退出。表现就是:start-dfs.sh执行完,jps里没有 DataNode。
所以正确的姿势是:格式化只在首次搭建时执行一次,之后无论什么原因重启集群,都不需要也不能再格式化。如果真的格式化了两次,补救办法是把所有机器上的数据目录(name.dir 和 data.dir)全部清空,然后重新格式化一次,让三台从同一个起点开始:
# 三台都执行,注意路径要和配置文件里一致 rm -rf /data/hadoop/name/* rm -rf /data/hadoop/data/* # 然后回到 node01 重新格式化 hdfs namenode -format清目录这个操作有风险,执行前先ls一遍确认路径没写错,尤其是不要在多块盘挂载的环境里对着根目录操作。
5.2 启动顺序、jps 校验与三个 Web 界面
启动顺序上,先起 HDFS,再起 YARN,最后起 JobHistory:
start-dfs.sh start-yarn.sh mapred --daemon start historyserver每起完一步就jps一次,不要一口气全起完再看。分步检查能立刻定位是哪一层出了问题。
node01 上应该看到:NameNode、DataNode、ResourceManager、NodeManager、JobHistoryServer、Jps。 node02 上应该看到:SecondaryNameNode、DataNode、NodeManager、Jps。 node03 上应该看到:DataNode、NodeManager、Jps。
少进程是最常见的现象,排查思路是先看日志。Hadoop 的日志在$HADOOP_HOME/logs/下,按角色命名,比如hadoop-hadoop-datanode-node02.log。直接看日志末尾 50 行,绝大多数启动失败的原因都写在里面,而且比搜索引擎给你的答案更准确。
三个 Web 界面是验证集群状态的快捷方式:
| 服务 | 地址 | 能看什么 |
|---|---|---|
| HDFS | http://node01:9870 | 集群概览、DataNode 列表、文件浏览、块健康度 |
| YARN | http://node01:8088 | 集群资源、运行中作业、调度队列 |
| JobHistory | http://node01:19888 | 已完成作业的历史详情 |
HDFS 界面里重点看两处:Live Nodes 是不是 3,以及 Summary 里的 Under-replicated blocks 是不是 0。如果是 0 且节点数对得上,说明 HDFS 层已经健康。3.x 版本的端口从 50070 改成了 9870,如果你照着老教程敲 50070 打不开页面,不是集群坏了,是端口变了。
5.3 关停顺序为什么不能反
停机的顺序和启动相反:先停 YARN,再停 HDFS,最后停 JobHistory。
mapred --daemon stop historyserver stop-yarn.sh stop-dfs.sh反过来的问题在于:如果先停 HDFS,DataNode 已经退出但 NodeManager 还活着,此时正在跑的容器如果需要读写 HDFS 就会失败,虽然测试环境里通常没有正在运行的作业,但养成正确顺序的习惯很重要。另外,直接kill -9掉 NameNode 进程是绝对要避免的,元数据虽然不会立刻损坏,但 edits 日志没有正常关闭,下次启动会做长时间的恢复,严重的会进入安全模式出不来。
6. 从上传文件到跑通一个作业:验证集群真的活了
jps有进程、Web 页面打得开,只说明服务起来了,不能说明集群能干活。真正的验证要落到"数据能写进去、作业能跑完"。
6.1 用副本分布验证 DataNode 是否真的在干活
先建目录、传文件、看结果:
hdfs dfs -mkdir -p /input hdfs dfs -put $HADOOP_HOME/README.txt /input/ hdfs dfs -ls /input hdfs dfs -cat /input/README.txt | head -20文件传上去之后,用hdfs fsck检查块的副本分布:
hdfs fsck /input/README.txt -files -blocks -locations输出里会列出每个块所在的 DataNode,正常情况下一共有三份副本、分布在不同主机上。如果看到某台机器承担了两份,说明副本放置策略没生效,通常是workers文件漏了某台机器,或者某台 DataNode 其实没启动。
再确认一下整体容量:
hdfs dfsadmin -report这个命令会列出每个 DataNode 的 IP、容量、已用空间、剩余空间。三台都出现且容量正常,HDFS 层就没问题了。如果只显示两台,回到上一节查 clusterID 和日志。
这里有个容易忽略的细节:hdfs dfs -put上传的文件默认块大小是 128MB,README 这种小文件只占一个块,三副本分布看起来不明显。想验证得更充分,可以传一个稍大的文件,或者上传多个文件,然后逐个fsck。测试集群里更快的办法是直接把dfs.blocksize临时调小到 1MB,传个几 MB 的文件就能看到多块分布,验证完再调回去。
6.2 提交一个自带示例作业看 YARN 调度
Hadoop 自带了一批示例 jar,路径在$HADOOP_HOME/share/hadoop/mapreduce/下,文件名类似hadoop-mapreduce-examples-3.3.6.jar。用pi程序验证最省事,因为它不需要输入数据:
hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar pi 10 10提交之后立刻去http://node01:8088看作业状态,正常会经历 RUNNING 然后 FINISHED。如果一直停在 ACCEPTED,说明资源申请不到,去查yarn.nodemanager.resource.memory-mb是不是配得太小、或者 NodeManager 没起来。如果状态是 FAILED,点进作业详情看每个 container 的日志,错误信息通常很明确。
想验证数据本地性,用wordcount更有说服力:
hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar wordcount /input /output hdfs dfs -cat /output/part-r-00000 | head跑完之后,在 YARN 界面的作业详情里看每个 Map Task 的节点信息。如果所有 Map Task 都只在一台机器上跑,说明数据分布不均或者workers配置有问题;正常应该看到任务分散在不同 DataNode 上,而且每个任务的 Host 字段就是该块所在的主机。
6.3 作业卡住或失败时的日志定位路径
作业出问题的第一现场永远是 YARN 的 Web 界面,而不是控制台输出。控制台只会告诉你Job failed,具体原因在容器日志里。如果开了日志聚合,作业结束后点进作业详情就能直接看到各容器的日志链接;没开聚合的话,要去对应节点的$HADOOP_HOME/logs/userlogs/下找。
一个通用的排查路径是这样的:
- 控制台看作业 ID 和失败阶段(Map 还是 Reduce)。
- YARN 界面搜作业 ID,看 Diagnostics 那一栏,通常会有直接的原因。
- 点进失败的 Task,看对应的 container 日志末尾。
- 如果是 Shuffle 阶段失败,回头检查
yarn.nodemanager.aux-services。 - 如果是容器被杀,看是内存问题还是虚拟内存检查问题。
- 如果是连接超时,回到 hosts 和免密那一节。
这套路径我在不同环境里用过很多次,大部分问题都能在前三步定位到。比盲目搜索"MapReduce 作业失败"高效得多。
7. 常见故障的排查链路与几个反直觉的坑
集群搭起来只是开始,真正消耗时间的是那些"看起来正常但实际不工作"的状态。这里列几个我实际遇到过、而且第一次遇到时都花了不短时间的问题。
7.1 DataNode 起来了又没了:clusterID 不一致
症状:start-dfs.sh执行完,node02 和 node03 上有 DataNode,node01 上没有;或者刚启动时三个都有,过几分钟 node01 的 DataNode 自己消失了。
排查链路是这样的:先看hadoop-hadoop-datanode-node01.log,如果里面出现Incompatible clusterIDs,就是格式化的问题,按 5.1 节的办法清数据目录重来。如果日志里是java.io.IOException: All directories in dfs.datanode.data.dir are invalid,说明数据目录被删了或者权限不对,重建目录并chown给运行用户。还有一种情况是磁盘满了,日志里会写No space left on device,测试环境里虚拟机磁盘给得小,很快就会被日志和数据块占满,定期df -h是个好习惯。
还有一种特别隐蔽的情况:node01 上的 DataNode 目录曾经被手动改过,比如为了"清理空间"删了几个块文件。DataNode 启动时做目录扫描,发现块文件数量和元数据对不上,会尝试恢复,恢复失败就退出。这种问题看日志能看出来,但结论是"数据目录被污染了",最省事的处理方式还是清空重建——测试集群里数据没有价值,重建的成本远低于修复。
7.2 副本数配 3 但只有 2 台机器会发生什么
这不是故障,而是配置和现实不匹配。dfs.replication=3在只有两台 DataNode 的环境里,文件依然能正常上传和读取,但dfsadmin -report会一直显示 Under-replicated blocks,HDFS 界面上的块健康度永远不会是 100%。更麻烦的是,NameNode 会持续尝试复制第三个副本,反复失败,日志里会有大量重复的复制失败记录,掩盖真正的问题。
判断依据很简单:Live Nodes数量小于dfs.replication的值,就把副本数改成实际节点数。三台机器配 3、两台配 2,这是最省心的对应关系。如果确实需要三副本而只有两台,那就得在评估时接受"副本不足"这个持续存在的告警,或者考虑加机器。
7.3 内存与虚拟内存检查导致的容器被杀
症状:作业跑着跑着某个 Task 突然失败,日志里写Container killed by YARN for exceeding memory limits。这时候不要急着去调大mapreduce.map.memory.mb,先看两个方向。
第一个方向是虚拟内存检查。如果报的是beyond virtual memory limits(注意措辞里的 virtual),就是 4.3 节说的那个参数在作怪,加yarn.nodemanager.vmem-check-enabled=false关掉。
第二个方向是物理内存真的不够。这时候要算一笔账:单节点可用内存(resource-memory-mb)要大于所有可能同时运行的容器内存之和。一个作业的 Map 容器默认 1024MB,如果调度器允许同时跑 3 个容器,就是 3072MB,超过节点可分配内存就会互相挤。解决办法要么调小单个容器内存(mapreduce.map.memory.mb=512),要么调大节点内存配额,要么限制并发数。这三个选项里,测试环境最实用的是调小容器内存。
7.4 我踩过的几个小坑清单
最后集中列一批零碎但高频的问题,都是我在不同环境里反复见到过的:
| 现象 | 大概率原因 | 快速验证 |
|---|---|---|
start-dfs.sh卡住无输出 | 免密未配或首次指纹未确认 | 手动ssh node02看是否要密码 |
命令提示hdfs: command not found | PATH 环境变量未配或未生效 | echo $HADOOP_HOME,source /etc/profile |
| Web 页面打不开但进程在 | 端口变了或防火墙没关 | ss -tlnp | grep 9870 |
上传文件报could only be replicated to 0 nodes | DataNode 未启动或磁盘满 | hdfs dfsadmin -report、df -h |
作业报Could not find or load main class | HADOOP_MAPRED_HOME未配 | 检查 mapred-site.xml |
NameNode 启动报Directory does not exist | 元数据目录未创建或权限不对 | ls -ld /data/hadoop/name |
| 从节点主机名解析失败 | hosts 文件不一致 | 三台分别getent hosts node0x |
| 改了配置但行为没变 | 配置未分发或分发了嵌套目录 | 三台比对文件内容 |
关于 PATH 还有一个细节值得说:hadoop、hdfs、yarn、mapred这几个命令都在$HADOOP_HOME/bin和$HADOOP_HOME/sbin下,前者是客户端命令,后者是集群管理脚本。如果只配了 bin 没配 sbin,你会发现hdfs能用但start-dfs.sh找不到。标准做法是在/etc/profile.d/里加一个脚本,把两个目录都写进去,然后source生效。改完记得确认新开的 shell 里也生效,别只在当前会话里 export 了一下就觉得配好了。
另外提一句关于"重建虚拟机"的经验。搭建过程中如果问题实在绕不出来,重建三台虚拟机重新走一遍流程,往往比继续在泥潭里挣扎更快。听起来很粗暴,但实际算下来:你已经知道了所有步骤,重建全程 30 分钟;而在一个状态混乱的集群上继续排查,很可能两小时还没头绪。前提是你把每一步都记下来,重建是为了验证"一次干净的操作能不能成功",而不是碰运气。我第一次搭这套环境时,前后重建了三遍,第三遍之后才真正理解每个配置文件在干什么——这个过程的收获,远大于一次照着教程的成功。