Hadoop基础概念入门

文件内容读取的代码可以分为三个大步骤。 

1、获取文件系统 
2、通过文件系统打开文件 
3、将文件内容输出

 

public static void read(Path path) throws IOException{
        FileSystem hdfs = HdfsUtils.getFilesystem();  //步骤 1
        FSDataInputStream fsDataInputStream =  hdfs.open(path); //步骤 2
        IOUtils.copyBytes(fsDataInputStream, System.out, 4096,false);  //步骤 3
    }

 

 

       Hadoop 是Google MapReduce的一个Java实现。MapReduce是一种简化的分布式编程模式,让程序自动分布到一个由普通机器组成的超大集群上并发执行。就如同java程序员可以不考虑内存泄露一样, MapReduce的run-time系统会解决输入数据的分布细节,跨越机器集群的程序执行调度,处理机器的失效,并且管理机器之间的通讯请求。这样的模式允许程序员可以不需要有什么并发处理或者分布式系统的经验,就可以处理超大的分布式系统得资源。

一、概论

 

    作为Hadoop程序员,他要做的事情就是:
    1、定义Mapper,处理输入的Key-Value对,输出中间结果。
    2、定义Reducer,可选,对中间结果进行规约,输出最终结果。
    3、定义InputFormat 和OutputFormat,可选,InputFormat将每行输入文件的内容转换为Java类供Mapper函数使用,不定义时默认为String。
    4、定义main函数,在里面定义一个Job并运行它。
    

    然后的事情就交给系统了。
    1.基本概念:Hadoop的HDFS实现了google的GFS文件系统,NameNode作为文件系统的负责调度运行在master,DataNode运行在每个机器上。同时Hadoop实现了Google的MapReduce,JobTracker作为MapReduce的总调度运行在master,TaskTracker则运行在每个机器上执行Task。

    2.main()函数,创建JobConf,定义Mapper,Reducer,Input/OutputFormat 和输入输出文件目录,最后把Job提交給JobTracker,等待Job结束。

    3.JobTracker,创建一个InputFormat的实例,调用它的getSplits()方法,把输入目录的文件拆分成FileSplist作为Mapper task 的输入,生成Mapper task加入Queue。

    4.TaskTracker 向 JobTracker索求下一个Map/Reduce。
      
     Mapper Task先从InputFormat创建RecordReader,循环读入FileSplits的内容生成Key与Value,传给Mapper函数,处理完后中间结果写成SequenceFile.
     Reducer Task 从运行Mapper的TaskTracker的Jetty上使用http协议获取所需的中间内容(33%),Sort/Merge后(66%),执行Reducer函数,最后按照OutputFormat写入结果目录。 

      TaskTracker 每10秒向JobTracker报告一次运行情况,每完成一个Task10秒后,就会向JobTracker索求下一个Task。

      Nutch项目的全部数据处理都构建在Hadoop之上,详见Scalable Computing with Hadoop


二、程序员编写的代码

 (可以查看hadoop-examples-0.20.203.0.jar,里面也有一个类grep)

    我们做一个简单的分布式的Grep,简单对输入文件进行逐行的正则匹配,如果符合就将该行打印到输出文件。因为是简单的全部输出,所以我们只要写Mapper函数,不用写Reducer函数,也不用定义Input/Output Format。

[java] view plaincopyprint?

  1. package  demo.hadoop  
  2. public   class  HadoopGrep {  
  3.      public   static   class  RegMapper  extends  MapReduceBase  implements  Mapper {  
  4.                private  Pattern pattern;  
  5.                public   void  configure(JobConf job) {  
  6.                          pattern  =  Pattern.compile(job.get( " mapred.mapper.regex " ));  
  7.               }  
  8.               public   void  map(WritableComparable key, Writable value, OutputCollector output, Reporter reporter)  
  9.                      throws  IOException {  
  10.                          String text  =  ((Text) value).toString();  
  11.                          Matcher matcher  =  pattern.matcher(text);  
  12.                        if  (matcher.find()) {  
  13.                         output.collect(key, value);  
  14.              }  
  15.     }  
  16.  }  
  17.   
  18.   private  HadoopGrep () {  
  19.  }  //  singleton   
  20.   
  21. public   static   void  main(String[] args)  throws  Exception {  
  22.   JobConf grepJob  =   new  JobConf(HadoopGrep. class );  
  23.   grepJob.setJobName( " grep-search " );  
  24.   grepJob.set( " mapred.mapper.regex " , args[ 2 ]);  
  25.   
  26.   grepJob.setInputPath( new  Path(args[ 0 ]));  
  27.   grepJob.setOutputPath( new  Path(args[ 1 ]));  
  28.   grepJob.setMapperClass(RegMapper. class );  
  29.   grepJob.setReducerClass(IdentityReducer. class );  
  30.   JobClient.runJob(grepJob);  
  31.  }  
  32. }  

 

          RegMapper类的configure()函数接受由main函数传入的查找字符串,map() 函数进行正则匹配,key是行数,value是文件行的内容,符合的文件行放入中间结果。
        main()函数定义由命令行参数传入的输入输出目录和匹配字符串,Mapper函数为RegMapper类,Reduce函数是什么都不做,直接把中间结果输出到最终结果的的IdentityReducer类,运行Job。


整个代码非常简单,丝毫没有分布式编程的任何细节。

三.运行Hadoop程序

        Hadoop这方面的文档写得不全面,综合参考GettingStartedWithHadoop 与Nutch Hadoop Tutorial 两篇后,再碰了很多钉子才终于完整的跑起来了,记录如下:     
3.1 local运行模式
       完全不进行任何分布式计算,不动用任何namenode,datanode的做法,适合一开始做调试代码。
       解压hadoop,其中conf目录是配置目录,hadoop的配置文件在hadoop-default.xml,如果要修改配置,不是直接修改该文件,而是修改hadoop-site.xml,将该属性在hadoop-site.xml里重新赋值。
       hadoop-default.xml的默认配置已经是local运行,不用任何修改,配置目录里唯一必须修改的是hadoop-env.sh 里JAVA_HOME的位置。
       将编译好的HadoopGrep与RegMapper.class 放入hadoop/build/classes/demo/hadoop/目录 

        或者编译成jar包HadoopGrep.jar放入hadoop/build/classes/demo/hadoop/目录

        找一个比较大的xx.log文件放,然后运行

        bin/hadoop demo.hadoop.HadoopGrep  input   /tmp/out  "[a-b]"
        (jar包运行:bin/hadoop jar HadoopGrep.jar  HadoopGrep  input   /tmp/output  "[a-b]" )
        说明:
         input  为xx.log文件所在目录 
         /tmp/output为输出目录 
         "[a-b]"   grep的字符串 

        查看输出目录的结果,查看hadoop/logs/里的运行日志。  
        在重新运行前,先删掉输出目录。
  

  3.2 集群运行模式

    (查看集群配置:http://blog.csdn.net/hguisu/article/details/7237395)

      1 )执行bin/hadoop dfs 可以看到它所支持的文件操作指令。   

      2) 创建目录输入inpu:   
           $ bin/hadoop dfs -mkdir input    

      3)上传文件xx.log到指定目录 input :   
           $ bin/hadoop dfs -put xx.log input

       4 )  执行 bin/hadoop demo.hadoop.HadoopGrep input  output
             (jar包运行:bin/hadoop jar HadoopGrep.jar  HadoopGrep  input   /tmp/output  "[a-b]" )

       5 ) 查看输出文件:

 

           将输出文件从分布式文件系统拷贝到本地文件系统查看:
            $ bin/hadoop fs -get output output
            $ cat output/*

            或者
            在分布式文件系统上查看输出文件:
            $ bin/hadoop fs -cat output/*

            重新执行前,运行hadoop/bin/hadoop dfs -rm output删除output目录

       7.运行hadoop/bin/stop-all.sh 结束。
    

四、效率

    经测试,Hadoop并不是万用灵丹,很取决于文件的大小和数量,处理的复杂度以及群集机器的数量,相连的带宽,当以上四者并不大时,hadoop优势并不明显。
    比如,不用hadoop用java写的简单grep函数处理100M的log文件只要4秒,用了hadoop local的方式运行是14秒,用了hadoop单机集群的方式是30秒,用双机集群10M网口的话更慢,慢到不好意思说出来的地步。

 

1. Hadoop框架的两个重点:MapReduce 和HDFS
MapReduce:
        (1)在map进行之前,需要对输入文件在客户端先进行“分片”,然后将分片信息上传到HDFS。
        (2)分片上传结束后,jobtracker拿到分片信息,来分配map,reduct task;map对每条记录的输出以<key,value> 的形式输出。
        (3)如果定义了combiner,则在本地会对map处理的结果进行处理:对相同key的聚合,对key的排序,value的迭代。combiner完成类似于本地reduce的功能。
        (4)在进入reduce阶段之前,系统会完成一些列操作(merge,sort):将list中key相同的数据进行合并、排序,最后形成<k1`,list<v1`>> 的数据;
             然后发往一个reduce
        (5)进入一个reduce,相同的key的map输出会到达同一个reduce,reduce对key相同的多个value进行“reduce操作”;
        > 没有combiner的处理过程:
              
        > 添加combiner的处理过程:
              
          

 
         ?为什么我买的map函数和reduce函数一般使用静态类?
           答:task内部可以共享静态类属性,每个task可能会多次调用map或reduce函数,但每个key只对应某个节点上的某个task的reduce函数的一次执行。
            多个task之间不能共享静态类属性,即使是在同一台机器上,因为是以进程的方式在运行。

        1. Map类:(继承TableMapper或者Mapper)
           Map原理:
                在map阶段,使用job.setInputFormatClass定义的InputFormat将输入的数据集分割成小数据块splites,同时InputFormat提供一个RecordReder的实现。本例子中使用的是TextInputFormat,他提供的RecordReder会将文本的一行的行号作为key,这一行的文本作为value。这就是自定义Map的输入是<LongWritable, Text>的原因。然后调用自定义Map的map方法,将一个个<LongWritable, Text>对输入给Map的map方法。注意输出应该符合自定义Map中定义的输出<IntPair, IntWritable>。最终是生成一个List<IntPair,IntWritable>。在map阶段的最后,会先调用job.setPartitionerClass对这个List进行分区,每个分区映射到一个reducer。每个分区内又调用job.setSortComparatorClass设置 的key比较函数类排序。可以看到,这本身就是一个二次排序。如果没有通过job.setSortComparatorClass设置key比较函数类,则使用key的实现的compareTo方法。在第一个例子中,使用了IntPair实现的compareTo方法,而在下一个例子中,专门定义了key比较函数类。       
                 Q: map的结果发给那个reduce?谁来管理这一切?
                 A:     Partitioner用于划分键值空间(key space)。 
                        Partitioner负责控制map输出结果key的分割。Key(或者一个key子集)被用于产生分区,通常使用的是Hash函数。分区的数目与一个作业的reduce任务的数目是                          一样的。因此,它控制将中间过程的key(也就是这条记录)应该发送给m个reduce任务中的哪一个来进行reduce 操作。
        
        2. Reduce类:(继承TableReducer或者Reducer)
           Reduce的原理:在reduce阶段,reducer接收到所有映射到这个reducer的map输出后,也是会调用job.setSortComparatorClass设置的key比较函数类对所有数据对排序。然后开始构造 一个key对应的value迭代器。这时就要用到分组,使用jobjob.setGroupingComparatorClass设置的分组函数类。只要这个比较器比较的两个key相同,他们就属于  同一个组,它们的value放在一个value迭代器,而这个迭代器的key使用属于同一个组的所有key的第一个key。最后就是进入Reducer的reduce方法,reduce方法的         输入是所有的(key和它的value迭代器)。同样注意输入与输出的类型必须与自定义的Reducer中声明的一致。
reduce的输出是没有排序的。

           Q:Reduce的数目应该设置多少?
           A:           Reduce的数目建议是0.95或1.75乘以 ( * mapred.tasktracker.reduce.tasks.maximum)。用0.95,所有reduce可以在maps一完成时就立刻启动,开始传输map的输出结 果。用1.75,速度快的节点可以在完成第一轮reduce任务后,可以开始第二轮,这样可以得到比较好的负载均衡的效果。上述比例因子比整体数目稍小一些是为了给框 架中的推测性任务(speculative-tasks) 或失败的任务预留一些reduce的资源。
           Q:Reduce的三个阶段都干了什么?
           A:  Reducer有3个主要阶段:shuffle、sort和reduce。 
                
                Shuffle :Reducer的输入就是Mapper已经排好序的输出。在这个阶段,框架通过HTTP为每个Reducer获得所有Mapper输出中与之相关的分块。 (其实就是copy的过  程)
                Sort :这个阶段,框架将按照key的值对Reducer的输入进行分组 (因为不同mapper的输出中可能会有相同的key,combain保证了同一台机器相同key的合并,但是                               不同机器也可能有相同的key)。 
                           map的输出是一边被取回一边被合并的。
        3. Job 的配置:
           1.使用job.setInputFormatClass定义的InputFormat将输入的数据集分割成小数据块splites,Hadoop Map/Reduce框架为每一个Split产生一个map任务.
             Map的数目通常是由输入数据的大小决定的,一般就是所有输入文件的总块(block)数。如果你输入10TB的数据,每个块(block)的大小是 128MB,你将需要大约82,000个map来完成任务,除非使用 setNumMapTasks(int)将这个数值设置得更高。
           2.如果需要中间过程对key的分组规则和reduce前对key的分组规则不同,那么可以通过 JobConf.setOutputValueGroupingComparator(Class)来指定一个Comparator。再加上 JobConf.setOutputKeyComparatorClass(Class)可用于控制中间过程的key如何被分组,所以结合两者可以实现按值的二次排序
           3.一些作业的参数可以被直截了当地进行设置(例如: setNumReduceTasks(int)),而另一些参数则与框架或者作业的其他参数之间微妙地相互影响,并且设置起来比较复杂(例如: setNumMapTasks(int)) 
           4. Mapper和Reducer的实现可以利用Reporter 来报告进度,或者仅是表明自己运行正常。我们从界面上看到的图形就是利用Reporter来进行进度的展示。
2. HDFS  与 HBse,MapReduce 有什么关系?
        (1)HBase部署在HDFS之上。
        (2)HBase使用HDFS的方式与MapReduce使用HDFS的方式截然不同:
                MapReduce 中,首先打开HDFS文件,然后map任务流式处理文件的内容,最后关闭文件。
                HBase中,数据文件在启动时就被打开,并在处理过程中始终保持打开状态,这是为了节省每次访问操作需要打开的代价。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhousenshan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值