赞
踩
java华为镜像下载地址:Index of java-local/jdk (huaweicloud.com)
hadoop历史版本下载:Index of /dist/hadoop/common (apache.org)
编写Dockerfile文件:
FROM centos:centos7 # 配置resove.conf解决软件包获取不到的问题 RUN curl -O http://mirrors.aliyun.com/repo/Centos-7.repo RUN mv -f Centos-7.repo /etc/yum.repos.d/CentOS-Base.repo RUN yum makecache # 安装openssh-server和sudo软件包,并且将sshd的UsePAM参数设置成no RUN yum install -y openssh-server sudo RUN sed -i 's/UsePAM yes/UsePAM no/g' /etc/ssh/sshd_config #安装openssh-clients RUN yum install -y openssh-clients # 添加测试用户root,密码3238,并且将此用户添加到sudoers里 RUN echo "root:3238" | chpasswd RUN echo "root ALL=(ALL) ALL" >> /etc/sudoers RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key # 启动sshd服务并且暴露22端口 RUN mkdir /var/run/sshd EXPOSE 22 CMD ["/usr/sbin/sshd", "-D"] ADD jdk-8u202-linux-x64.tar.gz /usr/local RUN mv /usr/local/jdk1.8.0_202 /usr/local/jdk1.8 ENV JAVA_HOME /usr/local/jdk1.8 ENV PATH $JAVA_HOME/bin:$PATH ADD hadoop-3.1.3.tar.gz /usr/local RUN mv /usr/local/hadoop-3.1.3 /usr/local/hadoop ENV HADOOP_HOME /usr/local/hadoop ENV PATH $HADOOP_HOME/bin:$PATH
构建镜像:
docker build -t hadoop .
注:低版本docker会出现使用mv命令时报错:can’t remove : Directory not empty,可以先用COPY命令而不用ADD命令将压缩包复制到容器内,然后自己后面到容器内去解压。
如果觉得这些操作太麻烦了,想要更省事点,我还提供了已经构建好的两个不同版本的镜像。(你就说我够不够贴心吧)
docker pull biluoer/hadoop:3.1.3-base
docker pull biluoer/hadoop:3.1.3
# 给拉取过来的镜像创建个新的tag
docker tag biluoer/hadoop:3.1.3 hadoop
# 然后删除旧tag
docker rmi biluoer/hadoop:3.1.3
# 运行三台容器后,把/root/bin添加到PATH里,就可以一键运行了
export PATH=$PATH:/root/bin
# 一键运行,回车走你
my-hadoop start
# 创建桥接网络,bridge是默认驱动,可以不加
docker network create [--driver bridge] hadoop-net
#创建桥接网络,并指定子网配置,范围:192.168.1.1-192.168.1.254
docker network create --subnet=192.168.1.0/24 hadoop-net
# 启动三台(一主二从)并指定网络,文件下载端口9864、DataNode客户端访问端口9866
# hadoop2,开放nn-web端口9870、历史服务器web端口19888,NameNode客户端连接端口8020
docker run -itd --hostname hadoop2 --name hadoop2 --net hadoop-net --ip 192.168.1.2 -p 9870:9870 -p 9864:9864 -p 19888:19888 -p 8020:8020 -p 9866:9866 hadoop
# hadoop3,开放yarn-web端口8088
docker run -itd --hostname hadoop3 --name hadoop3 --net hadoop-net --ip 192.168.1.3 -p 8088:8088 hadoop
# hadoop4,开放2nn-web端口9868(根据hdfs-site.xml配置,非必须)
docker run -itd --hostname hadoop4 --name hadoop4 --net hadoop-net --ip 192.168.1.4 -p 9868:9868 hadoop
注:如果某些功能用不到,就不需要开发相关的端口。
首先对主hadoop2配置:
可以先用ping命令测试下连通性:
ping hadoop2
如果没有ping通,在/etc/hosts文件里加上:
192.168.1.2 hadoop2 192.168.1.3 hadoop3 192.168.1.4 hadoop4
- 1
- 2
- 3
docker exec -it hadoop0 bash
#生成秘钥对
ssh-keygen
#剩下的一路回车即可
#分发公钥到其他主机,让hadoop2可以免密登录hadoop2、3、4
ssh-copy-id hadoop2
ssh-copy-id hadoop3
ssh-copy-id hadoop4
#同样的方法,让hadoop3、Hadoop4可以免密登录另外2台主机,其中hadoop4可配可不配,但hadoop2和hadoop3一定要配置
各节点的职责可通过配置文件自定义;NN、2NN、RM在不同服务器上可以减少资源争用、减小单点故障风险,方便扩展和维护。
hadoop2 | hadoop3 | hadoop4 | |
---|---|---|---|
HDFS | NameNode、DataNode | DataNode | SecondaryNameNode、DataNode |
YARN | NodeManager | ResourceManager、NodeManager | NodeManager |
进入/usr/local/hadoop/etc/hadoop目录,涉及的配置文件有:hadoop-env.sh、core-site.xml、hdfs-site.xml、yarn-site.xml、mapred-site.xml、works(2.x叫slaves)。
默认配置文件位置:
默认配置文件 | 所在位置 |
---|---|
core-default.xml | hadoop-common-3.1.3.jar/core-defaultxml |
hdfs-default.xml | hadoop-hdfs-3.1.3.jar/hdfs-defaultxml |
yarn-default.xml | hadoop-yarn-common-3.1.3.jar/yarn-defaultxml |
mapred-default.xml | hadoop-mapreduce-client-core-3.1.3.jar/mapred-defaultxml |
注:以下操作都是是hadoop2上,配置文件中的其他配置都是可加可不加的,并且除其他配置外的配置大多都有默认值,不是必须要加的,默认值可以通过查看默认配置文件来获取。
export JAVA_HOME=/usr/local/jdk1.8
<configuration> <!--指定NameNode的地址--> <property> <name>fs.defaultFS</name> <value>hdfs://hadoop2:8020</value> </property> <!--指定hadoop数据的存储目录--> <property> <name>hadoop.tmp.dir</name> <value>/usr/local/hadoop/data</value> </property> <!--配置HDFS网页登录使用的静态用户为root,用什么用户启动的集群,就配置什么用户,不然网页上进行删除文件等操作会没有权限--> <property> <name>hadoop.http.staticuser.user</name> <value>root</value> </property> <!--其他配置--> <!-- 配置垃圾桶(Trash)的清理时间间隔(以分钟为单位),默认值0,表示禁用垃圾桶功能 (垃圾桶是一个用于存储被删除文件或目录的临时区域) --> <property> <name>fs.trash.interval</name> <value>1440</value> </property> </configuration>
<configuration> <!--nn-web端访问地址--> <property> <name>dfs.namenode.http-address</name> <value>hadoop2:9870</value> </property> <!--2nn-web端访问地址--> <property> <name>dfs.namenode.secondary.http-address</name> <value>hadoop4:9868</value> </property> <!--其他配置--> <!--设置HDFS中文件的副本数,默认值3--> <property> <name>dfs.replication</name> <value>1</value> </property> <!--是否启用文件/目录的权限检查,false任何用户可读写,默认值true--> <property> <name>dfs.permissions</name> <value>false</value> </property> </configuration>
<configuration> <!--指定YARN-NodeManager需要运行的辅助服务--> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <!--指定ResourceManager的地址--> <property> <name>yarn.resourcemanager.hostname</name> <value>hadoop3</value> </property> <!-- 环境变量的继承,3.1的bug,缺少HADOOP_MAPRED_HOME,3.2后就不需要此配置了--> <property> <name>yarn.nodemanager.env-whitelist</name> <value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME,PATH,LANG,TZ</value> </property> <!--其他配置--> <!--启用YARN的日志聚合功能,把各个节点的日志收集到一起存放--> <property> <name>yarn.log-aggregation-enable</name> <value>true</value> </property> <!--设置日志聚集服务器地址--> <property> <name>yarn.log.server.url</name> <value>http://hadoop2:19888/jobhistory/logs</value> </property> <!-- 设置日志保留时间为7天--> <property> <name>yarn.log-aggregation.retain-seconds</name> <value>604800</value> </property> </configuration>
<configuration> <!-- 指定MapReduce程序运行在Yarn上 --> <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property> <!--其他配置--> <!--历史服务器端地址--> <property> <name>mapreduce.jobhistory.address</name> <value>hadoop102:10020</value> </property> <!--历史服务器web端地址--> <property> <name>mapreduce.jobhistory.webapp.address</name> <value>hadoop102:19888</value> </property> </configuration>
配置完成上述内容后,可以使用rsync进行配置文件的从hadoop2分发到其余容器。
rsync是一个同步工具,只会同步发生了变更的文件。
为了实现一次性分发,可以写一个脚本实现:
xsync
#!/bin/bash #1. 判断参数个数 if [ $# -lt 1 ] then echo Not Enough Arguement! exit; fi #2. 遍历集群所有机器 for host in hadoop2 hadoop3 hadoop4 do echo ==================== $host ==================== #3. 遍历所有目录,挨个发送 for file in $@ do #4. 判断文件是否存在 if [ -e $file ] then #5. 获取父目录 pdir=$(cd -P $(dirname $file); pwd) #6. 获取当前文件的名称 fname=$(basename $file) ssh $host "mkdir -p $pdir" rsync -av $pdir/$fname $host:$pdir else echo $file does not exists! fi done done
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
然后授权其执行权限和复制到/bin中,以便全局使用
chmod +x xsync cp xsync /bin/
- 1
- 2
#安装(hadoop2、3、4都需要)
yum -y install rsync
#配置xsync脚本,并复制到/bin目录下
#分发
xsync /usr/local/hadoop/etc/hadoop/
#也可以使用相对路径,需要当前路径为/usr/local/hadoop/etc/hadoop
xsync ./
echo "hadoop2" > workers
echo "hadoop3" >> workers
echo "hadoop4" >> workers
xsync workers
注意:格式 化 NameNode,会产生新的集群 id,导致 NameNode 和 DataNode 的集群 id 不一致,集群找不到已往数据。如果集群在运行过程中报错,需要重新格式化 NameNode 的话,一定要先停止namenode 和 datanode 进程,并且要删除所有机器的 data 和 logs 目录,然后再进行格式化。
#第一次启动,需要格式化NameNode,成功后会在hadoop根目录下生成data目录
hdfs namenode -format
#启动hdfs
/usr/local/hadoop/sbin/start-dfs.sh
#如果报错找不到变量,在sbin/start-dfs.sh、stop-dfs.sh顶部加上以下内容:缺啥加啥
HDFS_NAMENODE_USER=root
HDFS_DATANODE_USER=root
HDFS_SECONDARYNAMENODE_USER=root
#启动成功后,使用jps命令可以在hadoop2上看到DataNode和NameNode,hadoop3上看到DataNode,hadoop4上看到DataNode和SecondaryNameNode
jps
#nn-web页面:192.168.30.3:9870,2nn-web页面:192.168.30.3:9868
#停止hdfs
/usr/local/hadoop/sbin/stop-dfs.sh
#以下所有操作都在:hadoop3上
/usr/local/hadoop/sbin/start-yarn.sh
#如果报错找不到变量,在sbin/start-yarn.sh、stop-yarn.sh顶部加上以下内容:缺啥加啥
YARN_RESOURCEMANAGER_USER=root
YARN_NODEMANAGER_USER=root
#如果报错Permission denied,则hadoop3也要配置对其他主机的免密登录
#启动成功后,使用jps命令可以看到在hadoop2上多了NodeManager,hadoop3上多了ResourceManager和NodeManager,hadoop4上多了NodeManager
#yarn页面:192.168.30.3:8088
#停止yarn
/usr/local/hadoop/sbin/stop-yarn.s
便捷启动和便捷检查:在/root/bin/下编写集群启听脚本和jps-all脚本
最后授权和分发:
chmod +x my-hadoop chmod +x jps-all #临时将/root/bin目录添加到PATH中 export PATH=$PATH:/root/bin xsync /root/bin/ # 使用my-hadoop和jps-all my-hadoop start/stop jps-all
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
#!/bin/bash if [ $# -lt 1 ] then echo "No Args Input..." exit ; fi #也可以直接使用 HADOOP_HOME,而不需要新定义一个变量 hadoop_home=/usr/local/hadoop case $1 in "start") echo "==================== 启动 hadoop 集群 ===================" echo " --------------- 启动 hdfs ---------------" ssh hadoop2 "$hadoop_home/sbin/start-dfs.sh" echo "---------------- 启动 yarn ---------------" ssh hadoop3 "$hadoop_home/sbin/start-yarn.sh" echo "---------------- 启动 historyserver ---------------" ssh hadoop2 "$hadoop_home/bin/mapred --daemon start historyserver" ;; "stop") echo "==================== 关闭 hadoop 集群 ===================" echo "---------------- 关闭 historyserver ---------------" ssh hadoop2 "$hadoop_home/bin/mapred --daemon stop historyserver" echo "---------------- 关闭 yarn ---------------" ssh hadoop3 "$hadoop_home/sbin/stop-yarn.sh" echo "---------------- 关闭 hdfs ---------------" ssh hadoop2 "$hadoop_home/sbin/stop-dfs.sh" ;; *) echo "Input Args Error..." ;; esac
#!/bin/bash
for host in hadoop2 hadoop3 hadoop4
do
echo =============== $host ===============
ssh $host "/usr/local/jdk1.8/bin/jps"
done
文件保存位置为:/usr/local/hadoop/data/dfs/data/current/
#创建文件夹(fs-FileSystem),是否成功可以访问9870,去查看菜单Utilities下的“Browse the file system”页面
hadoop fs -mkdir /input
#上传文件,上传成功后可以到页面上去预览和下载
hadoop fs -put /bin/xsync /input
#方式1:在浏览器页面下载
#方式2:通过get命令下载(到当前路径)
hadoop fs -get /input/xsync
发现浏览器方式预览和下载文件会失败,是因为没有配置hostname和ip的对应关系,需要配置。(注意容器一定开放了端口:9864)
打开C:\Windows\System32\drivers\etc\hosts,加上以下内容:
192.168.30.3 hadoop2
192.168.30.3 hadoop3
192.168.30.3 hadoop4
#为了方便访问,还可以加上这个
192.168.30.3 hadoop
注意:集群模式下,输入路径和输出路径需要都是HDFS的路径。
#统计input目录下所有文件的单词种类和对应的数量
hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar wordcount /input /output
#访问ip:8088可以看到程序的执行情况,程序执行完成后访问ip:9870的文件系统,执行结果在output目录下的文件中
在yarn的web网页上,点击具体程序的history,发现页面打开失败。为了查看程序的历史执行情况,就需要配置历史服务器。
<!--历史服务器端地址-->
<property>
<name>mapreduce.jobhistory.address</name>
<value>hadoop102:10020</value>
</property>
<!--历史服务器web端地址-->
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>hadoop102:19888</value>
</property>
xsync etc/hadoop/
/usr/local/hadoop/bin/mapred --daemon start historyserver
jps
#停止历史服务器进程
/usr/local/hadoop/bin/mapred --daemon stop historyserver
日志聚集概念:应用运行完成以后,将程序运行日志信息上传到 HDFS 系统上。这样可以方便的查看到程序运行详情,方便开发调试。
注意:开启日志聚集功能,需要重新启动 NodeManager 、ResourceManager 和 HistoryServer。
<!-- 开启日志聚集功能 -->
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>
<!-- 设置日志聚集服务器地址 -->
<property>
<name>yarn.log.server.url</name>
<value>http://hadoop2:19888/jobhistory/logs</value>
</property>
<!-- 设置日志保留时间为 7 天 -->
<property>
<name>yarn.log-aggregation.retain-seconds</name>
<value>604800</value>
</property>
xsync $HADOOP_HOME/ect/hadoop/
# hadoop2上输入以下命令停止HistoryServer
$HADOOP_HOME/bin/mapred --daemon stop historyserver
# hadoop3上输入以下命令停止NM、RM,然后启动
$HADOOP_HOME/sbin/stop-yarn.sh
$HADOOP_HOME/sbin/start-yarn.sh
# hadoop2上
$HADOOP_HOME/bin/mapred --daemon start historyserver
#分别启动/停止 HDFS 组件
hdfs --daemon start/stop namenode/datanode/secondarynamenode
# 启动/停止 YARN
yarn --daemon start/stop resourcemanager/nodemanager
使用步骤如下:
HADOOP_HOME
,值为之前下载并解压好的hadoop3.1.0的目录,并在环境变量path
里加上%HADOOP_HOME%/bin
<dependencies> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>3.1.3</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.36</version> </dependency> </dependencies>
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
public class HdfsClientTest { private FileSystem fs; @Before public void init() throws Exception { // 客户端地址 URI uri = new URI("hdfs://hadoop2:8020"); // 配置 Configuration config = new Configuration(); // 让NameNode返回的DataNode地址不用使用ip地址,而是使用主机名,解决上传/下载文件失败的问题 // 注意前提是: // 1.在hosts文件里配置了主机名和ip的映射 // 2.主机开放了端口9866,通过“hadoop dfsadmin -report”可以查看DataNode的ip和端口 config.set("dfs.client.use.datanode.hostname", "true"); // 用户 String user = "root"; // 创建客户端 fs = FileSystem.get(uri, config, user); } public void close() throws IOException { fs.close(); } @Test public void testMkdir() throws Exception { fs.mkdirs(new Path("/test")); } /** * 参数优先级排序:客户端代码中设置的值 > ClassPath下的用户自定义配置文 * 件 > 服务器的自定义配置(xxx-site.xml)> 服务器的默认配置(xxx-default.xml) */ @Test public void testUpload() throws Exception { // 参数1:上传后是否删除源文件,参数2:是否允许覆盖,参数3:源文件路径,参数4:目标文件路径 fs.copyFromLocalFile(false, true, new Path("D:\\Download\\hadoop\\input\\hello.txt"), } @Test public void testDownload() throws Exception { // 参数1:是否删除源文件,参数2:源文件路径,参数3:目标文件路径, 参数4:是否开启文件的crc校验 fs.copyToLocalFile(false, new Path("/README.md"), new Path("D:\\Download\\"), true); } @Test public void testDelete() throws Exception { // 参数1:要删除的文件路径,参数2:是否递归删除 fs.delete(new Path("/test"), true); } @Test public void testRenameAndMove() throws IOException { // 重命名 // fs.rename(new Path("/README.md"), new Path("/readme.md")); // 移动 fs.rename(new Path("/readme.md"), new Path("/test/README.md")); } }
按照上述步骤操作会发现创建文件夹没有问题,但上传文件会失败。原因是上传文件时Java客户端会直接和DataNode打交道,而NameNode返回的DataNode信息是它的局域网ip和端口,所以Java客户端就没办法连接上DataNode进行写文件了。解决方法如下:
配置类加上配置:
config.set("dfs.client.use.datanode.hostname", "true")
通过
hadoop dfsadmin -report
查看DataNode的ip和端口,在C:\Windows\System32\drivers\etc\hosts
配置好ip和主机名的映射和开放端口(默认是9866)。由于我们三个容器部署在一台服务器上,并且之前已经配置好了hostname和ip的映射关系,9866端口也开放了,所以这一步不需要做任何操作。补充:这里可能出现报错:
Got error, status=ERROR, status message , ack with firstBadLink as 192.168.1.2:9866
,这个报错时有时无;当有报错时,通过在网页端查看文件的副本信息可以发现:有两个副本上传成功,而另一个失败了。但成功的两个副本所在DataNode并不是固定的,我把日志级别设置为debug后研究了一番,但还是没弄清原因,等待有缘人提供解决方法。
import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; import java.io.IOException; /** * KEYIN:mapper阶段输入数据的key的类型,LongWritable——偏移量 * VALUEIN:mapper阶段输入数据的value的类型,Text——一行文本内容 * KEYOUT:mapper阶段输出数据的key的类型,Text——单词 * VALUEOUT:mapper阶段输出数据的value的类型,IntWritable——单词出现次数 */ public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> { private final Text outK = new Text(); private final IntWritable outV = new IntWritable(1); @Override protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException { String[] words = value.toString().split(" "); for (String word : words) { outK.set(word); context.write(outK, outV); } } } /** * KEYIN:reduce阶段输入的key的类型 * VALUEIN:reduce阶段输入的value的类型 * KEYOUT:reduce阶段输出的key的类型 * VALUEOUT:reduce阶段输出的value的类型 */ public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> { private final IntWritable outV = new IntWritable(); @Override protected void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable value : values) { sum += value.get(); } outV.set(sum); context.write(key, outV); } } public class WordCountDriver { public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException { // 1 获取配置信息,或者job对象实例 Configuration conf = new Configuration(); Job job = Job.getInstance(conf); // 2 设置jar加载路径 job.setJarByClass(WordCountDriver.class); // 3 设置map和reduce类 job.setMapperClass(WordCountMapper.class); job.setReducerClass(WordCountReducer.class); // 4 设置map输出 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); // 5 设置最终输出 job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); // 6 设置输入和输出路径 // FileInputFormat.setInputPaths(job, new Path("D:\\Download\\hadoop\\input")); // FileOutputFormat.setOutputPath(job, new Path("D:\\Download\\hadoop\\output")); FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); // 7 提交job boolean result = job.waitForCompletion(true); System.out.println(result ? "job执行成功" : "job执行失败"); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。