做大数据这行,Hadoop完全分布式集群是绕不开的第一道坎。我当年第一次搭“真分布”时,在网上翻了一堆教程,跟着一步步点,结果卡在节点起不来、DataNode神秘消失这些莫名其妙的问题上,折腾了整整两天。后来回头看,核心问题其实就两件事:一是配置参数没吃透,二是环境细节被忽略。这篇文章把我后来多次搭建的经验整理成一套可以直接照着做的流程,目标很简单——在Ubuntu 16.04上,从零把一套多节点的Hadoop完全分布式集群跑起来,并且让你知道每一步在做什么、出了问题从哪查。内容不追求大而全,只围绕“能跑、能验证、能排查”这三个核心目标展开,适合刚接触大数据、第一次接触多节点集群、或者在伪分布式上憋了很久想上真环境的同学参考。
1. 环境准备:先把地基打牢
1.1 节点规划与基础配置
搭建完全分布式集群,第一步不是装软件,而是先把机器的网络和身份规划清楚。真分布和伪分布式最大的区别在于:进程分散在多台物理机或虚拟机上,节点之间需要通过网络互相通信。如果网络规划得乱,后面一切配置都会跟着乱。
我建议最少准备三台机器,分别承担Master和Worker角色。用虚拟机也好,用几台旧物理机也罢,并没有本质区别。但在规划阶段要记住一个原则:NameNode和ResourceManager所在的节点,资源要尽量给足,因为整个集群的元数据和资源调度都在这一台上。我见过有人把Master配得比Worker还差,结果集群一跑起来,NameNode先卡死了。
下面是一份可以参考的规划表:
| 节点角色 | 主机名 | IP示例 | 主要进程 |
|---|---|---|---|
| Master | hadoop-master | 192.168.1.10 | NameNode、ResourceManager、SecondaryNameNode |
| Worker1 | hadoop-worker1 | 192.168.1.11 | DataNode、NodeManager |
| Worker2 | hadoop-worker2 | 192.168.1.12 | DataNode、NodeManager |
主机名的设置非常关键。Hadoop集群内部的通信依赖主机名解析,所以我习惯先把三台机器的/etc/hostname改掉,再在每台机器的/etc/hosts里把三个节点的IP和主机名对应关系全部写上。注意是每台机器都要写全,不能只在Master上写。很多人只改了Master的hosts文件,结果Worker节点解析不了Master的主机名,直接导致Datanode启动后无法注册,这是非常典型的低级错误。
另外,建议所有节点使用同一个系统用户,比如统一用hadoop用户来安装和启动集群。这样能省掉大量权限问题。如果临时用了root,后面跑MapReduce任务时往往会因为文件权限问题报错,别问我怎么知道的。
1.2 统一JDK环境
Hadoop是Java写的,所以JDK是所有环节里最不能出岔子的一步。Ubuntu 16.04自带的是OpenJDK,但系统里可能同时存在多个Java版本,导致java -version和hadoop命令用的不是同一个JDK。
我建议在所有节点上安装同一版本的JDK 8,安装完手动验证一下:
sudo apt update sudo apt install openjdk-8-jdk -y java -version看到类似openjdk version "1.8.0_xxx"的输出,就说明装好了。然后在/etc/profile里统一配置JAVA_HOME,并让配置立即生效:
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 export PATH=$PATH:$JAVA_HOME/binsource /etc/profile echo $JAVA_HOME注意,这个JAVA_HOME路径在每台机器上都要确认一遍,因为不同系统的实际路径可能有细微差别。用readlink -f $(which java)可以找出真实路径,比凭记忆写死路径更靠谱。我之前就在一台机器上写错了路径,结果所有节点都启动成功,只有那台机器的Datanode一直报错,排查了很久才发现是JAVA_HOME指向了不存在的位置。
1.3 打通SSH免密登录
“真分布”集群里,Master节点启动HDFS或YARN时,会通过SSH登录到各个Worker节点去拉起远程进程。如果每次都要输密码,分布式集群根本没法自动启动。所以免密登录不是可选项,而是必选项。
首先在Master节点生成SSH密钥:
ssh-keygen -t rsa -P ""一路回车即可,不用设置密码。然后把公钥分发到三台机器(包括Master自己):
ssh-copy-id hadoop@hadoop-master ssh-copy-id hadoop@hadoop-worker1 ssh-copy-id hadoop@hadoop-worker2分完之后,做一次全量验证:
ssh hadoop-master hostname ssh hadoop-worker1 hostname ssh hadoop-worker2 hostname每条命令都能直接返回主机名,而不再要求输入密码,这才算过关。这里有个小细节:.ssh目录权限必须是700,authorized_keys文件权限必须是600。权限稍大,SSH就会基于安全策略直接拒绝免密登录,这是我踩过最频繁的坑之一。
2. Hadoop核心配置文件逐个拆解
2.1 core-site.xml:全局入口
环境准备完之后,真正决定集群能否跑起来的是Hadoop那一堆XML配置文件。很多教程直接让你复制一段现成配置,但复制完出了问题完全不知道改哪里。所以我更愿意把每个关键参数都讲明白。
第一个文件是core-site.xml,位于$HADOOP_HOME/etc/hadoop/目录下。它主要定义全局属性,其中最重要的就是默认文件系统地址。配置如下:
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://hadoop-master:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/home/hadoop/hadoop_data/tmp</value> </property> </configuration>fs.defaultFS指定了NameNode的RPC通信地址。这里的hdfs://hadoop-master:9000是客户端和DataNode访问NameNode的统一入口,主机名必须和hosts里的配置完全一致。我见过有人在这里写IP,也能跑,但如果后来IP变了,就得改配置重启集群,显然不如用主机名灵活。
hadoop.tmp.dir是Hadoop存放临时文件的根目录,NameNode的元数据、DataNode的数据块默认都会存放在这个目录下。如果不手动指定,默认指向/tmp,系统一清理,整个集群的数据就全没了。这个参数务必手工指定到一个持久化目录,比如/home/hadoop/hadoop_data/tmp,并提前建好目录。
2.2 hdfs-site.xml:数据与副本策略
接着是hdfs-site.xml,它控制的是HDFS的存储行为。先看配置:
<configuration> <property> <name>dfs.replication</name> <value>2</value> </property> <property> <name>dfs.namenode.name.dir</name> <value>/home/hadoop/hadoop_data/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/home/hadoop/hadoop_data/datanode</value> </property> <property> <name>dfs.namenode.secondary.http.address</name> <value>hadoop-master:50090</value> </property> </configuration>dfs.replication是数据块副本数。三台节点时,默认值是3,但如果你只有两台DataNode,副本数为3会一直报警告。所以我在三节点集群里习惯设为2,既保留冗余,又不会因为副本数不足导致HDFS进入安全模式。这个值不是越大越好,它直接影响存储利用率和写入效率。
dfs.namenode.name.dir和dfs.datanode.data.dir分别指定NameNode元数据目录和DataNode数据块目录。这两个目录必须分开,绝不能共用或指向系统临时目录。NameNode的元数据如果丢了,整个集群的文件目录树也就没了,比数据块丢失还要命。所以工业级的做法还会给元数据目录做多副本,起步阶段至少要做到目录独立、持久化存储。
2.3 yarn-site.xml和mapred-site.xml:调度与计算框架
存储层配置好之后,还要配置计算层。YARN负责资源调度,MapReduce负责计算逻辑,两者相互配合。先看yarn-site.xml:
<configuration> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.resourcemanager.hostname</name> <value>hadoop-master</value> </property> </configuration>yarn.nodemanager.aux-services必须设置成mapreduce_shuffle,这个名字看着怪,但它是MapReduce任务在NodeManager之间进行数据交换的辅助服务。如果不配,任务运行时会直接报Shuffle相关的错误。
yarn.resourcemanager.hostname指定了ResourceManager所在节点。同样,主机名要和hosts解析一致。还有一个我在生产环境里经常调的参数是yarn.nodemanager.resource.memory-mb,它控制单个NodeManager能分配多少内存给容器。在测试环境里不调也能跑,但如果你发现任务总是被Kill,大概率是默认内存参数和真实机器内存不匹配。
再看mapred-site.xml。Hadoop安装包默认只有mapred-site.xml.template模板文件,需要手动复制一份:
cp mapred-site.xml.template mapred-site.xml文件内容主要设置计算框架为YARN:
<configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> </configuration>如果把mapreduce.framework.name设成local,MapReduce任务就会在本机运行,不走集群资源调度,那就相当于退化成伪分布式了。
2.4 workers文件与环境变量
在较新的Hadoop版本中,slaves文件已经改名成workers,用来声明哪些节点是Worker节点。需要在Master节点的$HADOOP_HOME/etc/hadoop/workers文件里逐行写上Worker的主机名:
hadoop-worker1 hadoop-worker2注意,这里面只需要写Worker节点,不需要写Master自身,除非你希望Master也承担DataNode角色。我个人习惯是Master不装DataNode,只做调度和管理,让Worker专注存储和计算。这样的部署隔离对排查问题更友好,因为你一眼能看出进程是否分布正确。
环境变量方面,还要在hadoop-env.sh里再次确认JAVA_HOME:
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64之前提到在/etc/profile里配置了JAVA_HOME,为什么这里还要再配一次?因为Hadoop的启动脚本在某些场景下不会加载系统的profile变量,直接读取hadoop-env.sh。这是个非常隐蔽的坑,很多教程都不强调,但如果你发现所有节点都能启动、只有远程进程起不来,先看这里。
3. 启动流程、初始化与功能验证
3.1 首次格式化NameNode的时机与风险
配置全部完成后,在第一次启动集群之前,必须先格式化NameNode。格式化过程的本质是初始化HDFS的元数据目录,生成一个空的文件系统镜像。命令如下:
hdfs namenode -format这条命令只能在Master上执行一次。为什么强调“只能一次”?因为格式化会生成一个唯一的clusterID,DataNode启动后会上报这个ID并与NameNode保持一致性。如果你在集群运行后再次格式化NameNode,新的clusterID会和DataNode已有的不一致,所有DataNode都会被拒绝注册,表现就是集群里看不到任何DataNode。
所以,如果你不确定当前集群是否已经初始化过,就先用jps看一下进程,或者检查一下dfs.namenode.name.dir目录里是否有current文件夹。贸然格式化通常不会解决问题,反而会把问题搞得更复杂。
3.2 分布式启动与进程核对
格式化完成之后,在Master节点上执行一条命令就可以启动整条HDFS链路:
start-dfs.sh这条命令会通过SSH免密登录到所有Worker节点,在Master上拉起NameNode和SecondaryNameNode,在Worker上拉起DataNode。启动过程通常会打印各个节点上的进程日志位置,如果中间有失败,优先去看对应节点的日志文件。
如果HDFS启动成功,再执行:
start-yarn.sh它会启动ResourceManager和NodeManager。两步都完成后,用jps命令分别在各节点上检查进程:
- Master节点上应该有:
NameNode、ResourceManager、SecondaryNameNode - Worker节点上应该有:
DataNode、NodeManager
如果某个进程缺失,先不要重启整个集群,而是单节点单独启动对应进程。比如Master上的NameNode挂了,可以执行hdfs --daemon start namenode;Worker上的DataNode挂了,可以在对应机器上执行hdfs --daemon start datanode。这样定位问题比反复stop-all.sh再start-all.sh要高效得多。
3.3 功能验证:Web UI与文件读写
进程都起来之后,有两个Web页面可以直接验证集群状态。如果你用的是Hadoop 3.x,HDFS页面地址是http://hadoop-master:9870,而YARN页面是http://hadoop-master:8088。如果你还在用老版本Hadoop 2.x,那HDFS端口是50070,这个差异经常让照着新教程配老版本的人一脸懵。
打开HDFS页面后,重点看“Datanodes”列表,确认两个Worker节点的状态是正常的。如果节点显示为“Dead”,说明DataNode和NameNode之间的心跳断了,需要回看日志。
接着可以做一个文件上传的简单测试,验证HDFS是否真的可用:
hdfs dfs -mkdir /test hdfs dfs -put /etc/profile /test/ hdfs dfs -ls /test/正常能看到profile文件已经上传,并且Replication那一列显示副本数。再把它拉下来对比一下内容是否一致:
hdfs dfs -get /test/profile /tmp/profile_check diff /etc/profile /tmp/profile_check没有差异输出,说明HDFS存储链路完全没问题。
3.4 跑一个MapReduce任务做终验
文件上传只能验证HDFS,真正的分布式能力要用一个真实任务来验证。Hadoop自带了很多示例程序,最经典的是WordCount。执行下面这条命令:
hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar wordcount /test /test/output跑完后再用hdfs dfs -cat /test/output/part-r-00000查看结果。如果你看到每个单词以及对应的统计数字,那么这个集群的计算链路也通了。注意/test/output目录如果已经存在,任务会报错,所以每次跑之前记得先删掉:
hdfs dfs -rm -r /test/output我习惯在一套新集群上先跑一个WordCount,不是为了看词频,而是为了确认Map和Reduce任务能在不同节点上调度成功、Shuffle过程不报错。这一步过了,后面跑自己的业务数据就踏实了。
4. 常见问题排查与避坑实录
4.1 DataNode神秘消失
这是我在各个技术社区里看到频率最高的问题:NameNode起来了,Web页面上却没有DataNode。排查思路可以按下面这个顺序走:
| 检查项 | 排查命令/方法 | 可能原因 |
|---|---|---|
| DataNode进程是否存在 | 在Worker节点执行jps | 进程没起来,看启动日志 |
| 网络是否通 | ping、telnet 主机名 9000 | hosts配置错误或防火墙拦截 |
| clusterID是否一致 | 查看namenode和datanode的current/VERSION文件 | 重复格式化集群导致ID不一致 |
| 磁盘空间是否不足 | df -h | DataNode数据目录所在磁盘写满 |
大多数情况都逃不出这几类。尤其是clusterID不一致,解决办法是停止所有节点,分别在三台机器的hadoop_data目录下把遗留的namenode和datanode目录全部清空,然后只格式化一次NameNode,再重新启动集群。这个方法治标也治本,但一定谨慎,它会清掉所有数据。
4.2 SSH免密失效
SSH免密是分布式启动命令的生命线。如果start-dfs.sh卡在某个节点上不断要求输入密码,原因大致有几种:公钥没有正确分发到该节点;authorized_keys内容不对;目录权限过大。排查顺序是先手动执行ssh hadoop-worker1 hostname,看看能不能免密。不能的话,重新执行ssh-copy-id。还不行就登录到Worker节点,检查~/.ssh目录和authorized_keys文件的权限。注意,我见过最隐蔽的问题是用户的home目录本身权限是777,SSH会拒绝使用这种目录下的密钥。
4.3 内存不足与任务被Kill
MapReduce任务在运行过程中被系统Kill,日志里经常出现Container is running beyond physical memory limits。这不是Hadoop的bug,而是容器内存配置超过机器实际可用内存。解决办法是修改yarn-site.xml里的资源参数,比如:
<property> <name>yarn.nodemanager.resource.memory-mb</name> <value>8192</value> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>8192</value> </property>具体数值要根据机器总内存合理分配。比如一台8GB内存的Worker,给YARN分配6GB左右比较稳妥,要留出系统本身和DataNode的运行余量。不然NodeManager进程本身都可能因为内存问题先倒下。
4.4 其他高频问题
端口冲突也是测试环境里的常客。HDFS的9870或50070端口如果被占用,NameNode的Web界面就打不开。排查时用ss -lntp | grep 9870看看是哪个进程占用了端口,必要时改hdfs-site.xml里的端口配置。
还有一个容易被忽略的问题是防火墙。Ubuntu 16.04默认没开防火墙,但如果你之前手动开过UFW,就需要确保9000、9870、8088等端口是放行的,或者直接在测试环境里关闭:
sudo ufw disable最后提醒一点:Ubuntu 16.04的生命周期早就结束了,如果是在老环境里做学习实验,问题不大;但上生产系统时,更推荐用较新的LTS版本搭配对应兼容的Hadoop版本,能少踩很多依赖漏洞的坑。
最后再分享一个让我最省心的习惯:每次搭建前,先把所有节点的hosts文件、JAVA_HOME路径、免密登录情况集中验证一遍,再动手配Hadoop。表面上看多花了十分钟,实际能帮你省掉后面好几个小时的排查时间。这套“先环境、后配置、再启动、最后验证”的顺序,我后来在多少个集群上重复用都很稳。