多开虚拟机在本地实现hadoop分布式集群&hadoop streaming的使用
目录
文章目录
背景及环境
我通过多开虚拟机在一台物理机上搭建了一个hadoop的分布式集群,并调用hadoop streaming来实现map-reduce过程。
环境:
物理机的硬件条件为i7-12700H,含有独立显卡RTX 3060的笔记本,搭载的操作系统为Win11;
参与组成集群的三台虚拟机通过vmware管理,版本为Ubuntu 16.04LTS,每台虚拟机的内存为7GB,硬盘大小为20GB;
解决的几个痛点问题
这些都是我个人在尝试完成课程实验的过程中遇到的折磨、痛苦以及网上相关技术文档非常少或者几乎没有的问题,我通过查资料、问GPT、自己上手调试、自己不断摸索尝试,逐个解决了以下的问题,并且,我将尽可能去解释这些问题背后的底层原因,可能会掺杂我的个人观点,同时因为鄙人水平有限,不足之处、错误之处还希望各位看到此篇文章的人提出意见和看法,鄙人将不尽感激。
- 各节点的相互通信问题、权限问题
- hadoop配置文件的问题
- hadoop streaming的使用详解
1.各节点的相互通信问题、权限问题
一个一个来解决
节点间相互通信问题
vmware创建虚拟机时默认的网络设置为NAT模式
,但是vmware还提供了桥接
、主机
和自定义
模式。
为此我去查了资料(这篇文章 http://t.csdnimg.cn/wDxSS)
用NAT
和桥接
其实都可以,我用的是NAT模式
,但是使用本地DHCP服务自动分配IP可能会导致虚拟机重启或者关机再启动之后IP发生变化(虽然我发现挂起不会改变IP地址,但是好像因为物理机使用的是校园网,导致每次物理机重新连接校园网的时候,物理机的IP会被重新分配,进而这些虚拟机的IP有时也会发生改变),导致每次都要重新配置免密登录ssh
和IP-主机名
映射关系,很麻烦。
为了避免以上糟心事情的出现,我对vmware的虚拟网络编辑器做一些操作,同时虚拟机的IP配置需要设置成静态。
-
点击
编辑
-虚拟网络编辑器
,然后点击更改设置
,按照截图中的步骤做出修改。
注意:这里取消勾选后,所有的虚拟机(在用的或者未来创建的)都不会自动分配IP地址了,要么
①全部自己手动分配
②设置里面改回来,重新勾选 -
或者,可以新建一个VMware网络适配器,具体设置和VMnet8相同,只是勾不勾选IP地址分配的选项,然后在虚拟机设置当中让这几个节点都走新的这个VMware网络适配器。
-
在虚拟机的网络配置当中也需要做出修改,我的虚拟机为Ubuntu 16.04LTS,网关和服务器都填上面的子网IP,子网掩码也填上面的子网掩码。
(因为截图的时候已经连校园网了,上面的IP地址发生了变化,而虚拟机配的时候是在家里配的,建议用的时候连手机热点,别连校园网,不然每个虚拟机的网络设置需要作出相应的更改,尽管如此,处理起来的工作量也要比勾选使用本地DHCP服务分配虚拟机的IP地址
少很多)
别忘记勾选图中需要IPv4地址完成这个连接
,这样会优先使用IPv4,而非IPv6。
-
IP地址的手动分配范围是有规定的,首先,前三个部分需要和当前的VMnet网络适配器当中的子网IP一致,最后一个数需要避开排除网络地址(xxx.xxx.x.0)和广播地址(xxx.xxx.x.255),即1-254都是可以选择的。
-
配置完记得尝试能不能正常上网,以及配置免密登录ssh
补充
回头发现似乎不需要取消勾选使用本地DHCP服务分配IP地址
也可以进行静态IP的分配,只要避开DHCP服务分配IP地址的范围就行了,如下图所示,手动可分配IP的范围在1-127之间,而且你可以手动更改DHCP服务分配IP地址的范围。
权限问题
这里主要是我遇到的执行map和reduce的脚本的问题。
在调用hadoop streaming时指定的mapper和reducer脚本,如果只在主节点上有,那么hadoop会在执行整个job之前,先将这些脚本复制分发到各个子节点的相同位置。我并未尝试将脚本上传到hdfs,而是只在主节点本地存放了脚本,让hadoop在执行时自动帮我在子节点创建相应副本。
但是这很快就遇到了问题,因为我原先存放代码的路径为根目录,即/codes/mapper.py
和/codes/reducer.py
(codes是我在根目录下创建的文件夹),在随后的job中就报错找不到此文件
,其主要原因是根目录下非root用户是没有权限创建文件和文件夹的,所以我给每个节点手动复制了一份。
实际上这些脚本应该放置在/home/hadoop
的路径下,这样就只需要在主节点上写代码就行了,运行的时候hadoop会自动copy到其他子节点,当然你需要确保非root用户在这个位置创建的文件有读写以及可执行的权限。
2.hadoop配置文件的问题
hadoop的配置文件位于目录/usr/local/hadoop/etc/hadoop
下(我的hadoop的安装目录为/usr/local/hadoop
)
配置文件主要是core-site.xml
、hdfs-site.xml
、mapred-site.xml
和yarn-site.xml
这四个。
我给出了我的配置文件,这是基于我看到的hadoop配置文档的基础上的综合,调整了节点虚拟内存的上限和map-reduce任务使用内存上限,开启了聚集日志功能以便方便调试,以供参考
core-site.xml
文件
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://Master:9000</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>file:/usr/local/hadoop/tmp</value>
<description>Abase for other temporary directories.</description>
</property>
</configuration>
hdfs-site.xml
文件
<configuration>
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>Master:50090</value>
</property>
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>file:/usr/local/hadoop/tmp/dfs/name</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>file:/usr/local/hadoop/tmp/dfs/data</value>
</property>
<property>
<name>yarn.nodemanager.resource.memory-mb</name>
<value>4096</value>
</property>
<property>
<name>yarn.scheduler.maximum-allocation-mb</name>
<value>4096</value>
</property>
<property>
<name>dfs.permissions.umask-mode</name>
<value>000</value>
</property>
</configuration>
mapred-site.xml
文件
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>mapreduce.jobhistory.address</name>
<value>Master:10020</value>
</property>
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>Master:19888</value>
</property>
<property>
<name>yarn.app.mapreduce.am.env</name>
<value>HADOOP_MAPRED_HOME=/usr/local/hadoop</value>
</property>
<property>
<name>mapreduce.map.env</name>
<value>HADOOP_MAPRED_HOME=/usr/local/hadoop</value>
</property>
<property>
<name>mapreduce.reduce.env</name>
<value>HADOOP_MAPRED_HOME=/usr/local/hadoop</value>
</property>
<property>
<name>mapreduce.map.memory.mb</name>
<value>2048</value>
</property>
<property>
<name>mapreduce.reduce.memory.mb</name>
<value>2048</value>
</property>
<property>
<name>mapreduce.map.java.opts</name>
<value>-Xmx2048M</value>
</property>
<property>
<name>mapreduce.task.io.sort.mb</name>
<value>1024</value>
</property>
</configuration>
yarn-site.xml
文件,这里实现了日志聚集功能,可以通过http://master:8088/cluster
的web界面查看节点日志
<configuration>
<property>
<name>yarn.resourcemanager.hostname</name>
<value>Master</value>
</property>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<!-- 开启日志聚集功能 -->
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>
<!-- 设置日志聚集服务器地址 -->
<property>
<name>yarn.log.server.url</name>
<value>http://hadoop102:19888/jobhistory/logs</value>
</property>
<!-- 设置日志保留时间为7天 -->
<property>
<name>yarn.log-aggregation.retain-seconds</name>
<value>604800</value>
</property>
<property>
<name>yarn.nodemanager.vmem-pmem-ratio</name>
<value>4</value>
</property>
<!--YARN可以分配的最大物理内存-->
<property>
<name>yarn.nodemanager.resource.memory-mb</name>
<value>4096</value>
</property>
<!--单个容器(JVM进程)可以申请的最大物理内存(不能大于上面的参数)-->
<property>
<name>yarn.scheduler.maximum-allocation-mb</name>
<value>4096</value>
</property>
<property>
<name>yarn.nodemanager.resource.cpu-vcores</name>
<value>2</value>
</property>
</configuration>
按照此套配置文件配置的话:
-
浏览器中输入
http://localhost:9870/
,可以查看各节点的状态
-
浏览器中输入
http://master:8088/cluster
,可以查看当前集群是否有作业以及作业的执行情况
作业失败后常见调试手段:
- 点击上图的
Tracking UI
那一列的链接,可以简单查看出错原因 - 更加详细的报错原因可以通过在shell中执行以下指令实现:
重点看来自yarn logs -applicationId 想要查看的ApplicationID > temp1.log
LogType:stderr
的日志,可以更容易和快速地定位到问题(因为绝大部分可定位可修复的错误都会被输出输入到标准输出输入流),如下图所示(可以看到报错来自位于Master节点的容器,即主节点的容器,错误是调用blastn相关的错误)
3.hadoop streaming的使用详解
主要有两种调用方式,这里我均使用我的调用作为案例:
老版调用方式:
hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-*.jar \
-file /home/hadoop/pythoncodes/mapper.py \
-mapper /home/hadoop/pythoncodes/mapper.py \
-file /home/hadoop/pythoncodes/reducer.py \
-reducer /home/hadoop/pythoncodes/reducer.py \
-input hdfs:///filenames.txt \
-output /blast_output
新版调用方式:
mapred streaming -files /home/hadoop/pythoncodes/mapper.py,/home/hadoop/pythoncodes/reducer.py \
-input hdfs:///filenames.txt \
-output hdfs:///blast_output \
-mapper "python /home/hadoop/pythoncodes/mapper.py" \ #这里的python点明了脚本的语言
-reducer "python /home/hadoop/pythoncodes/reducer.py "
来解释一下各个参数的含义:
-mapper
&-reducer
:指定mapper以及reducer可执行文件或脚本,可以如新版调用那样,python
+空格
+脚本路径
,告诉hadoop streaming
运行的脚本是python语言脚本。-file
:将本地文件复制到Hadoop集群上,mapper和reducer就可以使用它,常常会和前面的-mapper
以及-reducer
配合,实现只需要在主节点编写map-reduce
脚本,但是这些脚本能够在其他节点顺利运行的效果。-input
&-output
:指定输入/输出数据路径
只要hadoop版本不是太老的话,建议用新版
几个注意点:
-
如果文件在hdfs上,那么就在前面加上
hdfs://
,告诉hadoop streaming
这个文件从hdfs上找,而不是本地 -
-input
这个后面跟输入文件的路径,注意,hadoop streaming
会将这个文件内容打开作为输入,如果你输入的是目录而非文件,那么hadoop streaming
会将整个目录下的所有文件打开,将其内容作为输入。 -
最常见的报错信息:
subprocess failed with code 1 at …………(后面省略,会报老长一串错误信息,这里的code后面的值不固定)
最常见的错误返回值和处理方式:
- code 1: 网上的说法很多,主要的可能原因和解决尝试有这么几种:1.安装python的路径不对,脚本代码的最前面加
#!/usr/bin/python
(这里填系统的python安装路径)。2.编码和空格以及制表符的问题,比如用python写的脚本,制表符或者空格符出错的,Linux和Windows之间文件来回交换就直接用的,都可能会导致这个问题,在脚本代码的最前面加# -*- coding:utf-8 -*-
,最终效果如下所示:
然后再次尝试,如果还是失败,那么可能需要结合详细日志仔细定位错误,但是一般情况下多是代码有问题,着重看代码即可,但也有可能某些非代码原因的错误的返回值也会是1,最后被子进程传递出来的时候显示的是
code 1
。
比如我这个代码,因为调用hadoop streaming的时候,-input
后面跟的是目录,我以为是把整个目录下的文件名作为输入,但是实际上是将整个目录下的文件作为输入,即输入目录下所有文件的内容,导致调用blastn
的时候,原本应该填写比对文件路径的地方被换成了比对文件内容(即序列文件的描述和碱基序列),blastn
报错,且报错代码刚好为1,这个1就被返回到了报错文件当中,我直到看了日志才发现这个问题。 - code 1: 网上的说法很多,主要的可能原因和解决尝试有这么几种:1.安装python的路径不对,脚本代码的最前面加
调试成功经验总结
- 写完脚本,别着急调用
hadoop streaming
,先试试本地测试能不能跑通,脚本代码有没有明显的漏洞或者错误。 - 然后尝试在集群上调用
hadoop streaming
,遇到失败不要急,把日志调出来看看,定位错误(一定要学会看日志,会看日志可以省去80%走弯路和胡乱摸索调试的时间!)。 - 如果集群上调用
hadoop streaming
一直不成功的话(因为集群上导致执行不成功的原因有很多,代码,节点间的通信,hdfs,权限),先搭个hadoop以单机模式运行,在上面调用hadoop streaming
,调试调试看看能不能成功(这个时候如果有错误信息,会被直接输出到终端)。 - 一开始的时候先从小的数据集开始,一方面作业跑起来快,等待时间短,另一方面日志报错内容会少一些,方便看日志。
附件
这里附上我的两个脚和运行成功的截图,大致框架我让gpt写的,写完之后我后续调试和定位错误又改了几个地方。
map-reduce
说明: 我有一批上传在hdfs的非洲猪瘟的不同种类的DNA碱基序列.fasta
文件,通过调用分析比对工具blastn
来进行分析,这个过程被map分配到各个节点上,最后将分析结果通过reduce收集回到主节点。
脚本思路: 首先我用blastn
手动在每个节点建立了目标序列(即拿这个序列和其他所有序列比对一遍)的数据库:(blastn
也支持文件之间的比对,也可以不像我那么做,转而将目标序列上传到hdfs,然后再下载到每个节点,参与比对)
makeblastdb -in subject_sequence.fasta -dbtype nucl -out blastn_db
因为hadoop streaming
的-input
的特性,所以我自己写了个python脚本,先在本地将这些序列文件的名称获取到,然后在每个文件名前面加上其hdfs中存放的位置,写入一个txt文件,将这个文件上传到hdfs,这样-input
输入的就是所有节点都可获取的位于hdfs上的序列文件。
map脚本通过读取-input
的内容,对每一个序列文件都调用blastn
命令,和数据库中的序列进行比对,将结果输出。
blastn -query <参与比对的.fasta文件> -db blastn_db -out your_output.txt -outfmt 6
而reduce脚本则合并map产生的各个结果。
代码如下:
mapper.py
:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
import os
import subprocess
def mapper():
for line in sys.stdin:
line = line.strip()
# HDFS文件路径
hdfs_file_path = line
# 本地临时文件路径
local_file_path = "/tmp/" + os.path.basename(hdfs_file_path)
try:
# 使用完整路径调用hadoop命令
hadoop_cmd = '/usr/local/hadoop/bin/hadoop'
subprocess.run([hadoop_cmd, 'fs', '-copyToLocal', hdfs_file_path, local_file_path], check=True)
# 调用ncbi-blastn处理该文件
blastn_path = '/home/hadoop/ncbi-blast-2.15.0+/bin/blastn'
blastn_cmd = [blastn_path, '-query', local_file_path, '-db', '/home/hadoop/blastn_db', '-outfmt', '6']
print(f"Running command: {' '.join(blastn_cmd)}", file=sys.stderr)
blast_output = subprocess.check_output(
blastn_cmd,
stderr=subprocess.STDOUT,
text=True
)
# 输出blastn的结果
print(f'{os.path.basename(local_file_path)}\t{blast_output.strip()}')
except subprocess.CalledProcessError as e:
print(f"blastn error: {e}", file=sys.stderr)
print(f"blastn output: {e.output}", file=sys.stderr)
except Exception as e:
print(f"Error: {str(e)}", file=sys.stderr)
finally:
# 删除本地临时文件
if os.path.exists(local_file_path):
os.remove(local_file_path)
if __name__ == "__main__":
mapper()
reducer.py
:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
# reducer函数
def reducer():
current_key = None
blast_results = []
mafft_results = []
for line in sys.stdin:
key, value = line.strip().split('\t', 1)
if current_key is not None and key != current_key:
# 处理前一个键的结果
print(f'{current_key}\t{" ".join(blast_results)}\t{" ".join(mafft_results)}')
blast_results = []
mafft_results = []
current_key = key
# 根据内容类型分别存储结果
if 'Query=' in value:
blast_results.append(value)
else:
mafft_results.append(value)
# 处理最后一个键的结果
if current_key is not None:
print(f'{current_key}\t{" ".join(blast_results)}\t{" ".join(mafft_results)}')
if __name__ == "__main__":
reducer()
运行结果:
这是8/21号跑的作业
终端方面,hadoop streaming
回复是Job成功完成
在Web界面,可以直观的看到此次Job的各项信息
使用hdfs dfs -get
命令将输出结果从hdfs下载到本地
本地查看hadoop streaming
的作业结果