《云计算应用开发实验》大作业报告
一.实验环境与实验工具
ubuntu 16.04真机 + hadoop2.6 + 本地伪分布
二.实验原理
以下内容为科普性内容,不过里面还是有一些关键的解释在配环境的时候用得上
Hadoop是一个能够让用户轻松架构和使用的分布式计算平台。
用户可以轻松地在Hadoop上开发和运行处理海量数据的应用程序。它主要有以下几个优点:
- 高可靠性。Hadoop按位存储和处理数据的能力值得人们信赖。
- 高扩展性。Hadoop是在可用的计算机集簇间分配数据并完成计算任务的,这些集簇可以方便地扩展到数以千计的节点中。
- 高效性。Hadoop能够在节点之间动态地移动数据,并保证各个节点的动态平衡,因此处理速度非常快。
- 高容错性。Hadoop能够自动保存数据的多个副本,并且能够自动将失败的任务重新分配
- 低成本。与一体机、商用数据仓库以及QlikView、Yonghong Z-Suite等数据集市相比,Hadoop是开源的,项目的软件成本因此会大大降低
Hadoop的核心就是HDFS和MapReduce
HDFS(Hadoop Distributed File System,Hadoop分布式文件系统),它是一个高度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,适合那些有着超大数据集(large data set)的应用程序。
HDFS的关键元素:
- Block:将一个文件进行分块,通常是64M。
- NameNode:保存整个文件系统的目录信息、文件信息及分块信息,这是由唯一一台主机专门保存,当然这台主机如果出错,NameNode就失效了。
- SecondaryNameNode 它不是 namenode 的冗余守护进程,而是提供周期检查点和清理任务。
- 出于对可扩展性和容错性等考虑,我们一般将SecondaryNameNode运行在一台非NameNode的机器上。
- DataNode:它负责管理连接到节点的存储(一个集群中可以有多个节点)。每个存储数据的节点运行一个 datanode 守护进程。
上面关于namenode和datanode的定义还是比较重要的
MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算。概念”Map(映射)”和”Reduce(归约)”,是它的主要思想。它极大地方便了编程人员在不会分布式并行编程的情况下,将自己的程序运行在分布式系统上。
MapReduce物理实体
- 客户端(client):编写mapreduce程序,配置作业,提交作业,这就是程序员完成的工作;
- JobTracker:初始化作业,分配作业,与TaskTracker通信,协调整个作业的执行;
- TaskTracker:保持与JobTracker的通信,在分配的数据片段上执行Map或Reduce任务,TaskTracker和JobTracker的不同有个很重要的方面,就是在执行任务时候TaskTracker可以有n多个,JobTracker则只会有一个;
- Hdfs:保存作业的数据、配置信息等等,最后的结果也是保存在hdfs上面
MapReduce作业运行机制:
- ### 这一过程在实验中体现得比较明显,之后会有代码的详细分析
1.在客户端启动一个作业。
2.向JobTracker请求一个Job ID。
3.将运行作业所需要的资源文件复制到HDFS上,包括MapReduce程序打包的JAR文件、配置文件和客户端计算所得的输入划分信息。这些文件都存放在JobTracker专门为该作业创建的文件夹中。文件夹名为该作业的Job ID。JAR文件默认会有10个副本(mapred.submit.replication属性控制);输入划分信息告诉了JobTracker应该为这个作业启动多少个map任务等信息。
4.JobTracker接收到作业后,将其放在一个作业队列里,等待作业调度器对其进行调度,当作业调度器根据自己的调度算法调度到该作业时,会根据输入划分信息为每个划分创建一个map任务,并将map任务分配给TaskTracker执行。对于map和reduce任务,TaskTracker根据主机核的数量和内存的大小有固定数量的map槽和reduce槽。
5.TaskTracker每隔一段时间会给JobTracker发送一个心跳,告诉JobTracker它依然在运行,同时心跳中还携带着很多的信息,比如当前map任务完成的进度等信息。当JobTracker收到作业的最后一个任务完成信息时,便把该作业设置成“成功”。当JobClient查询状态时,它将得知任务已完成,便显示一条消息给用户。
用最简短的语言解释MapReduce:
We want to count all the books in the library. You count up shelf #1, I count up shelf #2. That’s map. The more people we get, the faster it goes.
Now we get together and add our individual counts. That’s reduce.
三.实验过程
以下内容为正题,从环境搭建开始:
重搭真机伪分布环境
之前的实验是在虚拟机上跑的,利用两台虚拟机一台做主机一台做从机,虚拟机性能差,两台虚拟机之前的连接速度极慢,map reduce的过程也非常的慢,亲测是没跑动上面的算法,所以我重新在ubuntu16.04的真机上搭建了伪分布式的hadoop环境和hdfs分布式文件系统,当然也顺便把单机模式的hadoop也跑了一下wordcount,伪分布和完全分布就只是不需要配置slave文件,即自己就是自己的从机,因为TA给了配置文档,但是是全分布的,而且有些也不需要,我就把关键的地方说一下
1. java安装环境变量啥的基本常识,注意一下要在~/.bashrc
里面添加的环境变量才会一直生效,配置完后需要source一下才能立即生效
2. 配置ssh,ssh毕竟是ubuntu最常用的功能之一,方便以后ssh自己的笔记本,这里就不用改名为master了,好难看,还是保持的当前的名字,但是在/etc/hosts那里我还是改成了自己的pc名,ip就是本地ip:127.0.0.1,不用添加master和slave,然后配置ssh就是让自己的机器能直接通过账户密码ssh控制,添加密匙什么的按教程来就没问题
3. hadoop安装配置,并且和java一样设置hadoop的环境变量就是在.bashrc文件里添加hadoop的bin路径,然后关键是三个文件的配置,注意尝试过单机模式,就不用配置core-site文件,然后输入输出就是本地的文件,我跑了一下wordcount试了一下成功了就继续配置伪分布模式
* 修改core-site.xml
<property>
<name>fs.defaultFS</name>
<value>hdfs://cx-ThinkPad-X230:9000</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/usr/local/hadoop/hadoop/tmp</value>
</property>
这里第一个fs.defaultFS就是配置默认的文件系统,主要要写上自己主机名称,9000为端口号,默认为这个如果不是那么需要在代码中设置,
第二个是hadoop.tmp.dir,下面的路径名就是你第一次执行hdfs namenode -format
这在这个文件夹下生成hdfs系统,这是一个基目录,所以如果你的hdfs文件系统出了问题或者想重新配置,将这个文件夹删掉再重新执行hdfs namenode -format
即可(亲测经验之谈。。。)
- 修改hdfs-site.xml
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>/usr/local/hadoop/hadoop/tmp/dfs/name</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>/usr/local/hadoop/hadoop/tmp/dfs/data</value>
</property>
dfs.replication是副本数,即block的备份数
后面两个就是节点的路径,需要在第一个配置文件的子目录下面,这点十分重要,非常之坑,亲测我这里的路径和上面的路径名不一致,导致我后面只要一跑mapreduce导致系统直接注销,迷了我很久,后来从伪分布又换成单机,又换成伪分布,配了几次才发现问题
NameNode 和Datanode在前面已经介绍得很清楚,就不过多介绍
这里在hdfs文件系统format之后我尝试在/usr/local/hadoop/hadoop/tmp
即我指定的目录中查看我put上去的东西,结果并不能找到文件,而是一些奇怪的文件,看来hdfs文件系统将传上去的文件处理过,要访问只能通过hdfs方式查看
然后配置文件还要配置(其实伪分布模式可以不用搞这个的)
YARN 是从 MapReduce 中分离出来的,负责资源管理与任务调度。YARN 运行于 MapReduce 之上,提供了高可用性、高扩展性,如果配了用sbin/start-yarn.sh
启动即可,会看到多的nodemanager和recoursemanager,配置和教程一样就不多说了
上面的配置过程还是折腾了许久,因为没有按照TA的教材自己研究,网上教程版本太多,参差不齐,不过有问题才能学到东西,配置过程也加深了对hadoop和hdfs的理解,这个东西还是很强大的,也激起了继续折腾的兴趣
算法实现
1.Kmeans
这里先介绍一下Kmeans算法
K-means算法是硬聚类算法,是典型的基于原型的目标函数聚类方法的代表,采用距离作为相似性的评价指标,即认为两个对象的距离越近,其相似度就越大。该算法认为簇是由距离靠近的对象组成的,因此把得到紧凑且独立的簇作为最终目标。
算法过程如下:
- 从N个文档随机选取K个文档作为质心
- 对剩余的每个文档测量其到每个质心的距离,并把它归到最近的质心的类
- 重新计算已经得到的各个类的质心
- 迭代2~3步直至新的质心与原质心相等或小于指定阈值,算法结束
从 K-means 算法框架可以看出,该算法需要不断地进行样本分类调整,不断地计算调整后的新的聚类中心,因此当数据量非常大时,算法的时间开销是非常大的。而Hadoop的分布式计算平台恰好可以弥补这一缺点,所以K-means算法也十分适合hadoop
其实这个算法在hadoop实现非常简单粗暴,感觉也是hadoop和机器学习结合最通俗易懂的例子,个人觉得就像机器学习和hadoop结合的”wordcount”,这里主要是对代码的详细分析,包括了对mapreduce的理解
先介绍map和reduce过程
* map过程:
首先从HDFS上读取中心点,然后让当前文档与这k个中心点进行距离运算,找出距离最近的的一个中心点,以中心点为key值,将记录原样输出。
public static class Map extends Mapper<LongWritable, Text, IntWritable, Text>{
//中心集合
ArrayList<ArrayList<Double>> centers = null;
int k = 0;
//读取中心
protected void setup(Context context) throws IOException,
InterruptedException {
//从HDFS上读取数据
centers = Utils.getCentersFromHDFS(context.getConfiguration().get("centersPath"),false);
k = centers.size();//获得中心点的数目
}
/**
* 1.每次读取一条要分类的条记录与中心做对比,归类到对应的中心
* 2.以中心ID为key,中心包含的记录为value输出(例如: 1 0.2 。 1为聚类中心的ID,0.2为靠近聚类中心的某个值)
*/
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
//读取一行数据
ArrayList<Double> fileds = Utils.textToArray(value);
int sizeOfFileds = fileds.size();//维度
double mindistance = 1999999999;
int centerIndex = 0;
//依次取出k个中心点与当前读取的记录做计算
for(int i=0;i<k;i++){
double currentdistance = 0;
for(int j=1;j<sizeOfFileds;j++){
double centerPoint = Math.abs(centers.get(i).get(j));
double filed = Math.abs(fileds.get(j));
currentdistance += Math.pow((centerPoint - filed), 2);
}
//循环找出距离该记录最接近的中心点的ID
if(currentdistance<mindistance){
mindistance = currentdistance;
centerIndex = i;//记录下是第几个中心点
}
}
//以中心点为Key 将记录原样输出
context.write(new IntWritable(centerIndex+1), value);
}
}
从HDFS读取文件函数getCentersFromHDFS():根据路径读取文件,转换为ArrayList
public static ArrayList<ArrayList<Double>> getCentersFromHDFS(String centersPath,boolean isDirectory)
throws IOException{
ArrayList<ArrayList<Double>> result = new ArrayList<ArrayList<Double>>();
Path path = new Path(centersPath);
Configuration conf = new Configuration();
FileSystem fileSystem = path.getFileSystem(conf);
if(isDirectory){ //判断是否是目录
FileStatus[] listFile = fileSystem.listStatus(path);
for (int i = 0; i < listFile.length; i++) {
result.addAll(getCentersFromHDFS(listFile[i].getPath().toString(),false));
}
return result;
}
FSDataInputStream fsis = fileSystem.open(path);
LineReader lineReader = new LineReader(fsis, conf);
Text line = new Text();
while(lineReader.readLine(line) > 0){//读取中心点文件,将Text转换为ArrayList<ArrayList<Double>>返回
ArrayList<Double> tempList = textToArray(line);
result.add(tempList);
}
lineReader.close();
return result;
}
* reduce过程
以key值分簇,计算出该簇的下一轮的中心点。之后又回到main函数中,调用比较函数compareCenters()判断是否已经收敛。
//利用reduce的归并功能以中心为Key将记录归并到一起
public static class Reduce extends Reducer<IntWritable, Text, Text, Text>{
/**
* 1.Key为聚类中心的ID value为以该中心点的边缘点的集合
* 2.计数所有记录元素的平均值,求出新的中心
*/
protected void reduce(IntWritable key, Iterable<Text> value,Context context)
throws IOException, InterruptedException {
ArrayList<ArrayList<Double>> filedsList = new ArrayList<ArrayList<Double>>();
//依次读取记录集,每行为一个ArrayList<Double>
for(Iterator<Text> it =value.iterator();it.hasNext();){
ArrayList<Double> tempList = Utils.textToArray(it.next());
filedsList.add(tempList);
}
//计算新的中心
//每行的元素个数
int filedSize = filedsList.get(0).size();
double[] avg = new double[filedSize];
for(int i=1;i<filedSize;i++){
//求每列的平均值
double sum = 0;
int size = filedsList.size();
for(int j=0;j<size;j++){
sum += filedsList.get(j).get(i);
}
avg[i] = sum / size;
}
context.write(new Text("") , new Text(Arrays.toString(avg).replace("[", "").replace("]", "")));
}
}
* 主函数
读入输入的4个参数,执行选取初始中心点的函数getinitcenter(),初始化完成后进入第一次运算。
public static void main(String[] args)
throws ClassNotFoundException, IOException, InterruptedException {
Configuration conf = new Configuration();
if (args.length != 4) {
System.err.println("Usage: InvertedIndex <centerPath> <dataPath> <newCenterPath>");
System.exit(2);
}
args = new GenericOptionsParser(conf, args).getRemainingArgs();//获取运行命令时输入的参数
String centerPath = args[0];
String dataPath = args[1];
String newCenterPath = args[2];
int NumOfCenter=Integer.parseInt(args[3]);
Utils.getinitcenter(centerPath,dataPath,NumOfCenter);//随机选取初始点
int count = 0;
while(true){
run(centerPath,dataPath,newCenterPath,true);
System.out.println(" 第 " + ++count + " 次计算 ");
if(Utils.compareCenters(centerPath,newCenterPath )){//判断是否收敛,如果收敛则不用再进行运算
//run(centerPath,dataPath,newCenterPath,false);
break;
}
}
}
还有其他的一些通用函数这里就不介绍了,在附件的代码里都有详细的注释
Kmeans算法运行结果
上图是运行两次之后成功分簇,然后查看结果和原始的数据点,可以看到二维点集
(0,2),(0,0),(1.5,0),(5,0),(5,2)
已经分成了0,1两簇,第一簇包括(0,2),(0,0),(1.5,0)
,第二簇包括(5,0),(5,2)
2.shortestPath 最短路径算法
没错,我们虽然小组只有两个人但是就是折腾了两个出来,好不容易在真机上搭出来环境,跑得贼快,一个算法不过瘾,
还有原因是因为上面的例子其实基本就是博客写的,我们自己就加了个随机生成簇首点,估计写的同学都很多(估计TA也发现了大片代码雷同),
于是想重新搞一个难一点的,然后自己写,于是就选了这个算法
这个算法的思路也就是map和reduce的流程是参照博客上面的,但是实现都是自己敲的,算是比较深刻的实践了mapreduce编程,自我感觉要比kmeans难那么一丢丢,虽然做完了发现也很简单= =
首先说一下map和reduce的过程
文件的输入格式是
A B,12 E,5 D,1
B C,2 D,3
C E,1
D B,3 C,1 E,5
E A,1
K a,n1, b,n2 ... //表示节点到K 到 后面邻居节点的距离是n
然后在实验中发现下面的规则
注意节点与邻居表之间的分隔符是'\t'
,非常重要,因为在map的过程中会自动把一行文本按y遇到的第一个’\t’分隔成map的key和value,如果没有’\t’,那么会全部存为value,而key没有值(打印没有值,但是实际上我觉得应该是按行数来作为一个key value的)
- map过程
第一次将A B,12 E,5 D,1分解成为
Node <当前与源点距离> <邻接表>
A 0 B,12 E,5 D,1(源节点)
或者
B inf C,2 D,3(中间节点)
压入的时候就Node作为key,<当前距离> <邻接表>一同作为value
之后就是
如果当前与源点距离不为inf就把节点的的所有邻接表距离算出来(包含源点),并按照
Node作为key,当前距离+下一节点距离作为value
那么在reduce阶段,同一个key也就是每个node节点获得的values数据存的就是
Nodei <当前与源点距离> <邻接表>
Nodei dis1
Nodei dis2
Nodei dis3
reduce会自动按key值排序,所以我们获得到的nodei是按A,B,C..顺序得到的,所以也就类似BFS了
- reduce过程
key会排序,但是注意values数组并不会排序,而是按压入的顺序存储,
reduce过程任务是更新到当前节点的最短路径
从上面的Nodei dis中选一个最小的值与下面的距离比较,如果更小则更新
Nodei <当前与源点距离> <邻接表>
然后一直运行这个过程,直到一轮mapreduce之后所有节点都没有更新,则停止,最后输出源节点到所有节点的最短路径
代码+注释:
* map代码
public static class ShortestPathMapper extends Mapper<Text, Text, Text, Text> {
protected void map(Text key, Text value, Context context) throws IOException, InterruptedException {
String name=key.toString();
//configuration 里可以存一下控制变量,可在mapreduce过程中的获取
int conuter = context.getConfiguration().getInt("Mapcount", 1);
String adj = "";
//第一次就加上去初始的距离,源点为0其他为不可达
if (conuter == 1) {
if (name.equals("A")) {
adj = "0"+ " " +value.toString();
} else {
adj = "inf"+ " " +value.toString();
}
}
else {
adj = value.toString();
}
Node node = new Node();
node.getFormatString(adj);
if (!node.getDistance().equals("inf")){
//当前节点可达,就计算其所有邻接点距离
for (int i = 0; i < node.getNodeNum(); i++) {
String k = node.getName(i);
double new_dis=Double.parseDouble(node.getValue(i)) + Double.parseDouble(node.getDistance());
//System.out.println(k+" "+new_dis+"\n");
context.write(new Text(k), new Text(new_dis+""));
}
}
context.write(key, new Text(adj));
}
}
* reduce代码
public static class ShortestPathReducer extends Reducer<Text, Text, Text, Text> {
protected void reduce(Text key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
Node node = new Node();
boolean f=false; //标记该节点是否有更新最短距离
double min=9999999;//存到这个节点最短的距离
int i = 0;
//遍历每个节点的values,其中一个包含其邻接点信息,要单独取出来
for (Text t : values) {
String[] strs = StringUtils.split(t.toString(), ' ');
System.out.println(t.toString());
if (strs.length>1 ) {
//包含邻接点的信息的那条信息单独处理
node.getFormatString(t.toString());
}
else {
//否则取出距离
double dis_new=Double.parseDouble(strs[0]);
//和最短的比较,
if (dis_new<min){
min=dis_new;
}
}
i++;
}
//获取这一次reduce的最短距离和存储的最短距离
String dis_new=min+"";
String dis_old=node.getDistance();
//如果没有访问过又有新的距离,那么直接更新
if(dis_old.equals("inf")&&min!=9999999){
node.setDistance(dis_new);
f=true;
}
//否则需要判断距离是不是比当前的小,距离更近才更新
else if (min < Double.parseDouble(dis_old)){
node.setDistance(dis_new);
f=true;
}
如果有更新最短距离,那么更改停止标记
if (f==true){
context.getCounter(Stop_flag.Flag).increment(1L);
//context.getConfiguration().setBoolean("stop", true);
}
//获得stop标志,用于判断是否是最后一次输出
boolean stop=context.getConfiguration().getBoolean("stop", false);
if (stop==true)
context.write(key, new Text(node.getDistance()));
else
context.write(key, new Text(node.toString()));
}
}
* 主函数
static enum Stop_flag {Flag}
public static void main(String[] args) throws ClassNotFoundException, IOException, InterruptedException{
Configuration conf = new Configuration();
if (args.length != 2) {
System.err.println("Usage: ShortestPath <dataPath> <outPath>");
System.exit(2);
}
args = new GenericOptionsParser(conf, args).getRemainingArgs();
String dataPath = args[0];
String outPath = args[1];
FileSystem fs = FileSystem.get(conf);
int count = 1;
while (true) {
//System.out.println(conf.getInt("Mapcount", 0)+"\n");
conf.setInt("Mapcount", count);
conf.setBoolean("stop",false);
Job job = Job.getInstance(conf);
//jar包名
job.setJarByClass(ShortestPath.class);
//设置对应的class,在打包的jar里面
job.setMapperClass(ShortestPathMapper.class);
job.setReducerClass(ShortestPathReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setInputFormatClass(KeyValueTextInputFormat.class);
//每次的输出文件要为新的,这样可以看到更新的过程
Path outPath1 = new Path(outPath+"tmp" + count);
if (fs.exists(outPath1)) {
fs.delete(outPath1, true);
}
FileOutputFormat.setOutputPath(job, outPath1);
//输入文件第一次为输入的路径,后面就是上一次reduce生成的文件
if (count == 1)
FileInputFormat.addInputPath(job, new Path(dataPath));
else
FileInputFormat.addInputPath(job, new Path(outPath+"tmp" + (count - 1)));
if (job.waitForCompletion(true)) {
System.out.println("run time: "+count);
//if (conf.getBoolean("stop", false)) break;
long num = job.getCounters().findCounter(Stop_flag.Flag).getValue();
//如果所有节点都没有更新距离,那么算法结束
if (num==0) {
//结束的mapreduce输出只输出最短路径
conf.setInt("Mapcount", count);
//设置stop标记为true
conf.setBoolean("stop",true);
Job job1 = Job.getInstance(conf);
job1.setJarByClass(ShortestPath.class);
job1.setMapperClass(ShortestPathMapper.class);
job1.setReducerClass(ShortestPathReducer.class);
job1.setMapOutputKeyClass(Text.class);
job1.setMapOutputValueClass(Text.class);
job1.setInputFormatClass(KeyValueTextInputFormat.class);
//输入文件为最后的节点,输出文件为final文件
FileInputFormat.addInputPath(job1, new Path(outPath+"tmp" + count));
FileOutputFormat.setOutputPath(job1,new Path(outPath+"final"));
job1.waitForCompletion(true);
System.out.println("end ");
break;
}
}
count++;
}
}
ShortestPath算法运行结果
测试数据集
A B,3 D,1
B A,3 E,7 F,2
C D,2 E,5 F,3
D A,1 C,2 E,5
E B,7 C,5 D,5
F B,2 C,3
运行结果
上图红框为运行完成后查看生产的所有中间文件tmp1-3
和最终的运行结果final
A 0
B 3.0
C 3.0
D 1.0
E 6.0
F 5.0
表示从A点到各个节点的最短距离,结合上面的给出的数据图很容易验证是正确的,
然后黄色框的是把中间运行结果打印出来了,可以看到运行了三次,然后各点到A点的距离也在不断的减少,下面再贴一些mapreduce运行的中间过程截图
这打印的是reduce阶段接收到的每个节点的现在计算出的距离和邻接表,从上往下依次是A,B,C…原因就是因为reduce会按照key值排序,可以看出values列表是无序的,这就是之前也说过的问题。
左图为运行之后可以看到有些节点还是不可达的,距离为10^7或者9999999,(因为java的double精度丢失,很奇怪,竟然10^7就丢失精度了。。),右图为已经完成最短路径计算的。
实验部分至此结束
四.实验总结
这次大作业肯定是收获很大的,以后还是可以吹嘘自己是学过hadoop并且亲自写过mapreduce的,并且通过自己的实验从搭环境到wordcount程序跑起来,然后是学习别人的代码,从简单的例子,比如统计,排序,学习了mapreduce的框架和一些特性,这里总结一下框架,也就是每个hadoop程序都要走的
//配置文件,可以存储一些配置
Configuration conf = new Configuration();
//获取一个job
Job job = Job.getInstance(conf);
job.setJarByClass(ShortestPath.class);
//设置map和reduce的类,就自己写的map和reduce过程
job.setMapperClass(ShortestPathMapper.class);
job.setReducerClass(ShortestPathReducer.class);
//设置输入输出个格式,这里用到text
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setInputFormatClass(KeyValueTextInputFormat.class);
//输入输出文件,在hdfs文件系统上
FileInputFormat.addInputPath(job, new Path());
FileOutputFormat.setOutputPath(job, new Path());
然后还可以提前设置一些在mapreduce过程中用到的变量
//设置
conf.setInt("Mapcount", count);
conf.setBoolean("stop",true);
//在mapreduce过程中获取
boolean stop=context.getConfiguration().getBoolean("stop", false);
注意这些配置变量是不能在map和reduce过程中修改的,我在实验中尝试修改结果失败了
总之,实验过程虽然艰辛但是还是比较好玩的,hadoop这个平台以后有需要还会来继续研究研究,争取弄点更有意思的东西。