很多人看到“完全分布式”这四个字就有点发怵,觉得要比伪分布式难很多。其实你只要把一个核心观念转过来——分布式不是多装几台机器的事,而是要让多台机器“商量着干活”,Hadoop 完全分布式的搭建就是把这个商量过程手动配一遍。这篇博文从零开始,用最直白的话把整个搭建流程拆开讲,适合刚学会 Linux 基本命令、准备入门大数据的读者参考。我会把每一步背后的想法也写出来,这样你以后排查问题就有思路,而不是只会照着命令敲。
1. 动手之前,先想清楚“完全分布式”到底长什么样
1.1 一台 NameNode 加两台 DataNode:最朴素的集群模型
完全分布式和伪分布式最大的区别,在于角色的“分家”。伪分布式里官方给了一个非常“作弊”的玩法,让一个 Java 进程同时扮演所有角色,看着像集群,实际上还是一台机器。完全分布式就要诚实很多:每台机器各司其职。
最简单的完全分布式集群至少需要三台机器,角色分配可以这样:
| 主机名 | 角色 | 必须启动的进程 | 建议内存 |
|---|---|---|---|
| node01 | NameNode / ResourceManager | NameNode, ResourceManager | 至少 2GB |
| node02 | DataNode / NodeManager | DataNode, NodeManager | 至少 2GB |
| node03 | DataNode / NodeManager | DataNode, NodeManager | 至少 2GB |
NameNode 管的是“目录”,也就是元数据。DataNode 管的是“文件内容”,也就是真正的数据块。ResourceManager 管的是计算资源的分配,NodeManager 负责在各自机器上执行计算任务。你把它们想成一家公司的三层结构:NameNode 和 ResourceManager 是管理层,DataNode 和 NodeManager 是业务层。管理层负责记账和派活,业务层负责真正搬砖。
1.2 为什么不能再用一台电脑模拟
有的读者会问:我电脑配置一般,能不能开三台虚拟机?能,但你要知道资源开销在哪。Hadoop 集群里的每个进程都是独立的 JVM,哪怕数据量很小,JVM 本身也要吃内存。三台虚拟机每台分 2GB,物理机至少要有 8GB 内存才跑得舒服。如果你的电脑只有 8GB 内存,建议先升级到 16GB,或者退一步先把伪分布式研究透,等理解了进程之间如何通信再上多机。
这里还有一个容易忽略的点:虚拟机网络模式。搭建集群时不要用 NAT,直接用“仅主机模式”或者“自定义 VMnet”,让三台虚拟机处于同一个局域网,互相能 ping 通。用 NAT 的话,虚拟机之间通常也能通信,但容易和宿主机网络混在一起,调试端口时干扰因素更多。对于学习环境,我倾向于使用仅主机模式,配好静态 IP,简单干净。
1.3 我建议你这么做节点规划
初学者最容易犯的错误是给三台机器起名 node01、node02、node03,但 IP 随便配,或者干脆用 DHCP 自动获取。这不叫规划,这叫随缘。集群是需要长期稳定的东西,DHCP 一重启地址就变了,所有配置立刻失效。节点规划至少要定三件事:内网静态 IP、主机名、机器用途。
我自己常用的约定是:IP 段用 192.168.56.x 这种局域网网段,主机名和 IP 的对应关系统一写进每台机器的 /etc/hosts。比如:
192.168.56.10 node01 192.168.56.11 node02 192.168.56.12 node03为什么要写 /etc/hosts?因为 Hadoop 的配置里可以用主机名代替 IP,写主机名可读性强,而且后续如果换 IP,只需要改 hosts 文件,不用改一堆 XML 配置。这是一个很实用的习惯。
2. 零基础最容易翻车的准备阶段:从虚拟机到免密登录
2.1 Linux、JDK 和 Hadoop 版本怎么搭配
如果你完全是零基础,我建议操作系统直接用 CentOS 7 或者 Rocky Linux 8,教程多,踩坑后容易搜到结果。Ubuntu 也可以,但配置方式略有差异,初学者别把精力耗在系统差异上。
版本搭配上,Hadoop 3.3.x 配 Java 8 是最稳妥的组合。JDK 8 是老而弥坚的版本,Hadoop 框架本身没有用太多 Java 11 的新特性,使用 JDK 11 反而可能出现一些兼容性警告。记住:学习阶段选择版本组合,稳定比追新更重要。
安装 JDK 的过程我就不再展开说了,只强调一点:装完一定要确认java -version能用,并且设置 JAVA_HOME。很多初学者在 Hadoop 配置里写了一大堆参数,结果 Hadoop 启动脚本找不到 Java,报错信息还很绕——其实查一下 java 命令是否全局可用,一分钟就能排除。
2.2 固定 IP、hosts 和主机名,一个都不能少
给三台机器分别执行修改主机名的操作,例如:
hostnamectl set-hostname node01然后编辑 /etc/hosts,三台机器都要加上所有节点的解析。这里有个坑:有些教程让你把 hosts 里原来的主机名映射删掉,如果你不删,有些命令会解析到奇怪的名字上。实际操作中,保留默认的 localhost 解析,再追加三行映射即可。
为什么三台机器的 hosts 必须保持一致?因为 Hadoop 的进程会通过主机名互相通信。如果你只改了一台,另外两台解析不到 node01,启动时就会报 UnknownHost。这类错误排查起来很烦,为了避免它,我在三台机器上全部做同样的 hosts 配置。
IP 的配置也很关键。如果你用 CentOS,可以编辑/etc/sysconfig/network-scripts/ifcfg-ens33(网卡名可能不同),把 BOOTPROTO 改成 static,然后添加 IPADDR、NETMASK、GATEWAY 三项。改完执行systemctl restart network,再用ip addr验证。这一步看着不起眼,却是整个集群稳定性的地基。
2.3 SSH 免密登录:哪些机器需要,为什么必须做
这个环节很多教程都让做,但说得不够清楚。我把逻辑给你捋一下:
启动集群时,你通常在 node01 上执行 start-dfs.sh,脚本会通过 SSH 连到其他节点,远程启动 DataNode 或 SecondaryNameNode。如果 SSH 需要输密码,脚本执行过程中就会卡住,集群无法自动拉起。所以免密登录其实是为了“机器取代人工输入密码”。
需要配置免密的路径是:node01 到 node01、node01 到 node02、node01 到 node03。注意,node01 连自己也要免密,否则本地节点可能会因为需要输入密码而启动失败。
操作命令如下,在 node01 执行:
ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa ssh-copy-id node01 ssh-copy-id node02 ssh-copy-id node03然后逐个测试ssh node02是否直接进入,不需要密码。这里有一个零基础常见的报错:执行 ssh-copy-id 后要求输入密码,但输入又提示不正确。大多数原因是当前用户的家目录权限有问题。检查一下~目录、~/.ssh目录的权限,家目录不能是 777,.ssh目录建议是 700,authorized_keys文件建议是 600。权限太宽松,SSH 会拒绝信任。
2.4 关闭防火墙和 SELinux,学习环境必须做的妥协
网络通信全部正常之后,分布式搭建前的准备工作就剩这一件事。学习阶段的集群,建议把所有节点的 firewalld 直接停掉,并禁用开机自启:
systemctl stop firewalld systemctl disable firewalld然后临时关闭 SELinux:
setenforce 0永久关闭则编辑/etc/selinux/config,把SELINUX=enforcing改成SELINUX=disabled。注意要重启后才完全生效,或者你也可以保持临时关闭,先跑通再说。
我知道有经验的工程师会强调生产环境不能这么干,没错。防火墙和 SELinux 在真实生产环境中一定要按规则放行,但那是安全工程师的精细活,零基础阶段先把通信障碍全部清除,把 Hadoop 本身跑明白更重要。等你把集群搞熟了,再回来研究哪些端口需要放行——HDFS 的 9870、9820,YARN 的 8088、8042,这些都可以查官方文档来配置。
2.5 用专用用户运行集群,而不是 root
这个习惯我建议一开始就养好。创建一个 Hadoop 用户:
useradd hadoop passwd hadoop然后把解压好的 Hadoop 安装目录和之后的数据目录都授权给这个用户。为什么要用专用用户而不是 root?因为 Hadoop 的进程在 root 下运行,一旦脚本里出现相对路径的错误,风险会放大很多;而且 HDFS 文件系统的权限管理和 Linux 用户权限是联动的,用 root 时所有文件都是超管身份,后面学权限控制会很困惑。Hadoop 官方文档也明确不推荐用 root 运行集群。
3. 五个配置文件逐个拆解:想明白再复制粘贴
3.1 先定目录,再谈配置
Hadoop 解压后,你会看到 etc/hadoop 目录下有一堆 XML 模板。这些配置的核心语义只有一个:告诉各个进程“我是谁”“我的伙伴在哪”“数据往哪存”。零基础阶段至少要改五个文件:core-site.xml、hdfs-site.xml、yarn-site.xml、mapred-site.xml、workers。
在改之前,建议先创建一个专门的数据目录,比如/data/hadoop,并把 Hadoop 安装目录和数据目录分开放。为什么要分开?因为 Hadoop 解压包可能在升级时被整个替换,如果配置文件和真正的数据都堆在安装目录里,升级迁移时会很痛苦。把 name、data、tmp 这些状态数据放外面,升级时只要换安装包路径即可。
修改完成后记得把数据目录授权给 hadoop 用户:
mkdir -p /data/hadoop/{tmp,name,data} chown -R hadoop:hadoop /data/hadoop3.2 core-site.xml 与 hdfs-site.xml:HDFS 的“总部”在哪里
core-site.xml 的核心配置是fs.defaultFS,它决定客户端和集群通信时默认连接哪个 NameNode。我一般写成:
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://node01:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/data/hadoop/tmp</value> </property> </configuration>hadoop.tmp.dir是很多初学者忽略的配置。不配置的话,Hadoop 会默认使用系统的 /tmp 目录,而 Linux 系统重启后可能自动清理 /tmp 下的文件,结果就是 NameNode 的元数据“莫名其妙”丢失。这是一个典型的“默认值坑”,提前用专门目录能避开。
hdfs-site.xml 里要设的是数据存储路径和副本数。三台集群的配置参考如下:
<configuration> <property> <name>dfs.namenode.name.dir</name> <value>/data/hadoop/name</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/data/hadoop/data</value> </property> <property> <name>dfs.replication</name> <value>2</value> </property> <property> <name>dfs.namenode.secondary.http-address</name> <value>node02:9868</value> </property> </configuration>这里有两个点要讲透:第一,dfs.replication设成 2 是因为你只有两台 DataNode,副本数不能超过节点数,否则文件会一直处于 under-replicated 状态,看着像没备份好。如果你有 3 台 DataNode,设 3 比较合理。第二,SecondaryNameNode 不是 NameNode 的备份,它只是定期合并编辑日志,减轻 NameNode 的负担。所以它放在 node02 比较合适,别让这台机器同时背太多活,但也不用单独占用一整台机器。
3.3 yarn-site.xml 与 mapred-site.xml:让计算资源也能调度
HDFS 管存储,YARN 管计算。yarn-site.xml 里有两个必须的配置:
<configuration> <property> <name>yarn.resourcemanager.hostname</name> <value>node01</value> </property> <property> <name>yarn.nodemanager.aux-service</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.nodemanager.resource.memory-mb</name> <value>1024</value> </property> </configuration>yarn.resourcemanager.hostname告诉 NodeManager 去哪里找 ResourceManager。yarn.nodemanager.aux-service是 MapReduce 计算框架和 YARN 通信的桥梁,漏掉它,提交任务时会出现“shuffle 失败”之类的错误。yarn.nodemanager.resource.memory-mb要按虚拟机实际内存来写,我建议先设 1024,别贪多。你给虚拟机分了 2GB,如果这里写 2048,系统内存就不够用,NodeManager 启动后可能直接挂掉。
mapred-site.xml 原本叫 mapred-site.xml.template,需要先重命名再编辑。核心配置是计算框架用 YARN:
<configuration> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> <property> <name>mapreduce.application.classpath</name> <value>$HADOOP_HOME/share/hadoop/mapreduce/*:$HADOOP_HOME/share/hadoop/mapreduce/lib/*</value> </property> </configuration>第二个配置在 Hadoop 3.x 里很关键。新版 Hadoop 不会自动把 MapReduce 的依赖库放进 classpath,如果你不配置,运行 WordCount 时会报ClassNotFoundException: org.apache.hadoop.mapreduce.v2.app.MRAppMaster。这个问题我在刚开始用 Hadoop 3 的时候也遇到过,看着像是程序代码的问题,实际是 classpath 没设好。
3.4 workers 文件和 hadoop-env.sh:确认“谁出力”
在 Hadoop 3.x 里,slaves 文件已经改名为 workers。这个文件写的是哪些节点承担 DataNode 和 NodeManager 角色。注意不要写 NameNode 和 ResourceManager,它只管“工人”:
node02 node03另外在 etc/hadoop 目录下找到 hadoop-env.sh,在里面设置:
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk如果你把 JAVA_HOME 写到了 /etc/profile 里,这里还是要再写一次。因为 Hadoop 启动脚本是在独立环境中执行的,未必会加载 /etc/profile,这是一个很隐蔽的坑。直接在 hadoop-env.sh 里写死,比依赖系统环境变量更可靠。
4. 启动过程一步一步走:格式化、启动、验证必须按顺序来
4.1 格式化 NameNode 是唯一一次性的操作
所有配置都完成后,第一次启动前必须执行格式化命令。本质上是在 NameNode 的目录里生成一个集群身份标识文件,包含 namespaceID、clusterID 等信息。执行方式:
hdfs namenode -format看到输出中多次出现successfully formatted就说明成功了。格式化完成后,检查/data/hadoop/name/current目录,会有一个 VERSION 文件,里面就是集群的身份证。DataNode 启动时,会拿着自己的 clusterID 找 NameNode 核对,对不上就会被拒绝注册。
这是我特别想强调的一点:这个命令是有破坏性的,不要随手反复执行。如果你集群已经跑了一段时间,因为某种原因想重新格式化,必须先搞清楚后果。直接再执行一次格式化,会让 NameNode 生成一个新的 clusterID,而老 DataNode 上还保留着旧 clusterID,两边对不上,DataNode 会报错甚至退出。
4.2 后台脚本启动与 jps 检查
启动集群的操作在 node01 上执行。新版 Hadoop 有两个脚本,分工明确:
start-dfs.sh start-yarn.shstart-dfs.sh 会按 workers 文件里的列表远程启动 DataNode;start-yarn.sh 会启动 ResourceManager 和所有 NodeManager。跑完后,在每台机器上执行jps看进程。正常的输出是这样的。
node01 上:
12345 NameNode 12346 ResourceManagernode02 上:
23456 DataNode 23457 NodeManagernode03 上:
34567 DataNode 34568 NodeManager如果你发现 node02 和 node03 上没有任何 Java 进程,先别急着怀疑脚本,回到第 2 章检查 SSH 免密是否真的配通。启动脚本是远程执行的,免密不通,远端进程就起不来。这类问题占初学者启动失败原因的一半。
停止集群则用对应的 stop-dfs.sh 和 stop-yarn.sh。这里有个习惯要养成:停止的顺序无所谓,但启动时优先启动 HDFS,再启动 YARN,因为 MapReduce 作业依赖 HDFS 存储数据。
4.3 浏览器查看 9870 和 8088 端口
启动完成后,打开浏览器访问 http://node01:9870,能进入 NameNode 的 Web 界面。这个页面会显示集群的健康状态,包括 Live Nodes、Dead Nodes、存储容量、文件块数量。注意:如果你在 windows 本机访问虚拟机里的集群,需要在本机的 hosts 文件里加上 node01 的 IP 映射,或者直接用 IP 访问。直接输入 IP 比较省事,比如 http://192.168.56.10:9870。
再访问 http://node01:8088,这是 YARN 的资源调度界面,能看到当前集群有多少活跃的 NodeManager。DataNode 的 Web 端口默认是 9864,可以从 NameNode 界面里点进去看某台 DataNode 的存储情况。实际操作中,最重要的验证点就是:9870 页面显示 Live Nodes 是 2,8088 页面显示 Active Nodes 是 2。
4.4 跑一个 WordCount 验证整个链路
光看进程还不够,必须提交一个真实作业跑完一遍,才能证明从客户端到 NameNode、DataNode、ResourceManager、NodeManager 的整个链路都是通的。最简单也最经典的是 WordCount。
先创建一些测试文件,例如本地/home/hadoop/words.txt,里面写几行英文单词。把文件放进 HDFS:
hdfs dfs -mkdir -p /wordcount/input hdfs dfs -put words.txt /wordcount/input然后把目录权限确保可读,再运行 Hadoop 自带的示例 jar:
hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar wordcount /wordcount/input /wordcount/output运行过程中会打印一堆进度信息,最后出现Job completed successfully就大功告成。查看结果:
hdfs dfs -cat /wordcount/output/part-r-00000这里有一个坑:输出目录不能提前存在,否则作业一提交就报org.apache.hadoop.mapred.FileAlreadyExistsException。所以运行第二次之前,记得先:
hdfs dfs -rm -r /wordcount/output我第一次跑 WordCount 时就是忘了删输出目录,反复改代码没用,最后才发现是目录冲突。
5. 集群起不来的真实排查记录
5.1 最常见的 Connection refused 错误
先从最“玄学”的问题说起:进程明明都启动了,但客户端执行命令时总是报Connection refused。这一般不是防火墙,就是地址配错了。你仔细看报错信息里的 IP 和端口,如果它尝试连接的是某个你没配置过的地址,十有八九是 fs.defaultFS 写错了主机名,或者 hosts 文件没有同步。
排查思路是这样的:
- 在客户端机器上
ping node01看能否解析并连通。 - 在客户端机器上
telnet node01 9000看端口是否通。 - 再去 NameNode 所在机器上执行
netstat -tlnp | grep 9000确认进程真的在监听。
这三个步骤能定位 80% 的连接问题。记住一个原则:先确认网络通,再确认端口通,最后才怀疑配置文件。
5.2 DataNode 一个都不显示的真相
有时候 jps 显示 DataNode 进程在,但 Web 界面的 Live Nodes 却是 0,或者一开始是 2,过几分钟变成 0。这种情况不用猜,直接看 DataNode 日志。日志位置一般在$HADOOP_HOME/logs/hadoop-hadoop-datanode-node02.log。
我在实际检查中看到最多的报错是:
java.io.IOException: Inconsistent clusterID位置在 NameNode 和 DataNode 的 VERSION 文件里。处理办法有两个方向:如果集群刚搭建、还没有正式数据,我建议直接在所有节点上删掉 name 和 data 目录重新格式化;如果集群里已经有数据,那就把 DataNode 的 clusterID 手动改成和 NameNode 一致,或者把相关目录移走后重新启动再观察。
这也解释了为什么我会反复提醒格式化要慎重。你自己算一下,如果集群里存了几个 T 的数据,你一个格式化下去,数据副本全部失效,到时候就不是重启一次的问题了。
5.3 安全模式下的“假死”现象
启动完 NameNode 后,Web 页面可能会显示Safe mode is ON。安全模式是 NameNode 启动过程中的保护机制,它会限制对文件系统的写操作,等 DataNode 上报足够多的块位置信息后自动退出。刚启动的集群进入安全模式是正常的,一般几十秒内退出。
但如果你启动很久后仍然在安全模式,常见原因是 DataNode 一直没有成功注册,导致 NameNode 等不到足够的块上报信息。这时执行:
hdfs dfsadmin -safemode leave强制退出安全模式,可以应急,但你要知道根本问题还是报错信息,继续翻日志找原因。如果是刚搭建的测试环境,我更倾向于把 DataNode 的旧数据目录清掉,让它重新注册,而不是长期依赖跳过安全模式。
5.4 关于重新格式化,我的最终建议
重新格式化是一个“看起来简单、实则危险”的操作。如果你已经决定要彻底重来,正确流程是:
- 执行 stop-dfs.sh 和 stop-yarn.sh 停止所有进程。
- 在 node01 上删除
/data/hadoop/name和/data/hadoop/tmp。 - 在 node02、node03 上删除
/data/hadoop/data和/data/hadoop/tmp。 - 在 node01 上重新执行 hdfs namenode -format。
- 重新执行 start-dfs.sh 和 start-yarn.sh。
不要在 DataNode 上删除整个/data/hadoop,因为它的 data 目录在重新格式化之前不能留旧 clusterID。不要偷懒只删 NameNode 那台机器,数据节点上的旧身份不同步消除,你会重蹈 clusterID 不一致的覆辙。
6. 额外提醒:一些我自己踩过的小坑
6.1 复制虚拟机会带来哪些隐藏问题
很多零基础读者为了省事,喜欢直接复制虚拟机。无脑复制确实快,但会产生几个典型问题。一是主机名和 IP 完全一样,集群认为它们是同一台机器;二是 Machine ID 一样,SSH 可能拒绝连接;三是某些版本的系统复制后网卡 MAC 地址一样,网络起不来。
如果你必须用复制的办法,至少要处理三件事:修改 /etc/hostname、修改 /etc/hosts、删除/etc/machine-id后重启让它重新生成。另外确认一下网卡配置文件里的 HWADDR 是否和实际网卡一致。这个过程比手动装一台新的还麻烦,所以我的建议是:第一台认真装好,之后用“虚拟机模板”的方式克隆然后逐项修改,比复制已配置好的机器要干净得多。
6.2 YARN 内存设置不当导致作业被秒杀
提交 WordCount 后,如果作业日志显示 Container 被 kill,且报错信息里有unreasonable或者exceeds physical memory之类的字样,那就是内存设置的问题。YARN 给每个容器分配的内存不能超过 NodeManager 宣称的总内存。这里有个常见操作失误:在虚拟机只给了 2GB 内存的情况下,把 yarn.nodemanager.resource.memory-mb 设成 2048,系统再扣掉一些缓存,NodeManager 一启动就濒临崩溃。
我的实际处理方案是:虚拟机统一 2GB,YARN 的 memory 配 1024,同时不要额外开太多占用内存的应用。等集群跑通之后,你想深入调优,再逐步把内存调大。别一上来就想着“配大一点跑得快”,分布式调度资源是动态的,配置过大只会换来秒死。
6.3 集群的日常启动顺序与数据安全习惯
集群搭建完成后,你会反复开机和关机。我建议你形成一套固定的操作顺序。开机后按这个顺序执行:
start-dfs.sh start-yarn.sh jps关机前按这个顺序执行:
stop-yarn.sh stop-dfs.sh在 Linux 环境里,关机前不执行 stop 脚本,虽然 JVM 进程会随着系统关闭而终止,但 HDFS 可能没来得及把内存中的元数据刷盘,下次启动时 NameNode 会耗时很久做元数据恢复。如果系统非正常关机次数多,元数据损坏的概率会上升。一个良好的数据安全习惯是:尽量正常关机,并在每次关机前先停集群。
最后再分享一条经验:全分布式的搭建和伪装分布式最大的不同,是你必须理解“身份关系”。节点间靠主机名和 clusterID 互相确认身份,配置就是双方的身份证明。你如果能画出“哪台机器跑什么进程、通过什么地址连谁、数据和元数据分别存哪里”这张图,那这个集群基本就吃透了。以后无论是学 HDFS 高可用、YARN 调优,还是整合 Zookeeper,都是在今天这张图上添加新角色而已。