Hadoop入门学习笔记之一
http://hadoop.apache.org/docs/r1.2.1/api/index.html
适当的利用 null 在map中可以实现对文件的简单处理,如排序,和分集合输出等。
需要关心的内容
一个节点面对的是一个Map任务,一个Map任务面对的是一个split文件,一个map方法面对的是一个split文件生成的键值对。
mapper类中map方法的输入是InputFormat的ReadeRecord类读取到的键值对
学习一周之后问题总结:
1.实验时使用的文件过小,大量小文件问题,需要通过处理,最终形成sequencefile进行处理。
2.在设置sequenceFileOutputFormat.class时,设置setOutputKeyClass() setOutoutvalueClass(),中的数据类型时,有问题,
3.sequenceFile相关的 recordreader不知道应该怎么使用,总是会有错。
4.在未设置reduce过程时,系统默认的是将key value都作为text进行输出。
5.文件划分split的大小还不会设置
6.文件划分,record读写的自定义函数还不会重载
7.输入文件夹和输出文件的路径,以及在不同运行环境中,指的是hdfs中的位置还是pc中的路径,还不太明白,在eclipse中虚拟云中不带hdfs://的是本地路径,在真正的云环境中运行则都代表hdfs中的路径
8.构建较大的数据集,如果采用程序将大量的小文件合并成云上的 序列文件,时间非常长,如果将构建好的序列文件下载到本地,下次可以直接将该文件上传至云端,速度较快。
9.在大数据集是(分块64M)没有真正的分布在各个子节点中运行,是应为在eclipse环境中默认是使用的虚拟云。需要在代码中进行相关的配置,从而真正的运行到云端。
10.运行过程中class not found 、jar not found、500030端口中查看job运行为none登等情况的处理~
http://os.51cto.com/art/201305/392965.htm
如果程序中使用了系统的动态链接库.so文件,执行作业之前一定要首先确保每台节点上的执行环境已经配置好,特别是节点中有64位和32位的机器时,
首先将自己的自定义库代码针对不同的平台进行编译。(特别是在使用了JNI的情况下),否则最终的分配给节点 的任务还是执行失败。
编译打包:
引用的外部包路径+java源码路径+class输出路径
(将.java文件编译成.class文件, 将.class文件打包成.jar文件,jar文件的名字可以随便起)
$ javac -classpath /home/sunny/usr/hadoop-1.2.1/hadoop-core-1.2.1.jar:/home/sunny/usr/hadoop-1.2.1/lib/commons-cli-1.2.jar -d ./classes ./src/test/example/Facelib.java ./src/newMatching/*.java $ jar -cvf newMatching.jar -C ./classes/ .
放到云端执行:
jar包+主类的包名.主类名
sunny@MASTERPC:~/usr/hadoop-1.2.1/bin$ ./hadoop jar /home/sunny/workspace/eclipse/newMatching/newMatching.jar newMatching.MatchDoer
在分布式计算的时候,datanode节点需要从hdfs文件系统上将Job的jar文件下载到本地磁盘进行本地计算,所以需要在终端中提交任务的时候将jar包提交上去。
在eclipse中Run on hadoop 是在eclipse的虚拟云上进行执行的,没有生成jar文件。
在分布式执行的过程中可能遇到的问题:
1. class not found, job代码中添加job.setJarByClass(xxx.class);(xxx是自己的主类)
2. native lib .so not found, 如果使用了系统中的动态链接库,则需要保证每台及节点系统中已经配安装了相应的库文件,特别是在使用了JNI的情况下。
3.在eclipse中直接点击Run on Hadoop,默认使用的是本地文件系统,LocalFileSystem, 如果文件路径前没有显示的加上hdfs://master:9000/所处理的
就是本地磁盘路径。如果在代码中添加:
conf.set("fs.default.name", "hdfs://master:9000/"); //在你的文件地址前自动添加:hdfs://master:9000/ conf.set("hadoop.job.user","yourname"); conf.set("mapred.job.tracker","master:9001");
这样文件路径默认的就是HDFS分布式文件系统上的了。
综上:
1.eclipse中的hadoop就是在本地运行的java程序,(文件存储在本地磁盘,没有jobTrack进行作业调度,程序顺序执行);
2.加上conf.set之后文件系统使用的是hdfs,但依然没有jobTracker,进行map的节点只有本机一个,程序顺序执行;
3.通过终端命令进行job申请和提交才会触发jobtracker进行真正的分布式计算。
>>开发过程:
1.在eclipse中coding;
2.在eclipse中运行通过; (Run on Hadoop)
3.将eclipse生成的bin目录下的class文件打包成.jar文件;(jar -cvf yourname.jar -C ./bin .)
4.在命令行中提交作业,进行分布式执行; (./hadoop jar yourname.jar packename.classname args1 args2)
其中1~3步骤是编程调试阶段,第4步是真正运行阶段。 ^.^
master独自运行job 分布式运行
另一种方案:
在代码中添加: conf.set("mapred.job.tracker", "master:9000");
之后eclipse中的Run on hadoop 就会在云端执行(而不是仅仅在当前节点上map),但是其他节点会class not found异常(不知道是应为jar没找到还是主类没找到);
在eclipse工程上右键java build path->add external jar 上选中自己用jar -cvf生成的jar包,然后执行run on hadoop就可以分布式执行了。
使用这种方法之后,每次修改了程序后要先生成jar,然后在在eclipse中run on hadoop
(不知道对不对, java程序都是执行在vm上的, 不需要知道hadoop的安装路径,执行job相关的 程序就可以提交作业;jar是给别的节点用的,java程序是给自己执行的,
直接执行java程序仅仅是默认没有读取hadoop的配置文件,所以需要在程序中明确的指明hdfs和jobtracker)
在eclipse中开发,hadoop的配置文件什么时候生效,很困惑,直接在eclipse中点击Run运行,hadoop的conf下配置文件的有些选项不生效。
要在代码里conf.set("name",value);,但是单机模式时conf.set("mapred.child.java.opts","-Xss1m");也不生效,必须在RunConfigurations中配置VM变量。
从源码中可以看到,hadoop jar xxx.jar xxx ,hadoop脚本对参数jar的处理时调用RunJar类,该类的源码在src/core/org/apache/hadoop/util下,
其中的main函数(args, jar, manifest, unjar, runtime)
以上费劲周折使程序运行在真正的分步式化境中,但是这是又会有新的问题出现:
1.在程序中使用本地磁盘的路径:LocalFileSystem,或者对于FileInputStream这样的java API默认使用的就是本地地址,也可以使用LocalFileSystem
2.程序中的调试信息输出问题:eclipse中的虚拟环境为单机运行,所以程序执行起来与普通java程序类似,所有打印信息都在eclipse的console中输出,
当成许运行在分布式环境中时, main中的system.out,system.err等调试信息会在提交作业的终端中显示,但是mapper和reducer中的调试信息则不会显示到终端中,
查看他们的调试信息只能到web上的500030的JobTracker中找到相应的map task的log信息中查看。
参考:http://blog.csdn.net/azhao_dn/article/details/8003998
3.程序的修改,更改之后要先重新生成jar,才能是修改生效。
4. 在分布式环境中程序不仅仅运行在本节点上,所以一定要保证程序所需的 数据、链接库等在各个节点上都具备
从以上几点来看,在开发阶段使用eclipse的虚拟云可以大大提高开发和调试的效率。
分布式的节点在启动hadoop服务之后,会读取本节点中hadoop安装文件夹中conf下的配置文件,hdfs-site.xml下配置的相关
真正的分布式环境配置成功之后,如果启动了hdfs服务之后,有些datanode没有启动,则应该检查各个节点hadoop的配置文件是否相同。
(待思考的问题:各个分布式节点用户名要相同, hadoop安装目录要相同,hadoop配置文件要相同, java路径随便)
也可以尝试将各个节点/tmp/hadoop相关目录删除,yourhadooppath/logs/内容目录删除,重新namenode -format
跟namenode、jobtracker相关的配置选项只对master节点有作用,其他hdfs相关配置会对每个节点起作用。
配置文件、log文件、tmp临时文件对hadoop 各节点的正常运行还是很重要的。
hadoop中配置文件的相关选项还是不太了解。。 。。。 。
有时候4个节点都正常启动了,但是stop-all.sh之后再启动,关机某些节点在开启之后,启动服务,往往是最后启动的机器能够启动
dfs节点,这种开关机不同步导致的datanode节点不能正常完全启动的问题,。。。。全部重启,namenode -format,
集群节点较多的时候并不是mapper过程完全结束之后才开始reduce,很能在mapper完成大半后就开始reduce了。
setNumOfReduce之后,去掉了reduce过程,最终的输出结果是未经排序的!
有几个mapper就会将几个mapper的结果输出到最后的几个文件中part-m-0000
reduce的执行节点根据具体的执行状况分配给某节点进行,并不固定。
如果上传分块过小(<64M),则系统不再自动分块,也不会自动合并,按照上传分块为单位分配给mapper处理。
编写程序时的注意:
在反复遍历生成文件时,由于数据量很大,所以在循环内外的变量分配很重要, 不然内存会溢出。
打开的文件流,使用完之后一定要记得关闭。
定义的变量,在使用之前一定记得new 否则会产生null pointer的异常。
如果在mapper或者reducer中设置了全局变量,而在使用中这些变量又与初始值相关(如直接|、&、^,++等),则需要注意在一个单元使用完成之后进行
初始化,特别是reduce中对应的是key+list链表,一个key的所有value处理完成之后需要对全局变量进行初始化,为下一个key的处理做准备!
hadoop的运行效率不仅仅与节点的个数有关,job中的任务执行,文件的读写,数据量的划分,网路传输,精简不必要的工作
每个代码块的执行效率有关。
配置最大map和reduce并行task数量:
mapred-site.xml 配置:mapred.tasktracker.map.tasks.maximum和mapred.tasktracker.reduce.tasks.maximum, 默认都是2.
要求reduce<map, 这样可以留有一些备用节点,提高故障时作业的恢复时间,如果reduce>map,那么空余的reduce没有文件输入但是还是会
启动task生成空文件,浪费了系统资源和效率。
将大量的较小的中间键值对合并成较少的较大的中间键值对, 以减少网络中的流量(for reduce),在大数据处理时优势会显现的非常明显,可以通过web界面
查看各个过程处理的键值对数量。
mapreduce框架:
inputformat(文件分割+键值对生成) map 排序 combine(相同key的value累加) partitioner(hash映射分组)
排序+相同key构成value-list reduce(生成键值对) outputformat(写回HDFS)
过程中的系统默认:
TextInputFormat:将输入文件作为文本文件处理, 生成行行文本的键值对
IdentityMapper:将输入的键值对 原封不动的 输出
Combiner:null , 不进行中间结果的合并
Partitioner: HashPartitioner, 用hash进行分组
IdentityReducer: 将输入的中间结果键值对直接输出键值对
TextOutputFormat:将最终键值对结果生成文本文件, 每行一个<key, value>对, key 和 value之间用tab分隔。
---
setOutputKeyClass, setOutputValueClass来设置最终的key、value的类型 (?),setMapOutputKeyClass, 默认情况下和最终结果的键值对类型相同。
键值对 的类型对如何解析、理解和处理数据时有影响的。
combiner在编程时就等同于reduce,只有类的名称不同,(可能combiner的输入也是经过中间的排序和累加之后)
没有combine后,reduce接收到的就不再是<key,list<value>>的形式了。
综上: mapred默认框架是将输入文本文件按行解析, 排序, 最后按照<key, value>每行的形式输出到文本文件中。
其key的默认类型时Longwritabel, value的默认类型是Text,Long可以转化输出到文本中,显示的还是long数据的值。
当数据量很大时, 在reduce函数中进行本地排序往往不可行,算法对于不同大小的数据集不通用
通用框架的自定义: 根据输入的键值对进行处理,然后利用context上下文书写自己的下一阶段的键值对。
在进行combiner编程时,就把他当做是reducer。
要充分的利用系统的排序功能。
如果此时你run java aplication,你的程序只会在eclipse中虚拟的一个云环境中运行,而不会跑上云端去运行,所以无法再master:50070/jobtracker.jsp页面中监控到该作业;需要在main方法中添加几行代码,代码附录如下:
//在你的文件地址前自动添加:hdfs://master:9000/ conf.set("fs.default.name", "hdfs://master:9000/"); conf.set("hadoop.job.user","mango"); //指定jobtracker的ip和端口号,master在/etc/hosts中可以配置 conf.set("mapred.job.tracker","master:9001");
MapReduce框架
任务调度程序,将任务并行的分给节点进行计算
将输入文件划分为split供Mapper类使用, 考虑到数据要尽量进行本地运算,所以划分的split的大小应该<=文件块的存储大小(64M)
在完成combine和shuffle之后map的中间结果被直接写到本地磁盘,通知JpbTracker中间结果文件的位置, 再由JobTracker告知Reducer到
哪个Datanode上去取中间结果。 每个Reducer要想多个Mapper节点取得落在其负责范围内的中间结果然后执行reduce函数,形成一个最终结果文件。
执行过程:
job执行前的工作:
1.编写MapReduce代码,编译,打包成jar
2.将数据上传到HDFS上,分布式系统HDFS会自动将大文件划分成块进行分布式存储(FlieBlockLocations)
终端申请提交作业:
outputformat检查job输出目录是否已经存在,进行出错检查。
inputformat将HDFS上的job inputpath中的逻辑文件划分成逻辑的split块,(List<split>将记录每块在源文件中的偏移量和所在的主机)
将MapReduce的jar包、配置文件、split list信息上传到HDFS的一个目录中,(以Job ID作为目录名)
提交作业完毕
JobTracker:
将终端提交的作业放到作业队列中
开始执行当前作业时,首先将作业目录中的InputSplit信息取出来,根据实际运行情况为每个InputSplit创建一个Map任务
创建reduce等任务
TaskTracker
接到新任务后,将这个任务的程序jar文件、数据split从HDFS上复制到本地磁盘上,进行本地计算。
配置环境相关不错的网址:http://www.cnblogs.com/xia520pi/archive/2012/05/20/2510723.html