Hadoop hacker--府南河学习杂记

来源:互联网 发布:吉林公务员网络学校 编辑:程序博客网 时间:2024/05/01 01:36

谁让Google这么有魅力呢?谁让她推出MapReduce但又不给一个开源实现呢?于是,Hadoop应运而生!Hadoop也算得上大家闺秀系出名门,和著名的lucent有千丝万缕的关系。可惜Doug老先生精力有限,Hadoop的正式文档实在太少,网上流传的帖子多半止步于安装。非但没满足我的好奇心,反而还惹得更加心急火撩。没办法,只好从源码中找答案了!

行军线路图

Hadoop是跑在分布式文件系统HDFS上的MapReduce框架。显然,MapReduce的部分依赖于HDFS,而HDFS是完全可以独立运行的。就此看来,我们先搞定HDFS似乎比较合理。这个系列的文章正是记录了我这只菜鸟如何从HDFS开始,经过一番艰苦跋涉登顶MapReduce这个高峰的(此句为将来时态)。

起脚第一步:直接使用HDFS

这一段是为菜鸟们写的,同时也记录了府南河在“山脚下”转悠的点点滴滴。

安装Hadoop后,绝大多数人执行的第一个命令就是把本地文件上传到HDFS,然后就开始执行著名的wordcount例程了。当控制台缓慢的输出结果的时候心中免不了一阵狂喜,哈哈,我终于跑通第一个MapReduce了!欣喜之后,发现一个现实问题:跑如此简单的一个程序居然要花半分钟!?热情顿时打了一个折扣。不过这样也好,要是都尽善尽美了,我们也就无用武之地了。那么问题出在HDFS还是MapReduce本身呢?告诉你吧,现在我也不知道,因为本菜鸟还在半山腰上挂着呢!不过我相信坚持下去肯定能搞定。

Hadoop是这样上传本地文件的:
#>bin/hadoop dfs -put /local.txt /data.txt
所以要进入HDFS应该从这里下手。可就这一点点也困惑了本菜鸟好些天。我一直不知道这个hadoop程序是怎么编译出来的,看看它似乎还很小,而且还没后缀。直到某一天,一个误操作把这个文件托到了一个文本编辑器上。哇!原来这只是一个批处理文件!忽然意思到,我现在在Unix上,可还老带着Windows的习惯,总以为批处理一定是.sh结尾。不过这点小发现可真让我兴奋啊,立马给好友电话通报,并打包票两个月搞定HDFS。

hadoop这个批处理都是unix的命令,菜鸟只能连瞢带猜的看了。总而言之,精华集中在最后:
# run it
exec "$JAVA" $JAVA_HEAP_MAX $HADOOP_OPTS -classpath "$CLASSPATH" $CLASS "$@"
看到没?这不就是运行一个java程序嘛!前面啰哩啰嗦都是为了凑齐几个参数。好了把这几个参数echo出来吧。原来运行的是org.apache.hadoop.fs.FsShell。而这个shell的main()函数告诉我们它也很简单,就是把自己放到一个tool里,让tool来run自己。这个过程就像一个Runable用自己初始化一个线程,再让这个线程来run自己。于是看看这个shell的run函数。

这里可发现宝藏了,如果你对hadoop的文件操作命令不了解,看了这个函数,是不是有拨云见日的感觉?比如下面这个命令就是我看源码看出来的:
#>bin/hadoop dfs -put - /data.txt
把local文件的参数用‘-’代替,输入文件变成了系统标准输入。有了这招,我们在hadoop上创建点临时文件就方便多啦。

其实,到现在,你应该可以写出一个java程序来直接操纵HDFS了。兄弟们,试试吧!我刚做到这一步的时候,那兴奋劲真是难以表达。记得当时我正在五道口的雕刻时光里坐着,找不到人说话,立刻就给好友去了封email表达我的心情。

如果没写出来也没关系,只是证明你比我在这一点上还要菜一点点,稍后我会把程序贡献给大家的。再回到代码上来,这个run函数相当的长,但实际管用的没几句。大体结构如下:

检查参数

初始化:init()

分别对每个hdfs的命令调用相应的函数

需要我们关注的是第二部分初始化和第三部分中put命令的那个分支。初始化的工作就是创建一个FileSystem对象。这个对象不是new出来的,而是由FileSystem的静态方法生产出来的(工厂模式)。如何生产的呢?这个故事有点长,以后我们慢慢摆。再往下看,我们找到了put命令的处理函数:

if ("-put".equals(cmd) || "-copyFromLocal".equals(cmd)) {

        copyFromLocal(new Path(argv[i++]), argv[i++]);

      } 

这里有一长串的if...else,每个分支针对一个hdfs命令。再看看copyFromLocal()吧,这个函数是这样的:
  void copyFromLocal(Path src, String dstf) throws IOException {

    if (src.toString().equals("-")) {

      copyFromStdin(new Path(dstf));

    } else {

      fs.copyFromLocalFile(false, false, src, new Path(dstf));

    }

  }

这里我们可以看到如果命令行中用‘-’代替local文件的话,hadoop会copyFromStdin,否则从本地文件拷贝。再向下看,copyFromLocalFile函数里面实际使用了FileUtil类的copy方法,而FileUtil中的关键代码:
} else if (srcFS.isFile(src)) {
     InputStream in = srcFS.open(src);
OutputStream out = dstFS.create(dst, overwrite);
IOUtils.copyBytes(in, out, conf,
true);
}
这段代码首先拿到输入流in和输出流out,然后再通过IOUtils类的copytBytes函数拷贝。这个过程和我们平常读一个文件再写一个文件没什么区别了。从这里我们可以看到,HDFS把分布式文件系统和本地文件系统做了一次抽象。打开输入流使用open方法,而不是平常用的new FileInputStream(filePath);打开输出流使用的是create方法,不是new FileOutputStream(filePath)

到此为止,我们已经可以自由的使用HDFS了。随之而来的一个想法是:如果把一个数据库系统的文件层用HDFS替换,不就得到一个分布式的数据库了吗?虽然还不具备分布式计算的能力,但至少具备了分布式存储的能力。不知道HBase是不是这样的。

回到HDFS上,我们虽然可以读写HDFS文件了,但也只是揭开HDFS的第一层面纱。里面还有好多好东西等着我们去探询呢。接下来的问题有两个:

FileSystem对象是如何生成的,我们最关心的是对应分布式文件系统的那个FileSystem对象。

输出流是如何生成的,它是怎么读到分布式文件系统中的文件的。

这两个问题请看下集

附上一份原汁原味的实验代码。为保持新鲜,保留了调试时使用的和注释掉的代码:

package org.myorg;


import java.io.IOException;

import java.io.OutputStream;


import org.apache.hadoop.conf.Configuration;

import org.apache.hadoop.conf.Configured;

import org.apache.hadoop.fs.FSDataInputStream;

import org.apache.hadoop.fs.FSDataOutputStream;

import org.apache.hadoop.fs.FileSystem;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.util.ToolRunner;


public class FsHacker extends Configured implements org.apache.hadoop.util.Tool {


FileSystem fs;


public FsHacker() throws IOException {

super(new Configuration());

//System.out.println(getConf().get("fs.default.name", "file:///"));

fs = FileSystem.get(new Configuration());

//System.out.println(fs instanceof DistributedFileSystem);

}


public int run(String[] args) {

try{

//System.out.println(fs.getContentLength(new Path("/data/xyz")));

//showDfsFile("/data/xyz");

putFile();

}catch(Exception e){

e.printStackTrace();

}

return 0;

}


/**

* read a file content and print it in console

* like hadoop dfs -cat /data/xyz

* @param dfsFilePath

*/

public void showDfsFile(String dfsFilePath){

try {

FSDataInputStream in = fs.open(new Path("/data/xyz"));

//IOUtils.copyBytes(in, System.out, getConf());

copy(in, System.out);

} catch (IOException e) {

e.printStackTrace();

}

}

public void copy(FSDataInputStream in, OutputStream out)throws IOException{

byte[] buffer = new byte[1024];

try{

in.readFully(0,buffer,0,18);

out.write(buffer);

}finally{

in.close();

out.close();

}

}

/**

* create a new file in hdfs, /tmp1,

* it contains one line "hello 府南河"

* @throws IOException

*/

public void putFile()throws IOException{

Path p = new Path("/tmp1");

FSDataOutputStream out = fs.create(p);

byte[] content = "hello 府南河".getBytes();

out.write(content);

out.close();

}

public void close() throws IOException{

if (fs != null){

fs.close();

fs = null;

}

}

public static void main(String[] args) throws Exception{

int res = 0;

FsHacker testor = null;

try{

testor = new FsHacker();

res = ToolRunner.run(testor, args);

}finally{

if (testor != null){

testor.close();

}

}

System.exit(res);

}

}

原创粉丝点击