﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-The Way of C++ </title><link>http://www.cppblog.com/koson/</link><description /><language>zh-cn</language><lastBuildDate>Sun, 05 Apr 2026 13:43:27 GMT</lastBuildDate><pubDate>Sun, 05 Apr 2026 13:43:27 GMT</pubDate><ttl>60</ttl><item><title>ms 笔经攒RP</title><link>http://www.cppblog.com/koson/archive/2010/09/27/127823.html</link><dc:creator>koson</dc:creator><author>koson</author><pubDate>Sun, 26 Sep 2010 18:23:00 GMT</pubDate><guid>http://www.cppblog.com/koson/archive/2010/09/27/127823.html</guid><wfw:comment>http://www.cppblog.com/koson/comments/127823.html</wfw:comment><comments>http://www.cppblog.com/koson/archive/2010/09/27/127823.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/koson/comments/commentRss/127823.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/koson/services/trackbacks/127823.html</trackback:ping><description><![CDATA[<p>笔试的内容不是很难，下面是一些记录：<br>选择题：<br>1.两个长度各为N的有序数组进行合并，求可能的最多的比较次数，（2n-1）<br>2.两个长度为N的有序数组，要求在这两个数组中排第N的元素，最小的时间复杂度？ （ O(logn)，类似二分搜索）<br>3.逆波兰表达式求值，（竟然画了很久的后缀表达式没画出来，真杯具。。直接求值就行了）<br>4.一个关于二叉树的问题，大意是要在二叉树查找某个元素，求选项给出的查找序列哪个不可能出现？（考察二叉树的性质）<br>5.excell的列表示如AB...Z, AA AB ....ZZ, AAA AAB .... ZZZ, 求DEF的十进制值（求26进制的值，直接计算）<br>6.函数指针数组的写法问题。。<br>7.虚函数问题，大意是基类定义了一个保护成员，构造函数初始化为0，还定义了一个虚函数，基类是将成员--，而子类只重定义了虚函数，将成员++，主函数里，new了一个子类对象，然后定义一个基类指针指向此对象，又定义了一个基类引用指向此基类指针指向的对象，然后分别调用了虚函数，要求基类定义的成员的值。<br>8.给出一段程序，要求输出值，直接计算。程序里计算字符数组 char a[]={'a','b','c'}的长度采用sizeof(a)/sizeof(a[0])的方法。<br>9.指出给出选项中不可能存储在栈中的是。。。（全局静态变量，放在静态区中）<br>10.给出char *p="hello world", char a[]="byebye",strncpy(p,a,6),问这个程序运行后p的结果是什么？(这里*p是一个字符串常量，不能对它的元素进行修改，所以程序在运行时会出错)<br>主观题编程题：<br>大意是给出一个数组，这个数组每个元素都不同，并且可能是升序的，或者是升序+旋转后的结果，例如1,2,3,4,5,或者 4,5,1,2,3 或者 3,4,5,1,2等等，<br>然后给一个数，要找出这个数在所给数组中的索引值或者返回-1，要求复杂度必须小于o(n)。<br>相对比较简单吧，首先是判断是否是从左到右有升序的，若是，则用二分查找，复杂度为o(logn),如果不是，则根据要找的值与第一个值比较的结果，在左半部分或右半部分查找这个数，易知，查找次数肯定小于n，因而复杂度符合要求。<br>第二个小题是要给出一些测试数据并加以说明。<br>正式找工的第一场面试，不是很顺利，特此记录，攒下RP, ^.^</p>
<img src ="http://www.cppblog.com/koson/aggbug/127823.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/koson/" target="_blank">koson</a> 2010-09-27 02:23 <a href="http://www.cppblog.com/koson/archive/2010/09/27/127823.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>海量数据面试题整理（转）</title><link>http://www.cppblog.com/koson/archive/2010/09/23/127442.html</link><dc:creator>koson</dc:creator><author>koson</author><pubDate>Thu, 23 Sep 2010 12:42:00 GMT</pubDate><guid>http://www.cppblog.com/koson/archive/2010/09/23/127442.html</guid><wfw:comment>http://www.cppblog.com/koson/comments/127442.html</wfw:comment><comments>http://www.cppblog.com/koson/archive/2010/09/23/127442.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/koson/comments/commentRss/127442.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/koson/services/trackbacks/127442.html</trackback:ping><description><![CDATA[<p><strong>1. 给定a、b两个文件，各存放50<a name=baidusnap1></a><strong style="BACKGROUND-COLOR: #a0ffff; COLOR: black">亿个url</strong>，每个url各占64字节，内存限制是4G，让你找出a、b文件共同的url？</strong></p>
<p>方案1：可以估计每个文件安的大小为50G&#215;64=320G，远远大于内存限制的4G。所以不可能将其完全加载到内存中<a name=baidusnap4></a><strong style="BACKGROUND-COLOR: #ff66ff; COLOR: black">处理</strong>。考虑采取分而治之的<a name=baidusnap5></a><strong style="BACKGROUND-COLOR: #880000; COLOR: white">方法</strong>。</p>
<p>s 遍历文件a，对每个url求取<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image002_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image002 border=0 alt=clip_image002 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image002_thumb.png" width=103 height=21></a>，然后根据所取得的值将url分别存储到<a name=baidusnap0></a><strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>0个小文件（记为<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image004_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image004 border=0 alt=clip_image004 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image004_thumb.png" width=83 height=21></a>）中。这样每个小文件的大约为300M。</p>
<p>s 遍历文件b，采取和a相同的方式将url分别存储到<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>0各小文件（记为<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image006_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image006 border=0 alt=clip_image006 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image006_thumb.png" width=81 height=21></a>）。这样<strong style="BACKGROUND-COLOR: #ff66ff; COLOR: black">处理</strong>后，所有可能相同的url都在对应的小文件（<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image008_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image008 border=0 alt=clip_image008 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image008_thumb.png" width=179 height=21></a>）中，不对应的小文件不可能有相同的url。然后我们只要求出<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>0对小文件中相同的url即可。</p>
<p>s 求每对小文件中相同的url时，可以把其中一个小文件的url存储到hash_set中。然后遍历另一个小文件的每个url，看其是否在刚才构建的hash_set中，如果是，那么就是共同的url，存到文件里面就可以了。</p>
<p>方案2：如果允许有一定的错误率，可以使用Bloom filter，4G内存大概可以表示340亿bit。将其中一个文件中的url使用Bloom filter映射为这340亿bit，然后挨个读取另外一个文件的url，检查是否与Bloom filter，如果是，那么该url应该是共同的url（注意会有一定的错误率）。</p>
<p><strong>2. 有10个文件，每个文件1G，每个文件的每一行存放的都是用户的query，每个文件的query都可能重复。要求你按照query的频度排序。</strong></p>
<p>方案1：</p>
<p>s 顺序读取10个文件，按照hash(query)%10的结果将query写入到另外10个文件（记为<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image010_3.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image010 border=0 alt=clip_image010 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image010_thumb.png" width=72 height=21></a>）中。这样新生成的文件每个的大小大约也1G（假设hash函数是随机的）。</p>
<p>s 找一台内存在2G左右的机器，依次对<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image010%5B1%5D.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image010[1] border=0 alt=clip_image010[1] src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image010%5B1%5D_thumb.png" width=72 height=21></a>用hash_map(query, query_count)来统计每个query出现的次数。利用快速/堆/归并排序按照出现次数进行排序。将排序好的query和对应的query_cout输出到文件中。这样得到了10个排好序的文件（记为<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image012_3.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image012 border=0 alt=clip_image012 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image012_thumb.png" width=75 height=21></a>）。</p>
<p>s 对<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image012%5B1%5D.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image012[1] border=0 alt=clip_image012[1] src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image012%5B1%5D_thumb.png" width=75 height=21></a>这10个文件进行归并排序（内排序与外排序相结合）。</p>
<p>方案2：</p>
<p>一般query的总量是有限的，只是重复的次数比较多而已，可能对于所有的query，一次性就可以加入到内存了。这样，我们就可以采用trie树/hash_map等直接来统计每个query出现的次数，然后按出现次数做快速/堆/归并排序就可以了。</p>
<p>方案3：</p>
<p>与方案1类似，但在做完hash，分成多个文件后，可以交给多个文件来<strong style="BACKGROUND-COLOR: #ff66ff; COLOR: black">处理</strong>，采用分布式的架构来<strong style="BACKGROUND-COLOR: #ff66ff; COLOR: black">处理</strong>（比如MapReduce），最后再进行合并。</p>
<p><strong>3. 有一个1G大小的一个文件，里面每一行是一个词，词的大小不超过16字节，内存限制大小是1M。返回频数最高的<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>个词。</strong></p>
<p>方案1：顺序读文件中，对于每个词x，取<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image014_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image014 border=0 alt=clip_image014 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image014_thumb.png" width=91 height=21></a>，然后按照该值存到5000个小文件（记为<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image016_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image016 border=0 alt=clip_image016 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image016_thumb.png" width=87 height=21></a>）中。这样每个文件大概是200k左右。如果其中的有的文件超过了1M大小，还可以按照类似的<strong style="BACKGROUND-COLOR: #880000; COLOR: white">方法</strong>继续往下分，知道分解得到的小文件的大小都不超过1M。对每个小文件，统计每个文件中出现的词以及相应的频率（可以采用trie树/hash_map等），并取出出现频率最大的<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>个词（可以用含<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>个结点的最小堆），并把<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>词及相应的频率存入文件，这样又得到了5000个文件。下一步就是把这5000个文件进行归并（类似与归并排序）的过程了。</p>
<p><strong>4. 海量日志数据，提取出某日访问百度次数最多的那个IP。</strong></p>
<p>方案1：首先是这一天，并且是访问百度的日志中的IP取出来，逐个写入到一个大文件中。注意到IP是32位的，最多有<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image018_3.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image018 border=0 alt=clip_image018 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image018_thumb.png" width=20 height=21></a>个IP。同样可以采用映射的<strong style="BACKGROUND-COLOR: #880000; COLOR: white">方法</strong>，比如模<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>0，把整个大文件映射为<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>0个小文件，再找出每个小文中出现频率最大的IP（可以采用hash_map进行频率统计，然后再找出频率最大的几个）及相应的频率。然后再在这<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>0个最大的IP中，找出那个频率最大的IP，即为所求。</p>
<p><strong>5. 在2.5亿个整数中找出不重复的整数，内存不足以容纳这2.5亿个整数。</strong></p>
<p>方案1：采用2-Bitmap（每个数分配2bit，00表示不存在，01表示出现一次，10表示多次，11无意义）进行，共需内存<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image020_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image020 border=0 alt=clip_image020 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image020_thumb.png" width=106 height=21></a>内存，还可以接受。然后扫描这2.5亿个整数，查看Bitmap中相对应位，如果是00变01，01变10，10保持不变。所描完事后，查看bitmap，把对应位是01的整数输出即可。</p>
<p>方案2：也可采用上题类似的<strong style="BACKGROUND-COLOR: #880000; COLOR: white">方法</strong>，进行划分小文件的<strong style="BACKGROUND-COLOR: #880000; COLOR: white">方法</strong>。然后在小文件中找出不重复的整数，并排序。然后再进行归并，注意去除重复的元素。</p>
<p><strong>6. 海量数据分布在<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>台电脑中，想个办法高校统计出这批数据的TOP10。</strong></p>
<p>方案1：</p>
<p>s 在每台电脑上求出TOP10，可以采用包含10个元素的堆完成（TOP10小，用最大堆，TOP10大，用最小堆）。比如求TOP10大，我们首先取前10个元素调整成最小堆，如果发现，然后扫描后面的数据，并与堆顶元素比较，如果比堆顶元素大，那么用该元素替换堆顶，然后再调整为最小堆。最后堆中的元素就是TOP10大。</p>
<p>s 求出每台电脑上的TOP10后，然后把这<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>台电脑上的TOP10组合起来，共<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>0个数据，再利用上面类似的<strong style="BACKGROUND-COLOR: #880000; COLOR: white">方法</strong>求出TOP10就可以了。</p>
<p><strong>7. 怎么在海量数据中找出重复次数最多的一个？</strong></p>
<p>方案1：先做hash，然后求模映射为小文件，求出每个小文件中重复次数最多的一个，并记录重复次数。然后找出上一步求出的数据中重复次数最多的一个就是所求（具体参考前面的题）。</p>
<p><strong>8. 上千万或上亿数据（有重复），统计其中出现次数最多的钱N个数据。</strong></p>
<p>方案1：上千万或上亿的数据，现在的机器的内存应该能存下。所以考虑采用hash_map/搜索二叉树/红黑树等来进行统计次数。然后就是取出前N个出现次数最多的数据了，可以用第6题提到的堆机制完成。</p>
<p><strong>9. <strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>0万字符串，其中有些是重复的，需要把重复的全部去掉，保留没有重复的字符串。请怎么设计和实现？</strong></p>
<p>方案1：这题用trie树比较合适，hash_map也应该能行。</p>
<p><strong>10. 一个文本文件，大约有一万行，每行一个词，要求统计出其中最频繁出现的前10个词，请给出思想，给出时间复杂度分析。</strong></p>
<p>方案1：这题是考虑时间效率。用trie树统计每个词出现的次数，时间复杂度是O(n*le)（le表示单词的平准长度）。然后是找出出现最频繁的前10个词，可以用堆来实现，前面的题中已经讲到了，时间复杂度是O(n*lg10)。所以总的时间复杂度，是O(n*le)与O(n*lg10)中较大的哪一个。</p>
<p><strong>11. 一个文本文件，找出前10个经常出现的词，但这次文件比较长，说是上亿行或十亿行，总之无法一次读入内存，问最优解。</strong></p>
<p>方案1：首先根据用hash并求模，将文件分解为多个小文件，对于单个文件利用上题的<strong style="BACKGROUND-COLOR: #880000; COLOR: white">方法</strong>求出每个文件件中10个最常出现的词。然后再进行归并<strong style="BACKGROUND-COLOR: #ff66ff; COLOR: black">处理</strong>，找出最终的10个最常出现的词。</p>
<p><strong>12. <strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>w个数中找出最大的<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>个数。</strong></p>
<p>方案1：在前面的题中，我们已经提到了，用一个含<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>个元素的最小堆完成。复杂度为O(<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>w*lg<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>)。</p>
<p>方案2：采用快速排序的思想，每次分割之后只考虑比轴大的一部分，知道比轴大的一部分在比<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>多的时候，采用传统排序算法排序，取前<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>个。复杂度为O(<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>w*<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>)。</p>
<p>方案3：采用局部淘汰法。选取前<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>个元素，并排序，记为序列L。然后一次扫描剩余的元素x，与排好序的<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>个元素中最小的元素比，如果比这个最小的要大，那么把这个最小的元素删除，并把x利用插入排序的思想，插入到序列L中。依次循环，知道扫描了所有的元素。复杂度为O(<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>w*<strong style="BACKGROUND-COLOR: #ffff66; COLOR: black">100</strong>)。</p>
<p><strong>13. 寻找热门查询：</strong></p>
<p>搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来，每个查询串的长度为1-255字节。假设目前有一千万个记录，这些查询串的重复读比较高，虽然总数是1千万，但是如果去除重复和，不超过3百万个。一个查询串的重复度越高，说明查询它的用户越多，也就越热门。请你统计最热门的10个查询串，要求使用的内存不能超过1G。</p>
<p>(1) 请描述你解决这个问题的思路；</p>
<p>(2) 请给出主要的<strong style="BACKGROUND-COLOR: #ff66ff; COLOR: black">处理</strong>流程，算法，以及算法的复杂度。</p>
<p>方案1：采用trie树，关键字域存该查询串出现的次数，没有出现为0。最后用10个元素的最小推来对出现频率进行排序。</p>
<p><strong>14. 一共有N个机器，每个机器上有N个数。每个机器最多存O(N)个数并对它们操作。如何找到<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image022_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image022 border=0 alt=clip_image022 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image022_thumb.png" width=16 height=21></a>个数中的中数？</strong></p>
<p>方案1：先大体估计一下这些数的范围，比如这里假设这些数都是32位无符号整数（共有<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image018%5B1%5D.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image018[1] border=0 alt=clip_image018[1] src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image018%5B1%5D_thumb.png" width=20 height=21></a>个）。我们把0到<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image024_3.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image024 border=0 alt=clip_image024 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image024_thumb.png" width=44 height=21></a>的整数划分为N个范围段，每个段包含<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image026_3.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image026 border=0 alt=clip_image026 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image026_thumb.png" width=16 height=42></a>个整数。比如，第一个段位0到<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image028_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image028 border=0 alt=clip_image028 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image028_thumb.png" width=40 height=42></a>，第二段为<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image026%5B1%5D.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image026[1] border=0 alt=clip_image026[1] src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image026%5B1%5D_thumb.png" width=16 height=42></a>到<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image030_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image030 border=0 alt=clip_image030 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image030_thumb.png" width=40 height=42></a>，&#8230;，第N个段为<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image032_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image032 border=0 alt=clip_image032 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image032_thumb.png" width=45 height=42></a>到<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image024%5B1%5D.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image024[1] border=0 alt=clip_image024[1] src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image024%5B1%5D_thumb.png" width=44 height=21></a>。然后，扫描每个机器上的N个数，把属于第一个区段的数放到第一个机器上，属于第二个区段的数放到第二个机器上，&#8230;，属于第N个区段的数放到第N个机器上。注意这个过程每个机器上存储的数应该是O(N)的。下面我们依次统计每个机器上数的个数，一次累加，直到找到第k个机器，在该机器上累加的数大于或等于<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image034_4.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image034 border=0 alt=clip_image034 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image034_thumb.png" width=13 height=42></a>，而在第k-1个机器上的累加数小于<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image034%5B1%5D.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image034[1] border=0 alt=clip_image034[1] src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image034%5B1%5D_thumb.png" width=13 height=42></a>，并把这个数记为x。那么我们要找的中位数在第k个机器中，排在第<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image036_3.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image036 border=0 alt=clip_image036 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image036_thumb.png" width=37 height=42></a>位。然后我们对第k个机器的数排序，并找出第<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image036%5B1%5D.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image036[1] border=0 alt=clip_image036[1] src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image036%5B1%5D_thumb.png" width=37 height=42></a>个数，即为所求的中位数。复杂度是<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image038_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image038 border=0 alt=clip_image038 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image038_thumb.png" width=38 height=21></a>的。</p>
<p>方案2：先对每台机器上的数进行排序。排好序后，我们采用归并排序的思想，将这N个机器上的数归并起来得到最终的排序。找到第<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image034%5B2%5D.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image034[2] border=0 alt=clip_image034[2] src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image034%5B2%5D_thumb.png" width=13 height=42></a>个便是所求。复杂度是<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image040_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image040 border=0 alt=clip_image040 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image040_thumb.png" width=69 height=21></a>的。</p>
<p><strong>15. 最大间隙问题</strong></p>
<p>给定n个实数<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image042_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image042 border=0 alt=clip_image042 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image042_thumb.png" width=90 height=21></a>，求着n个实数在实轴上向量2个数之间的最大差值，要求线性的时间算法。</p>
<p>方案1：最先想到的<strong style="BACKGROUND-COLOR: #880000; COLOR: white">方法</strong>就是先对这n个数据进行排序，然后一遍扫描即可确定相邻的最大间隙。但该<strong style="BACKGROUND-COLOR: #880000; COLOR: white">方法</strong>不能满足线性时间的要求。故采取如下<strong style="BACKGROUND-COLOR: #880000; COLOR: white">方法</strong>：</p>
<p>s 找到n个数据中最大和最小数据max和min。</p>
<p>s 用n-2个点等分区间[min, max]，即将[min, max]等分为n-1个区间（前闭后开区间），将这些区间看作桶，编号为<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image044_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image044 border=0 alt=clip_image044 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image044_thumb.png" width=115 height=21></a>，且桶<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image046_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image046 border=0 alt=clip_image046 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image046_thumb.png" width=8 height=25></a>的上界和桶i+1的下届相同，即每个桶的大小相同。每个桶的大小为：<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image048_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image048 border=0 alt=clip_image048 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image048_thumb.png" width=139 height=42></a>。实际上，这些桶的边界构成了一个等差数列（首项为min，公差为<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image050_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image050 border=0 alt=clip_image050 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image050_thumb.png" width=89 height=21></a>），且认为将min放入第一个桶，将max放入第n-1个桶。</p>
<p>s 将n个数放入n-1个桶中：将每个元素<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image052_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image052 border=0 alt=clip_image052 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image052_thumb.png" width=20 height=21></a>分配到某个桶（编号为index），其中<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image054_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image054 border=0 alt=clip_image054 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image054_thumb.png" width=152 height=42></a>，并求出分到每个桶的最大最小数据。</p>
<p>s 最大间隙：除最大最小数据max和min以外的n-2个数据放入n-1个桶中，由抽屉原理可知至少有一个桶是空的，又因为每个桶的大小相同，所以最大间隙不会在同一桶中出现，一定是某个桶的上界和气候某个桶的下界之间隙，且该量筒之间的桶（即便好在该连个便好之间的桶）一定是空桶。也就是说，最大间隙在桶i的上界和桶j的下界之间产生<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image056_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image056 border=0 alt=clip_image056 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image056_thumb.png" width=50 height=21></a>。一遍扫描即可完成。</p>
<p><strong>16. 将多个集合合并成没有交集的集合：给定一个字符串的集合，格式如：<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image058_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image058 border=0 alt=clip_image058 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image058_thumb.png" width=240 height=15></a>。要求将其中交集不为空的集合合并，要求合并完成的集合之间无交集，例如上例应输出<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image060_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image060 border=0 alt=clip_image060 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image060_thumb.png" width=240 height=20></a>。</strong></p>
<p>(1) 请描述你解决这个问题的思路；</p>
<p>(2) 给出主要的<strong style="BACKGROUND-COLOR: #ff66ff; COLOR: black">处理</strong>流程，算法，以及算法的复杂度；</p>
<p>(3) 请描述可能的改进。</p>
<p>方案1：采用并查集。首先所有的字符串都在单独的并查集中。然后依扫描每个集合，顺序合并将两个相邻元素合并。例如，对于<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image062_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image062 border=0 alt=clip_image062 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image062_thumb.png" width=88 height=21></a>，首先查看aaa和bbb是否在同一个并查集中，如果不在，那么把它们所在的并查集合并，然后再看bbb和ccc是否在同一个并查集中，如果不在，那么也把它们所在的并查集合并。接下来再扫描其他的集合，当所有的集合都扫描完了，并查集代表的集合便是所求。复杂度应该是O(NlgN)的。改进的话，首先可以记录每个节点的根结点，改进查询。合并的时候，可以把大的和小的进行合，这样也减少复杂度。</p>
<p><strong>17. 最大子序列与最大子矩阵问题</strong></p>
<p>数组的最大子序列问题：给定一个数组，其中元素有正，也有负，找出其中一个连续子序列，使和最大。</p>
<p>方案1：这个问题可以动态规划的思想解决。设<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image064_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image064 border=0 alt=clip_image064 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image064_thumb.png" width=21 height=21></a>表示以第i个元素<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image066_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image066 border=0 alt=clip_image066 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image066_thumb.png" width=21 height=21></a>结尾的最大子序列，那么显然<a href="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image068_2.png"><img style="BORDER-RIGHT-WIDTH: 0px; DISPLAY: inline; BORDER-TOP-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px" title=clip_image068 border=0 alt=clip_image068 src="http://images.cnblogs.com/cnblogs_com/youwang/WindowsLiveWriter/1340f2e6fa8e_D5D1/clip_image068_thumb.png" width=240 height=20></a>。基于这一点可以很快用代码实现。</p>
<p>最大子矩阵问题：给定一个矩阵（二维数组），其中数据有大有小，请找一个子矩阵，使得子矩阵的和最大，并输出这个和。</p>
<p>方案1：可以采用与最大子序列类似的思想来解决。如果我们确定了选择第i列和第j列之间的元素，那么在这个范围内，其实就是一个最大子序列问题。如何确定第i列和第j列可以词用暴搜的<strong style="BACKGROUND-COLOR: #880000; COLOR: white">方法</strong>进行。</p>
<img src ="http://www.cppblog.com/koson/aggbug/127442.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/koson/" target="_blank">koson</a> 2010-09-23 20:42 <a href="http://www.cppblog.com/koson/archive/2010/09/23/127442.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>struct结构体的数据对齐</title><link>http://www.cppblog.com/koson/archive/2010/09/22/127341.html</link><dc:creator>koson</dc:creator><author>koson</author><pubDate>Wed, 22 Sep 2010 06:49:00 GMT</pubDate><guid>http://www.cppblog.com/koson/archive/2010/09/22/127341.html</guid><wfw:comment>http://www.cppblog.com/koson/comments/127341.html</wfw:comment><comments>http://www.cppblog.com/koson/archive/2010/09/22/127341.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/koson/comments/commentRss/127341.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/koson/services/trackbacks/127341.html</trackback:ping><description><![CDATA[<p>struct<br>{<br>&nbsp;&nbsp; char a;<br>&nbsp;&nbsp; int b;<br>&nbsp;&nbsp; short c;<br>};<br>每个成员起始地址必须为相应类型的大小的倍数，如a,起始地址为0，为sizeof(char)=1的倍数，而到b的时候，因为这时地址为1，不是sizeof(int)=4的倍数，所以编译器会添加3个字节大小，此时地址为4，作为b的起始地址，b占用4个字节，所以此时内存地址为8，8是sizeof(short)=2的倍数，所以c的起始地址为8，占两个字节，所以此时总的分配内存大小为10，但是因为整个结构体的最大成员为sizeof(int)=4,而10不为4的倍数，所以要添加两个字节为12.<br>使用#pragma pack(n)可以指定编译器按n个字节作为对齐方式，这个指定只对那些大于n个字节的成员有效，如上例，若指定了以2个字节作为对齐，则a的起始地址为0，接着到b,因为sizeof(int)=4 &lt; 2,所以要指定的对齐方式，此时地址为1，不为2的倍数，所以要加1，所以b的起始地址为2，占用4个字节大小之后地址为6，接着是c，因为6是2的倍数，所以6为c的起始地址，占用两个字节，总的大小为8</p>
<img src ="http://www.cppblog.com/koson/aggbug/127341.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/koson/" target="_blank">koson</a> 2010-09-22 14:49 <a href="http://www.cppblog.com/koson/archive/2010/09/22/127341.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>XP下Virtualbox虚拟Ubuntu共享文件夹设置（转）</title><link>http://www.cppblog.com/koson/archive/2010/09/15/126626.html</link><dc:creator>koson</dc:creator><author>koson</author><pubDate>Tue, 14 Sep 2010 16:57:00 GMT</pubDate><guid>http://www.cppblog.com/koson/archive/2010/09/15/126626.html</guid><wfw:comment>http://www.cppblog.com/koson/comments/126626.html</wfw:comment><comments>http://www.cppblog.com/koson/archive/2010/09/15/126626.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/koson/comments/commentRss/126626.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/koson/services/trackbacks/126626.html</trackback:ping><description><![CDATA[<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 1. 安装增强功能包(Guest Additions)</p>
<p>&nbsp;&nbsp;&nbsp; 安装好<u><font color=#108ac6>ubuntu</font></u>后，运行<a class=channel_keylink href="http://linux.chinaitlab.com/Special/Ubuntu/Index.html" target=_blank><u><font color=#108ac6>Ubuntu</font></u></a>并登录。然后在VirtualBox的菜单里选择"设备(Devices)" -&gt; "安装增强功能包(Install Guest Additions)"。</p>
<p>&nbsp;&nbsp;&nbsp; 你会发现在Ubuntu桌面上多出一个光盘图标，这张光盘默认被自动加载到了文件夹/media/cdom0。进入命令行终端，输入：</p>
<p>&nbsp;&nbsp;&nbsp; cd /media/cdom0</p>
<p>&nbsp;&nbsp;&nbsp; sudo ./VboxLinuxAdditions.run</p>
<p>&nbsp;&nbsp;&nbsp; 开始安装工具包。安装完毕后会提示要重启Ubuntu。</p>
<p>&nbsp;&nbsp;&nbsp; 2. 设置共享文件夹</p>
<p>&nbsp;&nbsp;&nbsp; 重启完成后点击"设备(Devices)" -&gt; 共享文件夹(Shared Folders)菜单，添加一个共享文件夹，选项固定和临时是指该文件夹是否是持久的。共享名可以任取一个自己喜欢的，比如"gongxiang"，尽量使用英文名称。</p>
<p>&nbsp;&nbsp;&nbsp; 3. 挂载共享文件夹</p>
<p>&nbsp;&nbsp;&nbsp; 重新进入虚拟Ubuntu，在命令行终端下输入：</p>
<p>&nbsp;&nbsp;&nbsp; sudo mkdir /mnt/shared</p>
<p>&nbsp;&nbsp;&nbsp; sudo mount -t vboxsf gongxiang /mnt/shared</p>
<p>&nbsp;&nbsp;&nbsp; 其中"gongxiang"是之前创建的共享文件夹的名字。OK，现在Ubuntu和主机可以互传文件了。</p>
<p>&nbsp;&nbsp;&nbsp; 假如您不想每一次都手动挂载，可以在/etc/fstab中添加一项</p>
<p>&nbsp;&nbsp;&nbsp; gongxiang /mnt/shared vboxsf rw,gid=100,uid=1000,auto 0 0</p>
<p>&nbsp;&nbsp;&nbsp; 这样就能够自动挂载了。</p>
<p>&nbsp;&nbsp;&nbsp; 4. 卸载的话使用下面的命令：</p>
<p>&nbsp;&nbsp;&nbsp; sudo umount -f /mnt/shared</p>
<p>&nbsp;&nbsp;&nbsp; 注意：</p>
<p>&nbsp;&nbsp;&nbsp; 共享文件夹的名称千万不要和挂载点的名称相同。比如，上面的挂载点是/mnt/shared，如果共享文件夹的名字也是shared的话，在挂载的时候就会出现如下的错误信息(看<a href="http://www.virtualbox.org/ticket/2265"><u><font color=#108ac6>http://www.virtualbox.org/ticket/2265</font></u></a>)：</p>
<p>&nbsp;&nbsp;&nbsp; /sbin/mount.vboxsf: mounting failed with the error: Protocol error</p>
<p>&nbsp;&nbsp;&nbsp; 原因分析可以看Tips on running Sun Virtualbox的Shared Folder on a Linux Guest节。</p>
<img src ="http://www.cppblog.com/koson/aggbug/126626.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/koson/" target="_blank">koson</a> 2010-09-15 00:57 <a href="http://www.cppblog.com/koson/archive/2010/09/15/126626.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux下tar gz bz2 tgz z等众多压缩文件的解压方法（转）</title><link>http://www.cppblog.com/koson/archive/2010/09/15/126625.html</link><dc:creator>koson</dc:creator><author>koson</author><pubDate>Tue, 14 Sep 2010 16:56:00 GMT</pubDate><guid>http://www.cppblog.com/koson/archive/2010/09/15/126625.html</guid><wfw:comment>http://www.cppblog.com/koson/comments/126625.html</wfw:comment><comments>http://www.cppblog.com/koson/archive/2010/09/15/126625.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/koson/comments/commentRss/126625.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/koson/services/trackbacks/126625.html</trackback:ping><description><![CDATA[<p>在具体总结各类压缩文件之前呢，首先要弄清两个概念：打包和压缩。打包是指将一大堆文件或目录什么的变成一个总的文件，压缩则是将一个大的文件通过一些压缩算法变成一个小文件。为什么要区分这两个概念呢？其实这源于Linux中的很多压缩程序只能针对一个文件进行压缩，这样当你想要压缩一大堆文时，你就得先借助另它的工具将这一大堆文件先打成一个包，然后再就原来的压缩程序进行压缩。<br>　　Linux下最常用的打包程序就是tar了，使用tar程序打出来的包我们常称为tar包，tar包文件的命令通常都是以.tar结尾的。生成tar包后，就可以用其它的程序来进行压缩了，所以首先就来讲讲tar命令的基本用法：<br>　　tar命令的选项有很多(用man tar可以查看到)，但常用的就那么几个选项，下面来举例说明一下：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # tar -cf all.tar *.jpg<br>　　这条命令是将所有.jpg的文件打成一个名为all.tar的包。-c是表示产生新的包，-f指定包的文件名。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # tar -rf all.tar *.gif<br>　　这条命令是将所有.gif的文件增加到all.tar的包里面去。-r是表示增加文件的意思。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # tar -uf all.tar logo.gif<br>　　这条命令是#更新原来tar包all.tar中logo.gif文件，-u是表示更新文件的意思。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # tar -tf all.tar<br>　　这条命令是列出all.tar包中所有文件，-t是列出文件的意思<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # tar -xf all.tar<br>　　这条命令是解出all.tar包中所有文件，-x是解开的意思<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 以上就是tar的最基本的用法。为了方便用户在打包解包的同时可以压缩或解压文件，tar提供了一种特殊的功能。这就是tar可以在打包或解包的同时调用其它的压缩程序，比如调用gzip、bzip2等。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1) tar调用gzip<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gzip是GNU组织开发的一个压缩程序，.gz结尾的文件就是gzip压缩的结果。与gzip相对的解压程序gunzip。tar中使用-z这个参数来调用gzip。下面来举例说明一下：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # tar -czf all.tar.gz *.jpg<br>　　这条命令是将所有.jpg的文件打成一个tar包，并且将其用gzip压缩，生成一个gzip压缩过的包，包名all.tar.gz<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # tar -xzf all.tar.gz<br>　　这条命令是将上面产生的包解开。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2) tar调用bzip2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bzip2是一个压缩能力更强的压缩程序，.bz2结尾的文件就是bzip2压缩的结果。与bzip2相对的解压程序是bunzip2。tar中使用-j这个参数来调用gzip。下面来举例说明一下：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # tar -cjf all.tar.bz2 *.jpg<br>　　这条命令是将所有.jpg的文件打成一个tar包，并且将其用bzip2压缩，生成一个bzip2压缩过的包，包名为all.tar.bz2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # tar -xjf all.tar.bz2<br>　　这条命令是将上面产生的包解开。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3)tar调用compress<br>&nbsp; compress也是一个压缩程序，但是好象使用compress的人不如gzip和bzip2的人多。.Z结尾的文件就是bzip2压缩的结果。与 compress相对的解压程序是uncompress。tar中使用-Z这个参数来调用compress。下面来举例说明一下：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # tar -cZf all.tar.Z *.jpg<br>　　这条命令是将所有.jpg的文件打成一个tar包，并且将其用compress压缩，生成一个uncompress压缩过的包，包名为all.tar.Z<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # tar -xZf all.tar.Z<br>　　这条命令是将上面产生的包解开<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 有了上面的知识，你应该可以解开多种压缩文件了，下面对于tar系列的压缩文<br>件作一个小结：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #1)对于.tar结尾的文件<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tar -xf all.tar<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2)对于.gz结尾的文件<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gzip -d all.gz<br>　　gunzip all.gz<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3)对于.tgz或.tar.gz结尾的文件<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tar -xzf all.tar.gz<br>　　tar -xzf all.tgz<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4)对于.bz2结尾的文件<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bzip2 -d all.bz2<br>　　bunzip2 all.bz2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 5)对于tar.bz2结尾的文件<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tar -xjf all.tar.bz2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 6)对于.Z结尾的文件<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; uncompress all.Z<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 7)对于.tar.Z结尾的文件<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tar -xZf all.tar.z<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 另外对于Window下的常见压缩文件.zip和.rar，Linux也有相应的方法来解压它们：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1)对于.zip<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; linux下提供了zip和unzip程序，zip是压缩程序，unzip是解压程序。它们的参<br>数选项很多，这里只做简单介绍，依旧举例说明一下其用法：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # zip all.zip *.jpg<br>　　这条命令是将所有.jpg的文件压缩成一个zip包<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # unzip all.zip<br>　　这条命令是将all.zip中的所有文件解压出来<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2)对于.rar<br>&nbsp;要在linux下处理.rar文件，需要安装RAR for Linux，可以从网上下载，但要记住，RAR for Linux不是免费的；可从<a href="http://www.rarsoft.com/download.htm" target=_blank><u><font color=#006600>http://www.rarsoft.com/download.htm</font></u></a>下载RARfor Linux 3.2.<br>0，然后安装：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # tar -xzpvf rarlinux-3.2.0.tar.gz<br>　　# cd rar<br>　　# make<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这样就安装好了，安装后就有了rar和unrar这两个程序，rar是压缩程序，unrar是解压程序。它们的参数选项很多，这里只做简单介绍，依旧举例说明一下其用法：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # rar a all *.jpg<br>　　这条命令是将所有.jpg的文件压缩成一个rar包，名为all.rar，该程序会将.rar扩展名将自动附加到包名后。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # unrar e all.rar<br>　　这条命令是将all.rar中的所有文件解压出来<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 到此为至，我们已经介绍过linux下的tar、gzip、gunzip、bzip2、bunzip2、compress、uncompress、 zip、unzip、rar、unrar等程式，你应该已经能够使用它们对.tar、.gz、.tar.gz、.tgz、.bz2、.tar.bz2、.Z、.tar.Z、.zip、.rar这10种压缩文件进行解压了，以后应该不需要为下载了一个软件而不知道如何在Linux下解开而烦恼了。而且以上方法对于Unix也基本有效。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 本文介绍了linux下的压缩程式tar、gzip、gunzip、bzip2、bunzip2、compress、uncompress、 zip、 unzip、rar、unrar等程式，以及如何使用它们对.tar、.gz、.tar.gz、.tgz、.bz2、.tar.bz2、.Z、. tar.Z、.zip、.rar这10种压缩文件进行操作。 </p>
<img src ="http://www.cppblog.com/koson/aggbug/126625.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/koson/" target="_blank">koson</a> 2010-09-15 00:56 <a href="http://www.cppblog.com/koson/archive/2010/09/15/126625.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>开始漫长而艰辛的找工路</title><link>http://www.cppblog.com/koson/archive/2010/09/14/126551.html</link><dc:creator>koson</dc:creator><author>koson</author><pubDate>Mon, 13 Sep 2010 20:05:00 GMT</pubDate><guid>http://www.cppblog.com/koson/archive/2010/09/14/126551.html</guid><wfw:comment>http://www.cppblog.com/koson/comments/126551.html</wfw:comment><comments>http://www.cppblog.com/koson/archive/2010/09/14/126551.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/koson/comments/commentRss/126551.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/koson/services/trackbacks/126551.html</trackback:ping><description><![CDATA[<p>下来两三个月，将是找工阶段的高峰期，不管以前再多么的各种不如意，都必须全身心的投入进去，不可懈怠，希望找到自己满意的一份工作！</p>
<img src ="http://www.cppblog.com/koson/aggbug/126551.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/koson/" target="_blank">koson</a> 2010-09-14 04:05 <a href="http://www.cppblog.com/koson/archive/2010/09/14/126551.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一些有用链接</title><link>http://www.cppblog.com/koson/archive/2010/07/19/120776.html</link><dc:creator>koson</dc:creator><author>koson</author><pubDate>Mon, 19 Jul 2010 06:50:00 GMT</pubDate><guid>http://www.cppblog.com/koson/archive/2010/07/19/120776.html</guid><wfw:comment>http://www.cppblog.com/koson/comments/120776.html</wfw:comment><comments>http://www.cppblog.com/koson/archive/2010/07/19/120776.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/koson/comments/commentRss/120776.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/koson/services/trackbacks/120776.html</trackback:ping><description><![CDATA[http://caibinbupt.javaeye.com/ 蔡斌，hadoop源码分析<br>http://www.oschina.net/p/hive/recomm HIVE的详细教程<br>http://www.tbdata.org/archives/category/hive 淘宝数据平台团队<br>http://www.cnblogs.com/spork/archive/2010/01/11/1644342.html Map/Reduce数据流<br><br><img src ="http://www.cppblog.com/koson/aggbug/120776.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/koson/" target="_blank">koson</a> 2010-07-19 14:50 <a href="http://www.cppblog.com/koson/archive/2010/07/19/120776.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Hadoop分布式文件系统：架构和设计要点</title><link>http://www.cppblog.com/koson/archive/2010/07/19/120775.html</link><dc:creator>koson</dc:creator><author>koson</author><pubDate>Mon, 19 Jul 2010 06:42:00 GMT</pubDate><guid>http://www.cppblog.com/koson/archive/2010/07/19/120775.html</guid><wfw:comment>http://www.cppblog.com/koson/comments/120775.html</wfw:comment><comments>http://www.cppblog.com/koson/archive/2010/07/19/120775.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/koson/comments/commentRss/120775.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/koson/services/trackbacks/120775.html</trackback:ping><description><![CDATA[<p style="MARGIN-BOTTOM: 0in" id=nekf0 class=western>Hadoop<font id=nekf1 face="宋体, SimSun"><span id=nekf2 lang=zh-CN>分布式文件系统：架构和设计要点</span></font><br id=nekf3>原文：http://hadoop.apache.org/core/docs/current/hdfs_design.html<br id=nekf4><font id=nekf5 face="宋体, SimSun"><span id=nekf6 lang=zh-CN>一、前提和设计目标</span></font><br id=nekf7>1<font id=nekf8 face="宋体, SimSun"><span id=nekf9 lang=zh-CN>、硬件错误是常态，而非异常情况，</span></font>HDFS<font id=nekf10 face="宋体, SimSun"><span id=nekf11 lang=zh-CN>可能是有成百上千的</span></font>server<font id=nekf12 face="宋体, SimSun"><span id=nekf13 lang=zh-CN>组成，任何一个组件都有可能一直失效，因此错误检测和快速、自动的恢复是</span></font>HDFS<font id=nekf14 face="宋体, SimSun"><span id=nekf15 lang=zh-CN>的核心架构目标。</span></font><br id=nekf16>2<font id=nekf17 face="宋体, SimSun"><span id=nekf18 lang=zh-CN>、跑在</span></font>HDFS<font id=nekf19 face="宋体, SimSun"><span id=nekf20 lang=zh-CN>上的应用与一般的应用不同，它们主要是以流式读为主，做批量处理；比之关注数据访问的低延迟问题，更关键的在于数据访问的高吞吐量。</span></font><br id=nekf21>3<font id=nekf22 face="宋体, SimSun"><span id=nekf23 lang=zh-CN>、</span></font>HDFS<font id=nekf24 face="宋体, SimSun"><span id=nekf25 lang=zh-CN>以支持大数据集合为目标，一个存储在上面的典型文件大小一般都在千兆至</span></font>T<font id=nekf26 face="宋体, SimSun"><span id=nekf27 lang=zh-CN>字节，一个单一</span></font>HDFS<font id=nekf28 face="宋体, SimSun"><span id=nekf29 lang=zh-CN>实例应该能支撑数以千万计的文件。</span></font><br id=nekf30>4<font id=nekf31 face="宋体, SimSun"><span id=nekf32 lang=zh-CN>、 </span></font>HDFS<font id=nekf33 face="宋体, SimSun"><span id=nekf34 lang=zh-CN>应用对文件要求的是</span></font>write-one-read-many<font id=nekf35 face="宋体, SimSun"><span id=nekf36 lang=zh-CN>访问模型。一个文件经过创建、写，关闭之后就不需要改变。这一假设简化了数据一致性问题，使高吞吐量的数据访问成为可能。典型的如</span></font>MapReduce<font id=nekf37 face="宋体, SimSun"><span id=nekf38 lang=zh-CN>框架，或者一个</span></font>web crawler<font id=nekf39 face="宋体, SimSun"><span id=nekf40 lang=zh-CN>应用都很适合这个模型。</span></font><br id=nekf41>5<font id=nekf42 face="宋体, SimSun"><span id=nekf43 lang=zh-CN>、移动计算的代价比之移动数据的代价低。一个应用请求的计算，离它操作的数据越近就越高效，这在数据达到海量级别的时候更是如此。将计算移动到数据附近，比之将数据移动到应用所在显然更好，</span></font>HDFS<font id=nekf44 face="宋体, SimSun"><span id=nekf45 lang=zh-CN>提供给应用这样的接口。</span></font><br id=nekf46>6<font id=nekf47 face="宋体, SimSun"><span id=nekf48 lang=zh-CN>、在异构的软硬件平台间的可移植性。</span></font><br id=nekf49><br id=nekf50><font id=nekf51 face="宋体, SimSun"><span id=nekf52 lang=zh-CN>二、</span></font>Namenode<font id=nekf53 face="宋体, SimSun"><span id=nekf54 lang=zh-CN>和</span></font>Datanode<br id=nekf55>&nbsp;&nbsp;&nbsp; HDFS<font id=nekf56 face="宋体, SimSun"><span id=nekf57 lang=zh-CN>采用</span></font>master/slave<font id=nekf58 face="宋体, SimSun"><span id=nekf59 lang=zh-CN>架构。一个</span></font>HDFS<font id=nekf60 face="宋体, SimSun"><span id=nekf61 lang=zh-CN>集群是有一个</span></font>Namenode<font id=nekf62 face="宋体, SimSun"><span id=nekf63 lang=zh-CN>和一定数目的</span></font>Datanode<font id=nekf64 face="宋体, SimSun"><span id=nekf65 lang=zh-CN>组成。</span></font>Namenode<font id=nekf66 face="宋体, SimSun"><span id=nekf67 lang=zh-CN>是一个中心服务器，负责管理文件系统的</span></font>namespace<font id=nekf68 face="宋体, SimSun"><span id=nekf69 lang=zh-CN>和客户端对文件的访问。</span></font>Datanode<font id=nekf70 face="宋体, SimSun"><span id=nekf71 lang=zh-CN>在集群中一般是一个节点一个，负责管理节点上它们附带的存储。在内部，一个文件其实分成一个或多个</span></font>block<font id=nekf72 face="宋体, SimSun"><span id=nekf73 lang=zh-CN>，这些</span></font>block<font id=nekf74 face="宋体, SimSun"><span id=nekf75 lang=zh-CN>存储在</span></font>Datanode<font id=nekf76 face="宋体, SimSun"><span id=nekf77 lang=zh-CN>集合里。</span></font>Namenode<font id=nekf78 face="宋体, SimSun"><span id=nekf79 lang=zh-CN>执行文件系统的</span></font>namespace<font id=nekf80 face="宋体, SimSun"><span id=nekf81 lang=zh-CN>操作，例如打开、关闭、重命名文件和目录，同时决定</span></font>block<font id=nekf82 face="宋体, SimSun"><span id=nekf83 lang=zh-CN>到具体</span></font>Datanode<font id=nekf84 face="宋体, SimSun"><span id=nekf85 lang=zh-CN>节点的映射。</span></font>Datanode<font id=nekf86 face="宋体, SimSun"><span id=nekf87 lang=zh-CN>在</span></font>Namenode<font id=nekf88 face="宋体, SimSun"><span id=nekf89 lang=zh-CN>的指挥下进行</span></font>block<font id=nekf90 face="宋体, SimSun"><span id=nekf91 lang=zh-CN>的创建、删除和复制。</span></font>Namenode<font id=nekf92 face="宋体, SimSun"><span id=nekf93 lang=zh-CN>和</span></font>Datanode<font id=nekf94 face="宋体, SimSun"><span id=nekf95 lang=zh-CN>都是设计成可以跑在普通的廉价的运行</span></font>linux<font id=nekf96 face="宋体, SimSun"><span id=nekf97 lang=zh-CN>的机器上。</span></font>HDFS<font id=nekf98 face="宋体, SimSun"><span id=nekf99 lang=zh-CN>采用</span></font>java<font id=nekf100 face="宋体, SimSun"><span id=nekf101 lang=zh-CN>语言开发，因此可以部署在很大范围的机器上。一个典型的部署场景是一台机器跑一个单独的</span></font>Namenode<font id=nekf102 face="宋体, SimSun"><span id=nekf103 lang=zh-CN>节点，集群中的其他机器各跑一个</span></font>Datanode<font id=nekf104 face="宋体, SimSun"><span id=nekf105 lang=zh-CN>实例。这个架构并不排除一台机器上跑多个</span></font>Datanode<font id=nekf106 face="宋体, SimSun"><span id=nekf107 lang=zh-CN>，不过这比较少见。</span></font><br id=nekf108><img id=nekf109 border=0 name=graphics1 alt="" align=bottom src="http://docs.google.com/File?id=ddxkntwd_24fwt83gcp_b" width=577 height=394><br id=nekf110><font id=nekf111 face="宋体, SimSun"><span id=nekf112 lang=zh-CN>单一节点的</span></font>Namenode<font id=nekf113 face="宋体, SimSun"><span id=nekf114 lang=zh-CN>大大简化了系统的架构。</span></font>Namenode<font id=nekf115 face="宋体, SimSun"><span id=nekf116 lang=zh-CN>负责保管和管理所有的</span></font>HDFS<font id=nekf117 face="宋体, SimSun"><span id=nekf118 lang=zh-CN>元数据，因而用户数据就不需要通过</span></font>Namenode<font id=nekf119 face="宋体, SimSun"><span id=nekf120 lang=zh-CN>（也就是说文件数据的读写是直接在</span></font>Datanode<font id=nekf121 face="宋体, SimSun"><span id=nekf122 lang=zh-CN>上）。</span></font><br id=nekf123><br id=nekf124><font id=nekf125 face="宋体, SimSun"><span id=nekf126 lang=zh-CN>三、文件系统的</span></font>namespace<br id=nekf127>&nbsp;&nbsp; HDFS<font id=nekf128 face="宋体, SimSun"><span id=nekf129 lang=zh-CN>支持传统的层次型文件组织，与大多数其他文件系统类似，用户可以创建目录，并在其间创建、删除、移动和重命名文件。</span></font>HDFS<font id=nekf130 face="宋体, SimSun"><span id=nekf131 lang=zh-CN>不支持</span></font>user quotas<font id=nekf132 face="宋体, SimSun"><span id=nekf133 lang=zh-CN>和访问权限，也不支持链接（</span></font>link)<font id=nekf134 face="宋体, SimSun"><span id=nekf135 lang=zh-CN>，不过当前的架构并不排除实现这些特性。</span></font>Namenode<font id=nekf136 face="宋体, SimSun"><span id=nekf137 lang=zh-CN>维护文件系统的</span></font>namespace<font id=nekf138 face="宋体, SimSun"><span id=nekf139 lang=zh-CN>，任何对文件系统</span></font>namespace<font id=nekf140 face="宋体, SimSun"><span id=nekf141 lang=zh-CN>和文件属性的修改都将被</span></font>Namenode<font id=nekf142 face="宋体, SimSun"><span id=nekf143 lang=zh-CN>记录下来。应用可以设置</span></font>HDFS<font id=nekf144 face="宋体, SimSun"><span id=nekf145 lang=zh-CN>保存的文件的副本数目，文件副本的数目称为文件的 </span></font>replication<font id=nekf146 face="宋体, SimSun"><span id=nekf147 lang=zh-CN>因子，这个信息也是由</span></font>Namenode<font id=nekf148 face="宋体, SimSun"><span id=nekf149 lang=zh-CN>保存。</span></font><br id=nekf150><br id=nekf151><font id=nekf152 face="宋体, SimSun"><span id=nekf153 lang=zh-CN>四、数据复制</span></font><br id=nekf154>&nbsp;&nbsp;&nbsp; HDFS<font id=nekf155 face="宋体, SimSun"><span id=nekf156 lang=zh-CN>被设计成在一个大集群中可以跨机器地可靠地存储海量的文件。它将每个文件存储成</span></font>block<font id=nekf157 face="宋体, SimSun"><span id=nekf158 lang=zh-CN>序列，除了最后一个</span></font>block<font id=nekf159 face="宋体, SimSun"><span id=nekf160 lang=zh-CN>，所有的</span></font>block<font id=nekf161 face="宋体, SimSun"><span id=nekf162 lang=zh-CN>都是同样的大小。文件的所有</span></font>block<font id=nekf163 face="宋体, SimSun"><span id=nekf164 lang=zh-CN>为了容错都会被复制。每个文件的</span></font>block<font id=nekf165 face="宋体, SimSun"><span id=nekf166 lang=zh-CN>大小和</span></font>replication<font id=nekf167 face="宋体, SimSun"><span id=nekf168 lang=zh-CN>因子都是可配置的。</span></font>Replication<font id=nekf169 face="宋体, SimSun"><span id=nekf170 lang=zh-CN>因子可以在文件创建的时候配置，以后也可以改变。</span></font>HDFS<font id=nekf171 face="宋体, SimSun"><span id=nekf172 lang=zh-CN>中的文件是</span></font>write-one<font id=nekf173 face="宋体, SimSun"><span id=nekf174 lang=zh-CN>，并且严格要求在任何时候只有一个</span></font>writer<font id=nekf175 face="宋体, SimSun"><span id=nekf176 lang=zh-CN>。</span></font>Namenode<font id=nekf177 face="宋体, SimSun"><span id=nekf178 lang=zh-CN>全权管理</span></font>block<font id=nekf179 face="宋体, SimSun"><span id=nekf180 lang=zh-CN>的复制，它周期性地从集群中的每个</span></font>Datanode<font id=nekf181 face="宋体, SimSun"><span id=nekf182 lang=zh-CN>接收心跳包和一个</span></font>Blockreport<font id=nekf183 face="宋体, SimSun"><span id=nekf184 lang=zh-CN>。心跳包的接收表示该</span></font>Datanode<font id=nekf185 face="宋体, SimSun"><span id=nekf186 lang=zh-CN>节点正常工作，而</span></font>Blockreport<font id=nekf187 face="宋体, SimSun"><span id=nekf188 lang=zh-CN>包括了该</span></font>Datanode<font id=nekf189 face="宋体, SimSun"><span id=nekf190 lang=zh-CN>上所有的</span></font>block<font id=nekf191 face="宋体, SimSun"><span id=nekf192 lang=zh-CN>组成的列表。</span></font></p>
<p style="MARGIN-BOTTOM: 0in" id=nekf193 class=western><img id=nekf194 border=0 name=graphics2 alt="" align=bottom src="http://docs.google.com/File?id=ddxkntwd_25gq43fcg4_b" width=576 height=352><br id=nekf195>1<font id=nekf196 face="宋体, SimSun"><span id=nekf197 lang=zh-CN>、副本的存放，副本的存放是</span></font>HDFS<font id=nekf198 face="宋体, SimSun"><span id=nekf199 lang=zh-CN>可靠性和性能的关键。</span></font>HDFS<font id=nekf200 face="宋体, SimSun"><span id=nekf201 lang=zh-CN>采用一种称为</span></font>rack-aware<font id=nekf202 face="宋体, SimSun"><span id=nekf203 lang=zh-CN>的策略来改进数据的可靠性、有效性和网络带宽的利用。这个策略实现的短期目标是验证在生产环境下的表现，观察它的行为，构建测试和研究的基础，以便实现更先进的策略。庞大的</span></font>HDFS<font id=nekf204 face="宋体, SimSun"><span id=nekf205 lang=zh-CN>实例一般运行在多个机架的计算机形成的集群上，不同机架间的两台机器的通讯需要通过交换机，显然通常情况下，同一个机架内的两个节点间的带宽会比不同机架间的两台机器的带宽大。</span></font><br id=nekf206>&nbsp;&nbsp;&nbsp; <font id=nekf207 face="宋体, SimSun"><span id=nekf208 lang=zh-CN>通过一个称为</span></font>Rack Awareness<font id=nekf209 face="宋体, SimSun"><span id=nekf210 lang=zh-CN>的过程，</span></font>Namenode<font id=nekf211 face="宋体, SimSun"><span id=nekf212 lang=zh-CN>决定了每个</span></font>Datanode<font id=nekf213 face="宋体, SimSun"><span id=nekf214 lang=zh-CN>所属的</span></font>rack id<font id=nekf215 face="宋体, SimSun"><span id=nekf216 lang=zh-CN>。一个简单但没有优化的策略就是将副本存放在单独的机架上。这样可以防止整个机架（非副本存放）失效的情况，并且允许读数据的时候可以从多个机架读取。这个简单策略设置可以将副本分布在集群中，有利于组件失败情况下的负载均衡。但是，这个简单策略加大了写的代价，因为一个写操作需要传输</span></font>block<font id=nekf217 face="宋体, SimSun"><span id=nekf218 lang=zh-CN>到多个机架。</span></font><br id=nekf219>&nbsp;&nbsp;&nbsp; <font id=nekf220 face="宋体, SimSun"><span id=nekf221 lang=zh-CN>在大多数情况下，</span></font>replication<font id=nekf222 face="宋体, SimSun"><span id=nekf223 lang=zh-CN>因子是</span></font>3<font id=nekf224 face="宋体, SimSun"><span id=nekf225 lang=zh-CN>，</span></font>HDFS<font id=nekf226 face="宋体, SimSun"><span id=nekf227 lang=zh-CN>的存放策略是将一个副本存放在本地机架上的节点，一个副本放在同一机架上的另一个节点，最后一个副本放在不同机架上的一个节点。机架的错误远远比节点的错误少，这个策略不会影响到数据的可靠性和有效性。三分之一的副本在一个节点上，三分之二在一个机架上，其他保存在剩下的机架中，这一策略改进了写的性能。</span></font><br id=nekf228><br id=nekf229>2<font id=nekf230 face="宋体, SimSun"><span id=nekf231 lang=zh-CN>、副本的选择，为了降低整体的带宽消耗和读延时，</span></font>HDFS<font id=nekf232 face="宋体, SimSun"><span id=nekf233 lang=zh-CN>会尽量让</span></font>reader<font id=nekf234 face="宋体, SimSun"><span id=nekf235 lang=zh-CN>读最近的副本。如果在</span></font>reader<font id=nekf236 face="宋体, SimSun"><span id=nekf237 lang=zh-CN>的同一个机架上有一个副本，那么就读该副本。如果一个</span></font>HDFS<font id=nekf238 face="宋体, SimSun"><span id=nekf239 lang=zh-CN>集群跨越多个数据中心，那么</span></font>reader<font id=nekf240 face="宋体, SimSun"><span id=nekf241 lang=zh-CN>也将首先尝试读本地数据中心的副本。</span></font><br id=nekf242><br id=nekf243>3<font id=nekf244 face="宋体, SimSun"><span id=nekf245 lang=zh-CN>、</span></font>SafeMode<br id=nekf246>&nbsp;&nbsp;&nbsp; Namenode<font id=nekf247 face="宋体, SimSun"><span id=nekf248 lang=zh-CN>启动后会进入一个称为</span></font>SafeMode<font id=nekf249 face="宋体, SimSun"><span id=nekf250 lang=zh-CN>的特殊状态，处在这个状态的</span></font>Namenode<font id=nekf251 face="宋体, SimSun"><span id=nekf252 lang=zh-CN>是不会进行数据块的复制的。</span></font>Namenode<font id=nekf253 face="宋体, SimSun"><span id=nekf254 lang=zh-CN>从所有的 </span></font>Datanode<font id=nekf255 face="宋体, SimSun"><span id=nekf256 lang=zh-CN>接收心跳包和</span></font>Blockreport<font id=nekf257 face="宋体, SimSun"><span id=nekf258 lang=zh-CN>。</span></font>Blockreport<font id=nekf259 face="宋体, SimSun"><span id=nekf260 lang=zh-CN>包括了某个</span></font>Datanode<font id=nekf261 face="宋体, SimSun"><span id=nekf262 lang=zh-CN>所有的数据块列表。每个</span></font>block<font id=nekf263 face="宋体, SimSun"><span id=nekf264 lang=zh-CN>都有指定的最小数目的副本。当</span></font>Namenode<font id=nekf265 face="宋体, SimSun"><span id=nekf266 lang=zh-CN>检测确认某个</span></font>Datanode<font id=nekf267 face="宋体, SimSun"><span id=nekf268 lang=zh-CN>的数据块副本的最小数目，那么该</span></font>Datanode<font id=nekf269 face="宋体, SimSun"><span id=nekf270 lang=zh-CN>就会被认为是安全的；如果一定百分比（这个参数可配置）的数据块检测确认是安全的，那么</span></font>Namenode<font id=nekf271 face="宋体, SimSun"><span id=nekf272 lang=zh-CN>将退出</span></font>SafeMode<font id=nekf273 face="宋体, SimSun"><span id=nekf274 lang=zh-CN>状态，接下来它会确定还有哪些数据块的副本没有达到指定数目，并将这些</span></font>block<font id=nekf275 face="宋体, SimSun"><span id=nekf276 lang=zh-CN>复制到其他</span></font>Datanode<font id=nekf277 face="宋体, SimSun"><span id=nekf278 lang=zh-CN>。</span></font><br id=nekf279><br id=nekf280><font id=nekf281 face="宋体, SimSun"><span id=nekf282 lang=zh-CN>五、文件系统元数据的持久化</span></font><br id=nekf283>&nbsp;&nbsp;&nbsp; Namenode<font id=nekf284 face="宋体, SimSun"><span id=nekf285 lang=zh-CN>存储</span></font>HDFS<font id=nekf286 face="宋体, SimSun"><span id=nekf287 lang=zh-CN>的元数据。对于任何对文件元数据产生修改的操作，</span></font>Namenode<font id=nekf288 face="宋体, SimSun"><span id=nekf289 lang=zh-CN>都使用一个称为</span></font>Editlog<font id=nekf290 face="宋体, SimSun"><span id=nekf291 lang=zh-CN>的事务日志记录下来。例如，在</span></font>HDFS<font id=nekf292 face="宋体, SimSun"><span id=nekf293 lang=zh-CN>中创建一个文件，</span></font>Namenode<font id=nekf294 face="宋体, SimSun"><span id=nekf295 lang=zh-CN>就会在</span></font>Editlog<font id=nekf296 face="宋体, SimSun"><span id=nekf297 lang=zh-CN>中插入一条记录来表示；同样，修改文件的</span></font>replication<font id=nekf298 face="宋体, SimSun"><span id=nekf299 lang=zh-CN>因子也将往 </span></font>Editlog<font id=nekf300 face="宋体, SimSun"><span id=nekf301 lang=zh-CN>插入一条记录。</span></font>Namenode<font id=nekf302 face="宋体, SimSun"><span id=nekf303 lang=zh-CN>在本地</span></font>OS<font id=nekf304 face="宋体, SimSun"><span id=nekf305 lang=zh-CN>的文件系统中存储这个</span></font>Editlog<font id=nekf306 face="宋体, SimSun"><span id=nekf307 lang=zh-CN>。整个文件系统的</span></font>namespace<font id=nekf308 face="宋体, SimSun"><span id=nekf309 lang=zh-CN>，包括</span></font>block<font id=nekf310 face="宋体, SimSun"><span id=nekf311 lang=zh-CN>到文件的映射、文件的属性，都存储在称为</span></font>FsImage<font id=nekf312 face="宋体, SimSun"><span id=nekf313 lang=zh-CN>的文件中，这个文件也是放在</span></font>Namenode<font id=nekf314 face="宋体, SimSun"><span id=nekf315 lang=zh-CN>所在系统的文件系统上。</span></font><br id=nekf316>&nbsp;&nbsp;&nbsp; Namenode<font id=nekf317 face="宋体, SimSun"><span id=nekf318 lang=zh-CN>在内存中保存着整个文件系统</span></font>namespace<font id=nekf319 face="宋体, SimSun"><span id=nekf320 lang=zh-CN>和文件</span></font>Blockmap<font id=nekf321 face="宋体, SimSun"><span id=nekf322 lang=zh-CN>的映像。这个关键的元数据设计得很紧凑，因而一个带有</span></font>4G<font id=nekf323 face="宋体, SimSun"><span id=nekf324 lang=zh-CN>内存的 </span></font>Namenode<font id=nekf325 face="宋体, SimSun"><span id=nekf326 lang=zh-CN>足够支撑海量的文件和目录。当</span></font>Namenode<font id=nekf327 face="宋体, SimSun"><span id=nekf328 lang=zh-CN>启动时，它从硬盘中读取</span></font>Editlog<font id=nekf329 face="宋体, SimSun"><span id=nekf330 lang=zh-CN>和</span></font>FsImage<font id=nekf331 face="宋体, SimSun"><span id=nekf332 lang=zh-CN>，将所有</span></font>Editlog<font id=nekf333 face="宋体, SimSun"><span id=nekf334 lang=zh-CN>中的事务作用（</span></font>apply)<font id=nekf335 face="宋体, SimSun"><span id=nekf336 lang=zh-CN>在内存中的</span></font>FsImage <font id=nekf337 face="宋体, SimSun"><span id=nekf338 lang=zh-CN>，并将这个新版本的</span></font>FsImage<font id=nekf339 face="宋体, SimSun"><span id=nekf340 lang=zh-CN>从内存中</span></font>flush<font id=nekf341 face="宋体, SimSun"><span id=nekf342 lang=zh-CN>到硬盘上</span></font>,<font id=nekf343 face="宋体, SimSun"><span id=nekf344 lang=zh-CN>然后再</span></font>truncate<font id=nekf345 face="宋体, SimSun"><span id=nekf346 lang=zh-CN>这个旧的</span></font>Editlog<font id=nekf347 face="宋体, SimSun"><span id=nekf348 lang=zh-CN>，因为这个旧的</span></font>Editlog<font id=nekf349 face="宋体, SimSun"><span id=nekf350 lang=zh-CN>的事务都已经作用在</span></font>FsImage<font id=nekf351 face="宋体, SimSun"><span id=nekf352 lang=zh-CN>上了。这个过程称为</span></font>checkpoint<font id=nekf353 face="宋体, SimSun"><span id=nekf354 lang=zh-CN>。在当前实现中，</span></font>checkpoint<font id=nekf355 face="宋体, SimSun"><span id=nekf356 lang=zh-CN>只发生在</span></font>Namenode<font id=nekf357 face="宋体, SimSun"><span id=nekf358 lang=zh-CN>启动时，在不久的将来我们将实现支持周期性的</span></font>checkpoint<font id=nekf359 face="宋体, SimSun"><span id=nekf360 lang=zh-CN>。</span></font><br id=nekf361>&nbsp;&nbsp;&nbsp; Datanode<font id=nekf362 face="宋体, SimSun"><span id=nekf363 lang=zh-CN>并不知道关于文件的任何东西，除了将文件中的数据保存在本地的文件系统上。它把每个</span></font>HDFS<font id=nekf364 face="宋体, SimSun"><span id=nekf365 lang=zh-CN>数据块存储在本地文件系统上隔离的文件中。 </span></font>Datanode<font id=nekf366 face="宋体, SimSun"><span id=nekf367 lang=zh-CN>并不在同一个目录创建所有的文件，相反，它用启发式地方法来确定每个目录的最佳文件数目，并且在适当的时候创建子目录。在同一个目录创建所有的文件不是最优的选择，因为本地文件系统可能无法高效地在单一目录中支持大量的文件。当一个</span></font>Datanode<font id=nekf368 face="宋体, SimSun"><span id=nekf369 lang=zh-CN>启动时，它扫描本地文件系统，对这些本地文件产生相应的一个所有</span></font>HDFS<font id=nekf370 face="宋体, SimSun"><span id=nekf371 lang=zh-CN>数据块的列表，然后发送报告到</span></font>Namenode<font id=nekf372 face="宋体, SimSun"><span id=nekf373 lang=zh-CN>，这个报告就是</span></font>Blockreport<font id=nekf374 face="宋体, SimSun"><span id=nekf375 lang=zh-CN>。</span></font><br id=nekf376><br id=nekf377><font id=nekf378 face="宋体, SimSun"><span id=nekf379 lang=zh-CN>六、通讯协议</span></font><br id=nekf380>&nbsp;&nbsp;&nbsp; <font id=nekf381 face="宋体, SimSun"><span id=nekf382 lang=zh-CN>所有的</span></font>HDFS<font id=nekf383 face="宋体, SimSun"><span id=nekf384 lang=zh-CN>通讯协议都是构建在</span></font>TCP/IP<font id=nekf385 face="宋体, SimSun"><span id=nekf386 lang=zh-CN>协议上。客户端通过一个可配置的端口连接到</span></font>Namenode<font id=nekf387 face="宋体, SimSun"><span id=nekf388 lang=zh-CN>，通过</span></font>ClientProtocol<font id=nekf389 face="宋体, SimSun"><span id=nekf390 lang=zh-CN>与 </span></font>Namenode<font id=nekf391 face="宋体, SimSun"><span id=nekf392 lang=zh-CN>交互。而</span></font>Datanode<font id=nekf393 face="宋体, SimSun"><span id=nekf394 lang=zh-CN>是使用</span></font>DatanodeProtocol<font id=nekf395 face="宋体, SimSun"><span id=nekf396 lang=zh-CN>与</span></font>Namenode<font id=nekf397 face="宋体, SimSun"><span id=nekf398 lang=zh-CN>交互。从</span></font>ClientProtocol<font id=nekf399 face="宋体, SimSun"><span id=nekf400 lang=zh-CN>和 </span></font>Datanodeprotocol<font id=nekf401 face="宋体, SimSun"><span id=nekf402 lang=zh-CN>抽象出一个远程调用</span></font>(RPC<font id=nekf403 face="宋体, SimSun"><span id=nekf404 lang=zh-CN>），在设计上，</span></font>Namenode<font id=nekf405 face="宋体, SimSun"><span id=nekf406 lang=zh-CN>不会主动发起</span></font>RPC<font id=nekf407 face="宋体, SimSun"><span id=nekf408 lang=zh-CN>，而是是响应来自客户端和 </span></font>Datanode <font id=nekf409 face="宋体, SimSun"><span id=nekf410 lang=zh-CN>的</span></font>RPC<font id=nekf411 face="宋体, SimSun"><span id=nekf412 lang=zh-CN>请求。</span></font><br id=nekf413><br id=nekf414><font id=nekf415 face="宋体, SimSun"><span id=nekf416 lang=zh-CN>七、健壮性</span></font><br id=nekf417>&nbsp;&nbsp;&nbsp; HDFS<font id=nekf418 face="宋体, SimSun"><span id=nekf419 lang=zh-CN>的主要目标就是实现在失败情况下的数据存储可靠性。常见的三种失败：</span></font>Namenode failures, Datanode failures<font id=nekf420 face="宋体, SimSun"><span id=nekf421 lang=zh-CN>和网络分割（</span></font>network partitions)<font id=nekf422 face="宋体, SimSun"><span id=nekf423 lang=zh-CN>。</span></font><br id=nekf424>1<font id=nekf425 face="宋体, SimSun"><span id=nekf426 lang=zh-CN>、硬盘数据错误、心跳检测和重新复制</span></font><br id=nekf427>&nbsp;&nbsp;&nbsp; <font id=nekf428 face="宋体, SimSun"><span id=nekf429 lang=zh-CN>每个</span></font>Datanode<font id=nekf430 face="宋体, SimSun"><span id=nekf431 lang=zh-CN>节点都向</span></font>Namenode<font id=nekf432 face="宋体, SimSun"><span id=nekf433 lang=zh-CN>周期性地发送心跳包。网络切割可能导致一部分</span></font>Datanode<font id=nekf434 face="宋体, SimSun"><span id=nekf435 lang=zh-CN>跟</span></font>Namenode<font id=nekf436 face="宋体, SimSun"><span id=nekf437 lang=zh-CN>失去联系。 </span></font>Namenode<font id=nekf438 face="宋体, SimSun"><span id=nekf439 lang=zh-CN>通过心跳包的缺失检测到这一情况，并将这些</span></font>Datanode<font id=nekf440 face="宋体, SimSun"><span id=nekf441 lang=zh-CN>标记为</span></font>dead<font id=nekf442 face="宋体, SimSun"><span id=nekf443 lang=zh-CN>，不会将新的</span></font>IO<font id=nekf444 face="宋体, SimSun"><span id=nekf445 lang=zh-CN>请求发给它们。寄存在</span></font>dead Datanode<font id=nekf446 face="宋体, SimSun"><span id=nekf447 lang=zh-CN>上的任何数据将不再有效。</span></font>Datanode<font id=nekf448 face="宋体, SimSun"><span id=nekf449 lang=zh-CN>的死亡可能引起一些</span></font>block<font id=nekf450 face="宋体, SimSun"><span id=nekf451 lang=zh-CN>的副本数目低于指定值，</span></font>Namenode<font id=nekf452 face="宋体, SimSun"><span id=nekf453 lang=zh-CN>不断地跟踪需要复制的 </span></font>block<font id=nekf454 face="宋体, SimSun"><span id=nekf455 lang=zh-CN>，在任何需要的情况下启动复制。在下列情况可能需要重新复制：某个</span></font>Datanode<font id=nekf456 face="宋体, SimSun"><span id=nekf457 lang=zh-CN>节点失效，某个副本遭到损坏，</span></font>Datanode<font id=nekf458 face="宋体, SimSun"><span id=nekf459 lang=zh-CN>上的硬盘错误，或者文件的</span></font>replication<font id=nekf460 face="宋体, SimSun"><span id=nekf461 lang=zh-CN>因子增大。</span></font><br id=nekf462><br id=nekf463>2<font id=nekf464 face="宋体, SimSun"><span id=nekf465 lang=zh-CN>、集群均衡</span></font><br id=nekf466>&nbsp;&nbsp; HDFS<font id=nekf467 face="宋体, SimSun"><span id=nekf468 lang=zh-CN>支持数据的均衡计划，如果某个</span></font>Datanode<font id=nekf469 face="宋体, SimSun"><span id=nekf470 lang=zh-CN>节点上的空闲空间低于特定的临界点，那么就会启动一个计划自动地将数据从一个</span></font>Datanode<font id=nekf471 face="宋体, SimSun"><span id=nekf472 lang=zh-CN>搬移到空闲的</span></font>Datanode<font id=nekf473 face="宋体, SimSun"><span id=nekf474 lang=zh-CN>。当对某个文件的请求突然增加，那么也可能启动一个计划创建该文件新的副本，并分布到集群中以满足应用的要求。这些均衡计划目前还没有实现。</span></font><br id=nekf475><br id=nekf476>3<font id=nekf477 face="宋体, SimSun"><span id=nekf478 lang=zh-CN>、数据完整性</span></font><br id=nekf479>&nbsp; <font id=nekf480 face="宋体, SimSun"><span id=nekf481 lang=zh-CN>从某个</span></font>Datanode<font id=nekf482 face="宋体, SimSun"><span id=nekf483 lang=zh-CN>获取的数据块有可能是损坏的，这个损坏可能是由于</span></font>Datanode<font id=nekf484 face="宋体, SimSun"><span id=nekf485 lang=zh-CN>的存储设备错误、网络错误或者软件</span></font>bug<font id=nekf486 face="宋体, SimSun"><span id=nekf487 lang=zh-CN>造成的。</span></font>HDFS<font id=nekf488 face="宋体, SimSun"><span id=nekf489 lang=zh-CN>客户端软件实现了</span></font>HDFS<font id=nekf490 face="宋体, SimSun"><span id=nekf491 lang=zh-CN>文件内容的校验和。当某个客户端创建一个新的</span></font>HDFS<font id=nekf492 face="宋体, SimSun"><span id=nekf493 lang=zh-CN>文件，会计算这个文件每个</span></font>block<font id=nekf494 face="宋体, SimSun"><span id=nekf495 lang=zh-CN>的校验和，并作为一个单独的隐藏文件保存这些校验和在同一个</span></font>HDFS namespace<font id=nekf496 face="宋体, SimSun"><span id=nekf497 lang=zh-CN>下。当客户端检索文件内容，它会确认从</span></font>Datanode<font id=nekf498 face="宋体, SimSun"><span id=nekf499 lang=zh-CN>获取的数据跟相应的校验和文件中的校验和是否匹配，如果不匹配，客户端可以选择从其他</span></font>Datanode<font id=nekf500 face="宋体, SimSun"><span id=nekf501 lang=zh-CN>获取该</span></font>block<font id=nekf502 face="宋体, SimSun"><span id=nekf503 lang=zh-CN>的副本。</span></font><br id=nekf504><br id=nekf505>4<font id=nekf506 face="宋体, SimSun"><span id=nekf507 lang=zh-CN>、元数据磁盘错误</span></font><br id=nekf508>&nbsp;&nbsp;&nbsp; FsImage<font id=nekf509 face="宋体, SimSun"><span id=nekf510 lang=zh-CN>和</span></font>Editlog<font id=nekf511 face="宋体, SimSun"><span id=nekf512 lang=zh-CN>是</span></font>HDFS<font id=nekf513 face="宋体, SimSun"><span id=nekf514 lang=zh-CN>的核心数据结构。这些文件如果损坏了，整个</span></font>HDFS<font id=nekf515 face="宋体, SimSun"><span id=nekf516 lang=zh-CN>实例都将失效。因而，</span></font>Namenode<font id=nekf517 face="宋体, SimSun"><span id=nekf518 lang=zh-CN>可以配置成支持维护多个</span></font>FsImage<font id=nekf519 face="宋体, SimSun"><span id=nekf520 lang=zh-CN>和</span></font>Editlog<font id=nekf521 face="宋体, SimSun"><span id=nekf522 lang=zh-CN>的拷贝。任何对</span></font>FsImage<font id=nekf523 face="宋体, SimSun"><span id=nekf524 lang=zh-CN>或者</span></font>Editlog<font id=nekf525 face="宋体, SimSun"><span id=nekf526 lang=zh-CN>的修改，都将同步到它们的副本上。这个同步操作可能会降低 </span></font>Namenode<font id=nekf527 face="宋体, SimSun"><span id=nekf528 lang=zh-CN>每秒能支持处理的</span></font>namespace<font id=nekf529 face="宋体, SimSun"><span id=nekf530 lang=zh-CN>事务。这个代价是可以接受的，因为</span></font>HDFS<font id=nekf531 face="宋体, SimSun"><span id=nekf532 lang=zh-CN>是数据密集的，而非元数据密集。当</span></font>Namenode<font id=nekf533 face="宋体, SimSun"><span id=nekf534 lang=zh-CN>重启的时候，它总是选取最近的一致的</span></font>FsImage<font id=nekf535 face="宋体, SimSun"><span id=nekf536 lang=zh-CN>和</span></font>Editlog<font id=nekf537 face="宋体, SimSun"><span id=nekf538 lang=zh-CN>使用。</span></font><br id=nekf539>&nbsp;&nbsp; Namenode<font id=nekf540 face="宋体, SimSun"><span id=nekf541 lang=zh-CN>在</span></font>HDFS<font id=nekf542 face="宋体, SimSun"><span id=nekf543 lang=zh-CN>是单点存在，如果</span></font>Namenode<font id=nekf544 face="宋体, SimSun"><span id=nekf545 lang=zh-CN>所在的机器错误，手工的干预是必须的。目前，在另一台机器上重启因故障而停止服务的</span></font>Namenode<font id=nekf546 face="宋体, SimSun"><span id=nekf547 lang=zh-CN>这个功能还没实现。</span></font><br id=nekf548><br id=nekf549>5<font id=nekf550 face="宋体, SimSun"><span id=nekf551 lang=zh-CN>、快照</span></font><br id=nekf552>&nbsp;&nbsp; <font id=nekf553 face="宋体, SimSun"><span id=nekf554 lang=zh-CN>快照支持某个时间的数据拷贝，当</span></font>HDFS<font id=nekf555 face="宋体, SimSun"><span id=nekf556 lang=zh-CN>数据损坏的时候，可以恢复到过去一个已知正确的时间点。</span></font>HDFS<font id=nekf557 face="宋体, SimSun"><span id=nekf558 lang=zh-CN>目前还不支持快照功能。</span></font><br id=nekf559><br id=nekf560><font id=nekf561 face="宋体, SimSun"><span id=nekf562 lang=zh-CN>八、数据组织</span></font><br id=nekf563>1<font id=nekf564 face="宋体, SimSun"><span id=nekf565 lang=zh-CN>、数据块</span></font><br id=nekf566>&nbsp;&nbsp;&nbsp; <font id=nekf567 face="宋体, SimSun"><span id=nekf568 lang=zh-CN>兼容</span></font>HDFS<font id=nekf569 face="宋体, SimSun"><span id=nekf570 lang=zh-CN>的应用都是处理大数据集合的。这些应用都是写数据一次，读却是一次到多次，并且读的速度要满足流式读。</span></font>HDFS<font id=nekf571 face="宋体, SimSun"><span id=nekf572 lang=zh-CN>支持文件的</span></font>write- once-read-many<font id=nekf573 face="宋体, SimSun"><span id=nekf574 lang=zh-CN>语义。一个典型的</span></font>block<font id=nekf575 face="宋体, SimSun"><span id=nekf576 lang=zh-CN>大小是</span></font>64MB<font id=nekf577 face="宋体, SimSun"><span id=nekf578 lang=zh-CN>，因而，文件总是按照</span></font>64M<font id=nekf579 face="宋体, SimSun"><span id=nekf580 lang=zh-CN>切分成</span></font>chunk<font id=nekf581 face="宋体, SimSun"><span id=nekf582 lang=zh-CN>，每个</span></font>chunk<font id=nekf583 face="宋体, SimSun"><span id=nekf584 lang=zh-CN>存储于不同的 </span></font>Datanode<br id=nekf585>2<font id=nekf586 face="宋体, SimSun"><span id=nekf587 lang=zh-CN>、步骤</span></font><br id=nekf588>&nbsp;&nbsp;&nbsp; <font id=nekf589 face="宋体, SimSun"><span id=nekf590 lang=zh-CN>某个客户端创建文件的请求其实并没有立即发给</span></font>Namenode<font id=nekf591 face="宋体, SimSun"><span id=nekf592 lang=zh-CN>，事实上，</span></font>HDFS<font id=nekf593 face="宋体, SimSun"><span id=nekf594 lang=zh-CN>客户端会将文件数据缓存到本地的一个临时文件。应用的写被透明地重定向到这个临时文件。当这个临时文件累积的数据超过一个</span></font>block<font id=nekf595 face="宋体, SimSun"><span id=nekf596 lang=zh-CN>的大小（默认</span></font>64M)<font id=nekf597 face="宋体, SimSun"><span id=nekf598 lang=zh-CN>，客户端才会联系</span></font>Namenode<font id=nekf599 face="宋体, SimSun"><span id=nekf600 lang=zh-CN>。</span></font>Namenode<font id=nekf601 face="宋体, SimSun"><span id=nekf602 lang=zh-CN>将文件名插入文件系统的层次结构中，并且分配一个数据块给它，然后返回</span></font>Datanode<font id=nekf603 face="宋体, SimSun"><span id=nekf604 lang=zh-CN>的标识符和目标数据块给客户端。客户端将本地临时文件</span></font>flush<font id=nekf605 face="宋体, SimSun"><span id=nekf606 lang=zh-CN>到指定的 </span></font>Datanode<font id=nekf607 face="宋体, SimSun"><span id=nekf608 lang=zh-CN>上。当文件关闭时，在临时文件中剩余的没有</span></font>flush<font id=nekf609 face="宋体, SimSun"><span id=nekf610 lang=zh-CN>的数据也会传输到指定的</span></font>Datanode<font id=nekf611 face="宋体, SimSun"><span id=nekf612 lang=zh-CN>，然后客户端告诉</span></font>Namenode<font id=nekf613 face="宋体, SimSun"><span id=nekf614 lang=zh-CN>文件已经关闭。此时</span></font>Namenode<font id=nekf615 face="宋体, SimSun"><span id=nekf616 lang=zh-CN>才将文件创建操作提交到持久存储。如果</span></font>Namenode<font id=nekf617 face="宋体, SimSun"><span id=nekf618 lang=zh-CN>在文件关闭前挂了，该文件将丢失。</span></font><br id=nekf619>&nbsp;&nbsp; <font id=nekf620 face="宋体, SimSun"><span id=nekf621 lang=zh-CN>上述方法是对通过对</span></font>HDFS<font id=nekf622 face="宋体, SimSun"><span id=nekf623 lang=zh-CN>上运行的目标应用认真考虑的结果。如果不采用客户端缓存，由于网络速度和网络堵塞会对吞估量造成比较大的影响。</span></font><br id=nekf624><br id=nekf625>3<font id=nekf626 face="宋体, SimSun"><span id=nekf627 lang=zh-CN>、流水线复制</span></font><br id=nekf628>&nbsp;&nbsp;&nbsp; <font id=nekf629 face="宋体, SimSun"><span id=nekf630 lang=zh-CN>当某个客户端向</span></font>HDFS<font id=nekf631 face="宋体, SimSun"><span id=nekf632 lang=zh-CN>文件写数据的时候，一开始是写入本地临时文件，假设该文件的</span></font>replication<font id=nekf633 face="宋体, SimSun"><span id=nekf634 lang=zh-CN>因子设置为</span></font>3<font id=nekf635 face="宋体, SimSun"><span id=nekf636 lang=zh-CN>，那么客户端会从</span></font>Namenode <font id=nekf637 face="宋体, SimSun"><span id=nekf638 lang=zh-CN>获取一张</span></font>Datanode<font id=nekf639 face="宋体, SimSun"><span id=nekf640 lang=zh-CN>列表来存放副本。然后客户端开始向第一个</span></font>Datanode<font id=nekf641 face="宋体, SimSun"><span id=nekf642 lang=zh-CN>传输数据，第一个</span></font>Datanode<font id=nekf643 face="宋体, SimSun"><span id=nekf644 lang=zh-CN>一小部分一小部分（</span></font>4kb)<font id=nekf645 face="宋体, SimSun"><span id=nekf646 lang=zh-CN>地接收数据，将每个部分写入本地仓库，并且同时传输该部分到第二个</span></font>Datanode<font id=nekf647 face="宋体, SimSun"><span id=nekf648 lang=zh-CN>节点。第二个</span></font>Datanode<font id=nekf649 face="宋体, SimSun"><span id=nekf650 lang=zh-CN>也是这样，边收边传，一小部分一小部分地收，存储在本地仓库，同时传给第三个</span></font>Datanode<font id=nekf651 face="宋体, SimSun"><span id=nekf652 lang=zh-CN>，第三个</span></font>Datanode<font id=nekf653 face="宋体, SimSun"><span id=nekf654 lang=zh-CN>就仅仅是接收并存储了。这就是流水线式的复制。</span></font><br id=nekf655><br id=nekf656><font id=nekf657 face="宋体, SimSun"><span id=nekf658 lang=zh-CN>九、可访问性</span></font><br id=nekf659>&nbsp;&nbsp;&nbsp; HDFS<font id=nekf660 face="宋体, SimSun"><span id=nekf661 lang=zh-CN>给应用提供了多种访问方式，可以通过</span></font>DFSShell<font id=nekf662 face="宋体, SimSun"><span id=nekf663 lang=zh-CN>通过命令行与</span></font>HDFS<font id=nekf664 face="宋体, SimSun"><span id=nekf665 lang=zh-CN>数据进行交互，可以通过</span></font>java API<font id=nekf666 face="宋体, SimSun"><span id=nekf667 lang=zh-CN>调用，也可以通过</span></font>C<font id=nekf668 face="宋体, SimSun"><span id=nekf669 lang=zh-CN>语言的封装</span></font>API<font id=nekf670 face="宋体, SimSun"><span id=nekf671 lang=zh-CN>访问，并且提供了浏览器访问的方式。正在开发通过</span></font>WebDav<font id=nekf672 face="宋体, SimSun"><span id=nekf673 lang=zh-CN>协议访问的方式。具体使用参考文档。</span></font><br id=nekf674><font id=nekf675 face="宋体, SimSun"><span id=nekf676 lang=zh-CN>十、空间的回收</span></font><br id=nekf677>1<font id=nekf678 face="宋体, SimSun"><span id=nekf679 lang=zh-CN>、文件的删除和恢复</span></font><br id=nekf680>&nbsp;&nbsp;&nbsp; <font id=nekf681 face="宋体, SimSun"><span id=nekf682 lang=zh-CN>用户或者应用删除某个文件，这个文件并没有立刻从</span></font>HDFS<font id=nekf683 face="宋体, SimSun"><span id=nekf684 lang=zh-CN>中删除。相反，</span></font>HDFS<font id=nekf685 face="宋体, SimSun"><span id=nekf686 lang=zh-CN>将这个文件重命名，并转移到</span></font>/trash<font id=nekf687 face="宋体, SimSun"><span id=nekf688 lang=zh-CN>目录。当文件还在</span></font>/trash<font id=nekf689 face="宋体, SimSun"><span id=nekf690 lang=zh-CN>目录时，该文件可以被迅速地恢复。文件在</span></font>/trash<font id=nekf691 face="宋体, SimSun"><span id=nekf692 lang=zh-CN>中保存的时间是可配置的，当超过这个时间，</span></font>Namenode<font id=nekf693 face="宋体, SimSun"><span id=nekf694 lang=zh-CN>就会将该文件从</span></font>namespace<font id=nekf695 face="宋体, SimSun"><span id=nekf696 lang=zh-CN>中删除。文件的删除，也将释放关联该文件的数据块。注意到，在文件被用户删除和</span></font>HDFS<font id=nekf697 face="宋体, SimSun"><span id=nekf698 lang=zh-CN>空闲空间的增加之间会有一个等待时间延迟。</span></font><br id=nekf699>&nbsp;&nbsp;&nbsp; <font id=nekf700 face="宋体, SimSun"><span id=nekf701 lang=zh-CN>当被删除的文件还保留在</span></font>/trash<font id=nekf702 face="宋体, SimSun"><span id=nekf703 lang=zh-CN>目录中的时候，如果用户想恢复这个文件，可以检索浏览</span></font>/trash<font id=nekf704 face="宋体, SimSun"><span id=nekf705 lang=zh-CN>目录并检索该文件。</span></font>/trash<font id=nekf706 face="宋体, SimSun"><span id=nekf707 lang=zh-CN>目录仅仅保存被删除文件的最近一次拷贝。</span></font>/trash<font id=nekf708 face="宋体, SimSun"><span id=nekf709 lang=zh-CN>目录与其他文件目录没有什么不同，除了一点：</span></font>HDFS<font id=nekf710 face="宋体, SimSun"><span id=nekf711 lang=zh-CN>在该目录上应用了一个特殊的策略来自动删除文件，目前的默认策略是删除保留超过</span></font>6<font id=nekf712 face="宋体, SimSun"><span id=nekf713 lang=zh-CN>小时的文件，这个策略以后会定义成可配置的接口。</span></font><br id=nekf714><br id=nekf715>2<font id=nekf716 face="宋体, SimSun"><span id=nekf717 lang=zh-CN>、</span></font>Replication<font id=nekf718 face="宋体, SimSun"><span id=nekf719 lang=zh-CN>因子的减小</span></font><br id=nekf720>&nbsp;&nbsp;&nbsp; <font id=nekf721 face="宋体, SimSun"><span id=nekf722 lang=zh-CN>当某个文件的</span></font>replication<font id=nekf723 face="宋体, SimSun"><span id=nekf724 lang=zh-CN>因子减小，</span></font>Namenode<font id=nekf725 face="宋体, SimSun"><span id=nekf726 lang=zh-CN>会选择要删除的过剩的副本。下次心跳检测就将该信息传递给</span></font>Datanode<font id=nekf727 face="宋体, SimSun"><span id=nekf728 lang=zh-CN>， </span></font>Datanode<font id=nekf729 face="宋体, SimSun"><span id=nekf730 lang=zh-CN>就会移除相应的</span></font>block<font id=nekf731 face="宋体, SimSun"><span id=nekf732 lang=zh-CN>并释放空间，同样，在调用</span></font>setReplication<font id=nekf733 face="宋体, SimSun"><span id=nekf734 lang=zh-CN>方法和集群中的空闲空间增加之间会有一个时间延迟。</span></font><br id=nekf735><br id=nekf736><font id=nekf737 face="宋体, SimSun"><span id=nekf738 lang=zh-CN>参考资料：</span></font><br id=nekf739>HDFS Java API: http://hadoop.apache.org/core/docs/current/api/<br id=nekf740>HDFS source code: http://hadoop.apache.org/core/version_control.html<br id=nekf741></p>
<img src ="http://www.cppblog.com/koson/aggbug/120775.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/koson/" target="_blank">koson</a> 2010-07-19 14:42 <a href="http://www.cppblog.com/koson/archive/2010/07/19/120775.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Hadoop Map/Reduce教程</title><link>http://www.cppblog.com/koson/archive/2010/07/19/120774.html</link><dc:creator>koson</dc:creator><author>koson</author><pubDate>Mon, 19 Jul 2010 06:40:00 GMT</pubDate><guid>http://www.cppblog.com/koson/archive/2010/07/19/120774.html</guid><wfw:comment>http://www.cppblog.com/koson/comments/120774.html</wfw:comment><comments>http://www.cppblog.com/koson/archive/2010/07/19/120774.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/koson/comments/commentRss/120774.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/koson/services/trackbacks/120774.html</trackback:ping><description><![CDATA[<p>目的<br>这篇教程从用户的角度出发，全面地介绍了Hadoop Map/Reduce框架的各个方面。</p>
<p>先决条件<br>请先确认Hadoop被正确安装、配置和正常运行中。更多信息见：</p>
<p>Hadoop快速入门对初次使用者。 <br>Hadoop集群搭建对大规模分布式集群。 <br>概述<br>Hadoop Map/Reduce是一个使用简易的软件框架，基于它写出来的应用程序能够运行在由上千个商用机器组成的大型集群上，并以一种可靠容错的方式并行处理上T级别的数据集。</p>
<p>一个Map/Reduce 作业（job） 通常会把输入的数据集切分为若干独立的数据块，由 map任务（task）以完全并行的方式处理它们。框架会对map的输出先进行排序， 然后把结果输入给reduce任务。通常作业的输入和输出都会被存储在文件系统中。 整个框架负责任务的调度和监控，以及重新执行已经失败的任务。</p>
<p>通常，Map/Reduce框架和分布式文件系统是运行在一组相同的节点上的，也就是说，计算节点和存储节点通常在一起。这种配置允许框架在那些已经存好数据的节点上高效地调度任务，这可以使整个集群的网络带宽被非常高效地利用。</p>
<p>Map/Reduce框架由一个单独的master JobTracker 和每个集群节点一个slave TaskTracker共同组成。master负责调度构成一个作业的所有任务，这些任务分布在不同的slave上，master监控它们的执行，重新执行已经失败的任务。而slave仅负责执行由master指派的任务。</p>
<p>应用程序至少应该指明输入/输出的位置（路径），并通过实现合适的接口或抽象类提供map和reduce函数。再加上其他作业的参数，就构成了作业配置（job configuration）。然后，Hadoop的 job client提交作业（jar包/可执行程序等）和配置信息给JobTracker，后者负责分发这些软件和配置信息给slave、调度任务并监控它们的执行，同时提供状态和诊断信息给job-client。</p>
<p>虽然Hadoop框架是用JavaTM实现的，但Map/Reduce应用程序则不一定要用 Java来写 。</p>
<p>Hadoop Streaming是一种运行作业的实用工具，它允许用户创建和运行任何可执行程序 （例如：Shell工具）来做为mapper和reducer。 <br>Hadoop Pipes是一个与SWIG兼容的C++ API （没有基于JNITM技术），它也可用于实现Map/Reduce应用程序。 <br>输入与输出<br>Map/Reduce框架运转在&lt;key, value&gt; 键值对上，也就是说， 框架把作业的输入看为是一组&lt;key, value&gt; 键值对，同样也产出一组 &lt;key, value&gt; 键值对做为作业的输出，这两组键值对的类型可能不同。</p>
<p>框架需要对key和value的类(classes)进行序列化操作， 因此，这些类需要实现 Writable接口。 另外，为了方便框架执行排序操作，key类必须实现 WritableComparable接口。</p>
<p>一个Map/Reduce 作业的输入和输出类型如下所示：</p>
<p>(input) &lt;k1, v1&gt; -&gt; map -&gt; &lt;k2, v2&gt; -&gt; combine -&gt; &lt;k2, v2&gt; -&gt; reduce -&gt; &lt;k3, v3&gt; (output)</p>
<p>例子：WordCount v1.0<br>在深入细节之前，让我们先看一个Map/Reduce的应用示例，以便对它们的工作方式有一个初步的认识。</p>
<p>WordCount是一个简单的应用，它可以计算出指定数据集中每一个单词出现的次数。</p>
<p>这个应用适用于 单机模式， 伪分布式模式 或 完全分布式模式 三种Hadoop安装方式。</p>
<p>源代码<br>&nbsp; WordCount.java <br>1. package org.myorg; <br>2.&nbsp;&nbsp; <br>3. import java.io.IOException; <br>4. import java.util.*; <br>5.&nbsp;&nbsp; <br>6. import org.apache.hadoop.fs.Path; <br>7. import org.apache.hadoop.conf.*; <br>8. import org.apache.hadoop.io.*; <br>9. import org.apache.hadoop.mapred.*; <br>10. import org.apache.hadoop.util.*; <br>11.&nbsp;&nbsp; <br>12. public class WordCount { <br>13.&nbsp;&nbsp; <br>14.&nbsp;&nbsp;&nbsp; public static class Map extends MapReduceBase implements Mapper&lt;LongWritable, Text, Text, IntWritable&gt; { <br>15.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private final static IntWritable one = new IntWritable(1); <br>16.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private Text word = new Text(); <br>17.&nbsp;&nbsp; <br>18.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void map(LongWritable key, Text value, OutputCollector&lt;Text, IntWritable&gt; output, Reporter reporter) throws IOException { <br>19.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String line = value.toString(); <br>20.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; StringTokenizer tokenizer = new StringTokenizer(line); <br>21.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while (tokenizer.hasMoreTokens()) { <br>22.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; word.set(tokenizer.nextToken()); <br>23.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; output.collect(word, one); <br>24.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>25.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>26.&nbsp;&nbsp;&nbsp; } <br>27.&nbsp;&nbsp; <br>28.&nbsp;&nbsp;&nbsp; public static class Reduce extends MapReduceBase implements Reducer&lt;Text, IntWritable, Text, IntWritable&gt; { <br>29.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void reduce(Text key, Iterator&lt;IntWritable&gt; values, OutputCollector&lt;Text, IntWritable&gt; output, Reporter reporter) throws IOException { <br>30.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int sum = 0; <br>31.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while (values.hasNext()) { <br>32.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sum += values.next().get(); <br>33.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>34.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; output.collect(key, new IntWritable(sum)); <br>35.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>36.&nbsp;&nbsp;&nbsp; } <br>37.&nbsp;&nbsp; <br>38.&nbsp;&nbsp;&nbsp; public static void main(String[] args) throws Exception { <br>39.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JobConf conf = new JobConf(WordCount.class); <br>40.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setJobName("wordcount"); <br>41.&nbsp;&nbsp; <br>42.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setOutputKeyClass(Text.class); <br>43.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setOutputValueClass(IntWritable.class); <br>44.&nbsp;&nbsp; <br>45.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setMapperClass(Map.class); <br>46.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setCombinerClass(Reduce.class); <br>47.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setReducerClass(Reduce.class); <br>48.&nbsp;&nbsp; <br>49.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setInputFormat(TextInputFormat.class); <br>50.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setOutputFormat(TextOutputFormat.class); <br>51.&nbsp;&nbsp; <br>52.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FileInputFormat.setInputPaths(conf, new Path(args[0])); <br>53.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FileOutputFormat.setOutputPath(conf, new Path(args[1])); <br>54.&nbsp;&nbsp; <br>55.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JobClient.runJob(conf); <br>57.&nbsp;&nbsp;&nbsp; } <br>58. } <br>59.&nbsp;&nbsp; </p>
<p>用法<br>假设环境变量HADOOP_HOME对应安装时的根目录，HADOOP_VERSION对应Hadoop的当前安装版本，编译WordCount.java来创建jar包，可如下操作：</p>
<p>$ mkdir wordcount_classes <br>$ javac -classpath ${HADOOP_HOME}/hadoop-${HADOOP_VERSION}-core.jar -d wordcount_classes WordCount.java <br>$ jar -cvf /usr/joe/wordcount.jar -C wordcount_classes/ .</p>
<p>假设：</p>
<p>/usr/joe/wordcount/input - 是HDFS中的输入路径 <br>/usr/joe/wordcount/output - 是HDFS中的输出路径 <br>用示例文本文件做为输入：</p>
<p>$ bin/hadoop dfs -ls /usr/joe/wordcount/input/ <br>/usr/joe/wordcount/input/file01 <br>/usr/joe/wordcount/input/file02 </p>
<p>$ bin/hadoop dfs -cat /usr/joe/wordcount/input/file01 <br>Hello World Bye World </p>
<p>$ bin/hadoop dfs -cat /usr/joe/wordcount/input/file02 <br>Hello Hadoop Goodbye Hadoop</p>
<p>运行应用程序：</p>
<p>$ bin/hadoop jar /usr/joe/wordcount.jar org.myorg.WordCount /usr/joe/wordcount/input /usr/joe/wordcount/output</p>
<p>输出是：</p>
<p>$ bin/hadoop dfs -cat /usr/joe/wordcount/output/part-00000 <br>Bye 1 <br>Goodbye 1 <br>Hadoop 2 <br>Hello 2 <br>World 2 </p>
<p>应用程序能够使用-files选项来指定一个由逗号分隔的路径列表，这些路径是task的当前工作目录。使用选项-libjars可以向map和reduce的classpath中添加jar包。使用-archives选项程序可以传递档案文件做为参数，这些档案文件会被解压并且在task的当前工作目录下会创建一个指向解压生成的目录的符号链接（以压缩包的名字命名）。 有关命令行选项的更多细节请参考 Commands manual。</p>
<p>使用-libjars和-files运行wordcount例子：<br>hadoop jar hadoop-examples.jar wordcount -files cachefile.txt -libjars mylib.jar input output</p>
<p>解释<br>WordCount应用程序非常直截了当。</p>
<p>Mapper(14-26行)中的map方法(18-25行)通过指定的 TextInputFormat(49行)一次处理一行。然后，它通过StringTokenizer 以空格为分隔符将一行切分为若干tokens，之后，输出&lt; &lt;word&gt;, 1&gt; 形式的键值对。</p>
<p>对于示例中的第一个输入，map输出是：<br>&lt; Hello, 1&gt; <br>&lt; World, 1&gt; <br>&lt; Bye, 1&gt; <br>&lt; World, 1&gt; </p>
<p>第二个输入，map输出是：<br>&lt; Hello, 1&gt; <br>&lt; Hadoop, 1&gt; <br>&lt; Goodbye, 1&gt; <br>&lt; Hadoop, 1&gt; </p>
<p>关于组成一个指定作业的map数目的确定，以及如何以更精细的方式去控制这些map，我们将在教程的后续部分学习到更多的内容。</p>
<p>WordCount还指定了一个combiner (46行)。因此，每次map运行之后，会对输出按照key进行排序，然后把输出传递给本地的combiner（按照作业的配置与Reducer一样），进行本地聚合。</p>
<p>第一个map的输出是：<br>&lt; Bye, 1&gt; <br>&lt; Hello, 1&gt; <br>&lt; World, 2&gt; </p>
<p>第二个map的输出是：<br>&lt; Goodbye, 1&gt; <br>&lt; Hadoop, 2&gt; <br>&lt; Hello, 1&gt; </p>
<p>Reducer(28-36行)中的reduce方法(29-35行) 仅是将每个key（本例中就是单词）出现的次数求和。</p>
<p>因此这个作业的输出就是：<br>&lt; Bye, 1&gt; <br>&lt; Goodbye, 1&gt; <br>&lt; Hadoop, 2&gt; <br>&lt; Hello, 2&gt; <br>&lt; World, 2&gt; </p>
<p>代码中的run方法中指定了作业的几个方面， 例如：通过命令行传递过来的输入/输出路径、key/value的类型、输入/输出的格式等等JobConf中的配置信息。随后程序调用了JobClient.runJob(55行)来提交作业并且监控它的执行。</p>
<p>我们将在本教程的后续部分学习更多的关于JobConf， JobClient， Tool和其他接口及类(class)。</p>
<p>Map/Reduce - 用户界面<br>这部分文档为用户将会面临的Map/Reduce框架中的各个环节提供了适当的细节。这应该会帮助用户更细粒度地去实现、配置和调优作业。然而，请注意每个类/接口的javadoc文档提供最全面的文档；本文只是想起到指南的作用。</p>
<p>我们会先看看Mapper和Reducer接口。应用程序通常会通过提供map和reduce方法来实现它们。</p>
<p>然后，我们会讨论其他的核心接口，其中包括： JobConf，JobClient，Partitioner， OutputCollector，Reporter， InputFormat，OutputFormat等等。</p>
<p>最后，我们将通过讨论框架中一些有用的功能点（例如：DistributedCache， IsolationRunner等等）来收尾。</p>
<p>核心功能描述<br>应用程序通常会通过提供map和reduce来实现 Mapper和Reducer接口，它们组成作业的核心。</p>
<p>Mapper<br>Mapper将输入键值对(key/value pair)映射到一组中间格式的键值对集合。</p>
<p>Map是一类将输入记录集转换为中间格式记录集的独立任务。 这种转换的中间格式记录集不需要与输入记录集的类型一致。一个给定的输入键值对可以映射成0个或多个输出键值对。</p>
<p>Hadoop Map/Reduce框架为每一个InputSplit产生一个map任务，而每个InputSplit是由该作业的InputFormat产生的。</p>
<p>概括地说，对Mapper的实现者需要重写 JobConfigurable.configure(JobConf)方法，这个方法需要传递一个JobConf参数，目的是完成Mapper的初始化工作。然后，框架为这个任务的InputSplit中每个键值对调用一次 map(WritableComparable, Writable, OutputCollector, Reporter)操作。应用程序可以通过重写Closeable.close()方法来执行相应的清理工作。</p>
<p>输出键值对不需要与输入键值对的类型一致。一个给定的输入键值对可以映射成0个或多个输出键值对。通过调用 OutputCollector.collect(WritableComparable,Writable)可以收集输出的键值对。</p>
<p>应用程序可以使用Reporter报告进度，设定应用级别的状态消息，更新Counters（计数器），或者仅是表明自己运行正常。</p>
<p>框架随后会把与一个特定key关联的所有中间过程的值（value）分成组，然后把它们传给Reducer以产出最终的结果。用户可以通过 JobConf.setOutputKeyComparatorClass(Class)来指定具体负责分组的 Comparator。</p>
<p>Mapper的输出被排序后，就被划分给每个Reducer。分块的总数目和一个作业的reduce任务的数目是一样的。用户可以通过实现自定义的 Partitioner来控制哪个key被分配给哪个 Reducer。</p>
<p>用户可选择通过 JobConf.setCombinerClass(Class)指定一个combiner，它负责对中间过程的输出进行本地的聚集，这会有助于降低从Mapper到 Reducer数据传输量。</p>
<p>这些被排好序的中间过程的输出结果保存的格式是(key-len, key, value-len, value)，应用程序可以通过JobConf控制对这些中间结果是否进行压缩以及怎么压缩，使用哪种 CompressionCodec。</p>
<p>需要多少个Map？<br>Map的数目通常是由输入数据的大小决定的，一般就是所有输入文件的总块（block）数。</p>
<p>Map正常的并行规模大致是每个节点（node）大约10到100个map，对于CPU 消耗较小的map任务可以设到300个左右。由于每个任务初始化需要一定的时间，因此，比较合理的情况是map执行的时间至少超过1分钟。</p>
<p>这样，如果你输入10TB的数据，每个块（block）的大小是128MB，你将需要大约82,000个map来完成任务，除非使用 setNumMapTasks(int)（注意：这里仅仅是对框架进行了一个提示(hint)，实际决定因素见这里）将这个数值设置得更高。</p>
<p>Reducer<br>Reducer将与一个key关联的一组中间数值集归约（reduce）为一个更小的数值集。</p>
<p>用户可以通过 JobConf.setNumReduceTasks(int)设定一个作业中reduce任务的数目。</p>
<p>概括地说，对Reducer的实现者需要重写 JobConfigurable.configure(JobConf)方法，这个方法需要传递一个JobConf参数，目的是完成Reducer的初始化工作。然后，框架为成组的输入数据中的每个&lt;key, (list of values)&gt;对调用一次 reduce(WritableComparable, Iterator, OutputCollector, Reporter)方法。之后，应用程序可以通过重写Closeable.close()来执行相应的清理工作。</p>
<p>Reducer有3个主要阶段：shuffle、sort和reduce。</p>
<p>Shuffle<br>Reducer的输入就是Mapper已经排好序的输出。在这个阶段，框架通过HTTP为每个Reducer获得所有Mapper输出中与之相关的分块。</p>
<p>Sort<br>这个阶段，框架将按照key的值对Reducer的输入进行分组 （因为不同mapper的输出中可能会有相同的key）。</p>
<p>Shuffle和Sort两个阶段是同时进行的；map的输出也是一边被取回一边被合并的。</p>
<p>Secondary Sort<br>如果需要中间过程对key的分组规则和reduce前对key的分组规则不同，那么可以通过 JobConf.setOutputValueGroupingComparator(Class)来指定一个Comparator。再加上 JobConf.setOutputKeyComparatorClass(Class)可用于控制中间过程的key如何被分组，所以结合两者可以实现按值的二次排序。</p>
<p>Reduce<br>在这个阶段，框架为已分组的输入数据中的每个 &lt;key, (list of values)&gt;对调用一次 reduce(WritableComparable, Iterator, OutputCollector, Reporter)方法。</p>
<p>Reduce任务的输出通常是通过调用 OutputCollector.collect(WritableComparable, Writable)写入 文件系统的。</p>
<p>应用程序可以使用Reporter报告进度，设定应用程序级别的状态消息，更新Counters（计数器），或者仅是表明自己运行正常。</p>
<p>Reducer的输出是没有排序的。</p>
<p>需要多少个Reduce？<br>Reduce的数目建议是0.95或1.75乘以 (&lt;no. of nodes&gt; * mapred.tasktracker.reduce.tasks.maximum)。</p>
<p>用0.95，所有reduce可以在maps一完成时就立刻启动，开始传输map的输出结果。用1.75，速度快的节点可以在完成第一轮reduce任务后，可以开始第二轮，这样可以得到比较好的负载均衡的效果。</p>
<p>增加reduce的数目会增加整个框架的开销，但可以改善负载均衡，降低由于执行失败带来的负面影响。</p>
<p>上述比例因子比整体数目稍小一些是为了给框架中的推测性任务（speculative-tasks） 或失败的任务预留一些reduce的资源。</p>
<p>无Reducer<br>如果没有归约要进行，那么设置reduce任务的数目为零是合法的。</p>
<p>这种情况下，map任务的输出会直接被写入由 setOutputPath(Path)指定的输出路径。框架在把它们写入FileSystem之前没有对它们进行排序。</p>
<p>Partitioner<br>Partitioner用于划分键值空间（key space）。</p>
<p>Partitioner负责控制map输出结果key的分割。Key（或者一个key子集）被用于产生分区，通常使用的是Hash函数。分区的数目与一个作业的reduce任务的数目是一样的。因此，它控制将中间过程的key（也就是这条记录）应该发送给m个reduce任务中的哪一个来进行reduce操作。</p>
<p>HashPartitioner是默认的 Partitioner。</p>
<p>Reporter<br>Reporter是用于Map/Reduce应用程序报告进度，设定应用级别的状态消息， 更新Counters（计数器）的机制。</p>
<p>Mapper和Reducer的实现可以利用Reporter 来报告进度，或者仅是表明自己运行正常。在那种应用程序需要花很长时间处理个别键值对的场景中，这种机制是很关键的，因为框架可能会以为这个任务超时了，从而将它强行杀死。另一个避免这种情况发生的方式是，将配置参数mapred.task.timeout设置为一个足够高的值（或者干脆设置为零，则没有超时限制了）。</p>
<p>应用程序可以用Reporter来更新Counter（计数器）。</p>
<p>OutputCollector<br>OutputCollector是一个Map/Reduce框架提供的用于收集 Mapper或Reducer输出数据的通用机制 （包括中间输出结果和作业的输出结果）。</p>
<p>Hadoop Map/Reduce框架附带了一个包含许多实用型的mapper、reducer和partitioner 的类库。</p>
<p>作业配置<br>JobConf代表一个Map/Reduce作业的配置。</p>
<p>JobConf是用户向Hadoop框架描述一个Map/Reduce作业如何执行的主要接口。框架会按照JobConf描述的信息忠实地去尝试完成这个作业，然而：</p>
<p>一些参数可能会被管理者标记为 final，这意味它们不能被更改。 <br>一些作业的参数可以被直截了当地进行设置（例如： setNumReduceTasks(int)），而另一些参数则与框架或者作业的其他参数之间微妙地相互影响，并且设置起来比较复杂（例如： setNumMapTasks(int)）。 <br>通常，JobConf会指明Mapper、Combiner(如果有的话)、 Partitioner、Reducer、InputFormat和 OutputFormat的具体实现。JobConf还能指定一组输入文件 (setInputPaths(JobConf, Path...) /addInputPath(JobConf, Path)) 和(setInputPaths(JobConf, String) /addInputPaths(JobConf, String)) 以及输出文件应该写在哪儿 (setOutputPath(Path))。</p>
<p>JobConf可选择地对作业设置一些高级选项，例如：设置Comparator； 放到DistributedCache上的文件；中间结果或者作业输出结果是否需要压缩以及怎么压缩； 利用用户提供的脚本(setMapDebugScript(String)/setReduceDebugScript(String)) 进行调试；作业是否允许预防性（speculative）任务的执行 (setMapSpeculativeExecution(boolean))/(setReduceSpeculativeExecution(boolean)) ；每个任务最大的尝试次数 (setMaxMapAttempts(int)/setMaxReduceAttempts(int)) ；一个作业能容忍的任务失败的百分比 (setMaxMapTaskFailuresPercent(int)/setMaxReduceTaskFailuresPercent(int)) ；等等。</p>
<p>当然，用户能使用 set(String, String)/get(String, String) 来设置或者取得应用程序需要的任意参数。然而，DistributedCache的使用是面向大规模只读数据的。</p>
<p>任务的执行和环境<br>TaskTracker是在一个单独的jvm上以子进程的形式执行 Mapper/Reducer任务（Task）的。</p>
<p>子任务会继承父TaskTracker的环境。用户可以通过JobConf中的 mapred.child.java.opts配置参数来设定子jvm上的附加选项，例如： 通过-Djava.library.path=&lt;&gt; 将一个非标准路径设为运行时的链接用以搜索共享库，等等。如果mapred.child.java.opts包含一个符号@taskid@， 它会被替换成map/reduce的taskid的值。</p>
<p>下面是一个包含多个参数和替换的例子，其中包括：记录jvm GC日志； JVM JMX代理程序以无密码的方式启动，这样它就能连接到jconsole上，从而可以查看子进程的内存和线程，得到线程的dump；还把子jvm的最大堆尺寸设置为512MB， 并为子jvm的java.library.path添加了一个附加路径。</p>
<p>&lt;property&gt; <br>&nbsp; &lt;name&gt;mapred.child.java.opts&lt;/name&gt; <br>&nbsp; &lt;value&gt; <br>&nbsp;&nbsp;&nbsp;&nbsp; -Xmx512M -Djava.library.path=/home/mycompany/lib -verbose:gc -Xloggc:/tmp/@taskid@.gc <br>&nbsp;&nbsp;&nbsp;&nbsp; -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false <br>&nbsp; &lt;/value&gt; <br>&lt;/property&gt;</p>
<p>用户或管理员也可以使用mapred.child.ulimit设定运行的子任务的最大虚拟内存。mapred.child.ulimit的值以（KB)为单位，并且必须大于或等于-Xmx参数传给JavaVM的值，否则VM会无法启动。</p>
<p>注意：mapred.child.java.opts只用于设置task tracker启动的子任务。为守护进程设置内存选项请查看 cluster_setup.html</p>
<p>${mapred.local.dir}/taskTracker/是task tracker的本地目录， 用于创建本地缓存和job。它可以指定多个目录（跨越多个磁盘），文件会半随机的保存到本地路径下的某个目录。当job启动时，task tracker根据配置文档创建本地job目录，目录结构如以下所示：</p>
<p>${mapred.local.dir}/taskTracker/archive/ :分布式缓存。这个目录保存本地的分布式缓存。因此本地分布式缓存是在所有task和job间共享的。 <br>${mapred.local.dir}/taskTracker/jobcache/$jobid/ : 本地job目录。 <br>${mapred.local.dir}/taskTracker/jobcache/$jobid/work/: job指定的共享目录。各个任务可以使用这个空间做为暂存空间，用于它们之间共享文件。这个目录通过job.local.dir 参数暴露给用户。这个路径可以通过API JobConf.getJobLocalDir()来访问。它也可以被做为系统属性获得。因此，用户（比如运行streaming）可以调用System.getProperty("job.local.dir")获得该目录。 <br>${mapred.local.dir}/taskTracker/jobcache/$jobid/jars/: 存放jar包的路径，用于存放作业的jar文件和展开的jar。job.jar是应用程序的jar文件，它会被自动分发到各台机器，在task启动前会被自动展开。使用api JobConf.getJar() 函数可以得到job.jar的位置。使用JobConf.getJar().getParent()可以访问存放展开的jar包的目录。 <br>${mapred.local.dir}/taskTracker/jobcache/$jobid/job.xml： 一个job.xml文件，本地的通用的作业配置文件。 <br>${mapred.local.dir}/taskTracker/jobcache/$jobid/$taskid： 每个任务有一个目录task-id，它里面有如下的目录结构： <br>${mapred.local.dir}/taskTracker/jobcache/$jobid/$taskid/job.xml： 一个job.xml文件，本地化的任务作业配置文件。任务本地化是指为该task设定特定的属性值。这些值会在下面具体说明。 <br>${mapred.local.dir}/taskTracker/jobcache/$jobid/$taskid/output 一个存放中间过程的输出文件的目录。它保存了由framwork产生的临时map reduce数据，比如map的输出文件等。 <br>${mapred.local.dir}/taskTracker/jobcache/$jobid/$taskid/work： task的当前工作目录。 <br>${mapred.local.dir}/taskTracker/jobcache/$jobid/$taskid/work/tmp： task的临时目录。（用户可以设定属性mapred.child.tmp 来为map和reduce task设定临时目录。缺省值是./tmp。如果这个值不是绝对路径， 它会把task的工作路径加到该路径前面作为task的临时文件路径。如果这个值是绝对路径则直接使用这个值。 如果指定的目录不存在，会自动创建该目录。之后，按照选项 -Djava.io.tmpdir='临时文件的绝对路径'执行java子任务。 pipes和streaming的临时文件路径是通过环境变量TMPDIR='the absolute path of the tmp dir'设定的）。 如果mapred.child.tmp有./tmp值，这个目录会被创建。 <br>下面的属性是为每个task执行时使用的本地参数，它们保存在本地化的任务作业配置文件里：</p>
<p>名称 类型 描述 <br>mapred.job.id String job id <br>mapred.jar String job目录下job.jar的位置 <br>job.local.dir String job指定的共享存储空间 <br>mapred.tip.id String task id <br>mapred.task.id String task尝试id <br>mapred.task.is.map boolean 是否是map task <br>mapred.task.partition int task在job中的id <br>map.input.file String map读取的文件名 <br>map.input.start long map输入的数据块的起始位置偏移 <br>map.input.length long map输入的数据块的字节数 <br>mapred.work.output.dir String task临时输出目录 </p>
<p>task的标准输出和错误输出流会被读到TaskTracker中，并且记录到 ${HADOOP_LOG_DIR}/userlogs</p>
<p>DistributedCache 可用于map或reduce task中分发jar包和本地库。子jvm总是把 当前工作目录 加到 java.library.path 和 LD_LIBRARY_PATH。 因此，可以通过 System.loadLibrary或 System.load装载缓存的库。有关使用分布式缓存加载共享库的细节请参考 native_libraries.html</p>
<p>作业的提交与监控<br>JobClient是用户提交的作业与JobTracker交互的主要接口。</p>
<p>JobClient 提供提交作业，追踪进程，访问子任务的日志记录，获得Map/Reduce集群状态信息等功能。</p>
<p>作业提交过程包括：</p>
<p>检查作业输入输出样式细节 <br>为作业计算InputSplit值。 <br>如果需要的话，为作业的DistributedCache建立必须的统计信息。 <br>拷贝作业的jar包和配置文件到FileSystem上的Map/Reduce系统目录下。 <br>提交作业到JobTracker并且监控它的状态。 <br>作业的历史文件记录到指定目录的"_logs/history/"子目录下。这个指定目录由hadoop.job.history.user.location设定，默认是作业输出的目录。因此默认情况下，文件会存放在mapred.output.dir/_logs/history目录下。用户可以设置hadoop.job.history.user.location为none来停止日志记录。</p>
<p>用户使用下面的命令可以看到在指定目录下的历史日志记录的摘要。 <br>$ bin/hadoop job -history output-dir <br>这个命令会打印出作业的细节，以及失败的和被杀死的任务细节。<br>要查看有关作业的更多细节例如成功的任务、每个任务尝试的次数（task attempt）等，可以使用下面的命令 <br>$ bin/hadoop job -history all output-dir </p>
<p>用户可以使用 OutputLogFilter 从输出目录列表中筛选日志文件。</p>
<p>一般情况，用户利用JobConf创建应用程序并配置作业属性， 然后用 JobClient 提交作业并监视它的进程。</p>
<p>作业的控制<br>有时候，用一个单独的Map/Reduce作业并不能完成一个复杂的任务，用户也许要链接多个Map/Reduce作业才行。这是容易实现的，因为作业通常输出到分布式文件系统上的，所以可以把这个作业的输出作为下一个作业的输入实现串联。</p>
<p>然而，这也意味着，确保每一作业完成(成功或失败)的责任就直接落在了客户身上。在这种情况下，可以用的控制作业的选项有：</p>
<p>runJob(JobConf)：提交作业，仅当作业完成时返回。 <br>submitJob(JobConf)：只提交作业，之后需要你轮询它返回的 RunningJob句柄的状态，并根据情况调度。 <br>JobConf.setJobEndNotificationURI(String)：设置一个作业完成通知，可避免轮询。 <br>作业的输入<br>InputFormat 为Map/Reduce作业描述输入的细节规范。</p>
<p>Map/Reduce框架根据作业的InputFormat来：</p>
<p>检查作业输入的有效性。 <br>把输入文件切分成多个逻辑InputSplit实例， 并把每一实例分别分发给一个 Mapper。 <br>提供RecordReader的实现，这个RecordReader从逻辑InputSplit中获得输入记录， 这些记录将由Mapper处理。 <br>基于文件的InputFormat实现（通常是 FileInputFormat的子类） 默认行为是按照输入文件的字节大小，把输入数据切分成逻辑分块（logical InputSplit ）。 其中输入文件所在的FileSystem的数据块尺寸是分块大小的上限。下限可以设置mapred.min.split.size 的值。</p>
<p>考虑到边界情况，对于很多应用程序来说，很明显按照文件大小进行逻辑分割是不能满足需求的。 在这种情况下，应用程序需要实现一个RecordReader来处理记录的边界并为每个任务提供一个逻辑分块的面向记录的视图。</p>
<p>TextInputFormat 是默认的InputFormat。</p>
<p>如果一个作业的Inputformat是TextInputFormat， 并且框架检测到输入文件的后缀是.gz和.lzo，就会使用对应的CompressionCodec自动解压缩这些文件。 但是需要注意，上述带后缀的压缩文件不会被切分，并且整个压缩文件会分给一个mapper来处理。</p>
<p>InputSplit<br>InputSplit 是一个单独的Mapper要处理的数据块。</p>
<p>一般的InputSplit 是字节样式输入，然后由RecordReader处理并转化成记录样式。</p>
<p>FileSplit 是默认的InputSplit。 它把 map.input.file 设定为输入文件的路径，输入文件是逻辑分块文件。</p>
<p>RecordReader<br>RecordReader 从InputSlit读入&lt;key, value&gt;对。</p>
<p>一般的，RecordReader 把由InputSplit 提供的字节样式的输入文件，转化成由Mapper处理的记录样式的文件。 因此RecordReader负责处理记录的边界情况和把数据表示成keys/values对形式。</p>
<p>作业的输出<br>OutputFormat 描述Map/Reduce作业的输出样式。</p>
<p>Map/Reduce框架根据作业的OutputFormat来：</p>
<p>检验作业的输出，例如检查输出路径是否已经存在。 <br>提供一个RecordWriter的实现，用来输出作业结果。 输出文件保存在FileSystem上。 <br>TextOutputFormat是默认的 OutputFormat。</p>
<p>任务的Side-Effect File<br>在一些应用程序中，子任务需要产生一些side-file，这些文件与作业实际输出结果的文件不同。</p>
<p>在这种情况下，同一个Mapper或者Reducer的两个实例（比如预防性任务）同时打开或者写 FileSystem上的同一文件就会产生冲突。因此应用程序在写文件的时候需要为每次任务尝试（不仅仅是每次任务，每个任务可以尝试执行很多次）选取一个独一无二的文件名(使用attemptid，例如task_200709221812_0001_m_000000_0)。</p>
<p>为了避免冲突，Map/Reduce框架为每次尝试执行任务都建立和维护一个特殊的 ${mapred.output.dir}/_temporary/_${taskid}子目录，这个目录位于本次尝试执行任务输出结果所在的FileSystem上，可以通过 ${mapred.work.output.dir}来访问这个子目录。 对于成功完成的任务尝试，只有${mapred.output.dir}/_temporary/_${taskid}下的文件会移动到${mapred.output.dir}。当然，框架会丢弃那些失败的任务尝试的子目录。这种处理过程对于应用程序来说是完全透明的。</p>
<p>在任务执行期间，应用程序在写文件时可以利用这个特性，比如 通过 FileOutputFormat.getWorkOutputPath()获得${mapred.work.output.dir}目录， 并在其下创建任意任务执行时所需的side-file，框架在任务尝试成功时会马上移动这些文件，因此不需要在程序内为每次任务尝试选取一个独一无二的名字。</p>
<p>注意：在每次任务尝试执行期间，${mapred.work.output.dir} 的值实际上是 ${mapred.output.dir}/_temporary/_{$taskid}，这个值是Map/Reduce框架创建的。 所以使用这个特性的方法是，在 FileOutputFormat.getWorkOutputPath() 路径下创建side-file即可。</p>
<p>对于只使用map不使用reduce的作业，这个结论也成立。这种情况下，map的输出结果直接生成到HDFS上。</p>
<p>RecordWriter<br>RecordWriter 生成&lt;key, value&gt; 对到输出文件。</p>
<p>RecordWriter的实现把作业的输出结果写到 FileSystem。</p>
<p>其他有用的特性<br>Counters<br>Counters 是多个由Map/Reduce框架或者应用程序定义的全局计数器。 每一个Counter可以是任何一种 Enum类型。同一特定Enum类型的Counter可以汇集到一个组，其类型为Counters.Group。</p>
<p>应用程序可以定义任意(Enum类型)的Counters并且可以通过 map 或者 reduce方法中的 Reporter.incrCounter(Enum, long)或者 Reporter.incrCounter(String, String, long) 更新。之后框架会汇总这些全局counters。</p>
<p>DistributedCache<br>DistributedCache 可将具体应用相关的、大尺寸的、只读的文件有效地分布放置。</p>
<p>DistributedCache 是Map/Reduce框架提供的功能，能够缓存应用程序所需的文件 （包括文本，档案文件，jar文件等）。</p>
<p>应用程序在JobConf中通过url(hdfs://)指定需要被缓存的文件。 DistributedCache假定由hdfs://格式url指定的文件已经在 FileSystem上了。</p>
<p>Map-Redcue框架在作业所有任务执行之前会把必要的文件拷贝到slave节点上。 它运行高效是因为每个作业的文件只拷贝一次并且为那些没有文档的slave节点缓存文档。</p>
<p>DistributedCache 根据缓存文档修改的时间戳进行追踪。 在作业执行期间，当前应用程序或者外部程序不能修改缓存文件。</p>
<p>distributedCache可以分发简单的只读数据或文本文件，也可以分发复杂类型的文件例如归档文件和jar文件。归档文件(zip,tar,tgz和tar.gz文件)在slave节点上会被解档（un-archived）。 这些文件可以设置执行权限。</p>
<p>用户可以通过设置mapred.cache.{files|archives}来分发文件。 如果要分发多个文件，可以使用逗号分隔文件所在路径。也可以利用API来设置该属性： DistributedCache.addCacheFile(URI,conf)/ DistributedCache.addCacheArchive(URI,conf) and DistributedCache.setCacheFiles(URIs,conf)/ DistributedCache.setCacheArchives(URIs,conf) 其中URI的形式是 hdfs://host:port/absolute-path#link-name 在Streaming程序中，可以通过命令行选项 -cacheFile/-cacheArchive 分发文件。</p>
<p>用户可以通过 DistributedCache.createSymlink(Configuration)方法让DistributedCache 在当前工作目录下创建到缓存文件的符号链接。 或者通过设置配置文件属性mapred.create.symlink为yes。 分布式缓存会截取URI的片段作为链接的名字。 例如，URI是 hdfs://namenode:port/lib.so.1#lib.so， 则在task当前工作目录会有名为lib.so的链接， 它会链接分布式缓存中的lib.so.1。</p>
<p>DistributedCache可在map/reduce任务中作为 一种基础软件分发机制使用。它可以被用于分发jar包和本地库（native libraries）。 DistributedCache.addArchiveToClassPath(Path, Configuration)和 DistributedCache.addFileToClassPath(Path, Configuration) API能够被用于 缓存文件和jar包，并把它们加入子jvm的classpath。也可以通过设置配置文档里的属性 mapred.job.classpath.{files|archives}达到相同的效果。缓存文件可用于分发和装载本地库。</p>
<p>Tool<br>Tool 接口支持处理常用的Hadoop命令行选项。</p>
<p>Tool 是Map/Reduce工具或应用的标准。应用程序应只处理其定制参数， 要把标准命令行选项通过 ToolRunner.run(Tool, String[]) 委托给 GenericOptionsParser处理。</p>
<p>Hadoop命令行的常用选项有：<br>-conf &lt;configuration file&gt; <br>-D &lt;property=value&gt; <br>-fs &lt;local|namenode:port&gt; <br>-jt &lt;local|jobtracker:port&gt;</p>
<p>IsolationRunner<br>IsolationRunner 是帮助调试Map/Reduce程序的工具。</p>
<p>使用IsolationRunner的方法是，首先设置 keep.failed.tasks.files属性为true （同时参考keep.tasks.files.pattern）。</p>
<p>然后，登录到任务运行失败的节点上，进入 TaskTracker的本地路径运行 IsolationRunner：<br>$ cd &lt;local path&gt;/taskTracker/${taskid}/work <br>$ bin/hadoop org.apache.hadoop.mapred.IsolationRunner ../job.xml</p>
<p>IsolationRunner会把失败的任务放在单独的一个能够调试的jvm上运行，并且采用和之前完全一样的输入数据。</p>
<p>Profiling<br>Profiling是一个工具，它使用内置的java profiler工具进行分析获得(2-3个)map或reduce样例运行分析报告。</p>
<p>用户可以通过设置属性mapred.task.profile指定系统是否采集profiler信息。 利用api JobConf.setProfileEnabled(boolean)可以修改属性值。如果设为true， 则开启profiling功能。profiler信息保存在用户日志目录下。缺省情况，profiling功能是关闭的。</p>
<p>如果用户设定使用profiling功能，可以使用配置文档里的属性 mapred.task.profile.{maps|reduces} 设置要profile map/reduce task的范围。设置该属性值的api是 JobConf.setProfileTaskRange(boolean,String)。 范围的缺省值是0-2。</p>
<p>用户可以通过设定配置文档里的属性mapred.task.profile.params 来指定profiler配置参数。修改属性要使用api JobConf.setProfileParams(String)。当运行task时，如果字符串包含%s。 它会被替换成profileing的输出文件名。这些参数会在命令行里传递到子JVM中。缺省的profiling 参数是 -agentlib:hprof=cpu=samples,heap=sites,force=n,thread=y,verbose=n,file=%s。</p>
<p>调试<br>Map/Reduce框架能够运行用户提供的用于调试的脚本程序。 当map/reduce任务失败时，用户可以通过运行脚本在任务日志（例如任务的标准输出、标准错误、系统日志以及作业配置文件）上做后续处理工作。用户提供的调试脚本程序的标准输出和标准错误会输出为诊断文件。如果需要的话这些输出结果也可以打印在用户界面上。</p>
<p>在接下来的章节，我们讨论如何与作业一起提交调试脚本。为了提交调试脚本， 首先要把这个脚本分发出去，而且还要在配置文件里设置。</p>
<p>如何分发脚本文件：<br>用户要用 DistributedCache 机制来分发和链接脚本文件</p>
<p>如何提交脚本：<br>一个快速提交调试脚本的方法是分别为需要调试的map任务和reduce任务设置 "mapred.map.task.debug.script" 和 "mapred.reduce.task.debug.script" 属性的值。这些属性也可以通过 JobConf.setMapDebugScript(String) 和 JobConf.setReduceDebugScript(String) API来设置。对于streaming， 可以分别为需要调试的map任务和reduce任务使用命令行选项-mapdebug 和 -reducedegug来提交调试脚本。</p>
<p>脚本的参数是任务的标准输出、标准错误、系统日志以及作业配置文件。在运行map/reduce失败的节点上运行调试命令是： <br>$script $stdout $stderr $syslog $jobconf</p>
<p>Pipes 程序根据第五个参数获得c++程序名。 因此调试pipes程序的命令是<br>$script $stdout $stderr $syslog $jobconf $program</p>
<p>默认行为<br>对于pipes，默认的脚本会用gdb处理core dump， 打印 stack trace并且给出正在运行线程的信息。</p>
<p>JobControl<br>JobControl是一个工具，它封装了一组Map/Reduce作业以及他们之间的依赖关系。</p>
<p>数据压缩<br>Hadoop Map/Reduce框架为应用程序的写入文件操作提供压缩工具，这些工具可以为map输出的中间数据和作业最终输出数据（例如reduce的输出）提供支持。它还附带了一些 CompressionCodec的实现，比如实现了 zlib和lzo压缩算法。 Hadoop同样支持gzip文件格式。</p>
<p>考虑到性能问题（zlib）以及Java类库的缺失（lzo）等因素，Hadoop也为上述压缩解压算法提供本地库的实现。更多的细节请参考 这里。</p>
<p>中间输出<br>应用程序可以通过 JobConf.setCompressMapOutput(boolean)api控制map输出的中间结果，并且可以通过 JobConf.setMapOutputCompressorClass(Class)api指定 CompressionCodec。</p>
<p>作业输出<br>应用程序可以通过 FileOutputFormat.setCompressOutput(JobConf, boolean) api控制输出是否需要压缩并且可以使用 FileOutputFormat.setOutputCompressorClass(JobConf, Class)api指定CompressionCodec。</p>
<p>如果作业输出要保存成 SequenceFileOutputFormat格式，需要使用 SequenceFileOutputFormat.setOutputCompressionType(JobConf, SequenceFile.CompressionType)api，来设定 SequenceFile.CompressionType (i.e.RECORD / BLOCK - 默认是RECORD)。</p>
<p>例子：WordCount v2.0<br>这里是一个更全面的WordCount例子，它使用了我们已经讨论过的很多Map/Reduce框架提供的功能。</p>
<p>运行这个例子需要HDFS的某些功能，特别是 DistributedCache相关功能。因此这个例子只能运行在 伪分布式 或者 完全分布式模式的 Hadoop上。</p>
<p>源代码<br>&nbsp; WordCount.java <br>1. package org.myorg; <br>2.&nbsp;&nbsp; <br>3. import java.io.*; <br>4. import java.util.*; <br>5.&nbsp;&nbsp; <br>6. import org.apache.hadoop.fs.Path; <br>7. import org.apache.hadoop.filecache.DistributedCache; <br>8. import org.apache.hadoop.conf.*; <br>9. import org.apache.hadoop.io.*; <br>10. import org.apache.hadoop.mapred.*; <br>11. import org.apache.hadoop.util.*; <br>12.&nbsp;&nbsp; <br>13. public class WordCount extends Configured implements Tool { <br>14.&nbsp;&nbsp; <br>15.&nbsp;&nbsp;&nbsp; public static class Map extends MapReduceBase implements Mapper&lt;LongWritable, Text, Text, IntWritable&gt; { <br>16.&nbsp;&nbsp; <br>17.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static enum Counters { INPUT_WORDS } <br>18.&nbsp;&nbsp; <br>19.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private final static IntWritable one = new IntWritable(1); <br>20.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private Text word = new Text(); <br>21.&nbsp;&nbsp; <br>22.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private boolean caseSensitive = true; <br>23.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private Set&lt;String&gt; patternsToSkip = new HashSet&lt;String&gt;(); <br>24.&nbsp;&nbsp; <br>25.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private long numRecords = 0; <br>26.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private String inputFile; <br>27.&nbsp;&nbsp; <br>28.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void configure(JobConf job) { <br>29.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; caseSensitive = job.getBoolean("wordcount.case.sensitive", true); <br>30.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; inputFile = job.get("map.input.file"); <br>31.&nbsp;&nbsp; <br>32.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (job.getBoolean("wordcount.skip.patterns", false)) { <br>33.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Path[] patternsFiles = new Path[0]; <br>34.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try { <br>35.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; patternsFiles = DistributedCache.getLocalCacheFiles(job); <br>36.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } catch (IOException ioe) { <br>37.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.err.println("Caught exception while getting cached files: " + StringUtils.stringifyException(ioe)); <br>38.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>39.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (Path patternsFile : patternsFiles) { <br>40.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; parseSkipFile(patternsFile); <br>41.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>42.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>43.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>44.&nbsp;&nbsp; <br>45.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private void parseSkipFile(Path patternsFile) { <br>46.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try { <br>47.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BufferedReader fis = new BufferedReader(new FileReader(patternsFile.toString())); <br>48.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String pattern = null; <br>49.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while ((pattern = fis.readLine()) != null) { <br>50.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; patternsToSkip.add(pattern); <br>51.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>52.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } catch (IOException ioe) { <br>53.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.err.println("Caught exception while parsing the cached file '" + patternsFile + "' : " + StringUtils.stringifyException(ioe)); <br>54.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>55.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>56.&nbsp;&nbsp; <br>57.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void map(LongWritable key, Text value, OutputCollector&lt;Text, IntWritable&gt; output, Reporter reporter) throws IOException { <br>58.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; String line = (caseSensitive) ? value.toString() : value.toString().toLowerCase(); <br>59.&nbsp;&nbsp; <br>60.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (String pattern : patternsToSkip) { <br>61.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; line = line.replaceAll(pattern, ""); <br>62.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>63.&nbsp;&nbsp; <br>64.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; StringTokenizer tokenizer = new StringTokenizer(line); <br>65.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while (tokenizer.hasMoreTokens()) { <br>66.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; word.set(tokenizer.nextToken()); <br>67.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; output.collect(word, one); <br>68.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; reporter.incrCounter(Counters.INPUT_WORDS, 1); <br>69.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>70.&nbsp;&nbsp; <br>71.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((++numRecords % 100) == 0) { <br>72.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; reporter.setStatus("Finished processing " + numRecords + " records " + "from the input file: " + inputFile); <br>73.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>74.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>75.&nbsp;&nbsp;&nbsp; } <br>76.&nbsp;&nbsp; <br>77.&nbsp;&nbsp;&nbsp; public static class Reduce extends MapReduceBase implements Reducer&lt;Text, IntWritable, Text, IntWritable&gt; { <br>78.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public void reduce(Text key, Iterator&lt;IntWritable&gt; values, OutputCollector&lt;Text, IntWritable&gt; output, Reporter reporter) throws IOException { <br>79.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int sum = 0; <br>80.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while (values.hasNext()) { <br>81.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sum += values.next().get(); <br>82.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>83.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; output.collect(key, new IntWritable(sum)); <br>84.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>85.&nbsp;&nbsp;&nbsp; } <br>86.&nbsp;&nbsp; <br>87.&nbsp;&nbsp;&nbsp; public int run(String[] args) throws Exception { <br>88.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JobConf conf = new JobConf(getConf(), WordCount.class); <br>89.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setJobName("wordcount"); <br>90.&nbsp;&nbsp; <br>91.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setOutputKeyClass(Text.class); <br>92.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setOutputValueClass(IntWritable.class); <br>93.&nbsp;&nbsp; <br>94.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setMapperClass(Map.class); <br>95.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setCombinerClass(Reduce.class); <br>96.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setReducerClass(Reduce.class); <br>97.&nbsp;&nbsp; <br>98.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setInputFormat(TextInputFormat.class); <br>99.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setOutputFormat(TextOutputFormat.class); <br>100.&nbsp;&nbsp; <br>101.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; List&lt;String&gt; other_args = new ArrayList&lt;String&gt;(); <br>102.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i=0; i &lt; args.length; ++i) { <br>103.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ("-skip".equals(args[i])) { <br>104.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DistributedCache.addCacheFile(new Path(args[++i]).toUri(), conf); <br>105.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; conf.setBoolean("wordcount.skip.patterns", true); <br>106.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } else { <br>107.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; other_args.add(args[i]); <br>108.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>109.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>110.&nbsp;&nbsp; <br>111.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FileInputFormat.setInputPaths(conf, new Path(other_args.get(0))); <br>112.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FileOutputFormat.setOutputPath(conf, new Path(other_args.get(1))); <br>113.&nbsp;&nbsp; <br>114.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; JobClient.runJob(conf); <br>115.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0; <br>116.&nbsp;&nbsp;&nbsp; } <br>117.&nbsp;&nbsp; <br>118.&nbsp;&nbsp;&nbsp; public static void main(String[] args) throws Exception { <br>119.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int res = ToolRunner.run(new Configuration(), new WordCount(), args); <br>120.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.exit(res); <br>121.&nbsp;&nbsp;&nbsp; } <br>122. } <br>123.&nbsp;&nbsp; </p>
<p>运行样例<br>输入样例：</p>
<p>$ bin/hadoop dfs -ls /usr/joe/wordcount/input/ <br>/usr/joe/wordcount/input/file01 <br>/usr/joe/wordcount/input/file02 </p>
<p>$ bin/hadoop dfs -cat /usr/joe/wordcount/input/file01 <br>Hello World, Bye World! </p>
<p>$ bin/hadoop dfs -cat /usr/joe/wordcount/input/file02 <br>Hello Hadoop, Goodbye to hadoop.</p>
<p>运行程序：</p>
<p>$ bin/hadoop jar /usr/joe/wordcount.jar org.myorg.WordCount /usr/joe/wordcount/input /usr/joe/wordcount/output</p>
<p>输出：</p>
<p>$ bin/hadoop dfs -cat /usr/joe/wordcount/output/part-00000 <br>Bye 1 <br>Goodbye 1 <br>Hadoop, 1 <br>Hello 2 <br>World! 1 <br>World, 1 <br>hadoop. 1 <br>to 1 </p>
<p>注意此时的输入与第一个版本的不同，输出的结果也有不同。</p>
<p>现在通过DistributedCache插入一个模式文件，文件中保存了要被忽略的单词模式。</p>
<p>$ hadoop dfs -cat /user/joe/wordcount/patterns.txt <br>\. <br>\, <br>\! <br>to </p>
<p>再运行一次，这次使用更多的选项：</p>
<p>$ bin/hadoop jar /usr/joe/wordcount.jar org.myorg.WordCount -Dwordcount.case.sensitive=true /usr/joe/wordcount/input /usr/joe/wordcount/output -skip /user/joe/wordcount/patterns.txt</p>
<p>应该得到这样的输出：</p>
<p>$ bin/hadoop dfs -cat /usr/joe/wordcount/output/part-00000 <br>Bye 1 <br>Goodbye 1 <br>Hadoop 1 <br>Hello 2 <br>World 2 <br>hadoop 1 </p>
<p>再运行一次，这一次关闭大小写敏感性（case-sensitivity）：</p>
<p>$ bin/hadoop jar /usr/joe/wordcount.jar org.myorg.WordCount -Dwordcount.case.sensitive=false /usr/joe/wordcount/input /usr/joe/wordcount/output -skip /user/joe/wordcount/patterns.txt</p>
<p>输出：</p>
<p>$ bin/hadoop dfs -cat /usr/joe/wordcount/output/part-00000 <br>bye 1 <br>goodbye 1 <br>hadoop 2 <br>hello 2 <br>world 2 </p>
<p>程序要点<br>通过使用一些Map/Reduce框架提供的功能，WordCount的第二个版本在原始版本基础上有了如下的改进：</p>
<p>展示了应用程序如何在Mapper (和Reducer)中通过configure方法 修改配置参数(28-43行)。 <br>展示了作业如何使用DistributedCache 来分发只读数据。 这里允许用户指定单词的模式，在计数时忽略那些符合模式的单词(104行)。 <br>展示Tool接口和GenericOptionsParser处理Hadoop命令行选项的功能 (87-116, 119行)。 <br>展示了应用程序如何使用Counters(68行)，如何通过传递给map（和reduce） 方法的Reporter实例来设置应用程序的状态信息(72行)。 <br>Java和JNI是Sun Microsystems, Inc.在美国和其它国家的注册商标。</p>
<p>&nbsp;</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/superxgl/archive/2010/01/11/5171929.aspx">http://blog.csdn.net/superxgl/archive/2010/01/11/5171929.aspx</a></p>
<img src ="http://www.cppblog.com/koson/aggbug/120774.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/koson/" target="_blank">koson</a> 2010-07-19 14:40 <a href="http://www.cppblog.com/koson/archive/2010/07/19/120774.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于hive的日志数据统计实战</title><link>http://www.cppblog.com/koson/archive/2010/07/19/120773.html</link><dc:creator>koson</dc:creator><author>koson</author><pubDate>Mon, 19 Jul 2010 06:39:00 GMT</pubDate><guid>http://www.cppblog.com/koson/archive/2010/07/19/120773.html</guid><wfw:comment>http://www.cppblog.com/koson/comments/120773.html</wfw:comment><comments>http://www.cppblog.com/koson/archive/2010/07/19/120773.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/koson/comments/commentRss/120773.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/koson/services/trackbacks/120773.html</trackback:ping><description><![CDATA[<span><span><strong><span><span><span><span>
<p><strong><span><span>一、<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><strong><span>hive</span>&nbsp;</strong><strong><span>简介</span></strong><strong></strong></p>
<p><span><strong><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; hive</span>&nbsp;</strong><strong><span>是一个基于</span>&nbsp;</strong><strong><span>hadoop</span>&nbsp;</strong><strong><span>的开源数据仓库工具，用于存储和处理海量结构化数据。</span>&nbsp;</strong><strong><span><span>&nbsp;&nbsp;&nbsp;</span></span></strong><strong><span>它把海量数据存储于</span>&nbsp;</strong><strong><span>hadoop</span>&nbsp;</strong><strong><span>文件系统，而不是数据库，但提供了一套类数据库的数据存储和处理机制，并采用</span>&nbsp;</strong><strong><span>HQL</span>&nbsp;</strong><strong><span>（类</span>&nbsp;</strong><strong><span>SQL</span>&nbsp;</strong><strong><span>）语言对这些数据进行自动化管理和处理。我们可以把</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>中海量结构化数据看成一个个的表，而实际上这些数据是分布式存储在</span>&nbsp;</strong><strong><span>HDFS</span>&nbsp;</strong><strong><span>中的。</span>&nbsp;</strong><strong><span>Hive</span>&nbsp;</strong><strong><span>经过对语句进行解析和转换，最终生成一系列基于</span>&nbsp;</strong><strong><span>hadoop</span>&nbsp;</strong><strong><span>的</span>&nbsp;</strong><strong><span>map/reduce</span>&nbsp;</strong><span>任务，通过执行这些任务完成数据处理<strong><span>。</span></strong></span><strong></strong></span></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>Hive</span>&nbsp;</strong><strong><span>诞生于</span>&nbsp;</strong><strong><span>facebook</span>&nbsp;</strong><strong><span>的日志分析需求，面对海量的结构化数据，</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>以较低的成本完成了以往需要大规模数据库才能完成的任务，并且学习门槛相对较低，应用开发灵活而高效。</span></strong><strong></strong></span></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>Hive</span>&nbsp;</strong><strong><span>自</span>&nbsp;</strong><strong><span>2009.4.29</span>&nbsp;</strong><strong><span>发布第一个官方稳定版</span>&nbsp;</strong><strong><span>0.3.0</span>&nbsp;</strong><strong><span>至今，不过一年的时间，正在慢慢完善，网上能找到的相关资料相当少，尤其中文资料更少，本文结合业务对</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>的应用做了一些探索，并把这些经验做一个总结，所谓前车之鉴，希望读者能少走一些弯路。</span></strong><strong></strong></span></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>Hive</span>&nbsp;</strong><strong><span>的官方</span>&nbsp;</strong><strong><span>wiki</span>&nbsp;</strong><strong><span>请参考这里</span>&nbsp;</strong><strong><span>:</span></strong><strong></strong></span></p>
<p><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><a href="http://wiki.apache.org/hadoop/Hive"><span><span>http://wiki.apache.org/hadoop/Hive</span></span></a></span></strong></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>官方主页在这里：</span></strong><strong></strong></span></p>
<p><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><a href="http://hadoop.apache.org/hive/"><span><span>http://hadoop.apache.org/hive/</span></span></a></span></strong></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>hive-0.5.0</span>&nbsp;</strong><strong><span>源码包和二进制发布包的下载地址</span></strong><strong></strong></span></p>
<p><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><a href="http://labs.renren.com/apache-mirror/hadoop/hive/hive-0.5.0/"><span><span>http://labs.renren.com/apache-mirror/hadoop/hive/hive-0.5.0/</span></span></a></span></strong></p>
<p><strong><span><span>二、<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><strong><span>部署</span></strong><strong></strong></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>由于</span>&nbsp;</strong><strong><span><span>Hive</span>&nbsp;</span></strong><strong><span>是基于</span>&nbsp;</strong><strong><span>hadoop</span>&nbsp;</strong><strong><span>的工具，所以</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>的部署需要一个正常运行的</span>&nbsp;</strong><strong><span>hadoop</span>&nbsp;</strong><strong><span>环境。以下介绍</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>的简单部署和应用。</span></strong><strong></strong></span></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>部署环境：</span></strong><strong></strong></span></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>操作系统：</span>&nbsp;</strong><strong><span>Red Hat Enterprise Linux AS release 4 (Nahant Update 7)</span></strong></span></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>Hadoop</span>&nbsp;</strong><strong><span>：</span>&nbsp;</strong><strong><span>hadoop-0.20.2</span>&nbsp;</strong><strong><span>，正常运行</span></strong><strong></strong></span></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>部署步骤如下：</span></strong><strong></strong></span></p>
<p><strong><span><span><span>1、</span>&nbsp;<span>&nbsp;&nbsp;</span></span></span></strong><span><strong><span>下载最新版本发布包</span>&nbsp;</strong><strong><span><a href="http://labs.renren.com/apache-mirror/hadoop/hive/hive-0.5.0/hive-0.5.0-dev.tar.gz"><span>hive-0.5.0-dev.tar.gz</span>&nbsp;</a></span></strong><strong><span>，传到</span>&nbsp;</strong><strong><span>hadoop</span>&nbsp;</strong><strong><span>的</span>&nbsp;</strong><strong><span>namenode</span>&nbsp;</strong><strong><span>节点上，解压得到</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>目录。假设路径为：</span>&nbsp;</strong><strong><span>/opt/hadoop/hive-0.5.0-bin</span></strong></span></p>
<p><strong><span><span><span>2、</span>&nbsp;<span>&nbsp;&nbsp;</span></span></span></strong><span><strong><span>设置环境变量</span>&nbsp;</strong><strong><span>HIVE_HOME</span>&nbsp;</strong><strong><span>，指向</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>根目录</span>&nbsp;</strong><strong><span>/opt/hadoop/hive-0.5.0-bin</span>&nbsp;</strong><strong><span>。由于</span>&nbsp;</strong><strong><span>hadoop</span>&nbsp;</strong><strong><span>已运行，检查环境变量</span>&nbsp;</strong><strong><span>JAVA_HOME</span>&nbsp;</strong><strong><span>和</span>&nbsp;</strong><strong><span>HADOOP_HOME</span>&nbsp;</strong><strong><span>是否正确有效。</span></strong><strong></strong></span></p>
<p><strong><span><span><span>3、</span>&nbsp;<span>&nbsp;&nbsp;</span></span></span></strong><span><strong><span>切换到</span>&nbsp;</strong><strong><span>$HIVE_HOME</span>&nbsp;</strong><strong><span>目录，</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>配置默认即可，运行</span>&nbsp;</strong><strong><span>bin/hive</span>&nbsp;</strong><strong><span>即可启动</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>，如果正常启动，将会出现&#8220;</span>&nbsp;</strong><strong><span>hive&gt;</span>&nbsp;</strong><strong><span>&#8221;提示符。</span></strong><strong></strong></span></p>
<p><strong><span><span><span>4、</span>&nbsp;<span>&nbsp;&nbsp;</span></span></span></strong><span><strong><span>在命令提示符中输入&#8220;</span>&nbsp;</strong><strong><span>show tables;</span>&nbsp;</strong><strong><span>&#8221;，如果正常运行，说明已部署成功，可供使用。</span></strong><strong></strong></span></p>
<p><span><strong><span>常见问题：</span></strong><strong></strong></span></p>
<p><strong><span><span><span>1、</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>执行&#8220;</span>&nbsp;</strong><strong><span>show tables;</span>&nbsp;</strong><strong><span>&#8221;命令提示&#8220;</span>&nbsp;</strong><strong><span>FAILED: Error in metadata: java.lang.IllegalArgumentException: URI:<span>&nbsp;&nbsp;</span>does not have a scheme</span>&nbsp;</strong><strong><span>&#8221;，这是由于</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>找不到存放元数据库的数据库而导致的，修改</span>&nbsp;</strong><strong><span>conf/</span>&nbsp;</strong><span><span><strong><span>hive-default.xml</span>&nbsp;</strong></span></span><strong><span>配置文件中的</span>&nbsp;</strong><strong><span>hive.metastore.local</span>&nbsp;</strong><strong><span>为</span>&nbsp;</strong><strong><span>true</span>&nbsp;</strong><strong><span>即可。由于</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>把结构化数据的元数据信息放在第三方数据库，此处设置为</span>&nbsp;</strong><strong><span>true</span>&nbsp;</strong><strong><span>，</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>将在本地创建</span>&nbsp;</strong><strong><span>derby</span>&nbsp;</strong><strong><span>数据库用于存放元数据。当然如果有需要也可以采用</span>&nbsp;</strong><strong><span>mysql</span>&nbsp;</strong><strong><span>等第三方数据库存放元数据，不过这时</span>&nbsp;</strong><strong><span>hive.metastore.local</span>&nbsp;</strong><strong><span>的配置值应为</span>&nbsp;</strong><strong><span>false</span>&nbsp;</strong><strong><span>。</span></strong><strong></strong></span></p>
<p><strong><span><span><span>2、</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>如果你已有一套</span>&nbsp;</strong><strong><span>nutch1.0</span>&nbsp;</strong><strong><span>系统正在跑，而你不想单独再去部署一套</span>&nbsp;</strong><strong><span>hadoop</span>&nbsp;</strong><strong><span>环境，你可以直接使用</span>&nbsp;</strong><strong><span>nutch1.0</span>&nbsp;</strong><strong><span>自带的</span>&nbsp;</strong><strong><span>hadoop</span>&nbsp;</strong><strong><span>环境，但这样的部署会导致</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>不能正常运行，提示找不到某些方法。这是由于</span>&nbsp;</strong><strong><span>nutch1.0</span>&nbsp;</strong><strong><span>使用了</span>&nbsp;</strong><strong><span>commons-lang-2.1.jar</span>&nbsp;</strong><strong><span>这个包，而</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>需要的是</span>&nbsp;</strong><strong><span>commons-lang-2.4.jar</span>&nbsp;</strong><strong><span>，下载一个</span>&nbsp;</strong><strong><span>2.4</span>&nbsp;</strong><strong><span>版本的包替换掉</span>&nbsp;</strong><strong><span>2.1</span>&nbsp;</strong><strong><span>即可，</span>&nbsp;</strong><strong><span>nutch</span>&nbsp;</strong><strong><span>和</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>都能正常运行。</span></strong><strong></strong></span></p>
<p><strong><span><span>三、<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><strong><span>应用场景</span></strong><strong></strong></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>本文主要讲述使用</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>的实践，业务不是关键，简要介绍业务场景，本次的任务是对搜索日志数据进行统计分析。</span></strong><strong></strong></span></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>集团搜索刚上线不久，日志量并不大</span>&nbsp;</strong><strong><span>。这些日志分布在</span>&nbsp;</strong><strong><span>5</span>&nbsp;</strong><strong><span>台前端机，按小时保存，并以小时为周期定时将上一小时产生的数据同步到日志分析机，统计数据要求按小时更新。这些统计项，包括关键词搜索量</span>&nbsp;</strong><strong><span>pv</span>&nbsp;</strong><strong><span>，类别访问量，每秒访问量</span>&nbsp;</strong><strong><span>tps</span>&nbsp;</strong><strong><span>等等。</span></strong><strong></strong></span></p>
<p><span><strong><span>基于</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>，我们将这些数据按天为单位建表，每天一个表，后台脚本根据时间戳将每小时同步过来的</span>&nbsp;</strong><strong><span>5</span>&nbsp;</strong><strong><span>台前端机的日志数据合并成一个日志文件，导入</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>系统，每小时同步的日志数据被追加到当天数据表中，导入完成后，当天各项统计项将被重新计算并输出统计结果。</span></strong><strong></strong></span></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>以上需求若直接基于</span>&nbsp;</strong><strong><span>hadoop</span>&nbsp;</strong><strong><span>开发，需要自行管理数据，针对多个统计需求开发不同的</span>&nbsp;</strong><strong><span>map/reduce</span>&nbsp;</strong><strong><span>运算任务，对合并、排序等多项操作进行定制，并检测任务运行状态，工作量并不小。但使用</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>，从导入到分析、排序、去重、结果输出，这些操作都可以运用</span>&nbsp;</strong><strong><span>hql</span>&nbsp;</strong><strong><span>语句来解决，一条语句经过处理被解析成几个任务来运行，即使是关键词访问量增量这种需要同时访问多天数据的较为复杂的需求也能通过表关联这样的语句自动完成，节省了大量工作量。</span></strong><strong></strong></span></p>
<p><strong><span><span>四、<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><strong><span>Hive</span>&nbsp;</strong><strong><span>实战</span></strong><strong></strong></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>初次使用</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>，应该说上手还是挺快的。</span>&nbsp;</strong><strong><span>Hive</span>&nbsp;</strong><strong><span>提供的类</span>&nbsp;</strong><strong><span>SQL</span>&nbsp;</strong><strong><span>语句与</span>&nbsp;</strong><strong><span>mysql</span>&nbsp;</strong><strong><span>语句极为相似，语法上有大量相同的地方，这给我们上手带来了很大的方便，但是要得心应手地写好这些语句，还需要对</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>有较好的了解，才能结合</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>特色写出精妙的语句。</span></strong><strong></strong></span></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>关于</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>语言的详细语法可参考官方</span>&nbsp;</strong><strong><span>wiki</span>&nbsp;</strong><strong><span>的语言手册</span>&nbsp;</strong><strong><span>:</span></strong></span></p>
<p><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><a href="http://wiki.apache.org/hadoop/Hive/LanguageManual"><span><span>http://wiki.apache.org/hadoop/Hive/LanguageManual</span></span></a></span></strong></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>虽然语法风格为我们提供了便利，但初次使用遇到的问题还是不少的，下面针对业务场景谈谈我们遇到的问题，和对</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>功能的定制。</span></strong><strong></strong></span></p>
<p><strong><span><span><span>1、</span>&nbsp;</span></span></strong><span><strong><span>分隔符问题</span></strong><strong></strong></span></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>&nbsp;</span></span>首先遇到的是日志数据的分隔符问题，我们的日志数据的大致格式如下：</span></strong><strong></strong></span></p>
<p><strong><span>2010-05-24 00:00:02@$_$@QQ2010@$_$@all@$_$@NOKIA_1681C@$_$@1@$_$@10@$_$@@$_$@-1@$_$@10@$_$@application@$_$@1</span></strong></p>
<p><strong></strong><span><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>从格式可见其分隔符是&#8220;</span>&nbsp;</strong><strong><span>@$_$@</span>&nbsp;</strong><strong><span>&#8221;，这是为了尽可能防止日志正文出现与分隔符相同的字符而导致数据混淆。本来</span>&nbsp;</strong><strong><span>hive</span></strong><strong><span>支持在建表的时候指定自定义分隔符的，但经过多次测试发现只支持单个字符的自定义分隔符，像&#8220;</span>&nbsp;</strong><strong><span>@$_$@</span>&nbsp;</strong><strong><span>&#8221;这样的分隔符是不能被支持的，但是我们可以通过对分隔符的定制解决这个问题，</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>的内部分隔符是&#8220;</span>&nbsp;</strong><strong><span>\001</span>&nbsp;</strong><strong><span>&#8221;，只要把分隔符替换成&#8220;</span></strong><strong><span>\001</span>&nbsp;</strong><strong><span>&#8221;即可。</span></strong><strong></strong></span></p>
<p><span><strong><span>经过探索我们发现有两条途径解决这个问题。</span></strong><strong></strong></span></p>
<p><strong><span><span><span>a)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>自定义</span>&nbsp;</strong><strong><span>outputformat</span>&nbsp;</strong><strong><span>和</span>&nbsp;</strong><strong><span>inputformat</span>&nbsp;</strong><strong><span>。</span></strong><strong></strong></span></p>
<p><span><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>Hive</span>&nbsp;</strong><strong><span>的</span>&nbsp;</strong><strong><span>outputformat/inputformat</span>&nbsp;</strong><strong><span>与</span>&nbsp;</strong><strong><span>hadoop</span>&nbsp;</strong><strong><span>的</span>&nbsp;</strong><strong><span>outputformat/inputformat</span>&nbsp;</strong><strong><span>相当类似，</span>&nbsp;</strong><strong><span>inputformat</span>&nbsp;</strong><strong><span>负责把输入数据进行格式化，然后提供给</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>，</span>&nbsp;</strong><strong><span>outputformat</span>&nbsp;</strong><strong><span>负责把</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>输出的数据重新格式化成目标格式再输出到文件，这种对格式进行定制的方式较为底层，对其进行定制也相对简单，重写</span>&nbsp;</strong><strong><span>InputFormat</span>&nbsp;</strong><strong><span>中</span>&nbsp;</strong><strong><span>RecordReader</span>&nbsp;</strong><strong><span>类中的</span>&nbsp;</strong><strong><span>next</span>&nbsp;</strong><strong><span>方法即可，示例代码如下：</span></strong><strong></strong></span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></span><strong><span>public</span>&nbsp;</strong><strong><span>boolean</span>&nbsp;</strong><span>next(LongWritable key, BytesWritable value)</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><strong><span>throws</span>&nbsp;</strong><span>IOException {</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></span><strong><span>while</span>&nbsp;</strong><span>(</span>&nbsp;<span><span>reader</span>&nbsp;</span><span><span>.next(key,&nbsp;</span></span><span><span>text</span>&nbsp;</span><span><span>)</span>&nbsp;</span><span>) {</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>String&nbsp;<span>strReplace</span>&nbsp;=&nbsp;</span><span>text</span>&nbsp;<span>.toString().toLowerCase().replace(</span>&nbsp;<span>"@$_$@"</span>&nbsp;<span>,&nbsp;</span><span>"\001"</span>&nbsp;<span>);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span>Text txtReplace =&nbsp;</span><strong><span>new</span>&nbsp;</strong><span>Text();</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span>txtReplace.set(<span>strReplace</span>&nbsp;);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>value.set(txtReplace.getBytes(), 0, txtReplace.getLength());</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><strong><span>return</span>&nbsp;</strong><strong><span>true</span>&nbsp;</strong><span>;</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>}</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;</span></span><strong><span>return</span>&nbsp;</strong><strong><span>false</span>&nbsp;</strong><span>;</span></p>
<p><span>}</span><strong></strong></p>
<p><span><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>重写</span>&nbsp;</strong><strong><span>HiveIgnoreKeyTextOutputFormat</span>&nbsp;</strong><strong><span>中</span>&nbsp;</strong><strong><span>RecordWriter</span>&nbsp;</strong><strong><span>中的</span>&nbsp;</strong><strong><span>write</span>&nbsp;</strong><strong><span>方法，示例代码如下：</span></strong><strong></strong></span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></span><strong><span>public</span>&nbsp;</strong><strong><span>void</span>&nbsp;</strong><span><span>write</span>&nbsp;(Writable w)&nbsp;</span><strong><span>throws</span>&nbsp;</strong><span>IOException {</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>String strReplace = ((Text)w).toString().replace(</span>&nbsp;<span>"\001"</span>&nbsp;<span>,&nbsp;</span><span>"@$_$@"</span>&nbsp;<span>);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>Text txtReplace =&nbsp;</span><strong><span>new</span>&nbsp;</strong><span>Text();</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>txtReplace.set(strReplace);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><strong><span>byte</span>&nbsp;</strong><span>[] output = txtReplace.getBytes();</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span>bytesWritable</span>&nbsp;<span>.set(output, 0, output.</span>&nbsp;<span>length</span>&nbsp;<span>);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>writer</span>&nbsp;.write(</span>&nbsp;<span>bytesWritable</span>&nbsp;<span>);</span></p>
<p><span>}</span></p>
<p><span><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>自定义</span>&nbsp;</strong><strong><span>outputformat/inputformat</span>&nbsp;</strong><strong><span>后，在建表时需要指定</span>&nbsp;</strong><strong><span>outputformat/inputformat</span>&nbsp;</strong><strong><span>，如下示例：</span></strong><strong></strong></span></p>
<p align=left><span>stored as INPUTFORMAT 'com.aspire.search.loganalysis.hive.SearchLogInputFormat' OUTPUTFORMAT 'com.aspire.search.loganalysis.hive.SearchLogOutputFormat'</span><strong></strong></p>
<p><strong><span><span><span>b)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>通过</span>&nbsp;</strong><strong><span>SerDe(serialize/deserialize)</span>&nbsp;</strong><strong><span>，在数据序列化和反序列化时格式化数据。</span></strong><strong></strong></span></p>
<p><span><strong><span>这种方式稍微复杂一点，对数据的控制能力也要弱一些，它使用正则表达式来匹配和处理数据，性能也会有所影响。但它的优点是可以自定义表属性信息</span>&nbsp;</strong><strong><span>SERDEPROPERTIES</span>&nbsp;</strong><strong><span>，在</span>&nbsp;</strong><strong><span>SerDe</span>&nbsp;</strong><strong><span>中通过这些属性信息可以有更多的定制行为。</span></strong><strong></strong></span></p>
<p><strong><span><span><span>2、</span>&nbsp;</span></span></strong><span><strong><span>数据导入导出</span></strong><strong></strong></span></p>
<p><strong><span><span><span>a)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>多版本日志格式的兼容</span></strong><strong></strong></span></p>
<p><span><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>由于</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>的应用场景主要是处理冷数据（只读不写），因此它只支持批量导入和导出数据，并不支持单条数据的写入或更新，所以如果要导入的数据存在某些不太规范的行，则需要我们定制一些扩展功能对其进行处理。</span></strong><strong></strong></span></p>
<p><span><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>我们需要处理的日志数据存在多个版本，各个版本每个字段的数据内容存在一些差异，可能版本</span>&nbsp;</strong><strong><span>A</span>&nbsp;</strong><strong><span>日志数据的第二个列是搜索关键字，但版本</span>&nbsp;</strong><strong><span>B</span>&nbsp;</strong><strong><span>的第二列却是搜索的终端类型，如果这两个版本的日志直接导入</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>中，很明显数据将会混乱，统计结果也不会正确。我们的任务是要使多个版本的日志数据能在</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>数据仓库中共存，且表的</span>&nbsp;</strong><strong><span>input/output</span>&nbsp;</strong><strong><span>操作能够最终映射到正确的日志版本的正确字段。</span></strong><strong></strong></span></p>
<p><span><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>这里我们不关心这部分繁琐的工作，只关心技术实现的关键点，这个功能该在哪里实现才能让</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>认得这些不同格式的数据呢？经过多方尝试，在中间任何环节做这个版本适配都将导致复杂化，最终这个工作还是在</span>&nbsp;</strong><strong><span>inputformat/outputformat</span>&nbsp;</strong><strong><span>中完成最为优雅，毕竟</span>&nbsp;</strong><strong><span>inputformat</span>&nbsp;</strong><strong><span>是源头，</span>&nbsp;</strong><strong><span>outputformat</span>&nbsp;</strong><strong><span>是最终归宿。具体来说，是在前面提到的</span>&nbsp;</strong><strong><span>inputformat</span>&nbsp;</strong><strong><span>的</span>&nbsp;</strong><strong><span>next</span>&nbsp;</strong><strong><span>方法中和在</span>&nbsp;</strong><strong><span>outputformat</span>&nbsp;</strong><span>的</span>&nbsp;<span><span>write</span>&nbsp;</span><span>方法中完成这个适配工作。</span></span></p>
<p><span><span><span>b)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span><span><span><span>Hive</span>&nbsp;</span><span>操作本地数据</span></span></p>
<p><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>一开始，总是把本地数据先传到</span>&nbsp;<span><span>HDFS</span>&nbsp;</span><span>，再由</span>&nbsp;<span><span>hive</span>&nbsp;</span><span>操作</span>&nbsp;<span><span>hdfs</span>&nbsp;</span><span>上的数据，然后再把数据从</span>&nbsp;<span><span>HDFS</span>&nbsp;</span><span>上传回本地数据。后来发现大可不必如此，</span>&nbsp;<span><span>hive</span>&nbsp;</span><span>语句都提供了&#8220;</span>&nbsp;<span><span>local</span>&nbsp;</span><span>&#8221;关键字，支持直接从本地导入数据到</span>&nbsp;<span><span>hive</span>&nbsp;</span><span>，也能从</span>&nbsp;<span><span>hive</span>&nbsp;</span><span>直接导出数据到本地，不过其内部计算时当然是用</span>&nbsp;<span><span>HDFS</span>&nbsp;</span><span>上的数据，只是自动为我们完成导入导出而已。</span></span></p>
<p><strong><span><span><span>3、</span>&nbsp;</span></span></strong><span><strong><span>数据处理</span></strong><strong></strong></span></p>
<p><span><strong><span>日志数据的统计处理在这里反倒没有什么特别之处，就是一些</span>&nbsp;</strong><strong><span>SQL</span>&nbsp;</strong><strong><span>语句而已，也没有什么高深的技巧，不过还是列举一些语句示例，以示</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>处理数据的方便之处，并展示</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>的一些用法。</span></strong><strong></strong></span></p>
<p><strong><span><span><span>a)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>为</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>添加用户定制功能，自定义功能都位于</span>&nbsp;</strong><strong><span>hive_contrib.jar</span>&nbsp;</strong><strong><span>包中</span></strong><strong></strong></span></p>
<p><strong><span><span>add jar /opt/hadoop/hive-0.5.0-bin/lib/hive_contrib.jar;</span></span></strong></p>
<p><strong><span><span><span>b)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>统计每个关键词的搜索量，并按搜索量降序排列，然后把结果存入表</span>&nbsp;</strong><strong><span>keyword_20100603</span>&nbsp;</strong><strong><span>中</span></strong><strong></strong></span></p>
<p><strong><span><span>create table keyword_20100603 as select keyword,count(keyword) as count from searchlog_20100603 group by keyword order by count desc;</span></span></strong></p>
<p><strong><span><span><span>c)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>统计每类用户终端的搜索量，并按搜索量降序排列，然后把结果存入表</span>&nbsp;</strong><strong><span>device_20100603</span>&nbsp;</strong><strong><span>中</span></strong><strong></strong></span></p>
<p><strong><span><span>create table device_20100603 as select device,count(device) as count from searchlog_20100603 group by device order by count desc;</span></span></strong></p>
<p><strong><span><span><span>d)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>创建表</span>&nbsp;</strong><strong><span>time_20100603</span>&nbsp;</strong><strong><span>，使用自定义的</span>&nbsp;</strong><strong><span>INPUTFORMAT</span>&nbsp;</strong><strong><span>和</span>&nbsp;</strong><strong><span>OUTPUTFORMAT</span>&nbsp;</strong><strong><span>，并指定表数据的真实存放位置在</span>&nbsp;</strong><strong><span>'/LogAnalysis/results/time_20100603'</span>&nbsp;</strong><strong><span>（</span>&nbsp;</strong><strong><span>HDFS</span>&nbsp;</strong><strong><span>路径），而不是放在</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>自己的数据目录中</span></strong><strong></strong></span></p>
<p><strong><span><span>create external table if not exists time_20100603(time string, count int) stored as INPUTFORMAT 'com.aspire.search.loganalysis.hive.XmlResultInputFormat' OUTPUTFORMAT 'com.aspire.search.loganalysis.hive.XmlResultOutputFormat' LOCATION '/LogAnalysis/results/time_20100603';</span></span></strong></p>
<p><strong><span><span><span>e)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>统计每秒访问量</span>&nbsp;</strong><strong><span>TPS</span>&nbsp;</strong><strong><span>，按访问量降序排列，并把结果输出到表</span>&nbsp;</strong><strong><span>time_20100603</span>&nbsp;</strong><strong><span>中，这个表我们在上面刚刚定义过，其真实位置在</span>&nbsp;</strong><strong><span>'/LogAnalysis/results/time_20100603'</span>&nbsp;</strong><strong><span>，并且由于</span>&nbsp;</strong><strong><span>XmlResultOutputFormat</span>&nbsp;</strong><strong><span>的格式化，文件内容是</span>&nbsp;</strong><strong><span>XML</span>&nbsp;</strong><strong><span>格式。</span></strong><strong></strong></span></p>
<p><strong><span><span>insert overwrite table time_20100603 select time,count(time) as count from searchlog_20100603 group by time order by count desc;</span></span></strong></p>
<p><strong><span><span><span>f)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>计算每个搜索请求响应时间的最大值，最小值和平均值</span></strong><strong></strong></span></p>
<p><strong><span><span>insert overwrite table response_20100603 select max(responsetime) as max,min(responsetime) as min,avg(responsetime) as avg from searchlog_20100603;</span></span></strong></p>
<p><strong><span><span><span>g)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>创建一个表用于存放今天与昨天的关键词搜索量和增量及其增量比率，表数据位于</span>&nbsp;</strong><strong><span>'/LogAnalysis/results/keyword_20100604_20100603'</span>&nbsp;</strong><strong><span>，内容将是</span>&nbsp;</strong><strong><span>XML</span>&nbsp;</strong><strong><span>格式。</span></strong><strong></strong></span></p>
<p><strong><span><span>create external table if not exists keyword_20100604_20100603(keyword string, count int, increment int, incrementrate double) stored as INPUTFORMAT 'com.aspire.search.loganalysis.hive.XmlResultInputFormat' OUTPUTFORMAT 'com.aspire.search.loganalysis.hive.XmlResultOutputFormat' LOCATION '/LogAnalysis/results/keyword_20100604_20100603';</span></span></strong></p>
<p><strong><span><span><span>h)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>设置表的属性，以便</span>&nbsp;</strong><strong><span>XmlResultInputFormat</span>&nbsp;</strong><strong><span>和</span>&nbsp;</strong><strong><span>XmlResultOutputFormat</span>&nbsp;</strong><strong><span>能根据</span>&nbsp;</strong><strong><span>output.resulttype</span>&nbsp;</strong><strong><span>的不同内容输出不同格式的</span>&nbsp;</strong><strong><span>XML</span>&nbsp;</strong><strong><span>文件。</span></strong><strong></strong></span></p>
<p><strong><span><span>alter table keyword_20100604_20100603 set tblproperties ('output.resulttype'='keyword');</span></span></strong></p>
<p><strong><span><span><span>i)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><span><strong><span>关联今天关键词统计结果表（</span>&nbsp;</strong><strong><span>keyword_20100604</span>&nbsp;</strong><strong><span>）与昨天关键词统计结果表（</span>&nbsp;</strong><strong><span>keyword_20100603</span>&nbsp;</strong><strong><span>），统计今天与昨天同时出现的关键词的搜索次数，今天相对昨天的增量和增量比率，并按增量比率降序排列，结果输出到刚刚定义的</span>&nbsp;</strong><strong><span>keyword_20100604_20100603</span>&nbsp;</strong><strong><span>表中，其数据文件内容将为</span>&nbsp;</strong><strong><span>XML</span>&nbsp;</strong><strong><span>格式。</span></strong><strong></strong></span></p>
<p><strong><span><span>insert overwrite table keyword_20100604_20100603 select cur.keyword, cur.count, cur.count-yes.count as increment, (cur.count-yes.count)/yes.count as incrementrate from keyword_20100604 cur join keyword_20100603 yes on (cur.keyword = yes.keyword) order by incrementrate desc;</span></span></strong></p>
<p><strong><span><span><span>j)</span>&nbsp;<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><strong><span>&nbsp;</span></strong></p>
<p><strong><span>&nbsp;</span></strong></p>
<p><strong><span><span><span>4、</span>&nbsp;</span></span></strong><span><strong><span>用户自定义函数</span>&nbsp;</strong><strong><span>UDF</span></strong></span></p>
<p><span><strong><span>部分统计结果需要以</span>&nbsp;</strong><strong><span>CSV</span>&nbsp;</strong><strong><span>的格式输出，对于这类文件体全是有效内容的文件，不需要像</span>&nbsp;</strong><strong><span>XML</span>&nbsp;</strong><strong><span>一样包含</span>&nbsp;</strong><strong><span>version</span>&nbsp;</strong><strong><span>，</span>&nbsp;</strong><strong><span>encoding</span>&nbsp;</strong><strong><span>等信息的文件头，最适合用</span>&nbsp;</strong><strong><span>UDF(user define function)</span>&nbsp;</strong><strong><span>了。</span></strong><strong></strong></span></p>
<p><span><strong><span>UDF</span>&nbsp;</strong><strong><span>函数可直接应用于</span>&nbsp;</strong><strong><span>select</span>&nbsp;</strong><strong><span>语句，对查询结构做格式化处理之后，再输出内容。自定义</span>&nbsp;</strong><strong><span>UDF</span>&nbsp;</strong><strong><span>需要继承</span>&nbsp;</strong></span><span>org.apache.hadoop.hive.ql.exec.UDF</span>&nbsp;<strong><span><span>，并实现</span>&nbsp;</span></strong><span>evaluate</span>&nbsp;<strong><span><span>函数，</span>&nbsp;</span></strong><span>Evaluate</span>&nbsp;<span><strong><span>函数支持重载，还支持可变参数。我们实现了一个支持可变字符串参数的</span>&nbsp;</strong><strong><span>UDF</span>&nbsp;</strong><strong><span>，支持把</span>&nbsp;</strong><strong><span>select</span>&nbsp;</strong><strong><span>得出的任意个数的不同类型数据转换为字符串后，按</span>&nbsp;</strong><strong><span>CSV</span>&nbsp;</strong><strong><span>格式输出，由于代码较简单，这里给出源码示例：</span></strong><strong></strong></span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></span><strong><span>public</span>&nbsp;</strong><span><span>String</span>&nbsp;evaluate(String... strs) {</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>StringBuilder sb =&nbsp;</span><strong><span>new</span>&nbsp;</strong><span>StringBuilder();</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><strong><span>for</span>&nbsp;</strong><span>(</span>&nbsp;<strong><span>int</span>&nbsp;</strong><span>i = 0; i &lt; strs.</span>&nbsp;<span>length</span>&nbsp;<span>; i++) {</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>sb.append(ConvertCSVField(strs[i])).append(</span>&nbsp;<span>','</span>&nbsp;<span>);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>}</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>sb.deleteCharAt(sb.length()-1);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><strong><span>return</span>&nbsp;</strong><span>sb.toString();</span></p>
<p><span>}</span><strong></strong></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></strong><strong><span>需要注意的是，要使用</span>&nbsp;</strong><strong><span>UDF</span>&nbsp;</strong><strong><span>功能，除了实现自定义</span>&nbsp;</strong><strong><span>UDF</span>&nbsp;</strong><strong><span>外，还需要加入包含</span>&nbsp;</strong><strong><span>UDF</span>&nbsp;</strong><strong><span>的包，示例：</span></strong><strong></strong></span></p>
<p><strong><span><span>add jar /opt/hadoop/hive-0.5.0-bin/lib/hive_contrib.jar;</span></span></strong></p>
<p><span><strong><span>然后创建临时方法，示例：</span></strong><strong></strong></span></p>
<p><strong><span><span>CREATE TEMPORARY FUNCTION Result2CSv AS &#8216;com.aspire.search.loganalysis.hive. Result2CSv';</span></span></strong></p>
<p><span><strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></strong><strong><span>使用完毕还要</span>&nbsp;</strong><strong><span>drop</span>&nbsp;</strong><strong><span>方法，示例：</span></strong><strong></strong></span></p>
<p><span><strong><span>DROP TEMPORARY FUNCTION Result2CSv;</span></strong><strong></strong></span></p>
<p><strong><span><span><span>5、</span>&nbsp;<span>&nbsp;&nbsp;</span></span></span></strong><span><strong><span>输出</span>&nbsp;</strong><strong><span>XML</span>&nbsp;</strong><strong><span>格式的统计结果</span></strong><strong></strong></span></p>
<p><span><strong><span>前面看到部分日志统计结果输出到一个表中，借助</span>&nbsp;</strong><strong><span>XmlResultInputFormat</span>&nbsp;</strong><strong><span>和</span>&nbsp;</strong><strong><span>XmlResultOutputFormat</span>&nbsp;</strong><strong><span>格式化成</span>&nbsp;</strong><strong><span>XML</span>&nbsp;</strong><strong><span>文件，考虑到创建这个表只是为了得到</span>&nbsp;</strong><strong><span>XML</span>&nbsp;</strong><strong><span>格式的输出数据，我们只需实现</span>&nbsp;</strong><strong><span>XmlResultOutputFormat</span>&nbsp;</strong><strong><span>即可，如果还要支持</span>&nbsp;</strong><strong><span>select</span>&nbsp;</strong><strong><span>查询，则我们还需要实现</span>&nbsp;</strong><strong><span>XmlResultInputFormat</span>&nbsp;</strong><strong><span>，这里我们只介绍</span>&nbsp;</strong><strong><span>XmlResultOutputFormat</span>&nbsp;</strong><strong><span>。</span></strong><strong></strong></span></p>
<p><span><strong><span>前面介绍过，定制</span>&nbsp;</strong><strong><span>XmlResultOutputFormat</span>&nbsp;</strong><strong><span>我们只需重写</span>&nbsp;</strong><strong><span>write</span>&nbsp;</strong><strong><span>即可，这个方法将会把</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>的以</span>&nbsp;</strong><strong><span>&#8217;\001&#8217;</span>&nbsp;</strong><strong><span>分隔的多字段数据格式化为我们需要的</span>&nbsp;</strong><strong><span>XML</span>&nbsp;</strong><strong><span>格式，被简化的示例代码如下：</span></strong><strong></strong></span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></span><strong><span>public</span>&nbsp;</strong><strong><span>void</span>&nbsp;</strong><span>write(Writable w)&nbsp;</span><strong><span>throws</span>&nbsp;</strong><span>IOException {</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>String[] strFields = ((Text) w).toString().split(</span>&nbsp;<span>"\001"</span>&nbsp;<span>);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>StringBuffer sbXml =&nbsp;</span><strong><span>new</span>&nbsp;</strong><span>StringBuffer();</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><strong><span>if</span>&nbsp;</strong><span>(</span>&nbsp;<span>strResultType</span>&nbsp;<span>.equals(</span>&nbsp;<span>"keyword"</span>&nbsp;<span>)) {</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span>sbXml.append(</span>&nbsp;<span>"&lt;record&gt;&lt;keyword&gt;"</span>&nbsp;<span>).append(strFields[0]).append(</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span>"&lt;/keyword&gt;&lt;count&gt;"</span>&nbsp;<span>).append(strFields[1]).append(<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span>"&lt;/count&gt;&lt;increment&gt;"</span>&nbsp;<span>).append(strFields[2]).append(</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span>"&lt;/increment&gt;&lt;rate&gt;"</span>&nbsp;<span>).append(strFields[3]).append(</span></p>
<p align=left><span>"&lt;/rate&gt;&lt;/result&gt;"</span>&nbsp;<span>);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>}</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>Text txtXml =&nbsp;</span><strong><span>new</span>&nbsp;</strong><span>Text();</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><strong><span>byte</span>&nbsp;</strong><span>[] strBytes = sbXml.toString().getBytes(</span>&nbsp;<span>"utf-8"</span>&nbsp;<span>);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>txtXml.set(strBytes, 0, strBytes.</span>&nbsp;<span>length</span>&nbsp;<span>);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><strong><span>byte</span>&nbsp;</strong><span>[] output = txtXml.getBytes();</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span>bytesWritable</span>&nbsp;<span>.set(output, 0, output.</span>&nbsp;<span>length</span>&nbsp;<span>);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span><span>writer</span>&nbsp;<span>.write(</span>&nbsp;<span>bytesWritable</span>&nbsp;<span>);</span></p>
<p align=left><span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span>}<strong></strong></span></p>
<p><strong><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>其中的</span>&nbsp;</span></strong><span>strResultType</span>&nbsp;<span>.equals(</span>&nbsp;<span>"keyword"</span>&nbsp;<span>)</span>&nbsp;<span><strong><span>指定关键词统计结果，这个属性来自以下语句对结果类型的指定，通过这个属性我们还可以用同一个</span>&nbsp;</strong><strong><span>outputformat</span>&nbsp;</strong><strong><span>输出多种类型的结果。</span></strong><strong></strong></span></p>
<p><strong><span><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>alter table keyword_20100604_20100603 set tblproperties ('output.resulttype'='keyword');</span></span></strong></p>
<p><span><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>仔细看看</span>&nbsp;</strong><strong><span>write</span>&nbsp;</strong><strong><span>函数的实现便可发现，其实这里只输出了</span>&nbsp;</strong><strong><span>XML</span>&nbsp;</strong><strong><span>文件的正文，而</span>&nbsp;</strong><strong><span>XML</span>&nbsp;</strong><strong><span>的文件头和结束标签在哪里输出呢？所幸我们采用的是基于</span>&nbsp;</strong><strong><span>outputformat</span>&nbsp;</strong><strong><span>的实现，我们可以在构造函数输出</span>&nbsp;</strong><strong><span>version</span>&nbsp;</strong><strong><span>，</span>&nbsp;</strong><strong><span>encoding</span>&nbsp;</strong><strong><span>等文件头信息，在</span>&nbsp;</strong><strong><span>close()</span>&nbsp;</strong><strong><span>方法中输出结束标签。</span></strong><strong></strong></span></p>
<p><span><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>这也是我们为什么不使用</span>&nbsp;</strong><strong><span>UDF</span>&nbsp;</strong><strong><span>来输出结果的原因，自定义</span>&nbsp;</strong><strong><span>UDF</span>&nbsp;</strong><strong><span>函数不能输出文件头和文件尾，对于</span>&nbsp;</strong><strong><span>XML</span>&nbsp;</strong><strong><span>格式的数据无法输出完整格式，只能输出</span>&nbsp;</strong><strong><span>CSV</span>&nbsp;</strong><strong><span>这类所有行都是有效数据的文件。</span></strong><strong></strong></span></p>
<p><strong><span><span>五、<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span></span></strong><strong><span>总结</span></strong><strong></strong></p>
<p><span><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>Hive</span>&nbsp;</strong><strong><span>是一个可扩展性极强的数据仓库工具，借助于</span>&nbsp;</strong><strong><span>hadoop</span>&nbsp;</strong><strong><span>分布式存储计算平台和</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>对</span>&nbsp;</strong><strong><span>SQL</span>&nbsp;</strong><strong><span>语句的理解能力，我们所要做的大部分工作就是输入和输出数据的适配，恰恰这两部分</span>&nbsp;</strong><strong><span>IO</span>&nbsp;</strong><strong><span>格式是千变万化的，我们只需要定制我们自己的输入输出适配器，</span>&nbsp;</strong><strong><span>hive</span></strong><strong><span>将为我们透明化存储和处理这些数据，大大简化我们的工作。本文的重心也正在于此，这部分工作相信每一个做数据分析的朋友都会面对的，希望对您有益。</span></strong><strong></strong></span></p>
<p><span><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>本文介绍了一次相当简单的基于</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>的日志统计实战，对</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>的运用还处于一个相对较浅的层面，目前尚能满足需求。对于一些较复杂的数据分析任务，以上所介绍的经验很可能是不够用的，甚至是</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>做不到的，</span>&nbsp;</strong><strong><span>hive</span>&nbsp;</strong><strong><span>还有很多进阶功能，限于篇幅本文未能涉及，待日后结合具体任务再详细阐述。</span></strong><strong></strong></span></p>
<p><span><strong><span><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></span>如您对本文有任何建议或指教，请评论，谢谢。</span></strong></span></p>
</span></span></span></span></strong></span></span>
<img src ="http://www.cppblog.com/koson/aggbug/120773.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/koson/" target="_blank">koson</a> 2010-07-19 14:39 <a href="http://www.cppblog.com/koson/archive/2010/07/19/120773.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>