古代,人们用牛来拉重物,当一头牛拉不动一根圆木时,他们不曾想过培育更大更壮的牛,而是使用多头牛。同样,我们也不需要尝试开发超级计算机,而应试着结合使用更多计算机系统。
一、什么是Hadoop
大数据目前很火!是的,当今我们正处于大数据时代,但是我们应如何存储和分析这些数据,如何从海量数据中取出有价值的信息,例如搜索引擎中如何快速查找出有用信息,淘宝网如何快速根据你的之前浏览页面给出你可能需要的信息?Hadoop是一个能够对大量数据进行分布式处理的一个软件框架。
读取一个磁盘中所有的数据需要很长的时间,写甚至更慢。一个很简单的减少读取时间的办法是同时从多个磁盘上读取数据。试想,如果我们拥有100个磁盘,每个磁盘存储1%的数据,并行读取,那么不到两分钟就可以读取所有数据。Hadoop运用这种思想,让多台机器分布并行处理数据,从而减少读取时间,大大提高效率。Hadoop的文件系统HDFS存储需要处理的数据,Hadoop的MapReduce提出了一个编程模型,该模型将磁盘读写问题进行抽象,并转换为对一个数据集(由键/值对组成)的计算,该计算由map和reduce两部分组成。
简而言之,对于大量数据的处理,Hadoop提供了一个可靠的共享存储和分析系统。Hadoop中的HDFS实现存储,而MapReduce实现分析处理,提取出我们需要的数据。纵然Hadoop还有其他功能,但这两部分是它的核心。
二、Hadoop与传统关系型数据库的区别
传统型关系型数据库也能实现数据存储和数据分析功能,Hadoop的优势或不同在哪?
首先,磁盘一个发展趋势是寻址时间的提高远远慢于传输速率的提高,Hadoop将数据传输到其它机器上处理,进行高速的流式读写操作,而关系型数据库是不断寻址访问磁盘处理;
第二,关系型数据库使用B树作为数据结构,当数据库系统更新大部分数据时,B树需要不断地使用“排序/合并”(sort/merge)来重建数据库。相比之下,Hadoop的MapReduce的效率更高。
第三,MapReduce和关系型数据库所操作的数据集也是不同的。关系型数据库操作的是结构化数据(有既定格式的实体化数据,如关系表、XML文档);MapReduce操作的是非结构化(没有什么特别的内部结构,就只是一些数据而已,数据之间一般也没什么关系,如纯文本、图像数据)或半结构化数据(如电子表格)。
三、HDFS
当数据集的大小超过一台独立物理计算机的存储能力时,就有必要对它进行分区(partition)并存储到若干台单独的计算机上。Hadoop的分布式文件系统称为HDFS,该系统架构于网络之上,势必会引入网络编程的复杂性,因此分布式文件系统比普通磁盘文件系统更为复杂。HDFS以流式数据访问模式来存储超大文件,运行于硬件集群上。
HDFS的构建思路是这样的:一次写入、多次读取是最高效的访问模式。数据集通常由数据源生成或从数据源复制而来,接着长时间在此数据集上进行各类分析。每次分析都将涉及该数据集的大部分数据甚至全部,因此读取整个数据集的时间延迟比读取第一条记录的时间延迟更重要。
HDFS同样也有块(block)的概念,但是大得多,默认为64 MB。与单一磁盘上的文件系统相似,HDFS上的文件也被划分为块大小的多个分块(chunk),作为独立的存储单元。但与其他文件系统不同的是,HDFS中小于一个块大小的文件不会占据整个块的空间。
在一个全配置的集群上,“运行Hadoop”意味着在网络分布的不同服务器上运行一组守护进程( daemons )。这些守护进程有特殊的角色,一些仅存在于单个服务器上,一些则运行在多个服务器上。它们包括:
NameNode(名字节点);
DataNode(数据节点);
SecondaryNameNode(次名字节点);
JobTracker(作业跟踪节点);
TaskTracker(任务跟踪节点);
1、NameNode
Hadoop在分布式计算与分布式存储中都采用了主/从(masterlslave)结构。NameNode是HDFS的书记员,它跟踪文件如何被分割成文件块,而这些块又被哪些节点存储,以及分布式文件系统的整体运行状态是否正常。NameNode一般放在主节点机器上,看作是任务的管理者。
2、DataNode
每一个集群上的从节点都会驻留一个DataNode守护进程,来执行分布式文件系统的繁重工作-------将HDFS数据块读取或者写入到本地文件系统的实际文件中。当希望对HDFS文件进行读写时,文件被分割为多个块,由NameNode告知客户端每个数据块驻留在哪个DataNode。客户端直接与DataNode守护进程通信,来处理与数据块相对应的本地文件。而后,DataNode会与其他DataNode进行通信,复制这些数据块以实现冗余。
初始化时,每个DataNode将当前存储的数据块告知NameNode,在这个初始映射完成后,DataNode仍会不断地更新NameNade,为之提供本地修改的相关信息,同时接收指令创建、移动或删除本地磁盘上的数据块。
3、Secondary NameNode
Secondary NameNode ( SNN)是一个用于监测HDFS集群状态的辅助守护进程。像NameNode一样,每个集群有一个SNN。SNN与NameNode的不同在于它不接收或记录HDFS的任何实时变化。相反,它与NameNode通信,根据集群所配置的时间间隔获取HDFS元数据的快照。
4、JobTracker
JobTracker守护进程是应用程序和Hadoop之间的纽带。一旦提交代码到集群上,JobTracker就会确定执行计划,包括决定处理哪些文件、为不同的任务分配节点以及监控所有任务的运行。如果任务失败,JobTracker将自动重启任务,但所分配的节点可能会不同,同时受到预定义的重试次数限制。
5、TaskTracker
与存储的守护进程(NameNode和DataNode)一样,计算的守护进程也遵循主/从架构:JobTracker作为主节点,监测MapReduce作业的整个执行过程,同时,TaskTracker管理各个任务在每个从节点上的执行情况。每个TaskTracker负责执行由JobTracker分配的单项任务。虽然每个从节点上仅有一个TaskTracker,但每个TaskTracker可以生成多个JVM( Java虚拟机)来并行地处理许多map或reduce任务。TaskTraeker的一个职责是持续不断地与JobTracker通信。如果JobTracker在指定的时间内没有收到来自TaskTracker的“心跳”,它会假定TaskTracker已经崩溃了,进而重新提交相应的任务到集群中的其他节点中。
如下,是一个Hadoop集群的拓扑图:
四、MapReduce
MapReduce是一种可用于数据处理的编程模型。MapReduce任务过程被分为两个处理阶段:map阶段和reduce阶段。每个阶段都以键/值对作为输入和输出,并由程序员选择它们的类型。程序员需要具体定义两个函数:map函数和reduce函数。将查询操作和数据集分解为组件---这就是map(映射);在查询中被映射的组件可以被同时处理,从而可以快速返回结果---这就是reduce(归约)。
MapReduce逻辑数据流:
Map: (K1,V 1)——>(K2,list(V2))
reduce: (K2,list( V2))——>list(K3,V3)
1、输入数据为一些文本,多个文本文件被分布在不同的节点上,各节点对文本处理,表示成键/值对形式(K1,V1),k1是该行记录相对于文件的偏移量(这个值我们在此用不到),V1就是文本值;
2、各节点(k1,v1)经过map函数处理,转化成list(k2,v2),k2指代是哪一个单词,V2表示该节点上单词k2的个数;
3、各节点交换数据,相同key的键值对list(k2,v2)被发送到同一节点上处理,称为是一个洗牌的过程,洗牌后产生键值对(k2,list(V2));
4、(k2,list(v2))经过Reduce函数处理,统计所有机器上k2的个数,输出键值对(K3,V3);
5、结果显示输出。
输入3段text字符,首先转换成键值对形式(0,“Hello world Bye World”)、(4,“hello Hadoop.....”)、(8,“ByeHadoop.....”),将这些键值对分布在三台机器上,并输入到map函数处理,每台机器的map函数产生一系列键值对输出,如上图。
各机器(从结点)交换键值对(洗牌),相同key的键值对发到同一节点上,如key=”bye”的键值对都发送到1号从节点,key=”hello”的键值对都发往2号从节点。相同key的键值对必须发往同一节点,但同一节点上不一定只有一种key。经过洗牌之后的键值对,在各自节点上被reduce函数处理,统计单词总数,输出结果。
下面我们以Hadoop自带的WordCount程序,具体讲解MapReduce是怎样编程的。
五、 MapReduce编程应用开发
1、导入Hadoop开发所需的包
package WordCount;
import java.io.IOException;//异常包
import java.util.StringTokenizer;//分隔字符串,默认用空格来分隔
importorg.apache.hadoop.conf.Configuration;//任务配置包,配置任务所需的map等函数
import org.apache.hadoop.fs.Path;//设置文件路径
importorg.apache.hadoop.io.IntWritable;//Hadoop自定义的数据局类型,类似Java中int
import org.apache.hadoop.io.Text;//Hadoop自定义的数据局类型,类似Java中String
import org.apache.hadoop.mapreduce.Job;//控制整个作业运行
importorg.apache.hadoop.mapreduce.Mapper;//map接口
importorg.apache.hadoop.mapreduce.Reducer;//reduce接口
importorg.apache.hadoop.mapreduce.lib.input.FileInputFormat;//定义输入路径
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;//定义输出路径
importorg.apache.hadoop.util.GenericOptionsParser;//解析用户指定的参数,获取基本选 //项以及根据需要修改配置
2、MapReduce中具体实现Map函数:功能是把输入的字符串分隔成key,并统计自己机器节点上的key的个数Value,输出(key,value)
public class WordCount {
public static class TokenizerMapper extendsMapper<Object, Text, Text, IntWritable>{
//TokenizerMapper类继承与Mapper类
private final static IntWritable one = new IntWritable(1);//初始化输入的key,对应 //mapper中的Object
private Text word = new Text();//初始化输入值value,对应mapper中的Text
public void map(Object key, Text value, Context context//实现Map函数
) throws IOException,InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());//以空格为分隔符,分割出 //各个key
while (itr.hasMoreTokens()) {//在while循环中,统计每个key的个数
word.set(itr.nextToken());
context.write(word, one);//输出的(key,value)
}
}
}
3、MapReduce中具体实现Reducer函数:功能是统计所有机器上的(key,value)对中相同key的个数。
public static class IntSumReducer extendsReducer<Text,IntWritable,Text,IntWritable> {
//IntSumReducer类继承与Reducer类
private IntWritable result = new IntWritable();//初始化输入的vlaue,对应Reducer中的 //IntWritable
public void reduce(Text key, Iterable<IntWritable> values, Contextcontext
) throws IOException,InterruptedException {//实现Reducer函数
int sum = 0;
for (IntWritable val : values) {//统计所有机器上相同key的个数
sum += val.get();
}
result.set(sum);//将结果赋值给result
context.write(key, result);//输出的(key,value)或者output.collect(key, new //IntWritable(sum));OutputCollector接口收集Mapper //和Reducer输出的<k,v>对
}
}
4、负责运行MapReduce作业的代码
public static void main(String[] args)throws Exception {
Configuration conf = new Configuration();//读取Hadoop配置到conf中
String[] otherArgs = new GenericOptionsParser(conf,args).getRemainingArgs();//解析 //用户指定的参数,获取基本选项以及根据需要修改配置
if(otherArgs.length != 2) {//检查输入参数
System.err.println("Usage: wordcount <in> <out>");
System.exit(2);
}
Jobjob = new Job(conf, "word count");//实例化一道作业
job.setJarByClass(WordCount.class);//设置该作业的运行接口类
job.setMapperClass(TokenizerMapper.class);//设置该作业的map类
job.setCombinerClass(IntSumReducer.class);//设置该作业组合时的类
job.setReducerClass(IntSumReducer.class);//设置该作业的reduce类
job.setOutputKeyClass(Text.class);//设置该作业的输出key
job.setOutputValueClass(IntWritable.class);//设置该作业的输出值
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));//设置该作业的输入路径
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));//设置该作业的输出路径
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}