老实说,Hadoop集群搭建在今天这个云原生时代看起来有点像“过时手艺”,但只要你打算真正搞懂分布式存储和分布式计算的底层原理,自己动手在CentOS7上搭一套Hadoop集群,依然是绕不开的基本功。我前阵子刚在某公司的内部测试环境里,用三台CentOS7.9虚拟机从零到一搭完一套三节点集群,中间踩了不少文档里根本不会写的坑。这篇东西就是把我当时的完整过程、配置思路和故障排错一起复盘出来,目标读者是那种已经能单机跑通Hadoop、但还没真正把多台机器“捏”成一个集群的人。
先说结论,整套流程走下来,核心就三件事:角色规划、配置文件一致性、免密与端口通联。版本选型上我会用Hadoop 3.3.x,原因后面单独讲。文章里所有命令、配置文件、验证步骤,都可以直接照着抄,但更重要的是理解每一步为什么要这么做。
1. 前期规划:三台机器到底怎么分配才合理
1.1 最小集群的资源角色划分
一个能正常演示和跑任务的Hadoop集群,最少需要三台机器。为什么是三台,不是两台?因为HDFS默认副本数是3,三台机器正好能保证每个数据块都有完整的三副本。虽然你可以把副本数改成2甚至1,但那样的话“分布式存储”的容错意义就大打折扣了,测试出来的很多东西也不具备参考价值。
我当时拿到的三台机器配置一样,都是4核8G、100G磁盘,操作系统CentOS7.9,内网IP段用了192.168.56.101到103。关于角色划分,遵循一个基础原则:NameNode和ResourceManager尽量放同一台,DataNode和NodeManager放在另外两台。这样做的好处是,作为“大脑”的两个进程集中在一起,调度路径短;而另外两台专心做数据存储和计算,互不干扰。
角色规划见下表:
| 主机名 | IP地址(示例) | HDFS角色 | YARN角色 | 主要进程 |
|---|---|---|---|---|
| hadoop01 | 192.168.56.101 | NameNode | ResourceManager | NameNode、ResourceManager |
| hadoop02 | 192.168.56.102 | DataNode | NodeManager | DataNode、NodeManager |
| hadoop03 | 192.168.56.103 | DataNode | NodeManager | DataNode、NodeManager |
有人会问,为什么SecondaryNameNode没有单独安排一台机器?在Hadoop 2.x之后,SecondaryNameNode这个角色已经逐渐边缘化,它既不是热备也不是冷备,只是定期合并编辑日志的辅助进程。在3.x版本里,如果你需要高可用,直接上NameNode HA;如果不追求高可用,那么让SecondaryNameNode也跑在hadoop01上就够了,不用额外占一台机器。这个规划思路对于学习集群来说,是最经济也最符合逻辑的。
1.2 JDK与系统环境准备
Hadoop本身是Java写的,所以JDK是第一前置条件。我使用的是开源的OpenJDK 8,注意不要图新鲜去装JDK 11或17,Hadoop 3.3.x官方支持矩阵里明确写了Java 8是兼容性最好的版本,某些新版本JDK会出现反射访问报错,排查起来还特别隐蔽。
三台机器上都要装,步骤很简单:
# 先检查系统里有没有自带OpenJDK,有的话卸掉 rpm -qa | grep java rpm -e --nodeps 包名 # 安装OpenJDK 8 yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel # 确认版本 java -version装完之后,还需要配置JAVA_HOME环境变量。这里有个很容易忽略的细节:Hadoop的启动脚本会尝试通过JAVA_HOME找到Java路径,如果你没有在/etc/profile里设置,那么后续配置hadoop-env.sh的时候也得写死路径,不如一开始就配置好。我的做法是:
cat >> /etc/profile <<EOF export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk export PATH=\$JAVA_HOME/bin:\$PATH EOF source /etc/profileCentOS7上还必须做两件基础工作:关闭防火墙(或者放行指定端口)和禁用SELinux。关于防火墙,学习环境直接systemctl stop firewalld和systemctl disable firewalld最省事,如果公司策略要求必须开防火墙,那至少要放行8020、9870、8088、9864、8042这几个关键端口。SELinux的禁用方法是修改/etc/selinux/config,把SELINUX=enforcing改成SELINUX=disabled,然后重启生效。
注意:关闭SELinux和防火墙,意味着集群基础网络环境处于全开放状态,这个操作只推荐在隔离的内网测试环境执行。生产环境需要走安全组策略,不能一刀切关闭。
1.3 主机名映射与SSH免密登录背后的道理
主机名规划要尽早做,因为Hadoop集群中节点之间是通过主机名互相识别的。三台机器都要修改主机名:
hostnamectl set-hostname hadoop01 # hadoop02、hadoop03 依次类推然后修改/etc/hosts,三台机器都加上相同的内容:
cat >> /etc/hosts <<EOF 192.168.56.101 hadoop01 192.168.56.102 hadoop02 192.168.56.103 hadoop03 EOF这里有个血泪教训:主机名千万不要带下划线、点号以外的特殊字符,比如srv_hadoop01这种命名方式Hadoop解析时会直接报UnknownHostException,而且报错信息极其误导人。另外,/etc/hosts里不要把自己的主机名解析到127.0.0.1回环地址上,否则NameNode启动时会把服务绑到本地回环,外部节点根本访问不到。
SSH免密登录是集群通信的基础。为什么必须免密?因为start-dfs.sh和start-yarn.sh这些启动脚本是通过SSH远程拉起其他节点进程的,如果不做免密,脚本执行时会挨个机器要密码,根本没法自动执行。做免密的正确方式是,在hadoop01上生成密钥对,然后把公钥分发给所有集群节点(包括hadoop01自己):
ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa ssh-copy-id hadoop01 ssh-copy-id hadoop02 ssh-copy-id hadoop03验证方法也很重要,不要只验证一条链路,要把所有节点两两之间都试一遍:
ssh hadoop01 hostname ssh hadoop02 hostname ssh hadoop03 hostname为什么要两两验证?因为实际运行中,YARN的NodeManager在作业调度时可能从任意节点发起通信,DataNode在向NameNode汇报心跳时也一样。如果你只在hadoop01上做了免密,其他节点之间互相访问时还是会弹密码,某些操作就会卡死。
2. 版本选型:为什么Hadoop 3.x才是落地的正经选择
2.1 2.x和3.x的关键差异
很多人学Hadoop时接触的资料还是2.x时代,网上大量的教程都是下载Hadoop 2.7或者2.10。但如果是从零开始搭建,我强烈建议直接用3.3.x。一个重要原因是,Hadoop 2.x已经停止维护,比如2.7系列在2018年就发布了最终版,之后再也没有任何安全补丁和Bug修复,现在搭一个不维护的版本出来,等于给团队埋安全雷。
除此之外,3.x在架构和默认配置上做了很多关键改动,直接体现为用户体验的提升。举个例子,HDFS Web UI的默认端口从2.x的50070改成了3.x的9870,很多老教程里的访问地址已经完全不适用了。再比如,Hadoop 3.x使用Java 8或更高版本,优化了启动速度,还支持GPU、FPGA等异构计算资源调度,这些在2.x里都有很大的限制。
对学习集群来说,3.x还有一个特别友好的变化:不再需要为每个节点单独指定DataNode目录文件权限。2.x里经常出现data目录permission denied导致DataNode起不来的问题,3.x默认用更合理的目录权限策略,省掉了很多新手噩梦。
2.2 从资源规划看堆内存与容器参数
选型3.x之后,还要提前想好JVM堆内存和相关参数。Hadoop环境的配置文件里,很多默认值对普通虚拟机来说过于激进。比如,3.x的YARN默认会把yarn.scheduler.maximum-allocation-vb等多项参数拉得比较高,如果虚拟机只有8G内存,却让NodeManager提交一个大容器任务,很容易出现内存超分,任务直接OOM被杀。
我建议在yarn-site.xml里宁可把上限压小,也不要让系统一开始就在内存边缘试探。例如我的4核8G虚拟机,把单个容器内存上限设置为2048 MB,这是经过估算的:系统自身占用大约1G,NodeManager预留1G,NameNode/ResourceManager各预留1G,剩下留给计算容器的空间大概4G。如果上限设为4096 MB,一旦两个节点同时跑大任务,物理内存立即爆掉。这个取舍在后面配置部分会详细写。
2.3 下载安装与基础目录约定
Hadoop的下载没什么花头,直接去官网镜像站拿3.3.6的tar包即可。很多人喜欢用opt目录,我个人的习惯是统一放在/data/下,因为/根分区在虚拟机里往往只有50G,扩容不变;而单独挂载的/data分区通常空间更大,适合存放HDFS的NameNode目录和DataNode目录。
mkdir -p /data/soft cd /data/soft wget https://mirrors.xxx/hadoop-3.3.6.tar.gz tar -zxvf hadoop-3.3.6.tar.gz mv hadoop-3.3.6 /data/soft/hadoop安装之后需要把Hadoop的bin和sbin目录加进PATH,三台机器都一样:
cat >> /etc/profile <<EOF export HADOOP_HOME=/data/soft/hadoop export PATH=\$HADOOP_HOME/bin:\$HADOOP_HOME/sbin:\$PATH export HDFS_NAMENODE_USER=root export HDFS_DATANODE_USER=root export HDFS_SECONDARYNAMENODE_USER=root export YARN_RESOURCEMANAGER_USER=root export YARN_NODEMANAGER_USER=root EOF source /etc/profile最后三个HDFS_*_USER和YARN_*_USER变量是很多教程里漏掉的,如果不设置,在root用户下执行启动脚本时会报“Cannot run program... Permission denied”之类的权限错误,因为Hadoop 3.x默认拒绝root用户直接启动集群,需要显式声明允许root作为启动用户。
3. 逐项拆解四个配置文件:每个字段都不是白写的
这里进入全文最核心的部分。Hadoop集群的配置分散在/data/soft/hadoop/etc/hadoop/目录下,只需要维护好core-site.xml、hdfs-site.xml、yarn-site.xml、mapred-site.xml和workers这五个文件。关键在于三台机器上的配置要完全一致,所以我的习惯是先在hadoop01上配置好,再使用scp同步到hadoop02和hadoop03。
3.1 core-site.xml:NameNode地址与临时目录
core-site.xml是整个集群最基础的配置项。第一个关键配置是fs.defaultFS,这个字段告诉所有客户端和集群内部组件,HDFS的入口是哪个节点、哪个端口。注意这里写的是hadoop01,而不是IP地址,好处是将来如果hadoop01的IP地址变更,只需要改/etc/hosts,不必改配置和客户端的访问代码。
第二个关键配置是hadoop.tmp.dir。这个目录是整个HDFS元数据的默认存储根路径,NameNode的dfs.namenode.name.dir如果不单独指定,默认就是file://${hadoop.tmp.dir}/dfs/name。我习惯单独给它分配一个磁盘分区/data/hadoop/tmp,避免和系统分区混在一起。
配置如下:
<configuration> <property> <name>fs.defaultFS</name> <value>hdfs://hadoop01:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/data/hadoop/tmp</value> </property> <property> <name>ha.zookeeper.quorum</name> <value>hadoop02:2181,hadoop03:2181</value> </property> </configuration>我这里把ha.zookeeper.quorum也写上去了,这是Hadoop 3.x在普通非HA集群里也会读取的一个参数,提前预留好对后面升级HA有帮助。但注意,如果你不做HA,这个参数其实可以省略,它不会影响集群启动。我在学习阶段会写上,纯属个人习惯。
3.2 hdfs-site.xml:副本数与DataNode目录
hdfs-site.xml里最需要关注的其实是三个点:NameNode元数据放在哪、DataNode的数据块放在哪、副本数是多少。
先看dfs.namenode.name.dir。这个参数指定NameNode的元数据存放路径,注意它必须是一个本地文件系统路径,而不是HDFS路径。我的配置会让NameNode把元数据同时写入两个目录,如果服务器上有两块独立磁盘,可以做互为备份。在虚拟机上没有这个条件,就只指定一个。
然后是dfs.datanode.data.dir。这是存储实际数据块的地方,可以配置多个目录,逗号分隔。它对数据块的分布策略有影响:如果你有两个不同的磁盘目录,HDFS会采用轮询方式把数据块轮流写到不同目录。虚拟机无脑配置一个目录就行,但生产环境一定要按“一个物理磁盘一个目录”的原则来写。
最后是副本数dfs.replication。三节点集群就写3,这也是默认值,但我要强调的是,设置副本数的前提是有那么多DataNode。如果你只有三台机器,副本数却保留了默认的3,数据上传没有问题;但如果你后来把集群缩容成两台,副本数不调下来的话,集群会陷入持续的“replicate”状态,时刻都在尝试补副本,日志里全是警告。
<configuration> <property> <name>dfs.namenode.name.dir</name> <value>/data/hadoop/namenode</value> </property> <property> <name>dfs.datanode.data.dir</name> <value>/data/hadoop/datanode</value> </property> <property> <name>dfs.replication</name> <value>3</value> </property> <property> <name>dfs.permissions.enabled</name> <value>false</value> </property> </configuration>dfs.permissions.enabled=false是出于测试方便的考虑。HDFS默认开启权限校验,文件的所有者、权限位判断很严格,作为学习环境经常会在hadoop01上传文件、在hadoop02查看文件,产生各种权限不匹配的问题。关闭权限校验可以让学习阶段的障碍少很多,但生产环境出于安全和审计需要,必须保持开启。
3.3 yarn-site.xml:ResourceManager调度与容器大小
yarn-site.xml是整个YARN调度器的核心配置。三个字段是必配的:yarn.resourcemanager.hostname指定ResourceManager在哪台机器;yarn.nodemanager.aux-services指定MapReduce所需的辅助服务;以及一系列内存参数。
yarn.resourcemanager.hostname直接决定了NodeManager往哪里注册。这个配置跟fs.defaultFS一样,最好写主机名而不是IP。启动时NodeManager会在日志里打“Connecting to ResourceManager at hadoop01/192.168.56.101:8031”,如果你把RM地址配错,NodeManager会一直无法注册。
yarn.nodemanager.aux-services这个字段看起来容易让人疑惑,为什么叫“辅助服务”而不是“计算框架”?它的原理是,NodeManager本身只是一个容器管理器,它并不知道分发下来的任务是什么类型。通过配置mapreduce_shuffle这个辅助服务,才能让MapReduce任务在NodeManager上拉取中间数据,完成shuffle阶段。如果没有这个配置,任务会卡在MapReduce作业的第一个阶段,报错信息往往是Application failed但是具体原因还要翻半天。
内存和CPU参数,我用下面这组实测过的配置:
<property> <name>yarn.scheduler.minimum-allocation-mb</name> <value>512</value> </property> <property> <name>yarn.scheduler.maximum-allocation-mb</name> <value>2048</value> </property> <property> <name>yarn.nodemanager.resource.memory-mb</name> <value>4096</value> </property> <property> <name>yarn.nodemanager.resource.cpu-vcores</name> <value>2</value> </property>这套参数背后的含义是:每个容器最小申请512MB内存,最大申请2048MB,每个NodeManager节点总共可供分配的资源为4096MB内存和2个虚拟核。注意yarn.nodemanager.resource.memory-mb是向下限定的,它必须小于物理机可用内存,而且要为操作系统和DataNode留出余量。我实测过在4G内存的小机器上把这个参数拉到6G,结果NodeManager把容器调度起来之后物理机内存直接被打满,系统OOM killer开始无差别杀进程。
3.4 mapred-site.xml与workers/hadoop-env.sh的细节
mapred-site.xml这个文件在Hadoop 3.x里默认是不存在的,模板文件叫mapred-site.xml.template,需要手动复制一份。绝大部分教程会告诉你配置三行,但我更推荐把下面这行也加上:
cp $HADOOP_HOME/etc/hadoop/mapred-site.xml.template $HADOOP_HOME/etc/hadoop/mapred-site.xml<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/*:$HADOOP_HOME/share/hadoop/common/*:$HADOOP_HOME/share/hadoop/common/lib/*:$HADOOP_HOME/share/hadoop/hdfs/*:$HADOOP_HOME/share/hadoop/hdfs/lib/*:$HADOOP_HOME/share/hadoop/yarn/*:$HADOOP_HOME/share/hadoop/yarn/lib/*</value> </property> </configuration>为什么mapreduce.framework.name=yarn?这是告诉MapReduce作业应该提交到YARN上调度,而不是走本地模式。如果你漏了这行,作业会全部以Local模式在本机跑,集群形同虚设。
mapreduce.application.classpath是很多新手忽略的点。在Hadoop 2.x时代这个不需要显式配置,但3.x中由于NodeManager在运行MapReduce任务时需要加载各种jar包,如果不手动配置classpath,会看到Container is running beyond virtual memory limits或ClassNotFoundException: org.apache.hadoop.mapreduce.v2.app.MRAppMaster这类稀奇古怪的报错。这行配置的来源是Hadoop官方文档给出的推荐值,可以直接抄。
然后是workers文件。这个文件的默认名字在3.x里就是workers,2.x叫slaves。它的作用是告诉start-dfs.sh和start-yarn.sh脚本,哪些机器节点需要被启动为DataNode和NodeManager。内容是:
hadoop01 hadoop02 hadoop03这里要特别注意,workers文件里不要写IP地址,写主机名。因为脚本是通过SSH连接到这些主机名上去启动进程的,写IP会导致脚本无法正确识别节点身份。
hadoop-env.sh里还需要单独设置JAVA_HOME。虽然我们在系统环境变量里已经配置了,但Hadoop的守护进程脚本在一些场景下不会读取系统全局环境变量。保险做法是:
vim $HADOOP_HOME/etc/hadoop/hadoop-env.sh # 找到 JAVA_HOME 行,设置为 export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk配置完成后,怎么确认三台机器同步成功了?可以用一个简单命令:
scp -r /data/soft/hadoop/etc/hadoop/* hadoop02:/data/soft/hadoop/etc/hadoop/ scp -r /data/soft/hadoop/etc/hadoop/* hadoop03:/data/soft/hadoop/etc/hadoop/同步之后,在hadoop02和hadoop03上各抽查一次grep "fs.defaultFS"etc/hadoop/core-site.xml,确认配置内容一致再往下走。
4. 启动流程与验证清单:从格式化到看到三台节点在线
4.1 为什么首次启动必须手动格式化,格式化多少次会出事
配置同步完之后,千万不要急着执行启动脚本,有一个唯一的、必须手工执行的前置步骤:格式化文件系统。这个操作对应命令是:
hdfs namenode -format我在第一次搭集群时犯过一个经典错误:格式化执行了两次。第一次格式化会初始化/data/hadoop/namenode下的元数据文件,包括current/VERSION、fsimage_0000000000000000000等等。如果你在NameNode启动后又重新格式化,问题就来了:NameNode自带的clusterID被重新生成了,但DataNode如果还没被同时重新格式化,它那一侧还保留着旧的clusterID,启动时就会发现两边集群ID不一致,报错Incompatible clusterIDs,然后DataNode启动失败。
所以规范化操作是:三台机器都清空之前可能存在的namenode和datanode目录,再只在hadoop01上执行一次格式化,然后立刻启动。清空目录的命令:
rm -rf /data/hadoop/namenode/* rm -rf /data/hadoop/datanode/*可能有人想问,Hadoop 3.x有没有自动修复集群ID的方法?确实有hdfs nameid之类的命令可以查看到集群ID,但不要指望用它们解决所有问题,最干净的做法还是删除重来。这也是为什么我在前面强调,学习环境的数据说丢就丢没关系,生产环境千万不要在已有大量数据时随便格式化。
4.2 分角色启动的顺序
启动顺序在这里非常关键。我见过一些人习惯把start-dfs.sh和start-yarn.sh一起执行,结果显示NameNode起来但DataNode迟迟不注册,然后开始怀疑配置。实际上,先启动HDFS,再启动YARN才是正确顺序,原因很简单:YARN的NodeManager启动时通常会做一次健康检查,需要向HDFS执行一些读写判断,如果HDFS还没就绪,NodeManager的容器就会启动失败。
执行也很简单:
# 在 hadoop01 上 start-dfs.sh start-yarn.shstart-dfs.sh执行的时候日志会打到屏幕上,你会看到脚本依次SSH登录到hadoop01、hadoop02、hadoop03,并输出“starting namenode, logging to /data/soft/hadoop/logs/hadoop-root-namenode-hadoop01.out”这样的信息。如果日志停在其中一台机器上不动了,大概率是SSH免密没有配好,回到上一节检查。
这里还有一个小技巧:第一次启动时,我强烈建议不要使用start-dfs.sh,而是手动分进程启动,便于第一时间看到哪个进程启动失败。手动启动的命令是:
# hadoop01 上 hdfs --daemon start namenode # hadoop01/02/03 上 hdfs --daemon start datanode确认每个进程都启动成功之后,再统一用start-yarn.sh拉起YARN。这种“手工+脚本”结合的方式,能让你在第一次搭建时对每台机器到底跑了什么进程建立起感性认识。
4.3 命令行与Web UI双重验证
进程到底起来没有,不能只靠启动脚本说“successfully started”,必须以实际验证为准。第一步是用jps命令检查每台机器上的Java进程:
# hadoop01 上预期输出 # NameNode # ResourceManager # (SecondaryNameNode 如果配了的话) # hadoop02/hadoop03 上预期输出 # DataNode # NodeManager第二步是用HDFS命令验证文件系统可写可读:
hdfs dfs -mkdir -p /test echo "hello hadoop cluster" | hdfs dfs -put - /test/hello.txt hdfs dfs -cat /test/hello.txt第三步是看Web UI。Hadoop 3.x默认接口列表我整理在下面:
| 组件 | 端口 | URL示例 |
|---|---|---|
| NameNode Web UI | 9870 | http://192.168.56.101:9870 |
| ResourceManager Web UI | 8088 | http://192.168.56.101:8088 |
| DataNode Web UI | 9864 | http://192.168.56.102:9864 |
打开NameNode界面后,第一件事是看右上角的“Live Nodes”数量,应该是3。如果显示的数据节点数小于3,说明部分DataNode没注册成功,需要去对应节点查看logs/hadoop-root-datanode-*.log尾部报错。ResourceManager界面上重点看“Active Nodes”和“Applications”栏目,如果active nodes是2,说明NodeManager注册也有问题。
我用过一次集群搭建,跑测试作业时总是在Map阶段卡住,Web UI上看某个节点上的Containers一直显示Runnung却无进度。后来发现是那台虚拟机的时钟不同步,导致NodeManager上执行的任务输出判断出现混乱。所以有个容易被忽略但很重要的步骤:确保三台机器的系统时间差异不要超过几十秒。
# 三台机器都执行 yum install -y ntpdate ntpdate -u ntp.aliyun.com时间同步这件事,确实和Hadoop集群的正常运转息息相关,因为Hadoop内部有大量的超时判断和心跳机制,时间漂移严重时会出现各种莫名其妙的“节点失联”误判。
5. 踩坑实录:三个最容易被初学者忽略的细节
5.1 副本数配错导致的“存储空间不够”假象
第一次搭集群时,我曾经在hdfs-site.xml里把副本数配成4,心想多留一份保险。但当时集群只有三台DataNode。往HDFS里写文件后,系统看起来一直是正常的,直到我做数据量测试时才发现问题:上传一个约500MB的文件,HDFS显示已使用空间是2GB。原因很简单:副本数大于节点数时,某些副本只能排队等新的DataNode加入,但元数据已经把这个“缺失副本”记账了,所以磁盘占用是账面上的总量,而实际可用副本只有三个。
这个案例说明一个很重要的排查思路:当你发现HDFS集群的某个DataNode磁盘使用量异常增长时,不要只盯着df -h,要先去hdfs dfsadmin -report看看每个数据块的真实副本数。该命令会列出每个DataNode的容量和已使用量,配合hdfs fsck /path -files -blocks,能快速定位哪些文件处在UNDER-REPLICATED状态。
5.2 端口占用与秘而不宣的启动“玄学”
“玄学”这个词其实是带引号的。我在启动过程中遇到过Address already in use的报错,你以为是因为多次启动导致端口被残留进程占用,kill -9 进程号能解决。但有时候,端口被占用的根源是另一个进程悄悄占用了,且这个进程根本不是Hadoop进程。比如YARN ResourceManager默认的8088端口,和某些监控服务或Web管理台是冲突的。排查命令特别简单:
netstat -tlnp | grep 8088 lsof -i:8088如果发现是其他服务占用,有两个办法:杀掉对方进程,或者在配置文件里改端口。我推荐改配置文件,因为杀别人进程治标不治本,而且你根本不知道对方后续会不会自动拉起来。
另一个“玄学”是启动脚本偶尔会报JAVA_HOME is not set and could not be found,但明明/etc/profile里已经配置了。这种情况在手动切用户时最容易出现,比如你用root配置了环境变量,又切换到别的用户去执行启动脚本。解决方式也非常朴素:要么始终用同一个用户执行,要么专门在hadoop-env.sh里把JAVA_HOME写死。我上面已经提过这一步。
5.3 hosts文件与集群通信的坑
hosts文件的问题我在前面提过,但值得单独拿出来说,因为它是所有Hadoop集群搭建里最隐蔽的一个坑。
正常情况下,三台机器都应该把彼此的IP和主机名映射写在/etc/hosts里。但CentOS7默认会把主机名解析到127.0.0.1。如果你把hadoop01的主机名和127.0.0.1绑定在一起,DataNode尝试连接hadoop01:9000时,连接的是自己这台机器的9000端口,而这台机器上根本没有监听该端口,结果就是DataNode永远无法注册成功,日志里反复出现“ConnectException: Connection refused”。
我的建议是,打开/etc/hosts检查最后一行,如果有类似:
127.0.0.1 hadoop01 localhost localhost.localdomain这一行必须修改,把主机名从回环地址里去掉,改成实际IP地址。正确写法是:
192.168.56.101 hadoop01在这之前,我见过一台机器因为hosts配置错误,导致所有进程都尝试连接到自己身上,而且根本不是配置错误,纯粹是安装时系统自动写进去的。排查这类问题时,可以先ping hadoop02看看解析的是不是实际IP,如果ping出来是127.0.0.1,那就是hosts没改干净。
6. 从能用到好用:NameNode高可用与后续扩展思路
6.1 单NameNode的痛与HA基本思路
三节点的Hadoop集群搭好之后,你会明显感觉到“整个过程就像做菜,费了半天劲,终于可以端上桌了”。但学习阶段有个潜在风险:NameNode是单点。它跑在hadoop01上,一旦这台机器宕机,整个HDFS元数据就不可访问了,所有存储在上面的文件全部“看不到”,即使DataNode机器都活着也白搭。
学习场景下可以忍受单点,但如果你后续要在公司内部做一个真正承载业务数据的Hadoop集群,就一定要考虑HA(高可用)。HA的思路其实并不复杂:准备两个NameNode节点,一个Active,一个Standby,中间通过JournalNode同步HDFS的元数据日志(EditLog),再配合ZooKeeper来做故障自动切换。当Active节点挂掉,Standby节点通过ZooKeeper感知后自动接管,整个过程客户端的连接不会中断。
HA需要的组件和配置数量会明显增加,至少需要增加两台JournalNode(生产通常是三台),以及一整个ZooKeeper集群。但同一个HDFS集群里,只要满足这个条件,架构就从“单点内存”升级成“双机热备”。对于这个三节点的测试环境来说,你可以把这个当作下一步的进阶练习,先把非HA集群跑熟练,再考虑HA,否则会把太多变量混在一起,排错时就分不清是Hadoop自身的问题还是ZooKeeper的问题。
6.2 配置项贴士与租户实践
集群一旦开始给团队用,就要注意一些生产化的配置习惯。“贴士”这个词我用在这儿,想表达的就是一些零散的但很有价值的小经验。
比如,dfs.datanode.failed.volumes.tolerated这个参数在生产集群里建议设置为大于0的值。它表示允许DataNode的单个数据盘损坏而不被整体剔出集群。默认值是0,意味着任何一块数据盘挂了,整个DataNode都会被标记为故障并从集群里移除,对HDFS来说是巨大的可用性损失。建议设置成至少1。
再比如,NameNode的堆内存。默认情况下NameNode的堆内存只有1G,如果文件数量很多,会出现GC告警。Hadoop 3.x支持根据内存自动调整HADOOP_HEAPSIZE,在hadoop-env.sh里配置:
export HADOOP_HEAPSIZE=4096设置完成后,重启NameNode生效。如果文件数还在持续增长,建议后续开启NameNode联邦(Federation),或者使用HDFS Router-based Federation做元数据分片,但这些就是更后面的话题了。
另外还有一个小建议:在给后来人讲解集群时,不要只告诉他“这么配”,一定要讲清楚配置项为什么这么设。我上面这几个坑和思路,就是围绕“为什么”来组织的。集群搭建本身很快,真正花时间的是对每一个决定背后的原因有把握,否则出了问题就只能靠搜索引擎自救。
最后再分享一个习惯:我会把整套配置文件和格式化、启动、验证、停机的命令整理成一个README,放在/data/soft/hadoop/目录下,同时把/etc/hosts和防火墙的变更记录也写进去。这样过了几个月再回头看,或者换一个人接手这套集群,都不需要重新上网翻文档,直接看这个说明就能快速了解环境怎么搭起来的。这是我个人在运维实践里觉得最值得推广的一个小习惯。