<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>StephenChan&#039;s Tech Space &#187; Hadoop</title>
	<atom:link href="http://blog.endlesscode.com/category/hadoop/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.endlesscode.com</link>
	<description>Stay Hungry. Stay Foolish.</description>
	<lastBuildDate>Tue, 25 Oct 2011 01:15:07 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>How MapReduce Works</title>
		<link>http://blog.endlesscode.com/2010/06/24/how-mapreduce-works/</link>
		<comments>http://blog.endlesscode.com/2010/06/24/how-mapreduce-works/#comments</comments>
		<pubDate>Thu, 24 Jun 2010 15:38:08 +0000</pubDate>
		<dc:creator>Stephen</dc:creator>
				<category><![CDATA[Hadoop]]></category>

		<guid isPermaLink="false">http://blog.endlesscode.com/?p=934</guid>
		<description><![CDATA[一、从Map到Reduce MapReduce其实是分治算法的一种实现，其处理过程亦和用管道命令来处理十分相似，一些简单的文本字符的处理甚至也可以使用Unix的管道命令来替代，从处理流程的角度来看大概如下： cat input &#124; grep &#124; sort &#124; uniq -c &#124; cat &#62; output # Input -&#62; Map -&#62; Shuffle &#38; Sort -&#62; Reduce -&#62; Output 简单的流程图如下： 对于Shuffle，简单地说就是将Map的输出通过一定的算法划分到合适的Reducer中进行处理。Sort当然就是对中间的结果进行按key排序，因为Reducer的输入是严格要求按key排序的。 Input-&#62;Map-&#62;Shuffle&#38;Sort-&#62;Reduce-&#62;Output只是从宏观的角度对MapReduce的简单描述，实际在MapReduce的框架中，即从编程的角度来看，其处理流程是Input-&#62;Map-&#62;Sort-&#62;Combine-&#62;Partition-&#62;Reduce-&#62;Output。用之前的对温度进行统计的例子来讲述这些过程。 Input Phase 输入的数据需要以一定的格式传递给Mapper的，格式有多种，如TextInputFormat、DBInputFormat、SequenceFileInput等等，可以使用JobConf.setInputFormat来设置，这个过程还应该包括对输入的数据进行任务粒度划分(split)然后再传递给Mapper。在温度的例子中，由于处理的都是文本数据，输入的格式使用默认的TextInputFormat即可。 Map Phase 对输入的key、value对进行处理，输出的是key、value的集合，即map (k1, v1) -&#62; list(k2, &#8230; <a href="http://blog.endlesscode.com/2010/06/24/how-mapreduce-works/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<h2>一、从Map到Reduce</h2>
<p>MapReduce其实是分治算法的一种实现，其处理过程亦和用管道命令来处理十分相似，一些简单的文本字符的处理甚至也可以使用Unix的管道命令来替代，从处理流程的角度来看大概如下：</p>
<pre class="brush:bash">cat input | grep |      sort      |  uniq -c | cat &gt; output
# Input -&gt; Map -&gt; Shuffle &amp; Sort -&gt; Reduce -&gt; Output</pre>
<p>简单的流程图如下：</p>
<p><img class="aligncenter size-full wp-image-941" title="procedure" src="http://blog.endlesscode.com/wp-content/uploads/2010/06/procedure.jpg" alt="procedure" width="592" height="438" />对于Shuffle，简单地说就是将Map的输出通过一定的算法划分到合适的Reducer中进行处理。Sort当然就是对中间的结果进行按key排序，因为Reducer的输入是严格要求按key排序的。<span id="more-934"></span></p>
<p>Input-&gt;Map-&gt;Shuffle&amp;Sort-&gt;Reduce-&gt;Output只是从宏观的角度对MapReduce的简单描述，实际在MapReduce的框架中，即从编程的角度来看，其处理流程是Input-&gt;Map-&gt;Sort-&gt;Combine-&gt;Partition-&gt;Reduce-&gt;Output。用之前的对温度进行统计的例子来讲述这些过程。</p>
<h3>Input Phase</h3>
<p>输入的数据需要以一定的格式传递给Mapper的，格式有多种，如TextInputFormat、DBInputFormat、SequenceFileInput等等，可以使用JobConf.setInputFormat来设置，这个过程还应该包括对输入的数据进行任务粒度划分(split)然后再传递给Mapper。在温度的例子中，由于处理的都是文本数据，输入的格式使用默认的TextInputFormat即可。</p>
<h3>Map Phase</h3>
<p>对输入的key、value对进行处理，输出的是key、value的集合，即map (k1, v1) -&gt; list(k2, v2)，使用JobConf.setMapperClass设置自己的Mapper。在例子中，将（行号、温度的文本数据）作为key/value输入，经过处理后，从温度的文件数据中提取出日期中的年份和该日的温度数据，形成新的key/value对，最后以list(年,  温度)的结果输出，如[(1950, 10), (1960, 40), (1960, 5)]。</p>
<h3>Sort Phase</h3>
<p>对Mapper输出的数据进行排序，可以通过JobConf.setOutputKeyComparatorClass来设置自己的排序规则。在例子中，经过排序之后，输出的list集合是按年份进行排序的list(年, 温度)，如[(1950, 10), (1950, 5), (1960, 40)]。</p>
<h3>Combine Phase</h3>
<p>这个阶段是将中间结果中有相同的key的&lt;key, value&gt;对合并成一对，Combine的过程与Reduce很相似，使用的甚至是Reduce的接口。通过Combine能够减少&lt;key, value&gt;的集合数量，从而减少网络流量。Combine只是一个可选的优化过程，并且无论Combine执行多少次(&gt;=0)，都会使Reducer产生相同的输出，使用JobConf.setCombinerClass来设置自定义的Combine Class。在例子中，假如map1产生出的结果为[(1950, 0), (1950, 20), (1950, 10)]，在map2产生出的结果为[(1950, 15), (1950, 25)]，这两组数据作为Reducer的输入并经过Reducer处理后的年最高温度结果为(1950, 25)，然而当在Mapper之后加了Combine（Combine先过滤出最高温度），则map1的输出是[(1950, 20)]和map2的输出是[(1950, 25)]，虽然其他的三组数据被抛弃了，但是对于Reducer的输出而言，处理后的年最高温度依然是(1950, 25)。</p>
<h3>Partition Phase</h3>
<p>把Mapper任务输出的中间结果按key的范围划分成R份（R是预先定义的Reduce任务的个数），默认的划分算法是&#8221;(key.hashCode() &amp; Integer.MAX_VALUE) % numPartitions&#8221;，这样保证了某一范围的key一定是由某个Reducer来处理，简化了Reducer的处理流程，使用JobConf.setPartitionClass来设置自定义的Partition Class。在例子中，默认就自然是对年份进行取模了。</p>
<h3>Reduce Phase</h3>
<p>Reducer获取Mapper输出的中间结果，作为输入对某一key范围区间进行处理，使用JobConf.setReducerClass来设置。在例子中，与Combine Phase中的处理是一样的，把各个Mapper传递过来的数据计算年最高温度。</p>
<h3>Output Phase</h3>
<p>Reducer的输出格式和Mapper的输入格式是相对应的，当然Reducer的输出还可以作为另一个Mapper的输入继续进行处理。</p>
<h2>二、Details of Job Run</h2>
<p>上面只是从task运行中描述了Map和Reduce的过程，实际上当从运行&#8221;hadoop jar&#8221;开始还涉及到很多其他的细节。从整个Job运行的流程来看，如下图所示：</p>
<p><img class="aligncenter size-full wp-image-943" title="hadoop_run" src="http://blog.endlesscode.com/wp-content/uploads/2010/06/hadoop_run.jpg" alt="hadoop_run" width="671" height="551" /></p>
<p>从上图可以看到，MapReduce运行过程中涉及有4个独立的实体：</p>
<ul>
<li>Client，用于提交MapReduce job。</li>
<li>JobTracker，负责协调job的运行。</li>
<li>TaskTrackers，运行 job分解后的多个tasks，task主要是负责运行Mapper和Reducer。</li>
<li>Distributed filesystem，用于存储上述实体运行时共享的job文件（如中间结果文件）。</li>
</ul>
<h3>Job Submission</h3>
<p>当调用了JobClient.runJob()之后，Job便开始被提交了，在Job提交这个步骤中，经历了以下的过程：</p>
<ol>
<li>Client向JobTacker申请一个新的job ID（step 2），job ID形如job_200904110811_0002的格式，是由JobTracker运行当前的job的时间和一个由JobTracker维护的自增计数（从1开始）组成的。</li>
<li>检查job的output specification，比如输出目录是否已经存在（存在则抛异常）、是否有权限写等等。</li>
<li>Computes the input splits for the job，这些input splits就是作为Mapper的输入。</li>
<li>Copies the resources needed to run the job, including the job JAR file, the configuration file and the computed input splits, to the jobtracker&#8217;s filesystem in a direcotry named after the job ID（step 3）。</li>
<li>Tells the jobtracker that the job is ready for execution（step 4）。</li>
</ol>
<h3>Job Initialization</h3>
<p>当JobTracker收到Job提交的请求后，将job保存在一个内部队列，并让Job Scheduler处理并初始化。初始化涉及到创建一个封装了其tasks的job对象，并保持对task的状态和进度的根据（step 5）。当创建要运行的一系列task对象后，Job Scheduler首先开始从文件系统中获取由JobClient计算的input splits（step 6），然后再为每个split创建map task。</p>
<h3>Task Assignment</h3>
<p>TaskTrackers会使用一个简单的loop为定期向JobTracker发送heartbeat调用，发送的间隔时间大约5秒，一般取决于集群服务器的规模和繁忙程度以及网络拥挤程度。这个heartbeat一方面是告知JobTracker当前TaskTracker处于live状态，同时是用于JobTracker和TaskTracker进行通信，TaskTracker会根据heartbeat的返回值来执行一定的操作（step 7）。</p>
<p>To choose a reduce task the JobTracker simply takes the next in its list of yet-to-be-run reduce tasks, since there are no data locality considerations. For a map task, however, it takes account of the TaskTracker&#8217;s network location and picks a task whose input splits is as close as possible to the tasktracker. In the optimal case, the task is data-local, that is , running on the same node that the split resides on. Alternatively, the task may be rack-local: on the same rack, but not the same node, as the split.</p>
<h3>Task Execution</h3>
<p>当TaskTrack被分配到一个task之后，接下来就是运行这个task。首先，它会需要的job JAR文件从shared filesystem拷贝到local filesystem，然后创建一个working direcotry并un-jars拷贝的JAR文件到该directory，最后就创建一个TaskRunner对象运行task。</p>
<p>TaskRunner在运行的时候是启动了一个新的JVM来run each task（step 10），这样是为了防止在用户自定义的Mapper出现异常令JVM挂了，从而连累到TaskTracker。TaskRunner子进程会使用umbilical接口和TaskTracker通信并每隔几秒向TaskTracker汇报进度。</p>
<p>对于使用Streaming和Pipes方式来创建的Mapper，也是作为TaskTracker的子进程来运行的。Streaming是使用标准输入输出来通信，而Pipes是使用socket来进行通信，如下图：</p>
<p><img class="aligncenter size-full wp-image-944" title="streamming_pipes" src="http://blog.endlesscode.com/wp-content/uploads/2010/06/streamming_pipes.jpg" alt="streamming_pipes" width="627" height="606" /></p>
<h3>Progress and Status Updates</h3>
<p>进度和状态是通过heartbeat来更新和维护的。来对于Map Task，进度就是已处理数据和所有输入数据的比例。对于Reduce Task，情况就有点复杂，包括3部分，拷贝中间结果文件、排序、Reduce调用，每部分占1/3。</p>
<h3>Job Completion</h3>
<p>当Job完成后，JobTracker会收一个Job Complete的通知，并将当前的Job状态更新为Successful，同时JobClient也会轮循获知提交的Job已经完成，将信息显示给用户。最后，JobTracker会清理和回收该Job的相关资源，并通知TaskTracker进行相同的操作（比如删除中间结果文件）。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.endlesscode.com/2010/06/24/how-mapreduce-works/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>HDFS简介</title>
		<link>http://blog.endlesscode.com/2010/06/16/hdfs-short-intro/</link>
		<comments>http://blog.endlesscode.com/2010/06/16/hdfs-short-intro/#comments</comments>
		<pubDate>Wed, 16 Jun 2010 07:21:02 +0000</pubDate>
		<dc:creator>Stephen</dc:creator>
				<category><![CDATA[Hadoop]]></category>

		<guid isPermaLink="false">http://blog.endlesscode.com/?p=915</guid>
		<description><![CDATA[一、HDFS HDFS全称是Hadoop Distributed System。HDFS是为以流的方式存取大文件而设计的。适用于几百MB，GB以及TB，并写一次读多次的场合。而对于低延时数据访问、大量小文件、同时写和任意的文件修改，则并不是十分适合。 目前HDFS支持的使用接口除了Java的还有，Thrift、C、FUSE、WebDAV、HTTP等。HDFS是以block-sized chunk组织其文件内容的，默认的block大小为64MB，对于不足64MB的文件，其会占用一个block，但实际上不用占用实际硬盘上的64MB，这可以说是HDFS是在文件系统之上架设的一个中间层。之所以将默认的block大小设置为64MB这么大，是因为block-sized对于文件定位很有帮助，同时大文件更使传输的时间远大于文件寻找的时间，这样可以最大化地减少文件定位的时间在整个文件获取总时间中的比例 。 构成HDFS主要是Namenode（master）和一系列的Datanode（workers）。Namenode是管理HDFS的目录树和相关的文件元数据，这些信息是以&#8221;namespace image&#8221;和&#8221;edit log&#8221;两个文件形式存放在本地磁盘，但是这些文件是在HDFS每次重启的时候重新构造出来的。Datanode则是存取文件实际内容的节点，Datanodes会定时地将block的列表汇报给Namenode。 由于Namenode是元数据存放的节点，如果Namenode挂了那么HDFS就没法正常运行，因此一般使用将元数据持久存储在本地或远程的机器上，或者使用secondary namenode来定期同步Namenode的元数据信息，secondary namenode有点类似于MySQL的Master/Salves中的Slave，&#8221;edit log&#8221;就类似&#8221;bin log&#8221;。如果Namenode出现了故障，一般会将原Namenode中持久化的元数据拷贝到secondary namenode中，使secondary namenode作为新的Namenode运行起来。 二、读写流程 文件读取 文件读取的过程如下： 使用HDFS提供的客户端开发库，向远程的Namenode发起RPC请求； Namenode会视情况返回文件的部分或者全部block列表，对于每个block，Namenode都会返回有该block拷贝的datanode地址； 客户端开发库会选取离客户端最接近的datanode来读取block； 读取完当前block的数据后，关闭与当前的datanode连接，并为读取下一个block寻找最佳的datanode； 当读完列表的block后，且文件读取还没有结束，客户端开发库会继续向Namenode获取下一批的block列表。 读取完一个block都会进行checksum验证，如果读取datanode时出现错误，客户端会通知Namenode，然后再从下一个拥有该block拷贝的datanode继续读。 写入文件 写入文件的过程比读取较为复杂： 使用HDFS提供的客户端开发库，向远程的Namenode发起RPC请求； Namenode会检查要创建的文件是否已经存在，创建者是否有权限进行操作，成功则会为文件创建一个记录，否则会让客户端抛出异常； 当客户端开始写入文件的时候，开发库会将文件切分成多个packets，并在内部以&#8221;data queue&#8221;的形式管理这些packets，并向Namenode申请新的blocks，获取用来存储replicas的合适的datanodes列表，列表的大小根据在Namenode中对replication的设置而定。 开始以pipeline（管道）的形式将packet写入所有的replicas中。开发库把packet以流的方式写入第一个datanode，该datanode把该packet存储之后，再将其传递给在此pipeline中的下一个datanode，直到最后一个datanode，这种写数据的方式呈流水线的形式。 最后一个datanode成功存储之后会返回一个ack packet，在pipeline里传递至客户端，在客户端的开发库内部维护着&#8221;ack queue&#8221;，成功收到datanode返回的ack packet后会从&#8221;ack queue&#8221;移除相应的packet。 如果传输过程中，有某个datanode出现了故障，那么当前的pipeline会被关闭，出现故障的datanode会从当前的pipeline中移除，剩余的block会继续剩下的datanode中继续以pipeline的形式传输，同时Namenode会分配一个新的datanode，保持replicas设定的数量。]]></description>
			<content:encoded><![CDATA[<h2>一、HDFS</h2>
<p>HDFS全称是Hadoop Distributed System。HDFS是为以流的方式存取大文件而设计的。适用于几百MB，GB以及TB，并写一次读多次的场合。而对于低延时数据访问、大量小文件、同时写和任意的文件修改，则并不是十分适合。</p>
<p>目前HDFS支持的使用接口除了Java的还有，Thrift、C、FUSE、WebDAV、HTTP等。HDFS是以block-sized chunk组织其文件内容的，默认的block大小为64MB，对于不足64MB的文件，其会占用一个block，但实际上不用占用实际硬盘上的64MB，这可以说是HDFS是在文件系统之上架设的一个中间层。之所以将默认的block大小设置为64MB这么大，是因为block-sized对于文件定位很有帮助，同时大文件更使传输的时间远大于文件寻找的时间，这样可以最大化地减少文件定位的时间在整个文件获取总时间中的比例 。</p>
<p>构成HDFS主要是Namenode（master）和一系列的Datanode（workers）。Namenode是管理HDFS的目录树和相关的文件元数据，这些信息是以&#8221;namespace image&#8221;和&#8221;edit log&#8221;两个文件形式存放在本地磁盘，但是这些文件是在HDFS每次重启的时候重新构造出来的。Datanode则是存取文件实际内容的节点，Datanodes会定时地将block的列表汇报给Namenode。</p>
<p>由于Namenode是元数据存放的节点，如果Namenode挂了那么HDFS就没法正常运行，因此一般使用将元数据持久存储在本地或远程的机器上，或者使用secondary namenode来定期同步Namenode的元数据信息，secondary namenode有点类似于MySQL的Master/Salves中的Slave，&#8221;edit log&#8221;就类似&#8221;bin log&#8221;。如果Namenode出现了故障，一般会将原Namenode中持久化的元数据拷贝到secondary namenode中，使secondary namenode作为新的Namenode运行起来。<span id="more-915"></span></p>
<h2>二、读写流程</h2>
<h3>文件读取</h3>
<p style="text-align: center;"><img class="aligncenter size-full wp-image-919" title="reading data from hdfs" src="http://blog.endlesscode.com/wp-content/uploads/2010/06/reading-data-from-hdfs.png" alt="reading data from hdfs" width="552" height="308" /></p>
<p>文件读取的过程如下：</p>
<ol>
<li>使用HDFS提供的客户端开发库，向远程的Namenode发起RPC请求；</li>
<li>Namenode会视情况返回文件的部分或者全部block列表，对于每个block，Namenode都会返回有该block拷贝的datanode地址；</li>
<li>客户端开发库会选取离客户端最接近的datanode来读取block；</li>
<li>读取完当前block的数据后，关闭与当前的datanode连接，并为读取下一个block寻找最佳的datanode；</li>
<li>当读完列表的block后，且文件读取还没有结束，客户端开发库会继续向Namenode获取下一批的block列表。</li>
<li>读取完一个block都会进行checksum验证，如果读取datanode时出现错误，客户端会通知Namenode，然后再从下一个拥有该block拷贝的datanode继续读。</li>
</ol>
<p style="text-align: center;">
<h3>写入文件</h3>
<p style="text-align: center;"><img class="aligncenter size-full wp-image-922" title="writing data to hdfs" src="http://blog.endlesscode.com/wp-content/uploads/2010/06/writing-data-to-hdfs.png" alt="writing data to hdfs" width="524" height="321" /></p>
<p>写入文件的过程比读取较为复杂：</p>
<ol>
<li>使用HDFS提供的客户端开发库，向远程的Namenode发起RPC请求；</li>
<li>Namenode会检查要创建的文件是否已经存在，创建者是否有权限进行操作，成功则会为文件创建一个记录，否则会让客户端抛出异常；</li>
<li>当客户端开始写入文件的时候，开发库会将文件切分成多个packets，并在内部以&#8221;data queue&#8221;的形式管理这些packets，并向Namenode申请新的blocks，获取用来存储replicas的合适的datanodes列表，列表的大小根据在Namenode中对replication的设置而定。</li>
<li>开始以pipeline（管道）的形式将packet写入所有的replicas中。开发库把packet以流的方式写入第一个datanode，该datanode把该packet存储之后，再将其传递给在此pipeline中的下一个datanode，直到最后一个datanode，这种写数据的方式呈流水线的形式。</li>
<li>最后一个datanode成功存储之后会返回一个ack packet，在pipeline里传递至客户端，在客户端的开发库内部维护着&#8221;ack queue&#8221;，成功收到datanode返回的ack packet后会从&#8221;ack queue&#8221;移除相应的packet。</li>
<li>如果传输过程中，有某个datanode出现了故障，那么当前的pipeline会被关闭，出现故障的datanode会从当前的pipeline中移除，剩余的block会继续剩下的datanode中继续以pipeline的形式传输，同时Namenode会分配一个新的datanode，保持replicas设定的数量。</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://blog.endlesscode.com/2010/06/16/hdfs-short-intro/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>简单的Streaming和Pipes示例</title>
		<link>http://blog.endlesscode.com/2010/06/16/simple-demo-of-streaming-and-pipes/</link>
		<comments>http://blog.endlesscode.com/2010/06/16/simple-demo-of-streaming-and-pipes/#comments</comments>
		<pubDate>Wed, 16 Jun 2010 07:20:27 +0000</pubDate>
		<dc:creator>Stephen</dc:creator>
				<category><![CDATA[Hadoop]]></category>

		<guid isPermaLink="false">http://blog.endlesscode.com/?p=901</guid>
		<description><![CDATA[一、Hadoop Streaming Streaming是Hadoop提供的一个可以使用其他编程语言来进行MapReduce来的API，因为Hadoop是基于Java（由于作者比较擅长Java，Lucene和Nutch都是出于Hadoop的作者）。Hadoop Streaming并不复杂，其只是使用了Unix的标准输入输出作为Hadoop和其他编程语言的开发接口，因此在其他的编程语言所写的程序中，只需要将标准输入作为程序的输入，将标准输出作为程序的输出就可以了。 在标准的输入输出中，key和value是以tab作为分隔符，并且在reduce的标准输入中，hadoop框架保证了输入的数据是经过了按key排序的。 下面的示例是用Python重写了上一个示例： # max_temperature_map.py #!/usr/bin/env python import sys for line in sys.stdin: val = line.strip().split() # 分隔年份和温度值，输出到标准输出 print "%s\t%s"%(val[0], val[1]) # max_temperature_reduce.py #!/usr/bin/env python import sys (last_key, max_val) = (None, 0) for line in sys.stdin: (key, &#8230; <a href="http://blog.endlesscode.com/2010/06/16/simple-demo-of-streaming-and-pipes/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<h3>一、Hadoop Streaming</h3>
<p>Streaming是Hadoop提供的一个可以使用其他编程语言来进行MapReduce来的API，因为Hadoop是基于Java（由于作者比较擅长Java，Lucene和Nutch都是出于Hadoop的作者）。Hadoop Streaming并不复杂，其只是使用了Unix的标准输入输出作为Hadoop和其他编程语言的开发接口，因此在其他的编程语言所写的程序中，只需要将标准输入作为程序的输入，将标准输出作为程序的输出就可以了。</p>
<p>在标准的输入输出中，key和value是以tab作为分隔符，并且在reduce的标准输入中，hadoop框架保证了输入的数据是经过了按key排序的。</p>
<p>下面的示例是用Python重写了上一个示例：</p>
<pre class="brush:python"># max_temperature_map.py
#!/usr/bin/env python
import sys
for line in sys.stdin:
 val = line.strip().split()
 # 分隔年份和温度值，输出到标准输出
 print "%s\t%s"%(val[0], val[1])

# max_temperature_reduce.py
#!/usr/bin/env python
import sys
(last_key, max_val) = (None, 0)
for line in sys.stdin:
 (key, temp) = line.strip().split('\t')
 if last_key and last_key != key:
 print "%s\t%s" % (last_key, max_val)
 (last_key, max_val) = (key, int(temp))
 else:
 (last_key, max_val) = (key, max(max_val, int(temp)))

if last_key:
 print "%s\t%s" % (last_key, max_val)</pre>
<p><span id="more-901"></span>&#8220;if last_key and last_key != key&#8221;这一行命令主要因为当已经完成了某(key, value[])对的处理后直接输出，然后再重置(last_key, max_val)进行新的(key, value[])处理。因为reduce的输入是经过了排序的，因此&#8221;if last_key and last_key != key&#8221;是处理某key对应的value列表。</p>
<p>执行命令：</p>
<pre class="brush:bash">hadoop jar /home/stephenchan/hadoop-0.20.2/contrib/streaming/hadoop-0.20.2-streaming.jar \
-input sample.txt \
-output pyoutput \
-mapper max_temperature_map.py \
-reducer max_temperature_reduce.py
# 不用MapReduce其实也相当于下面的命令：
# cat sample.txt | python max_temperature_map.py | sort | python max_temperature_reduce.py &gt; pyoutput.txt</pre>
<h3>二、Hadoop Pipes</h3>
<p>Hadoop Pipes是Hadoop MapReduce的C++接口。与使用标准输入输出的Hadoop Streaming不同（当然Streaming也可以用于C++），Hadoop Pipes在tasktacker和map/reduce进行通信时使用的socket作为管道，不是标准输入输出，而不是JNI。</p>
<p>Hadoop Pipes不能运行在standalone模式下，所以要先配置成pseudo-distributed模式，因为Hadoop Pipes依赖于Hadoop的分布式缓存技术，而分布式缓存只会在HDFS运行的时候才会支持。</p>
<p>与Java的接口不一样，Hadoop Pipes的key和value都是基于STL的string，因此在处理时开发人员需要手动地进行数据类型的转换。</p>
<p>C++示例代码：</p>
<pre class="brush:c++">/* max_temperature.cpp */
#include &lt;algorithm&gt;
#include &lt;limits&gt;
#include &lt;string&gt;

#include "hadoop/Pipes.hh"
#include "hadoop/TemplateFactory.hh"
#include "hadoop/StringUtils.hh"

class MaxTemperatureMapper : public HadoopPipes::Mapper {
 public:
 MaxTemperatureMapper(HadoopPipes::TaskContext&amp; context) {
 }

 void map(HadoopPipes::MapContext&amp; context) {
 std::string line = context.getInputValue();
 std::string year = line.substr(0, 4);
 std::string temp = line.substr(5, 3);
 context.emit(year, temp);
 }
};

class MaxTemperatureReducer : public HadoopPipes::Reducer {
 public:
 MaxTemperatureReducer(HadoopPipes::TaskContext&amp; context) {
 }

 void reduce(HadoopPipes::ReduceContext&amp; context) {
 int maxValue = 0;
 while (context.nextValue()) {
 maxValue = std::max(maxValue, HadoopUtils::toInt(context.getInputValue()));
 }
 context.emit(context.getInputKey(), HadoopUtils::toString(maxValue));
 }
};

int main(int argc, char *argv[]) {
 return HadoopPipes::runTask(HadoopPipes::TemplateFactory&lt;MaxTemperatureMapper, MaxTemperatureReducer&gt;());
}</pre>
<p>makefile代码：</p>
<pre class="brush:plain">PLATFORM=Linux-i386-32
CC = g++
CPPFLAGS = -m32 -I$(HADOOP_INSTALL)/c++/$(PLATFORM)/include

max_temperature: max_temperature.cpp
 $(CC) $(CPPFLAGS) $&lt; -Wall -L$(HADOOP_INSTALL)/c++/$(PLATFORM)/lib -lhadooppipes \
 -lhadooputils -lpthread -g -O2 -o $@</pre>
<p>在使用make命令生成了max_temperature可执行文件之后，就要使用&#8221;hadoop fs&#8221;将可执行文件和示例数据文件拷贝到伪dfs中去。</p>
<pre class="brush:bash">hadoop fs -put max_temperature bin/max_temperature
hadoop fs -put sample.txt temp_sample.txt
hadoop pipes \
-D hadoop.pipes.java.recordreader=true \
-D hadoop.pipes.java.recordwriter=true \
-input temp_sample.txt \
-output cppoutput \
-program bin/max_temperature
# 运行的结果也是保存在伪dfs中，使用"hadoop fs"来查看
hadoop fs -cat cppoutput/part*</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.endlesscode.com/2010/06/16/simple-demo-of-streaming-and-pipes/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>简单的MapReduce示例(Java)</title>
		<link>http://blog.endlesscode.com/2010/06/16/simple-demo-of-mapreduce-in-java/</link>
		<comments>http://blog.endlesscode.com/2010/06/16/simple-demo-of-mapreduce-in-java/#comments</comments>
		<pubDate>Wed, 16 Jun 2010 07:19:58 +0000</pubDate>
		<dc:creator>Stephen</dc:creator>
				<category><![CDATA[Hadoop]]></category>

		<guid isPermaLink="false">http://blog.endlesscode.com/?p=891</guid>
		<description><![CDATA[Hadoop的三种模式以及相应的安装细节Google上一大把，其实都基本上是一些参数的设置。 下面自己在.bashrc文件中的设置： # setting in .bashrc export HADOOP_INSTALL=/home/stephenchan/hadoop-0.20.2 export CLASSPATH=.:$HADOOP_INSTALL/hadoop-0.20.2-core.jar:$HADOOP_INSTALL/lib:$CLASSPATH export PATH=$PATH:$HADOOP_INSTALL/bin 样例的代码是来自＜Hadoop : The Definitive Guide&#62;一书，由于没有拿到天气的数据，所以自己用代码生成了一个类似的样例数据，只有年份和温度，用MapReduce来取每年的最高温度，但我省去了日期中的天，保留了年。代码在standalone模式下运行即可，官方的参考文档 ： http://hadoop.apache.org/common/docs/r0.20.2/cn/。目前Hadoop只在Linux下完全支持商用运行，在Unix和Windows只支持用来开发。 生成示例数据的Python代码（生成后再手动给三个年份设置最大的temp值（如999）来观察结果的正常与否）： #!/usr/bin/env python import string import random import hashlib random_chars = string.digits year_list = [1990, 2000, 2010] for idx in range(1, &#8230; <a href="http://blog.endlesscode.com/2010/06/16/simple-demo-of-mapreduce-in-java/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Hadoop的三种模式以及相应的安装细节Google上一大把，其实都基本上是一些参数的设置。</p>
<p>下面自己在.bashrc文件中的设置：</p>
<pre class="brush:bash"># setting in .bashrc
export HADOOP_INSTALL=/home/stephenchan/hadoop-0.20.2
export CLASSPATH=.:$HADOOP_INSTALL/hadoop-0.20.2-core.jar:$HADOOP_INSTALL/lib:$CLASSPATH
export PATH=$PATH:$HADOOP_INSTALL/bin</pre>
<p>样例的代码是来自＜Hadoop : The Definitive Guide&gt;一书，由于没有拿到天气的数据，所以自己用代码生成了一个类似的样例数据，只有年份和温度，用MapReduce来取每年的最高温度，但我省去了日期中的天，保留了年。代码在standalone模式下运行即可，官方的参考文档 ： <a href="http://hadoop.apache.org/common/docs/r0.20.2/cn/">http://hadoop.apache.org/common/docs/r0.20.2/cn/</a>。目前Hadoop只在Linux下完全支持商用运行，在Unix和Windows只支持用来开发。</p>
<p>生成示例数据的Python代码（生成后再手动给三个年份设置最大的temp值（如999）来观察结果的正常与否）：</p>
<pre class="brush:python">#!/usr/bin/env python

import string
import random
import hashlib
random_chars = string.digits
year_list = [1990, 2000, 2010]
for idx in range(1, 1000):
 year = year_list[idx%3]
 temp  = random.sample(random_chars, 3)
 s = "%s %s" % (year, "".join(temp))
 print s</pre>
<p><span id="more-891"></span>MaxTemperature.java的源代码：</p>
<pre class="brush:java">/* MaxTemperature.java */
import java.io.IOException;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapreduce.*;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class MaxTemperature {

 static class MaxTemperatureMapper extends Mapper&lt;LongWritable, Text, Text, IntWritable&gt; {

 public void map(LongWritable key, Text value, Context context)
 throws IOException, InterruptedException {

 String line = value.toString();
 // 从每行的数据中分解出年和温度
 String year = line.substring(0, 4);
 int airTemperature = Integer.parseInt(line.substring(5, 8));
 context.write(new Text(year), new IntWritable(airTemperature));
 }
 } 

 static class MaxTemperatureReducer extends Reducer&lt;Text, IntWritable, Text, IntWritable&gt; {

 public void reduce(Text key, Iterable&lt;IntWritable&gt; values, Context context)
 throws IOException, InterruptedException {

 int maxValue = Integer.MIN_VALUE;
 // key为年份, values为该年每天的温度，求出最大的温度值
 for (IntWritable value : values) {
 maxValue = Math.max(maxValue, value.get());
 }
 context.write(key, new IntWritable(maxValue));
 }
 }

 public static void main(String[] args) throws Exception {
 if(args.length !=2) {
 System.err.println("Usage: MaxTemperature &lt;input path&gt; &lt;output path&gt;");
 System.exit(-1);
 }

 Job job = new Job();
 job.setJarByClass(MaxTemperature.class);

 FileInputFormat.addInputPath(job, new Path(args[0]));
 FileOutputFormat.setOutputPath(job, new Path(args[1]));

 job.setMapperClass(MaxTemperatureMapper.class);
 job.setReducerClass(MaxTemperatureReducer.class);

 job.setOutputKeyClass(Text.class);
 job.setOutputValueClass(IntWritable.class);

 System.exit(job.waitForCompletion(true)?0:1);
 }
}</pre>
<p>执行的命令如下，虽然在Hadoop的命令文档中有提供&#8221;hadoop CLASSNAME&#8221;这样的命令，但我折腾了很久都一直提示&#8221;NoClassDefFoundError&#8221;，因此只能打包成jar然后再用&#8221;hadoop jar&#8221;命令运行。</p>
<pre class="brush:bash">mkdir MaxTemperature
javac -d MaxTemperature MaxTemperature.java
jar cvf MaxTemperature.jar -C MaxTemperature/ .
hadoop jar MaxTemperature.jar MaxTemperature sample.txt output</pre>
<p>结果的输出会在output的目录下。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.endlesscode.com/2010/06/16/simple-demo-of-mapreduce-in-java/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

