﻿<?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++博客-死神永生-文章分类-IR</title><link>http://www.cppblog.com/knzeus/category/18370.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 15 Dec 2011 12:38:50 GMT</lastBuildDate><pubDate>Thu, 15 Dec 2011 12:38:50 GMT</pubDate><ttl>60</ttl><item><title>【转载】Bigtable:结构化数据的分布式存储系统</title><link>http://www.cppblog.com/knzeus/articles/162119.html</link><dc:creator>knzeus</dc:creator><author>knzeus</author><pubDate>Wed, 14 Dec 2011 10:22:00 GMT</pubDate><guid>http://www.cppblog.com/knzeus/articles/162119.html</guid><wfw:comment>http://www.cppblog.com/knzeus/comments/162119.html</wfw:comment><comments>http://www.cppblog.com/knzeus/articles/162119.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/knzeus/comments/commentRss/162119.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/knzeus/services/trackbacks/162119.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 转载请注明：http://duanple.blog.163.com/blog/static/709717672010961173782/&nbsp;作者&nbsp;phylips@bmy摘要Bigtable是设计用来管理那些可能达到很大大小(比如可能是存储在数千台服务器上的数PB的数据)的结构化数据的分布式存储系统。Google的很多项目都将数据存储在Bigtable中，比如网页索引，google...&nbsp;&nbsp;<a href='http://www.cppblog.com/knzeus/articles/162119.html'>阅读全文</a><img src ="http://www.cppblog.com/knzeus/aggbug/162119.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/knzeus/" target="_blank">knzeus</a> 2011-12-14 18:22 <a href="http://www.cppblog.com/knzeus/articles/162119.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】map&amp;reduce介绍(下）</title><link>http://www.cppblog.com/knzeus/articles/162118.html</link><dc:creator>knzeus</dc:creator><author>knzeus</author><pubDate>Wed, 14 Dec 2011 10:14:00 GMT</pubDate><guid>http://www.cppblog.com/knzeus/articles/162118.html</guid><wfw:comment>http://www.cppblog.com/knzeus/comments/162118.html</wfw:comment><comments>http://www.cppblog.com/knzeus/articles/162118.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/knzeus/comments/commentRss/162118.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/knzeus/services/trackbacks/162118.html</trackback:ping><description><![CDATA[<div><span style="font-family: Arial, Helvetica, simsun, u5b8bu4f53; line-height: 25px; background-color: #ffffff; ">&nbsp; 转载请注明：</span><a href="http://duanple.blog.163.com/blog/static/70971767201092673696/" style="line-height: 25px; text-decoration: none; color: #189a0f; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; ">http://duanple.blog.163.com/blog/static/70971767201092673696/</a><a href="http://duanple.blog.163.com/blog/static/709717672010923203501/" style="line-height: 25px; text-decoration: none; color: #189a0f; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; "></a><span style="font-family: Arial, Helvetica, simsun, u5b8bu4f53; line-height: 25px; text-align: left; background-color: #ffffff; ">&nbsp;作者&nbsp;</span><a rel="nofollow" href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#112;&#104;&#121;&#108;&#105;&#112;&#115;&#64;&#98;&#109;&#121;" style="line-height: 25px; text-decoration: none; color: #189a0f; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; ">phylips@bmy</a><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">5.性能</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">在本节中我们将通过运行在大集群的机器上的两个计算来测量MapReduce的性能。一个计算在大概1TB的数据中搜索给定模式的文本。另一个计算对接近1T的数据进行排序。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">这两个程序就可以代表MapReduce用户所写的实际程序中的大部分子集：一类是将数据从一种表现形式转换为另一种表现形式的程序，另一类就是从一个大数据集合中抽取少量感兴趣的数据集。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">5.1&nbsp;&nbsp; 集群配置</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">所有的程序都是在一个由将近1800台机器组成的集群上执行。每台机器有2个打开了超线程的2G Intel Xeon处理器，4GB内存，2个160GB IDE硬盘，一个gigabit 以太网链路。这些机器安排在一个两级的树形交换网络上，根节点具有接近100-200 Gbps的总体带宽。所有机器具有相同的配置，因此在任意两个机器间的往返时间小于1ms。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">在4GB内存中，大概1-1.5G内存预留给在集群上运行的其他task。程序在一个周末的下午执行，此时cpu 硬盘 网络接近空闲。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;5.2&nbsp;&nbsp; Grep</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">Grep程序通过扫描10^10个100字节的记录，查找一个很少出现的三字符模式(该模式出现在92337个记录里)。输入被划分为近似64MB大小的片段(M=15000)，整个输出被放在一个文件中(R=1)。</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; "><img alt="【google论文三】MapReduce:简化大集群上的数据处理(下) - 星星 - 银河里的星星" src="http://img.ph.126.net/Theomtpztf7K3N2V6woA4w==/3360248271972909575.png" width="338" height="203" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; width: 319px; height: 188px; " />&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; "></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">图2展示了整个计算的处理过程。Y轴表示输入数据的扫描速率。伴随这安排用于进行该MapReduce操作的机器数的增多，该速率也在逐渐攀升，当有1764个worker被分配该任务后达到了30GB/s的峰值。当map task结束后，该速率开始下降，大概在80秒的时候基本上降为0。整个计算过程花费了接近150秒，这包括一分钟的启动时间(这个开销主要是由将程序传输给所有worker，与GFS交互以打开1000个输入文件以及得到本地化优化所需要的信息造成的)。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;5.3&nbsp;&nbsp; 排序</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">排序程序对10^10个100字节的记录进行排序(接近1TB数据)。这个程序根据TeraSort Benchmark进行了建模。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;排序程序总共由不到50行用户代码组成。Map函数只有3行，将10字节长的排序用key值从一个文本行中抽取出来，然后输出该key，以及原始的文本行，作为中间结果key/value对。我们使用内建的Identity函数作为reduce操作。该函数将中间结果不过任何改变地输出。最后的排好序的结果写到一个具有2个副本的GFS文件集合上(即该程序将会产生2TB的输出)。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;与之前的类似，输入数据被划分为64MB的片段(M=15000)。排好序的输出被划分为4000个输出(R=4000)。划分函数使用key的字节表示来将它们划分为R个片段。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;对于该benchmark的划分函数建立在对于key值分布的了解上。对于一个通常的排序问题里，我们会增加一个预先进行的MapReduce操作，该操作会收集key值的采样值，然后使用这些key值的采样来计算最终排序序列的划分点。</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; "><img alt="【google论文三】MapReduce:简化大集群上的数据处理(下) - 星星 - 银河里的星星" src="http://img.ph.126.net/IxxvbPiVeBFeVUoB5J4gXw==/3360248271972909603.png" width="543" height="409" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; width: 539px; height: 391px; " />&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; "></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">图3(a)展示了该排序程序的一个正常的处理过程。左上角的图表示输入速率。峰值速率大概是13GB/s，由于所有的map task在200秒前都结束了，所以该速率下降的很快。可以看到，输入速率要小于grep。这是因为排序的map task花费了大概一半的时间和IO带宽将中间结果写入到本地硬盘上。而与之相比，grep的中间结果输出几乎可以忽略不及。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;左边中间的图展示了数据从Map task向reduce task的网络传输速率。当第一个map task完成后，shuffling就开始了。图中的第一个峰值是由于第一批的1700个reduce task都启动后产生的(整个MapReduce操作被分配给大概大概1700个机器，每个机器同一时刻最多执行一个Reduce task)。大体上在300秒的时候，这一批的reduce task结束，然后启动了剩余的reduce task的shffling过程。大概在600秒时，这些shuffling才结束。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;左边最底下的图形展示了reduce task将排好序的数据写入最终输出文件的速率。在第一次的shuffling的结束与数据写入开始之间存在一个延时，是因为机器此时正忙着对中间数据进行排序。写入过程在一段时间内大概持续着大概2-4GB/s的速率。所有的写入大概在850秒的时候结束。假设启动的花费，整个计算花费了891秒。这接近于当前Terasort benchmark的最快结果1057秒。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;另外需要指出的是：输入速率比shuffle速率和输出速率高是因为我们的本地化优化(大部分的数据都是从本地硬盘读取的，这就绕过了网络带宽的限制)。Shuffle速率比输出速率高是因为输出阶段要写两份拷贝(保存两份是为了可靠性和可用性){这两份拷贝是需要耗费网络带宽的}。写两个副本是因为这是我们的底层文件系统提供的可靠性和可用性机制。如果底层文件系统使用了erasure code而不是副本，对于写数据的网络带宽需求将会减少。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;5.4&nbsp;&nbsp; 任务备份的影响</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">在图3(b),我们展示了一个没有开启任务备份的排序程序的执行过程。执行流类似与我们在图3(a)里看到的那样。除了在繁重的写活动出现后出现了一个长尾。在960秒时，只剩下5个reduce task还没有完成。然而这些掉队者，在300秒后才完成。整个计算花费了1283秒，增加了44%。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">5.5&nbsp;&nbsp; 机器失败</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">图3(3),展示了我们在计算执行几分钟后，杀掉1746个worker里面的200个后的执行过程。底层的集群调度器，立刻重启在这些机器上的worker进程(因为只是进程被杀掉了，机器仍然是可用的)。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;死掉的worker作为负的输入速率进行显示，因为前面以及完成的map task的工作都消失了需要重新执行。Map task的重新执行相对较快。加上启动时间，整个计算过程在933秒的时候结束，仅仅比正常情况下的执行时间增加了5%。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;6.&nbsp; 经验</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">在2003年2月，我们写出了第一版的MapReduce库，2007年8月对它进行了很多包括本地化优化，跨机器的任务执行的动态负载平衡等等在内的改进。从那时起，我们欣喜的发现MapReduce库可以如此广泛地应用在我们工作中的各种问题上。目前它已经在google内部应用在广泛的领域上：&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">大规模机器学习问题</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">用于Google新闻和购物的聚类问题</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">找到最流行的查询词</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">为了实验或者产品从网页中抽取属性(比如为了本地化搜索从大量网页中抽取地理位置)</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">大规模图形计算</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; "><img alt="【google论文三】MapReduce:简化大集群上的数据处理(下) - 星星 - 银河里的星星" src="http://img.ph.126.net/uqlXnomcO03wlBcym2oYLQ==/3360248271972909621.png" width="373" height="329" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; width: 340px; height: 253px; " />&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; "></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">图4展示了过去的时间里，提交到我们的源代码管理系统中的MapReduce程序的数目。从2003年的0到2004年9月接近900个。MapReduce之所以如此成功，是因为它使得在半小时内写出一个简单地可以在数千台机器上跑的程序成为可能。这大大加速了我们的开发和原型周期。此外，它还使得没有分布式或者并行系统编程经验的程序员可以很容易地使用大量的计算资源。</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; "><img alt="【google论文三】MapReduce:简化大集群上的数据处理(下) - 星星 - 银河里的星星" src="http://img.ph.126.net/HMe_frOUSSg4qO9sWgw8rw==/3360248271972909625.png" width="386" height="337" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; " />&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;在每个job结束时，MapReduce库还会记录该job使用的计算资源的统计信息。表1，我们展示了2004年8月，在google内部运行MapReduce job的一个子集的一些统计信息。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; "></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">6.1&nbsp;&nbsp; 大规模索引</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">目前为止，我们一个最重要的MapReduce应用就是用它完全重写了产品索引系统，该系统为google的网页搜索服务产生所需要的数据结果。索引系统以一个由爬虫抓取的存储在GFS上的很大的文档集合作为输入，总共数据量要超过20TB。索引流程由5到10个MapReduce操作组成。通过使用MapReduce(而不是使用之前版本的索引系统所使用的自适应的分布式传输)有如下几个优点：</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">索引代码很简单，少而且容易理解。因为用于容错，分布和并行化的代码都隐藏在了MapReduce库中。比如，我们通过使用MapReduce将原来的一个计算过程的代码量从3800行降低到了700行。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;MapReduce库的性能以及足够好了，这样我们就能将不相关地计算分离，而不是为了降低额外的传输费用而将它们合在一块。这使得我们很容易改变索引处理过程。比如过去在旧系统中可能需要几个月才能完成的变更，现在在新的系统中几天就可以完成。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;索引处理流程变得很容易操作。因为大部分由于机器失败，慢机器以及网络引发的问题都由MapReduce库自动处理掉了，不需要进行额外的干预。另外也很容易通过给索引系统增加新机器来提高性能。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">7.&nbsp; 相关工作</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">已经有很多系统提供了严格的编程模型，使用了很多限制来进行计算的并行化。MapReduce模型可以看做是基于我们的在现实中的海量计算经验，对这些模型的一个简化和提炼。更重要的是，我们提供了一个可以扩展到数千个处理器上的容错实现。与之相比，大部分的并行处理系统只是在小规模集群上实现的，将机器错误处理交给程序员。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;大同步模型和一些MPI实现为简化程序员编写并行程序提供了更高级别的抽象。这些系统与MapReduce的一个关键不同就是MapReduce使用了一个限制性的编程模型来为用户程序提供自动地并行化和透明的容错机制。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;我们的本地化优化策略主要源于这样的一些技术，比如active disks，在那里为了降低IO或者网络的数据传输，计算被放到那些靠近本地硬盘的处理元素中执行。我们是在由少量硬盘直接连接的PC上运行而不是在一个磁盘控制处理器上运行，但是策略是类似的。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;我们的任务备份机制类似于Charlotte系统中使用的eager调度机制。简单eager调度机制的一个缺点是如果给定的task引发了重复的失败，整个计算就无法完成。我们通过跳过坏记录的方式解决了这样的问题。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;MapReduce实现依赖于内部开发的一个集群管理系统，它负责在一个机器集合上分布调度用户任务。尽管不是本文关注的重点，该集群管理系统类似于Condor。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;作为MapReduce库的一部分的排序设施在操作过程上类似于Now-sort。源机器(map task)将数据划分进行排序，然后将每份传递给一个R个reduce worker中的一个。每个reduce worker在本地进行排序(如果可能的话就仅使用内存排序)。当然NOW-sort并不包含使得我们的库应用广泛的Map和Reduce函数。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Rive提供了一个进程间通过分布式队列进行数据传输的编程模型。像MapReduce一样，River尽量提高系统的平均性能，即使是由于硬件异构或者系统扰动出现了非对称的情况。River通过仔细的硬盘和网络传输调度来达到平衡的完成时间。MapReduce使用了不同的策略。通过限制编程模型，MapReduce框架能将问题划分为大量的细粒度task。这些task可以在可用的worker上进行动态的调度，这样跑的快的worker就可以处理更多的task。该编程模型也允许我们在job快结束的时候调度task进行冗余的执行，这样大大减少了非对称出现时的完成时间。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;BAD-FS有一个与MapReduce完全不同的编程模型。与MapReduce不同，它的目标是降低在广域网上的job的执行时间。但是，它们具有两个基本的相同点：1.都采用了冗余执行从失败导致数据丢失中快速恢复 2.都采用了本地化优化以降低数据在网络上的传输。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;TACC是一个设计用户简化构建高可用网络服务的系统。与MapReduce类似，它依赖于重新执行作为实现容错的一个机制。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;8.&nbsp;&nbsp;总结</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">MapReduce编程模型已经因各种目的在google内部成功使用。我们将这种成功归为几个原因。首先，模型很容易使用，即使对于没有分布式编程经验的程序员来说也是，因为它隐藏了并行化，容错，本地化优化，负载平衡的细节。第二，大量的问题可以简单地用MapReduce计算来表达。比如MapReduce被用来为google的网页搜索服务，排序，数据挖掘，机器学习很多其他的系统生成数据。第三，我们开发了一个可以扩展到数千台机器上MapReduce实现。该实现可以充分利用机器的资源，因此很适合用来处理在google碰到的很多大规模计算问题。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;通过这项工作我们也学到了很多。首先，通过限制编程模型可以使计算的并行化和分布很简单，同时也能让它容错。第二，网络带宽是一种稀缺资源。我们系统中大量的优化都是为了降低网络传输数据量：本地化优化允许我们从本地磁盘上读数据，将单份拷贝的中间数据写入本地磁盘节省了网络带宽。第三，冗余执行能用来降低慢机子的影响，以及用来处理机器失败和数据丢失。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">致谢：</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&#8230;&#8230;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">MapReduce从GFS上读取输入数据以及写出输出数据，因此我们要感谢&#8230;在开发GFS上的工作&#8230;我们还要感谢&#8230;在开发MapReduce使用的集群管理系统上的工作&#8230;&#8230;</p></div><img src ="http://www.cppblog.com/knzeus/aggbug/162118.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/knzeus/" target="_blank">knzeus</a> 2011-12-14 18:14 <a href="http://www.cppblog.com/knzeus/articles/162118.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】map&amp;reduce介绍(上）</title><link>http://www.cppblog.com/knzeus/articles/162113.html</link><dc:creator>knzeus</dc:creator><author>knzeus</author><pubDate>Wed, 14 Dec 2011 09:43:00 GMT</pubDate><guid>http://www.cppblog.com/knzeus/articles/162113.html</guid><wfw:comment>http://www.cppblog.com/knzeus/comments/162113.html</wfw:comment><comments>http://www.cppblog.com/knzeus/articles/162113.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/knzeus/comments/commentRss/162113.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/knzeus/services/trackbacks/162113.html</trackback:ping><description><![CDATA[<div><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; ">转载请注明：<a href="http://duanple.blog.163.com/blog/static/709717672010923203501/" style="text-decoration: none; color: #189a0f; ">http://duanple.blog.163.com/blog/static/709717672010923203501/</a>&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; ">作者&nbsp;<a rel="nofollow" href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#112;&#104;&#121;&#108;&#105;&#112;&#115;&#64;&#98;&#109;&#121;" style="text-decoration: none; color: #189a0f; ">phylips@bmy</a></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; ">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 摘要：</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">MapReduce是一个编程模型以及用来处理和生成大数据集的一个相关实现。用户通过描述一个map函数，处理一组key/value对进而生成一组key/value对的中间结果，然后描述一个reduce函数，将具有相同key的中间结果进行归并。正如论文所表明的，很多现实世界中的任务都可以用这个模型来表达。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">以这种函数式风格写出来的程序在一个由普通机器组成的集群上自动的进行并行化和执行。由一个运行时系统来关注输入数据的划分细节，在机器集合上的程序执行调度，处理机器失败以及管理所需要的机器间的通信。这就允许那些没有并行分布式系统编程经验的程序员很容易的使用大型分布式系统的资源。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">我们的MapReduce实现运行在一个有很多普通机器组成的集群上，而且具有高扩展性：一个典型的MapReduce计算将会在一个数千台机器的集群上处理很多T的数据。对于程序员来说，这个系统很好用，目前已经有数百个MapReduce程序实现，在google的集群上每天有上千个MapReduce job在跑。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">1.导引</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">在过去的五年来，作者和google的其他工程师已经实现了数百了用于特殊目的在大量原始数据(比如爬虫爬的文档，web访问日志等等)上进行的运算。为了计算各种类型的衍生数据：比如倒排索引，网页文档的图结构的各种不同表示，每个host的网页数，给定的一天中最常查询集合。大部分这样的计算在概念上都是很直接的。然而由于输入数据通常是很庞大的，因此为了能在合理的时间内结束，计算就不得不分布在成百上千台机器上执行。如何并行化计算，分布数据，处理错误都会使得原本简单的计算需要大量额外的代码去处理这些问题。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;为了应对这种复杂性，我们设计了一种抽象，使得我们可以表达我们需要执行的这种简单的运算，而将并行化，容错，数据分布，负载平衡这样的细节封装在库里。我们的抽象源于Lisp以及其他函数式编程语言中的map-reduce原语。我们发现我们大部分的计算都是首先在输入的每条记录上执行一个map操作以产生一个key/value的中间结果集合，然后为了得到相应的派生数据，对那些具有相同key的值应用一个reduce操作。通过使用由用户描述的map和reduce操作组成的函数式模型，使得我们很容易的进行计算并行化，同时使用重新执行作为基本的容错机制。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">文章的主要贡献就是提供了一个允许对大规模计算进行自动并行化以及数据分布的简单有力的接口。同时提供了一个可以在普通pc机组成的大集群上达到很高的性能针对该接口的实现。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">第2节描述了基本的编程模型并且给出了几个简单例子。第3节描述了一个面向我们的基于集群的计算环境的该接口的实现。第4节描述了该模型中我们认为很有用的几个概念。第5节对我们的实现通过几个task进行了测试。第6节介绍了MapReduce在google内部的使用，包括使用它重写我们的产品索引系统的一些经验。第7节讨论了相关的以及未来的工作。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2.编程模型</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">计算有一个key/value输入对集合，产生一系列的输出key/value对。Mapreduce库的用户通过两个函数：Map和Reduce来表达这个计算。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Map，由用户编写，有一个输入对，产生一集key/value对的中间结果。Mapreduce库将具有相同key(比如I)的那些中间值组织起来，然后将它们传给Reduce函数。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Reduce函数，也是由用户编写，接受一个中间值key(比如I)，以及对应于该key的value集合作为输入。它将这些value归并起来形成一个可能更小的value集合。通常每个Reduce调用产生0个或者1个输出值。中间值的value集合是通过一个迭代器来提供给用户的Reduce函数。这允许我们能处理那些太大以至于无法一次放入内存的value列表。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2.1例子</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">考虑在一个大文档集合中计算单词出现频率的问题。用户可以用类似如下伪代码的方式来编写代码。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">map(String key, String value):<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// key: document name<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// value: document contents<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for each word w in value:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EmitIntermediate(w, "1");&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">reduce(String key, Iterator values):<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// key: a word<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // values: a list of counts<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int result = 0;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for each v in values:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; result += ParseInt(v);<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Emit(AsString(result));</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">Map函数输出每个单词及其相应出现次数(在这个简单例子中，就是1)reduce函数将所有的次数加起来然后为每一个单词输出它。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;另外用户还需要向一个MapReduce描述对象中填写输入输出文件名称以及一些可选的参数。然后用户调用Mapreduce函数，将该描述对象传给它。用户代码需要链接Mapreduce库(采用c++实现)。附录A包含该例子的完整代码。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2.2类型</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">尽管前面的例子是以字符串类型作为输入输出，概念上来说用户提供的map和reduce函数有如下的类型关联；</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">Map: &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(k1,v1)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;-&gt; list(k2,v2)</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">Reduce: &nbsp;(k2,list(v2)) &nbsp;-&gt; list(v2)</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">即输入的key和value与输出的key value来自于不同的域，另外中间结果的key value与输出的key value具有相同的域。｛-&gt;前后分别代表了输入输出，所以list(k2,v2)代表了中间结果，可以看到中间结果与Reduce的key value名称都是k2,v2,以表示它们具有相同的域。也就是说k1和k2，v1和v2所代表的实际含义可能是不同的，比如url访问频率计数,map的输入就是&lt;logname,log&gt;,而中间结果与reduce则是&lt;url,出现次数&gt;｝</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;我们的用户程序以字符串形式传递给或者接受自用户定义函数，将字符串与相应类型的转换交给用户代码处理。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">2.3更多的实例</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">下面有一些可以使用MapReduce进行计算的简单而有趣的例子。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;分布式Grep：map函数输出该行如果它与给定的模式匹配。Reduce函数只需要将给定的中间结果输出。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;url访问频率计数：map函数处理网页访问日志，输出&lt;URL,1&gt;。Reduce函数将相同URL的value加起来，然后输出&lt;URL,total count&gt;对。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;网页链接逆向图：map函数输入&lt;target,source&gt;对表示从网页source到target URL的一条链接。Reduce函数将给定Target URL的所有source URL连接到一块，然后输出&lt;target,list(source)&gt;。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Host短语向量：一个term vector是指出现在一个文档或者文档集合中的最重要的单词的&lt;word,frequency&gt;对列表。Map函数对每一个文档输出&lt;hostname,term vector&gt;(host是从该文档对应的url中抽取出来)。Reduce函数接受到一个给定host的所有文档的term vector。它将这些term vector合并，并扔掉不常出现的那些terms，然后输出一个最终的&lt;hostname,term vector&gt;对。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;倒排索引：map函数解析每个文档，输出一个&lt;word,docid&gt;对序列。Reduce函数接受一个给定word的所有序列，对相应的docid进行排序，输出一个&lt;word,list(docid)&gt;。所有的输出对集合就形成了一个简单的倒排索引。通过很简单的改动，我们就可以让这个计算同时记住单词的在文档中的出现位置。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;分布式排序：map函数从每条记录中提取key，然后简单的输出&lt;key,record&gt;对。Reduce函数原样地输出所有的对。该计算依赖于4.1节描述的划分功能以及4.2节描述的排序属性。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;3.实现</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">对于MapReduce接口可以有很多不同的实现。正确的选择依赖于环境。比如一个实现可能适用于小型共享内存机器，另一个可能适用于一个大的NUMA多处理机，另一个适用于更大的通过网络互联的集群。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;本节描述一个面向google内部广泛使用的计算环境(由普通pc通过以太网交换机连接而成的大集群)的实现。在我们的环境里：</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">1.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 机器主要是运行linux的双核x86处理器，每台机器具有2-4GB内存</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">2.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用通用的网络硬件：在机器级别上，通常不是100Mbps就是1Gbps，平均下来，整体的等分带宽要低些。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">3.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 集群由成百上千台机器组成，因此失败变得很普通</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">4.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 存储是由连接到单个机器上的廉价的IDE硬盘提供的。一个内部开发的分布式文件系统被用来管理存储在硬盘上的数据。文件系统通过备份来为不可靠的硬件提供可用性和可靠性。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">5.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 用户提交job到调度系统。每个job由一组task组成，job被调度系统映射到集群中的一组可用机器集合上去执行。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;3.1 执行概览</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">通过自动将输入数据划分为M个片段，使得Map调用可以跨越多个机器执行。这些输入片段可以被不同的机器并行处理。Reduce调用的分布，是通过使用一个划分函数(比如hash(key) mod R)将中间结果的key的值域空间划分为R个片段。片段的个数R以及划分函数都是由用户描述的。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;图1展示了一个MapReduce操作在我们的实现中的整体流程。当用户程序调用MapReduce函数时，将会产生如下的动作序列(图中的标号与如下描述中的数字相对应)：</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; "><img alt="【google论文三】MapReduce:简化大集群上的数据处理(上) - 星星 - 银河里的星星" src="http://img.ph.126.net/iho4v-vDn3yBme9NIvl3ng==/946600346678478243.png" width="662" height="505" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; width: 578px; height: 459px; " /></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; "></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">1.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 用户程序中的MapReduce库首先将输入文件切分为M个片段(每个片段大小通常是16MB到64MB，该大小用户可以通过一个配置参数控制)。然后在一组机器集上启动该程序的所有拷贝。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">2.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在这些程序拷贝中有一个是特殊的：the master。其余的是称为worker，由master为它们分配任务。总共有M个map task和R个reduce task需要分配。Master选择空闲的worker，给它们每个分配一个map或者reduce task。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">3.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 被分配了map task的worker读取相应的输入片段内容。它从输入中解析出key/value对的集合，将每个对传递给用户定义的map函数处理。由map函数生成的中间结果被缓存在内存里。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">4.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 被缓存的那些对，通过划分函数被分成R个区域，然后周期性的被写入本地磁盘。然后将这些缓存对在本地磁盘上的位置返回给master，master再负责将这些位置信息传递给reduce worker。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">5.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 当一个reduce worker被master通知了这些位置信息后，它就使用RPC调用去map worker的本地磁盘里读取这些缓冲数据。当一个reduce worker已经读取了所有的缓冲数据后，它就将它们根据key值进行排序，以让具有相同key值的被组织在一块。排序是需要的因为通常很多不同的key值会被映射到同一个reduce task。如果中间结果的数据量太大以至于无法放入内存，就需要进行外排序。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">6.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Reduce worker在排好序的中间结果数据上迭代，对于碰到的每个唯一的中间key值，它就将该key值，以及与它对应的value集合传递给用户定义的reduce函数。该reduce函数的输出会被append到这个reduce worker的最终输出文件上。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">7.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 当所有的map task和reduce task完成后，master唤醒用户程序。这时用户程序从MapReduce调用里返回。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;成功完成后，MapReduce执行结果将会产生R个输出文件(一个reduce task对应一个，文件名由用户指定)。通常，用户不需要将这R个输出文件合并为一个文件，它们通常会作为另一个MapReduce调用的输入。或者通过另一个可以处理输入是划分为多个文件的分布式应用程序来使用它们。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;3.2 master数据结构</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">Master保存了几个数据结构。对于每个map和reduce task，它会保存它们的状态(空闲,处理中，完成)以及worker所在机器的标识(针对非空闲task)。｛注意task与work之间的关系，不是一对一的，一个worker可能处理多个task｝</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Master是将中间结果文件位置从map task传递到reduce task的渠道。因此对于每个完成的map task，master会保存由它产生的R个中间结果文件的大小及位置。当map task结束后，将会收到对于这些位置和大小信息的更新。这些信息又会被逐步推送给那些包含正在处理中的reduce task的worker。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;3.3 容错</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">因为MapReduce库是设计用来帮助在成百上千台集群上处理大量数据的，所以这个库就必须能够优雅地容忍机器失败。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Worker失败</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">Master周期性的ping每个worker。如果在一定时间内没有收到某个worker的响应，就会把它标记为失败。由该worker执行完成的那些map task{注意reduce task不需要}都必须重置为空闲状态。这样，它们就又可以被调度到其他的worker上重新执行。类似的，那些该woker上执行中的任何map reduce task{注意reduce task也需要}，必须重置为空闲状态，重新加入调度。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">已经完成的map task需要重新执行，是因为它们的输出被存储在失败的那台机器的本地磁盘上，因此就变成了不可访问的。已经完成的reduce task不需要重新执行，是因为它们的输出存放在一个全局文件系统上。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">当一个map task首先由work A执行，然后又有worker B执行(因为A失败了)。所有正在执行reduce task的worker都会收到该重新执行的通知。任何已经不能从worker A读数据的reduce task都将会从worker B读取。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;MapReduce可以很有效的应对大规模的worker失败。比如，在一个MapReduce操作期间，在一个运行中的集群上的网络维护可能导致80台机器几分钟内同时无法访问。MapReduce master简单的重新执行那些不可达机器上的任务。继续推进整个计算过程，最后完成该MapReduce操作。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Master失败</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">很容易让master写入上面描述的数据结构的周期性检查点。如果master死掉后，就可以从上次的检查点状态开始启动一个新的拷贝。然而，由于只有一个master，而且它的失败也是不太可能的{很明显如果有多个master，那么失败的概率就会大大上升}，因此在我们当前的实现中，如果master失败我们就结束MapReduce计算。在这种情况下，客户端可以进行检查，如果需要可以重试它们的MapReduce操作。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">失败出现时语义</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">当用户提供的map和reduce函数，针对它们的输入是一个确定性函数时，我们的分布式实现应该与整个程序串行执行时产生相同的输出。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;我们通过map和reduce task输出的原子性提交来实现这个属性。每个执行中的task将它们的输出写入私有的temp文件里。一个Reduce task产生一个这样的文件，一个map task产生R个这样的文件。当一个map task完成后，worker会给master发送一个包含这个R个temp文件名称的消息。如果master收到一个已经完成的map task的完成消息，它会忽略该消息。否则，它会将这个R个文件的名称记录在自己的一个数据结构中。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">当reduce task完成后，reduce worker会自动把temp输出文件重命名为最终的输出文件。如果相同的reduce task在多个机器上执行，多个重命名操作将会在同一个最终输出文件上执行。我们依赖于底层文件系统提供的原子性的重命名操作来保证最终的文件系统中只会包含一个reduce task执行产生的数据。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;map和reduce函数是确定性的以及等价于相同情况下串行执行结果的语义，主要优点是使得程序员可以很容易地理解程序的行为。当map和reduce操作不是确定性的时候，我们提供了虽然弱些但是仍然合理的语义。在出现不确定性操作时，一个特殊的reduce task R1的输出等价于该非确定性程序针对R1的串行执行输出。但是对于另一个reduce task R2，就可能对应另一个不同的非确定性程序的串行执行结果。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;考虑map task M和reduce task R1和R2.让e(Ri)代表已经完成的Ri的执行过程。语义可能会变得更弱，因为e(R1)可能读取了M某次执行的输出结果，而e(R2)可能读取了M另一次执行的输出。{首先M是不确定性的，其次M可能被重新执行过，这样R1 R2虽然读的是同一个task的输出，但是可能读取了不同的输出结果}。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;3.4 局部性</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">在我们的计算环境中，网络带宽是相对宝贵的。我们通过利用输入数据(由GFS管理)是存储在机器的本地磁盘上的这一事实来节省网络带宽。GFS将每个文件划分为64MB大小的块，每个块的几个副本存储在不同的机器上。MapReduce master充分考虑输入文件的位置信息，尽量将一个map task调度到包含相应的输入数据副本的那个机器上。如果不行，就尝试将map task调度到该task的输入数据附近的那些机器(比如让worker所在的机器与包含该数据的机器在同一个网络交换机上)。当在一个集群上运行一个具有很多worker的大型MapReduce操作时，大部分的输入数据都是从本地读取的，很少消耗网络带宽。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;3.5 任务粒度</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">如上所述，我们将map阶段划分为M个片段，将reduce阶段划分为R个片段。理想情况下，M和R都应当远远大于运行worker的机器数目。让每个worker执行很多不同的task可以提高动态负载平衡，也能加速worker失败后的恢复过程：它已经完成的很多map task可以传给所有其他机器。在我们的实现中M和R到底可以多大，有一些实际的限制。因为master必须进行O(M+R)个调度决定以及在内存中保存O(M*R)个状态｛即每个map task的R个输出文件的位置信息，总共M个task，所以是M*R｝。 (但是关于内存使用的常数因子是很小的：O(M*R)个状态大概由每个map task/reduce 对的一字节数据组成)。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;另外，R通常会被用户限制，因为每个reduce task的输出在不同的输出文件中。在实际中，我们通常这样选择M：使每个独立task输入数据限制在16MB到64MB之间(这样上面所说的本地化优化是最有效的)。我们让R大概是我们将要使用的worker机器的几倍。我们通常这样执行MapReduce操作，在有2000个worker机器时，让M = 20000,R = 5000。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;3.6 备份任务</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">一个影响MapReduce操作整体执行时间的最常见的因素是&#8221;掉队者&#8221;(花费相当长时间去完成MapReduce操作中最后剩下的极少数的那几个task的那台机器)。有很多原因可以导致掉队者的出现。比如：具有一块坏硬盘的机器可能会经历频繁的可修正错误而使得IO性能从30MB/s降低到1MB/s。集群调度系统可能会将那些引发CPU 内存 本地磁盘或者网络带宽等资源竞争的task调度到同一台机器上。我们最近见过的一个错误是由于机器初始化代码中的一个bug引起的处理器缓冲失灵，使得受影响的机器上的计算性能降低了一百倍。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;我们有一个可以缓解这种掉队者问题的通用机制。当MapReduce操作接近尾声的时候，master会备份那些还在执行中的task。只要该task的主本或者其中的一个副本完成了，我们就认为它完成了。通过采用这种机制，我们只使计算资源的利用率增长了仅仅几个百分点，但是明显地降低了完成整个MapReduce操作所需的时间。比如，在5.3节描述的排序例子中，如果不启用这个机制，整个完成时间将会增长53%。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.概念</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">尽管通过简单书写map和reduce函数提供的基本功能对于我们大部分的应用来说足够了，我们也发现了其中的一些扩展也很有用。这一节，我们就来描述下它们。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.1 划分函数</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">MapReduce用户指定他们期望的reduce task(也可以说输出文件)的数目R。任务产生的数据通过在中间结果的key上使用一个划分函数被划分开。系统提供一个使用hash的默认的划分函数(比如 &#8220;hash(key) mod R&#8221;)。然而在某些情况下，使用关于key的其他函数进行划分更有用。比如有时候输入是URL，我们希望来自相同host的输入可以存放在相同的输出文件上。为了支持这种情况，MapReduce库的用户必须提供一个特殊的划分函数。比如使用&#8221;hash(Hostname(urlkey)) mod R&#8221;作为划分函数，就可以让来自相同host的所有URL落在同一个输出文件上。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.2 排序保证</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">我们保证在一个给定的划分内，作为中间结果的key/value对是按照key值的增序进行处理的。这种有序化保证可以让每个划分的输出文件也是有序的。而这在输出文件格式需要支持按照key的有效的随机查找时非常有用，或者输出用户也会发现让对这些数据进行排序会很方便。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.3 合并函数</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">某些情况下，map task产生的中间结果有很多具有相同key的重复值，而且用户指定的reduce函数又满足交换率和结合率。一个很好的例子就是2.1节里描述的wordcount的例子。因为单词频率的分布倾向于遵循Zipf分布，每个map task将会产生成百上千个相同的记录比如&lt;the,1&gt;这样的。而所有的这些又将会通过网络传递给一个reduce task，然后通过reduce函数将它们累加起来。我们允许用户描述一个combiner函数，在数据通过网络发送之前对它们进行部分的归并。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Combiner函数在每个执行map task的机器上这些。通常用来实现combiner和reduce函数的代码是相同的。唯一的不同在MapReduce库如何处理它们的输出。一个reduce函数的输出将会被写到最终的输出文件，而combiner函数的输出会被写到一个将要发送给reduce task的中间结果文件中。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">4.4 输入和输出类型</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">MapReduce库提供了几种不同格式的输入数据支持。比如&#8221;text&#8221;输入模式：将每一行看做一个key/value对，key是该行的offset，value是该行的内容。另一个支持的模式是一个根据key排序的key/value对的序列。每个输入类型知道如何将它们自己通过有意义的边界划分，然后交给独立的map task处理(比如text模式，会保证划分只会发生在行边界上)。用户可以通过提供一个reader接口的实现来支持新的输入类型。对于大多数用户来说，仅仅使用那些预定义的输入类型就够用了。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;一个reader并不是必须从文件读数据。比如可以简单的定义一个从数据库或者是内存中的数据结构中读记录的reader。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;与之类似，我们也提供一组输出类型用于控制输出数据格式，同时用户也很容易添加对于新的输出类型的支持。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.5 副作用</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">MapReduce的用户发现某些情况下，在map和reduce操作中顺便产生一个文件作为额外的输出会很方便。这些的副作用是原子性以及幂等性依赖于应用程序编写者。通常应用程序编写者会写一个temp文件，一旦它已经生成完毕再将它原子性的重命名。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;我们并不为单个task产生的多个输出文件提供原子性的两阶段提交。因此那些具有跨文件一致性需求的产生多个输出文件的task应当是确定性的。这个限制在实际中还没有引起什么问题。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.6 跳过坏记录</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">有时候用户代码中的一些bug会导致Map或者Reduce函数在处理某个特定记录时一定会crash。这样的bug会使得MapReduce操作无法车成功完成。通常的处理方法是修复这个bug，但是有时候这样做显得并不灵活。因为bug可能是存在于第三方的库里，但是源代码是不可用的。而且有时候忽略一些记录是可以接受的，比如在一个大数据集上进行统计分析时。我们提供了一种可选的执行模式，在该模式下，MapReduce库会检测那些记录引发了该crash，然后跳过它们继续前进。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;每个worker进程安装了一个信号处理器捕获那些段错误和总线错误。在调用用户Map或者Reduce操作之前，MapReduce库使用一个全局变量存储该参数的序列号。如果用户代码产生了一个信号，信号处理器就会发送一个包含该序列号的&#8221;last gasp&#8221;的UDP包给master。当master发现在同一记录上发生了不止一次失败后，当它在相应的Map或者Reduce task重新执行时，它就会指出该记录应该被跳过。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.7 本地化执行</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">在Map和Reduce函数上进行调试会变得很有技巧，因为实际的计算发生在分布式系统上，通常是几百台机器，而且工作分配是有master动态决定的。为了降低debug，profile的难度以及进行小规模测试，我们开发了一个MapReduce库的变更实现，让MapReduce操作的所有工作在本地计算机上可以串行执行。用户可以控制将计算放在特殊的map task上执行。用户通过使用一个特殊的flag调用它们的程序，然后就可以简单的使用他们的调试和测试工具(比如gdb)。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.8 状态信息</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">Master运行一个内部的http服务器，然后发布一些用户可以查看的状态页面。这些状态页面展示了计算的进度，比如已经有多少任务完成，多少还在执行中，输入字节数，中间数据的字节数，输出的字节数，处理速率等等。该页面也会包含指向每个task的标准错误和标准输出文件的链接。用户可以使用这些数据来预测该计算还要花费多少时间，是否还需要为该计算添加更多的资源。计算远远低于预取时，这些页面也可以用来发现这些情况。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;另外，更高级别的状态页会展示那些worker失败了，当它们失败时在处理哪些map和reduce task。在诊断用户代码中的bug时，这些信息都是很有用的。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.9 计数器</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">MapReduce库提供了一些计数器设施来计算各种事件的发生。比如用户代码可能想计算处理的单词的总数，或者被索引的德语文档的个数等等。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">为了使用这些设施，用户代码需要创建一个命名计数器对象然后在Map 和/或 Reduce函数中累加这些计数器。比如：</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">Counter* uppercase;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">uppercase = GetCounter("uppercase");</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">map(String name, String contents):</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">for each word w in contents:</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">if (IsCapitalized(w)):</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">uppercase-&gt;Increment();</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">EmitIntermediate(w, "1");</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;来自独立worker机器的计数器的值将会周期性的发送给master(通过对master的ping的响应捎带过去)。Master将那些成功的map和reduce task的计数器值聚集，当MapReduce操作结束后，将它们返回给用户代码。当前的计数器值也会在master的状态页面上显示出来，这样用户就可以看到计算的实时进展。在计算计数器值时，master会忽略掉那些重复执行的相同map或者reduce task的值，以避免重复计数。(重复执行可能是由于备份任务的使用或者是task失败引发的重新执行而引起的。)</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;一些计数器值是由MapReduce库自动维护的，比如已经处理的输入key/vaule 对的个数，已经产生的输出key/vaule 对的个数。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;用户发现计数器设施对于MapReduce操作的行为的完整性检查是非常有用的。比如，在某些MapReduce操作中，用户代码可能想确定已产生的输出对的数目是否刚好等于已处理的输入对数目，或者已经被处理的德语文档在已处理的文档中是否在一个合理的比例上。&nbsp;<br /><br /><br /><br /><br /><br /><br /></p></div><img src ="http://www.cppblog.com/knzeus/aggbug/162113.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/knzeus/" target="_blank">knzeus</a> 2011-12-14 17:43 <a href="http://www.cppblog.com/knzeus/articles/162113.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】Google文件系统(下)  </title><link>http://www.cppblog.com/knzeus/articles/162112.html</link><dc:creator>knzeus</dc:creator><author>knzeus</author><pubDate>Wed, 14 Dec 2011 09:39:00 GMT</pubDate><guid>http://www.cppblog.com/knzeus/articles/162112.html</guid><wfw:comment>http://www.cppblog.com/knzeus/comments/162112.html</wfw:comment><comments>http://www.cppblog.com/knzeus/articles/162112.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/knzeus/comments/commentRss/162112.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/knzeus/services/trackbacks/162112.html</trackback:ping><description><![CDATA[<div><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">转载请注明：<a href="http://duanple.blog.163.com/blog/static/7097176720109151534289/" style="text-decoration: none; color: #189a0f; ">http://duanple.blog.163.com/blog/static/7097176720109151534289/</a>&nbsp;作者&nbsp;<a rel="nofollow" href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#112;&#104;&#121;&#108;&#105;&#112;&#115;&#64;&#98;&#109;&#121;" style="text-decoration: none; color: #189a0f; ">phylips@bmy</a></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;6.测量</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">在这一节，我们用一些小规模的测试来展示GFS架构和实现固有的一些瓶颈，有一些数字来源于google的实际集群。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;6.1小规模测试</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">我们在一个由一个master，两个master备份，16个chunkserver，16个client组成的GFS集群上进行了性能测量。这个配置是为了方便测试，实际中的集群通常会有数百个chunkserver，数百个client。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;所有机器的配置是，双核PIII 1.4GHz处理器，2GB内存，两个80G，5400rpm硬盘，以及100Mbps全双工以太网连接到HP2524交换机。所有19个GFS服务器连接在一个交换机，所有16个客户端连接在另一个上。两个交换机用1Gbps的线路连接。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;6.1.1读操作</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">N个客户端从文件系统中并发读。每个客户端在一个320GB的文件集合里随机4MB进行读取。然后重复256次，这样每个客户端实际上读取了1GB数据。Chunkserver总共只有32GB内存，因此我们估计在linux的buffer cache里最多有10%的命中率。我们的结果应该很接近一个几乎无缓存的结果。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; "><img alt="【google论文二】Google文件系统(下) - 星星 - 银河里的星星" src="http://img.ph.126.net/Y65FOmUpHLwvLT2Z9Zy9Qw==/3407536068060319469.png" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; " /></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">图3(a)展示了对于N个客户端的总的读取速率以及它的理论上的极限。当2个交换机通过一个1Gbps的链路连接时，它的极限峰值是125MB/s，客户端通过100Mbps连接，那么换成单个客户端的极限就是12.5MB/s。当只有一个客户端在读取时，观察到的读取速率是10MB/s，达到了单个客户端极限的80%。当16个读取者时，总的读取速率的94 MB/s，大概达到了链路极限(125MB/s)的75%，换成单个客户端就是6 MB/s。效率从80%降到了75%，是因为伴随着读取者的增加，多个读者从同一个chunkserver并发读数据的概率也随之变大。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; "></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">6.1.2写操作</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">N个客户端并行向N个不同的文件写数据。每个客户端以1MB的单个写操作总共向一个新文件写入1GB数据。总的写速率以及它的理论上的极限如图3(b)所示。极限值变成了67 MB/s，是因为我们需要将每个字节写入到16个chunkserver中的3个，每个具有12.5MB/s的输入连接。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;单个客户端的写入速率是6.3 MB/s，大概是极限值的一半。主要原因是我们的网络协议栈。它不能充分利用我们用于chunk副本数据推送的流水线模式。将数据从一个副本传递到另一个副本的延迟降低了整体的写速率。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;对于16个客户端，总体的写入速率达到了35 MB/s，平均每个客户端2.2 MB/s,大概是理论极限的一半。与写操作类似，伴随着写者的增加，多个写者从同一个chunkserver并发写数据的概率也随之变大。另外对于16个写者比16个读者更容易产生碰撞，因为每个写者将关联到3个不同的副本。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;写者比我们期望的要慢。在实际中，这还末变成一个主要问题，因为尽管它可能增加单个客户端的延时，但是当系统面对大量客户端时，其总的写入带宽并没有显著的影响。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;6.1.3记录追加</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">图3(c)展示了record append的性能。N个客户端向单个文件并行的append。性能取决于保存了该文件最后那个chunk的那些chunkserver，与客户端的数目无关。当只有一个客户端时，能达到6.0MB/s，当有16个客户端时就降到了4.8 MB/s。主要是由于拥塞以及不同的客户端的网络传输速率不同造成的。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;我们的应用程序倾向于并行创建多个这样的文件。换句话说，N个客户端向M个共享文件并行append，在这里N和M通常是几十甚至几百大小。因此在我们的实验中出现的chunkserver的网络拥塞问题在实际中并不是一个显著的问题，因为当一个文件的chunkserver比较繁忙的时候，它可以去写另一个。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">6.2现实的集群</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">我们选择在google内部使用的两个集群进行测试作为相似的那些集群的一个代表。集群A主要用于100多个工程的日常研发。它会从数TB的数据中读取数MB的数据，对这些数据进行转化或者分析，然后将结果再写回集群。集群B主要用于产品数据处理。它上面的任务持续时间更长，持续地在生成和处理数TB的数据集合，只是偶尔可能需要人为的参与。在这两种情况下，任务都是由分布在多个机器上的很进程组成，它们并行的读写很多文件。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">6.2.1存储</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; "><img alt="【google论文二】Google文件系统(下) - 星星 - 银河里的星星" src="http://img.ph.126.net/rTjOFXV6-uwNGOaJfRf0yQ==/3407536068060319507.png" width="414" height="215" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; width: 399px; height: 199px; " /></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;正如表中前5个字段所展示的，两个集群都有数百个chunkserver，支持TB级的硬盘空间，空间已经被充分使用但还没全满。已用的空间包含chunk的所有副本。通常文件存在三个副本，因此这两个集群实际分别存储了18TB和52TB的数据。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">这两个集群的文件数目很接近，尽管B集群有大量的死文件(那些已经被删除或者被新版本文件所替换但空间还没有被释放的文件)。而且它具有更多的trunk，因为它上面的文件通常更大。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;6.2.2元数据</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">所有的Chunkserver总共存储了数十G的元数据，大部分是用户数据的64kb块的校验和。Chunkserver上唯一的其他的元数据就是4.5节讨论的chunk的版本号。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;保存在master上的元数据要更小一些，只有数十MB，平均下来每个文件只有100来个字节。这也刚好符合我们的master的内存不会成为实际中系统容量限制的料想。每个文件的元数据主要是以前缀压缩格式存储的文件名称。还有一些其他的元数据比如文件所有者，权限，文件到chunk的映射以及chunk的当前版本。另外对于每个chunk我们还存储了当前的副本位置以及用于实现写时复制的引用计数。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;每个独立的server(chunkserver和master)只有50-100MB的元数据。因此，恢复是很快的：在server可以应答查询前只需要花几秒钟的时间就可以把它们从硬盘上读出来。然而，master的启动可能要慢一些，通常还需要30-60秒从所有的chunkserver获得chunk的位置信息。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;6.2.3读写速率</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; text-indent: 2em; "><img alt="【google论文二】Google文件系统(下) - 星星 - 银河里的星星" src="http://img314.ph.126.net/psowWcl6MohZ_ybGk5YLJQ==/3881258453863623877.png" width="471" height="254" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; width: 443px; height: 238px; " /></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;表3展示了不同时期的读写速率。进行这些测量时，两个集群都已经运行了大约一周(为了更新到最新版本的GFS，这两个集群被重启过)。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; "></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">从启动开始看，平均写速率小于30MB/s。当我们进行这些测量时，集群B正在以100MB/s的速率进行密集的写操作，同时产生了300MB/s的网络负载，因为写操作将会传给3个副本。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;读速率要远高于写速率。正如我们料想的那样，整个工作负载组成中，多要多于写。这两个集群都处在繁重的读活动中。尤其是，A已经在过去的一个星期中维持了580MB/s的读速率。它的网络配置可以支持750MB/s，因此它已经充分利用了资源。B集群可支持1300 MB/s的峰值读速率，但是应用只使用了380 MB/s。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;6.2.4 master负载</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">表3也表明发送给master的操作速率大概是每秒200-500个操作。Master可以轻易的处理这个级别的速率，因此对于这些工作负载来说，它不会成为瓶颈。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;在早期版本的GFS中，master偶尔会成为某些工作负载的瓶颈。为了查找文件，花费大量的时间在巨大的目录(包含上千万的文件)中进行线性扫描。因此，我们改变了master的数据结构，使之可以在名字空间内进行有效的二分搜索。现在它可以简单的支持每秒上千次的文件访问。如果必要的话，我们还可以进一步的在名字空间数据结构前端提供名字查找缓存。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;6.2.5 恢复时间</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">一台Chunkserver失败后，它上面的那些chunk的副本数就会降低，必须进行clone以维持正常的副本数。恢复这些chunk的时间取决于资源的数量。在一个实验中，我们关闭集群B中的一个chunkserver。该chunkserver大概有15000个chunk，总共600GB的数据。为减少对于应用程序的影响以及为调度决策提供余地，我们的默认参数设置将集群的并发clone操作限制在91个(占chunkserver个数的40%),同时每个clone操作最多可以消耗6.25MB/s(50Mbps)。所有的chunk在23.2分钟内被恢复，备份速率是440MB/s。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;在另一个实验中，我们关掉了两个chunkserver，每个具有16000个chunk，660GB的数据。这次失败使得266个chunk降低到了一个副本，但是两分钟内，它们就恢复到了至少2个副本，这样就让集群能够容忍另一个chunkserver发生失败，而不产生数据丢失。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;6.3 工作负载剖析</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">在这一节，我们将继续在两个新的集群上对工作负载进行细致的对比分析。集群X是用于研究开发的，集群Y是用于产品数据处理。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">6.3.1 方法和说明</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">这些结果只包含了客户端产生的请求，因此它们反映了应用程序的对整个文件系统的工作负载。并不包含为了执行客户端的请求进行的server间的请求，或者是内部的后台活动，比如写推送或者是重平衡。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;对于IO操作的统计是从GFS的server的PRC请求日志中重新构建出来的。比如为了增加并行性，GFS客户端代码可能将一个读操作拆分为多个RPC请求，我们通过它们推断出原始请求。因为我们的访问模式高度的程式化，希望每个错误都可以出现在日志中。应用程序显式的记录可以提供更精确的数据，但是重新编译以及重启正在运行中的客户端在逻辑上是不可能这样做的。而且由于机器数很多，收集这些数据也会变得很笨重。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;需要注意的是，不能将我们的工作负载过于一般化。因为GFS和应用程序是由google完全控制的，应用程序都是针对GFS进行专门优化的，同时GFS也是专门为这些应用而设计的。这种相互的影响可能也存在于一般的文件系统及其应用程序中，但是这种影响可能并不像我们上面所描述的那样。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;6.3.2 chunkserver负载</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; "><img alt="【google论文二】Google文件系统(下) - 星星 - 银河里的星星" src="http://img314.ph.126.net/v_nbkWJO9FAekx2hV4laQw==/3881258453863623881.png" width="436" height="277" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; width: 422px; height: 269px; " /></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;表4展示了操作根据大小的分布。读操作的大小表现出双峰分布，小型读操作(小于64kb)来自于那些在大量文件中查找小片数据的随机读客户端，大型读操作(超过512kb)来自于穿越整个文件的线性读操作。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; "></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">集群Y中大量的读操作没有返回数据。我们应用程序，尤其是在产品系统中，经常使用文件作为生产者消费者队列。生产者并行的往文件中append数据，而消费者则从文件尾部读数据。有时候，如果消费者超过了生产者，就没有数据返回。集群X很少出现这种情况，因为它主要是用来进行短期数据分析，而不是长期的分布式应用。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;写操作的大小也表现出双峰分布。大型的写操作(超过256KB)通常来自于写操作者的缓冲。那些缓冲更少数据的写操作者，检查点或者经常性的同步或者简单的数据生成组成了小型的写操作(低于64KB)。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;对于记录的append，Y集群比X集群可以看到更大的大record append比率。因为使用Y集群的产品系统，针对GFS进行了更多的优化。</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; "><img alt="【google论文二】Google文件系统(下) - 星星 - 银河里的星星" src="http://img317.ph.126.net/PVx3aRcYjiZOKfPkqoFm9w==/3779927462247619401.png" width="294" height="267" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; width: 310px; height: 271px; " />&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;表5展示了不同大小的数据传输总量。对于各种操作来说，大型的操作(超过256KB)构成了大部分的数据传输。但是小型(低于64KB)的读操作虽然传输了比较少的数据但是在数据读中也占据了相当的一部分，主要是由于随机seek造成的。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; "></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">6.3.4 append与write</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">记录append操作被大量的应用尤其是在我们的产品系统中。对于集群X来说，按字节传输来算，write与append的比例是108：1，根据操作数来算它们的比例是8：1。对于集群Y，比例变成了3.7：1和2.5：1。对于这两个集群来说，它们的append操作都要比write操作大一些｛操作数的比要远大于字节数的比，说明单个的append操作的字节数要大于write｝。对于集群X来说，在测量期间的记录append操作要低一些，这可能是由其中具有特殊缓冲大小设置的应用程序造成的。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;正如期望的，我们的数据变更操作处于支配地位的是追加而不是重写{write也可能是追加}。我们测量了在主副本上的数据重写数量。对于集群X来说，以字节大小计算的话重写大概占了整个数据变更的0.0001%，以操作个数计算，大概小于0.0003%。对于Y集群来说，这两个数字都是0.05%，尽管这也不算大，但是还是要高于我们的期望。结果显示，大部分的重写是由于错误或者超时导致的客户端重写而产生的。它们并不是工作负载的一部分，而是属于重试机制。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;6.3.4 master负载</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; "><img alt="【google论文二】Google文件系统(下) - 星星 - 银河里的星星" src="http://img317.ph.126.net/8QOiUgdw1OInfuS1qIA5IA==/3779927462247619413.png" width="431" height="179" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; width: 424px; height: 172px; " /></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;表6展示了对于master各种请求类型的剖析。大部分请求是为了得到chunk位置以及数据变更需要的租约持有信息。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;可以看到集群X和Y在delete请求上的限制区别，因为集群Y上存储的产品信息会周期性地生成被新版本数据所替换。这些不同被隐藏在open请求中，因为老版的数据在被写的时候的打开操作中被隐式的删除(类似与Unix的&#8221;w&#8221;打开模式)。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;查找匹配文件是一个类似于ls的模式匹配请求。不像其他的请求，它可能需要处理很大部分的名字空间，因此可能是很昂贵的。在集群Y上可以更频繁地看到它，因为自动化的数据处理任务为了了解整个应用程序的状态可能需要检查文件系统中的某些部分。与此相比，集群X需要更多显式的用户控制而且已经提前知道所需要的文件的名称。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; "></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">7.经验</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">在构建和部署GFS的过程中，我们总结出了很多经验，观点和技术。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;起初，GFS只是考虑作为我们产品系统的后端文件系统。随着时间的推移，开始在研究和开发中使用。一开始它基本不支持像权限，quota这些东西，但是现在它们都已经有了。产品系统是很容易控制的，但是用户却不是。因此需要更多的设施来避免用户间的干扰。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;我们最大的问题是硬盘和linux相关性。我们的很多硬盘声称支持各种IDE协议版本的linux驱动，但是实际上它们只能在最近的一些上才能可靠的工作。因此如果协议版本如果相差不大，硬盘大多数情况下都可以工作，但是有时候这种不一致会使得驱动和内核在硬盘状态上产生分歧。由于内核的问题，这将会导致数据被默默的污染。这个问题使得我们使用校验和来检测数据污染，如果出现这种情况，我们就需要修改内核来处理这种协议不一致的情况。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">之前，由于linux2.2内核的fsync()的花费，我们也碰到过一些问题。它的花费是与文件大小而不是被修改部分的大小相关的。这对于我们大的操作日志会是一个问题，尤其是在我们实现检查点之前。我们通过改用同步写来绕过了这个问题，最后迁移到Linux2.4来解决了它。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;另一个由于linux产生的问题是与读写锁相关的。在一个地址空间里的线程在从硬盘中读页数据(读锁)或者在mmap调用中修改地址空间(写锁)的时候，必须持有一个读写锁。在系统负载很高，产生资源瓶颈或者出现硬件失败时，我们碰到了瞬态的超时。最后，我们发现当磁盘读写线程处理前面映射的数据时，这个锁阻塞了网络线程将新的数据映射到内存。由于我们的工作瓶颈主要是在网络带宽而不是内存带宽，因此我们通过使用pread()加上额外的开销替代mmap()绕过了这个问题。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;尽管出现了一些问题，linux代码的可用性帮助了我们探索和理解系统的行为。在适当的时机，我们也会改进内核并与开源社区共享这些变化。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;8.相关工作</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">像其他的大型分布式文件系统比如AFS，GFS提供了一个本地的独立名字空间，使得数据可以为了容错或者负载平衡而透明的移动。但与AFS不同的是，为了提升整体的性能和容错能力，GFS将文件数据在多个存储服务器上存储，这点更类似于xFS或者Swift。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;硬盘是相对便宜的，而且与复杂的RAID策略相比，副本策略更简单。由于GFS完全采用副本策略进行冗余因此它会比xFS或者Swift消耗更多的原始存储。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">与AFS,xFS,Frangipani,Intermezzo这些系统相比，GFS在文件系统接口下并不提供任何缓存。我们的目标工作负载类型对于通常的单应用程序运行模式来说，基本上是不可重用的，因为这种模式通常需要读取大量数据集合或者在里面进行随机的seek，而每次只读少量的数据。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">一些分布式文件系统比如xFS,Frangipani,Minnesota&#8217;s GFS和GPFS删除了中央服务节点，依赖于分布式的算法进行一致性和管理。我们选择中央化测量是为了简化设计增加可靠性，获取灵活性。尤其是，一个中央化的master更容易实现复杂的chunk放置和备份策略，因为master具有大部分的相关信息以及控制了它们的改变。我们通过让master状态很小以及在其他机器上进行备份来解决容错。当前通过影子master机制提供可扩展性和可用性。对于master状态的更新，通过append到write-ahead 日志里进行持久化。因此我们可以通过类似于Harp里的主copy模式来提供一个比我们当前模式具有更强一致性的高可用性。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;我们未来将解决类似于Lustre的一个问题：大量客户端的整体性能。然而我们通过专注于我们自己的需求而不是构建一个POSIX兼容文件系统来简化了这个问题。另外，GFS加速不可靠组件的数量是很大的，因此容错是我们设计的中心。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;GFS很类似于NASD架构。但是NASD是基于网络连接的硬盘驱动器，GFS则使用普通机器作为chunkserver。与NASD不同，chunkserver在需要时分配固定大小的chunk，而没有使用变长对象。此外，GFS还实现了诸如重平衡，副本，产品环境需要的快速恢复。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;不像Minnesota&#8217;s GFS和NASD，我们并没有寻求改变存储设备的模型。我们更专注于解决使用现有商品化组件组成的复杂分布式系统的日常的数据处理需求。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;通过在生产者消费者队列中使用原子record append操作解决了与分布式操作系统River的类似问题。River使用基于内存的跨机器分布式队列以及小心的数据流控制来解决这个问题，而GFS只使用了一个可以被很多生产者append数据的文件。River模型支持m to n的分布式队列，但是缺乏容错，GFS目前只支持m to 1。多个消费者可以读取相同文件，但是它们必须协调好对输入负载进行划分(各自处理不相交的一部分)。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;9.总结</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">GFS包含了那些在商品化硬件上支持大规模数据处理的必要特征。尽管某些设计决定与我们特殊的应用类型相关，但是可以应用在具有类似需求和特征的数据处理任务中。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;针对我们当前的应用负载类型，我们重新审视传统的文件系统的一些假设。我们的审视，使得我们的设计中产生了一些与之根本不同的观点。我们将组件失败看做常态而不是异常，为经常进行的在大文件上的append进行优化，然后是读(通常是顺序的)，为了改进整个系统我们扩展并且放松了标准文件系统接口。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;我们的系统通过监控，备份关键数据，快速和自动恢复来提供容错。Chunk备份使得我们可以容忍chunkserver的失败。这些经常性的失败，驱动了一个优雅的在线修复机制的产生，它周期性地透明的进行修复尽快的恢复那些丢失的副本。另外，我们通过使用校验和来检测数据损坏，当系统中硬盘数目很大的时候，这种损坏变得很正常。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;我们的设计实现了对于很多执行大量任务的并发读者和写者的高吞吐率。通过从数据传输中分离文件系统控制，我们来实现这个目标，让master来处理文件系统控制，数据传输则直接在chunkserver和客户端之间进行。通过增大chunk的大小以及chunk的租约机制，降低了master在普通操作中的参与。这使中央的master不会成为瓶颈。我们相信在当前网络协议栈上的改进将会提供客户端写出速率的限制。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;GFS成功地满足了我们的存储需求，同时除了作为产品数据处理平台外，还作为研发的存储平台而被广泛使用。它是一个使我们可以持续创新以及面对整个web的海量数据挑战的重要工具。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">致谢</p></div><img src ="http://www.cppblog.com/knzeus/aggbug/162112.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/knzeus/" target="_blank">knzeus</a> 2011-12-14 17:39 <a href="http://www.cppblog.com/knzeus/articles/162112.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】Google文件系统(中)  </title><link>http://www.cppblog.com/knzeus/articles/162111.html</link><dc:creator>knzeus</dc:creator><author>knzeus</author><pubDate>Wed, 14 Dec 2011 09:33:00 GMT</pubDate><guid>http://www.cppblog.com/knzeus/articles/162111.html</guid><wfw:comment>http://www.cppblog.com/knzeus/comments/162111.html</wfw:comment><comments>http://www.cppblog.com/knzeus/articles/162111.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/knzeus/comments/commentRss/162111.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/knzeus/services/trackbacks/162111.html</trackback:ping><description><![CDATA[<div><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">转载请注明：<a href="http://duanple.blog.163.com/blog/static/7097176720109151211526/" style="text-decoration: none; color: #189a0f; ">http://duanple.blog.163.com/blog/static/7097176720109151211526/</a>&nbsp;作者&nbsp;<a rel="nofollow" href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#112;&#104;&#121;&#108;&#105;&#112;&#115;&#64;&#98;&#109;&#121;" style="text-decoration: none; color: #189a0f; ">phylips@bmy</a></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">3.系统交互</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">我们是以尽量最小化master在所有操作中的参与度来设计系统的。在这个背景下，我们现在描述下client，master以及chunkserver如何交互来实现数据变更，记录append以及快照的。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">3.1租约和变更顺序</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">一个变更是指一个改变chunk的内容或者元信息的操作，比如写操作或者append操作。每个变更都需要在所有的副本上执行。我们使用租约来保持多个副本间变更顺序的一致性。Master授权给其中的一个副本一个该chunk的租约，我们把它叫做主副本。这个主副本为针对该chunk的所有变更的选择一个执行顺序，然后所有的副本根据这个顺序执行变更。因此，全局的变更顺序首先是由master选择的租约授权顺序来确定的(可能有多个chunk需要进行修改)，而同一个租约内的变更顺序则是由那个主副本来定义的。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">租约机制是为了最小化master的管理开销而设计的。一个租约有一个初始化为60s的超时时间设置。然而只要这个chunk正在变更，那个主副本就可以向master请求延长租约。这些请求和授权通常是与master和chunkserver间的心跳信息一起发送的。有时候master可能想在租约过期前撤销它(比如，master可能想使对一个正在重命名的文件的变更无效)。即使master无法与主副本进行通信，它也可以在旧的租约过期后安全的将租约授权给另一个新的副本。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">如图2，我们将用如下的数字标识的步骤来表示一个写操作的控制流程。</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; "><img alt="【google论文二】Google文件系统(中) - 星星 - 银河里的星星" src="http://img775.ph.126.net/1X2cgmfqZdd1T15MSbbLEw==/4860791372816577919.png" width="238" height="270" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; width: 258px; height: 267px; " /></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">1.client向master询问那个chunkserver获取了当前chunk的租约以及其他副本所在的位置。如果没有人得到租约，master将租约授权给它选择的一个副本。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">2.master返回该主副本的标识符以及其他副本的位置。Client为未来的变更缓存这个数据。只有当主副本没有响应或者租约到期时它才需要与master联系。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">3.client将数据推送给所有的副本，client可以以任意的顺序进行推送。每个chunkserver会将数据存放在内部的LRU buffer里，直到数据被使用或者过期。通过将控制流与数据流分离，我们可以通过将昂贵的数据流基于网络拓扑进行调度来提高性能，而不用考虑哪个chunkserver是主副本。3.2节更深入地讨论了这点。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">4.一旦所有的副本接受到了数据，client发送一个写请求给主副本，这个请求标识了先前推送给所有副本的数据。主副本会给它收到的所有变更(可能来自多个client)安排一个连续的序列号来进行必需的串行化。它将这些变更根据序列号应用在本地副本上。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">5.主副本将写请求发送给所有的次副本，每个次副本以与主副本相同的串行化顺序应用这些变更。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">6.所有的次副本完成操作后向主副本返回应答</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">7.主副本向client返回应答。任何副本碰到的错误都会返回给client。出现错误时，该写操作可能已经在主副本以及一部分次副本上执行成功。(如果主副本失败，那么它不会安排一个序列号并且发送给其他人)。客户端请求将会被认为是失败的，被修改的区域将会处在非一致状态下。我们的客户端代码会通过重试变更来处理这样的错误。它会首先在3-7步骤间进行一些尝试后在重新从头重试这个写操作。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; "></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">如果应用程序的一个写操作很大或者跨越了chunk的边界，GFS client代码会将它转化为多个写操作。它们都会遵循上面的控制流程，但是可能会被来自其他client的操作插入或者覆盖。因此共享的文件区域可能会包含来自不同client的片段，虽然这些副本是一致的，因为所有的操作都按照相同的顺序在所有副本上执行成功了。但是文件区域会处在一种一致但是未定义的状态，正如2.7节描述的那样。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;3.2数据流</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">为了更有效的使用网络我们将数据流和控制流分离。控制流从client到达主副本，然后到达其他的所有次副本，而数据则是线性地通过一个仔细选择的chunkserver链像流水线那样推送过去的。我们的目标是充分利用每个机器的网络带宽，避免网络瓶颈和高延时链路，最小化数据推送的延时。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;为了充分利用每个机器的网络带宽，数据通过chunkserver链线性的推送过去而不是以其他的拓扑进行分布比如树型。因此每个机器的带宽可以全部用来发送数据而不是为多个接受者进行切分。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;为了尽可能的避免网络瓶颈和高延时链路，每个机器向网络中还没有收到该数据的最近的那个机器推送数据。假设client将数据推送给S1- S4，它会首先将数据推送给最近的chunkserver假设是S1，S1推送给最近的，假设S2，S2推送给S3，S4中离他最近的那个。我们网络拓扑足够简单，以至于距离可以通过IP地址估计出来。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;最后为了最小化延时，我们通过将TCP数据传输进行流水化。一旦一个chunkserver收到数据，它就开始立即往下发送数据。流水线对我们来说尤其有用，因为我们使用了一个全双工链路的交换网络。立即发送数据并不会降低数据接受速率。如果没有网络拥塞，向R个副本传输B字节的数据理想的时间耗费是B/T+RL,T代表网络吞吐率，L是机器间的网络延时。我们的网络连接是100Mbps(T),L远远低于1ms，因此1MB的数据理想情况下需要80ms就可以完成。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">3.3原子性的记录append</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">GFS提供一个原子性的append操作叫做record append(注意这与传统的append操作也是不同的)。在传统的写操作中，用户指定数据需要写的便宜位置。对于相同区域的并行写操作是不可串行的：该区域的末尾可能包含来自多个client的数据片段。但在一个record append操作中，client唯一需要说明的只有数据。GFS会将它至少原子性地append到文件中一次，append的位置是由GFS选定的，同时会将这个位置返回给client。这很类似于unix文件打开模式中的O_APPEND，当多个写者并发操作时不会产生竞争条件。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Record append在我们的分布式应用中被大量的使用。很多在不同机器的client并发地向同一个文件append。如果使用传统的写操作，client将需要进行复杂而又昂贵的同步化操作，比如通过一个分布式锁管理器。在我们的工作负载中，这样的文件通常作为一个多生产者/单消费者队列或者用来保存来自多个不同client的归并结果。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Record append是一种类型的变更操作，除了一点在主副本上的额外的逻辑外依然遵循3.1节的控制流。Client将所有的数据推送给所有副本后，它向主副本发送请求。主副本检查将该记录append到该chunk是否会导致该chunk超过它的最大值(64MB)。如果超过了，它就将该chunk填充到最大值，告诉次副本做同样的工作，然后告诉客户端该操作应该在下一个trunk上重试。(append的Record大小需要控制在最大trunk大小的四分之一以内，这样可以保证最坏情况下的碎片可以保持在一个可以接受的水平上 )。如果记录可以没有超过最大尺寸，就按照普通情况处理，主副本将数据append到它的副本上，告诉次副本将数据写在相同的偏移位置上，最后向client返回成功应答。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;如果record append在任何一个副本上失败，client就会重试这个操作。这样，相同chunk的多个副本就可能包含不同的数据，这些数据可能包含了相同记录的整个或者部分的重复值。GFS并不保证所有的副本在位级别上的一致性，它只保证数据作为一个原子单元最少写入一次。这个属性是由如下的简单观察推导出来的，当操作报告成功时，数据肯定被写入到某个trunk的所有副本的相同偏移位置上。此后，所有的副本至少达到了记录尾部的大小，因此未来的记录将会被放置在更高的便宜位置，或者是另一个不同的chunk，即使另一个副本变成了主副本。在我们的一致性保证里，record append操作成功后写下的数据区域是已定义的(肯定是一致的)，然而介于其间的数据则是不一致的(因此也是未定义的)。我们的应用程序可以处理这样的不一致区域，正如我们在2.7.2里讨论的那样。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;3.4快照</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">快照操作可以非常快速的保存文件或者目录树的一个拷贝，同时可以最小化对于正在执行的变更操作的中断。用户经常用它来创建大数据集的分支拷贝，以及拷贝的拷贝&#8230;&#8230;。或者用来创建检查点，以实验将要提交的拷贝或者回滚到更早的状态。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;像AFS，我们使用标准的写时拷贝技术来实现快照。当master收到一个快照请求时，它首先撤销将要进行快照的那些文件对应的chunk的所有已发出的租约。这就使得对于这些chunk的后续写操作需要与master交互来得到租约持有者。这就首先给master一个机会创建该chunk的新的拷贝。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;当这些租约被撤销或者过期后，master将这些操作以日志形式写入磁盘。然后复制该文件或者目录树的元数据，然后将这些日志记录应用到内存中的复制后的状态上，新创建的快照文件与源文件一样指向相同的chunk。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;当client在快照生效后第一次对一个chunk C进行写入时，它会发送请求给master找到当前租约拥有者。Master注意到对于chunk C的引用计数大于1。它延迟回复客户端的请求，选择一个新的chunk handle C`。然后让每个拥有C的那些chunkserver创建一个新的叫做C`的chunk。通过在相同的chunkserver上根据原始的chunk创建新chunk，就保证了数据拷贝是本地地，而不是通过网络(我们的硬盘比100Mbps网络快大概三倍)。这样，对于任何chunk的请求处理都没有什么不同：master为新才chunk C`的副本中的一个授权租约，然后返回给client，这样它就可以正常的写这个chunk了，client不需要知道该chunk实际上是从一个现有的chunk创建出来的。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.master操作</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">Master执行所有的名字空间操作。此外，它还管理整个系统的chunk备份：决定如何放置，创建新的chunk和相应的副本，协调整个系统的活动保证chunk都是完整备份的，在chunkserver间进行负载平衡，回收没有使用的存储空间。我们现在讨论这些主题。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.1名字空间管理和锁</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">很多master操作都需要花费很长时间：比如，一个快照操作要撤销该快照所包含的chunk的所有租约。我们并不想耽误其他运行中的master操作，因此我们允许多个操作同时是活动的，通过在名字空间区域使用锁来保证正确的串行化。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;&nbsp;不像传统的文件系统，GFS的目录并没有一种数据结构用来列出该目录下所有文件，而且也不支持文件或者目录别名(像unix的硬链接或者软连接那样)。GFS在逻辑上通过一个路径全称到元数据映射的查找表来表示它的名字空间。通过采用前缀压缩，这个表可以有效地在内存中表示。名字空间树中的每个节点(要么是文件的绝对路径名称要么是目录的)具有一个相关联的读写锁。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;每个master操作在它运行前，需要获得一个锁的集合。比如如果它想操作/d1/d2&#8230;/dn/leaf，那么它需要获得/d1,/d1/d2&#8230;&#8230;/d1/d2&#8230;/dn这些目录的读锁，然后才能得到路径/d1/d2&#8230;/dn/leaf的读锁或者写锁。Leaf可能是个文件或者目录，这取决于具体的操作。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;我们现在解释一下，当为/home/user创建快照/save/user时，锁机制如何防止文件/home/user/foo被创建。快照操作需要获得在/home /save上的读锁，以及/home/user和/save/user上的写锁。文件创建需要获得在/home和/home/user上的读锁，以及在/home/user/foo上的写锁。这两个操作将会被正确的串行化，因为它们试图获取在/home/user上的相冲突的锁。文件创建并不需要父目录的写锁，因为实际上这里并没有&#8221;目录&#8221;或者说是类似于inode的数据结构，需要防止被修改。读锁已经足够用来防止父目录被删除。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">这种锁模式的一个好处就是它允许对相同目录的并发变更操作。比如多个文件的创建可以在相同目录下并发创建：每个获得该目录的一个读锁，以及文件的一个写锁。目录名称上的读锁足够可以防止目录被删除，重命名或者快照。文件名称上的写锁将会保证重复创建相同名称的文件的操作只会被执行一次。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">因为名字空间有很多节点，所以读写锁对象只有在需要时才会被分配，一旦不再使用用就删除。为了避免死锁，锁是按照一个一致的全序关系进行获取的：首先根据所处的名字空间树的级别，相同级别的则根据字典序。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.2 备份放置</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">GFS在多个层次上都具有高度的分布式。它拥有数百个散步在多个机柜中的chunkserver。这些chunkserver又可以被来自不同或者相同机柜上的client访问。处在不同机柜的机器间的通信可能需要穿过一个或者更多的网络交换机。此外，进出一个机柜的带宽可能会小于机柜内所有机器的带宽总和。多级的分布式带来了数据分布式时的扩展性，可靠性和可用性方面的挑战。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Chunk的备份放置策略服务于两个目的：最大化数据可靠性和可用性，最小化网络带宽的使用。为了达到这两个目的，仅仅将备份放在不同的机器是不够的，这只能应对机器或者硬盘失败，以及最大化利用每台机器的带宽。我们必须在机柜间存放备份。这样能够保证当一个机柜整个损坏或者离线(比如网络交换机故障或者电路出问题)时，该chunk的存放在其他机柜的某些副本仍然是可用的。这也意味着对于一个chunk的流量，尤其是读取操作可以充分利用多个机柜的带宽。另一方面，写操作需要在多个机柜间进行，但这是我们可以接受的。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.3创建 重备份 重平衡</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">Chunk副本的创建主要有三个原因：chunk的创建，重备份，重平衡。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;当master创建一个chunk时，它将初始化的空的副本放置在何处。它会考虑几个因素：1.尽量把新的chunk放在那些低于平均磁盘空间使用值的那些chunkserver上。随着时间的推移，这会使得chunkserver的磁盘使用趋于相同2.尽量限制每个chunkserver上的最近的文件创建数，虽然创建操作是很简单的，但是它后面往往跟着繁重的写操作，因为chunk的创建通常是因为写者的需要而创建它。在我们的一次append多次读的工作负载类型中，一旦写入完成，它们就会变成只读的。3.正如前面讨论的，我们希望在机柜间存放chunk的副本</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;当chunk的可用备份数低于用户设定的目标值时，Master会进行重复制。有多个可能的原因导致它的发生：chunkserver不可用，chunkserver报告它的某个备份已被污染，一块硬盘由于错误而不可用或者用户设定的目标值变大了。需要重复制的chunk根据几个因素确定优先级。一个因素是它与备份数的目标值差了多少，比如我们给那些丢失了2个副本的chunk比丢失了1个的更高的优先级。另外，比起最近被删除的文件的chunk，我们更想备份那些仍然存在的文件的chunk(参考4.4节)。最后了，为了最小化失败对于运行中的应用程序的影响，我们提高那些阻塞了用户进度的chunk的优先级。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Master选择最高优先级的chunk，通过给某个chunkserver发送指令告诉它直接从一个现有合法部分中拷贝数据来进行克隆。新备份的放置与创建具有类似的目标：平均磁盘使用，限制在单个chunkserver上进行的clone操作数，使副本存放在不同机柜间。为了防止clone的流量淹没client的流量，master限制整个集群已经每个chunkserver上处在活动状态的clone操作数。另外每个chunkserver还会限制它用在clone操作上的带宽，通过控制它对源chunkserver的读请求。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">最后，master会周期性的对副本进行重平衡。它检查当前的副本分布，然后为了更好的磁盘空间使用和负载瓶颈，将副本进行移动。而且在这个过程中，master是逐步填充一个新的chunkserver，而不是立即将新的chunk以及大量沉重的写流量使他忙的不可开交。对于一个新副本的放置，类似于前面的那些讨论。另外，master必须选择删除哪个现有的副本。通常来说，它更喜欢那些存放在低于平均磁盘空闲率的chunkserver上的chunk，这样可以使磁盘使用趋于相等。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.4垃圾回收</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">文件删除后，GFS并不立即释放可用的物理存储。它会将这项工作推迟到文件和chunk级别的垃圾回收时做。我们发现，这种方法使得系统更简单更可靠。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.4.1机制</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">当文件被应用程序删除时，master会将这个删除操作像其他变化一样理解写入日志。文件不会被立即删除，而是被重命名为一个包含删除时间戳的隐藏名称。在master对文件系统进行常规扫描时，它会删除那些存在时间超过3天(这个时间是可以配置的)的隐藏文件。在此之前，文件依然可以用那个新的特殊名称进行读取，或者重命名回原来的名称来取消删除。当隐藏文件从名字空间删除后，它的元数据会被擦除。这样就有效地切断了它与所有chunk的关联。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;在chunk的类似的常规扫描中，master找到那些孤儿块(无法从任何文件到达)，擦除这些块的元数据。在与master周期性交互的心跳信息中，chunkserver报告它所拥有的chunk的那个子集，然后master返回那些不在master的元数据中出现的chunk的标识。Chunkserver就可以自由的删除这些chunk的那些副本了。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.4.2讨论</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">尽管程序设计语言中的分布式垃圾回收是一个需要复杂解决方案的难解问题，但是在我们这里它是很简单的。我们可以简单的找到对于chunk的所有引用：因为它们保存在只由master维护的一个文件-chunk映射里。我们可以找到所有chunk的副本：它们不过是存放在每个chunkserver的特定目录下的linux文件。任何master不知道的副本就是垃圾。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;采用垃圾回收方法收回存储空间与直接删除相比，提供了几个优势：1.在经常出现组件失败的大规模分布式系统中，它是简单而且可靠的。Chunk创建可能在某些chunkserver上成功，在另外一些失败，这样就留下一些master所不知道的副本。副本删除消息可能丢失，master必须记得在出现失败时进行重发。垃圾回收提供了一种同一的可信赖的清除无用副本的方式。2.它将存储空间回收与master常规的后台活动结合在一起，比如名字空间扫描，与chunkserver的握手。因此它们是绑在一块执行的，这样开销会被平摊。而且只有当master相对空闲时才会执行。Master就可以为那些具有时间敏感性的客户端请求提供更好的响应。3.空间回收的延迟为意外的不可逆转的删除提供了一道保护网。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;根据我们的经验，主要的缺点是，当磁盘空间很紧张时，这种延时会妨碍到用户对磁盘使用的调整。那些频繁创建和删除中间文件的应用程序不能够立即重用磁盘空间。我们通过当已删除的文件被再次删除时加速它的存储回收来解决这个问题。我们也允许用户在不同的名字空间内使用不同的重备份和回收策略。比如用户可以指定某个目录树下的文件的chunk使用无副本存储，任何已经删除的文件会被立即删除并且从当前文件系统中彻底删除。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;4.5过期副本检测</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">如果chunkserver失败或者在它停机期间丢失了某些更新，chunk副本就可能变为过期的。对于每个chunk，master维护一个版本号来区分最新和过期的副本。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;只要master为一个chunk授权一个新的租约，那么它的版本号就会增加，然后通知副本进行更新。在一致的状态下，Master和所有副本都会记录这个新的版本号。这发生在任何client被通知以前，因此也就是client开始向chunk中写数据之前。如果另一个副本当前不可用，它的chunk版本号就不会被更新。当chunkserver重启或者报告它的chunk和对应的版本号的时候，master会检测该chunkserver是否包含过期副本。如果master发现有些版本号大于它的记录，master就认为它在授权租约时失败了，所以采用更高的版本号的那个进行更新。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Master通过周期性的垃圾回收删除过期副本。在此之前，对于客户端对于该chunk的请求master会直接将过期副本当作根本不存在进行处理。作为另外一种保护措施，当master通知客户端那个chunkserver包含某chunk的租约或者当它在clone操作中让chunkserver从另一个chunkserver中读取chunk时，会将chunk的版本号包含在内。当clinet和chunkserver执行操作时，总是会验证版本号，这样就使得它们总是访问最新的数据。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;5.容错和诊断</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">在设计系统时，一个最大的挑战就是频繁的组件失败。组件的数量和质量使得这些问题变成一种常态而不再是异常。我们不能完全信任机器也不能完全信任磁盘。组件失败会导致系统不可用，甚至是损坏数据。我们讨论下如何面对这些挑战，以及当它们不可避免的发生时，在系统中建立起哪些工具来诊断问题。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;5.1高可用性</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">在GFS的数百台服务器中，在任何时间总是有一些是不可用的。我们通过两个简单有效的策略来保持整个系统的高可用性：快速恢复和备份。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">5.1.1快速恢复</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">Master和chunkserver都设计得无论怎么样地被终止，都可以在在几秒内恢复它们的状态并启动。事实上，我们并没有区分正常和异常的终止。服务器通常都是通过杀死进程来关闭。客户端和其他服务器的请求超时后会经历一个小的停顿，然后重连那个重启后的服务器，进行重试。6.2.2报告了观测到的启动时间。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;5.1.2chunk备份</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">正如之前讨论的，每个chunk备份在不同机柜上的多个chunkserver上。用户可以在不同名字空间内设置不同的备份级别，默认是3.当chunkserver离线或者通过检验和检测到某个chunk损坏后(5.2节)，master会克隆现有的副本使得副本的数保持充足。尽管副本已经很好的满足了我们的需求，我们还探寻一些其他的具有同等或者更少code的跨机器的冗余方案，来满足我们日益增长的只读存储需求。我们期望在我们的非常松散耦合的系统中实现这些更复杂的冗余模式是具有挑战性但是可管理的。因为我们的负载主要是append和读操作而不是小的随机写操作。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;5.1.3master备份</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">为了可靠性，master的状态需要进行备份。它的操作日志和检查点备份在多台机器上。对于状态的变更只有当它的操作日志被写入到本地磁盘和所有的远程备份后，才认为它完成。为了简单起见，master除了负责进行各种后台活动比如：垃圾回收外，还要负责处理所有的变更。当它失败后，几乎可以立即重启。如果它所在的机器或者硬盘坏了，独立于GFS的监控设施会利用备份的操作日志在别处重启一个新的master进程。Client仅仅使用master的一个典型名称(比如gfs-test)来访问它，这是一个DNS名称，如果master被重新部署到一个新的机器上，可以改变它。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;此外，当主master down掉之后，还有多个影子master可以提供对文件系统的只读访问。它们是影子，而不是镜像，这意味着它们可能比主master要滞后一些，通常可能是几秒。对于那些很少发生变更的文件或者不在意轻微过时的应用程序来说，它们增强了读操作的可用性。实际上，因为文件内容是从chunkserver中读取的，应用程序并不会看到过期的文件内容。文件元数据可能在短期内是过期的，比如目录内容或者访问控制信息。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;为了保持自己的实时性，影子服务器会读取不断增长的操作日志的副本，然后像主master那样将这些变化序列应用在自己的数据结构上。与主master一样，它也会在启动时向chunkserver拉数据来定位chunk的副本，也会同它们交换握手信息以监控它们的状态。只有在主master决定创建或者删除副本时引起副本位置信息更新时，它才依赖于主master。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;5.2数据完整性</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">每个chunkserver通过校验和来检测存储数据中的损坏。GFS集群通常具有分布在几百台机器上的数千块硬盘，这样它就会经常出现导致数据损坏或丢失的硬盘失败。我们可以从chunk的其他副本中恢复被损坏的数据，但是如果通过在chunkserver间比较数据来检测数据损坏是不现实的。另外，有分歧的备份仍然可能是合法的：根据GFS的变更语义，尤其是前面提到的原子性的record append操作，并不保证所有副本是完全一致的。因此每个chunkserver必须通过维护一个检验和来独立的验证它自己的拷贝的完整性。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;一个chunk被划分为64kb大小的块。每个块有一个相应的32bit的校验和。与其他的元数据一样，校验和与用户数据分离的，它被存放在内存中，同时通过日志进行持久化存储。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;对于读操作，chunkserver在向请求者(可能是一个client或者其他的chunkserver)返回数据前，需要检验与读取边界重叠的那些数据库的校验和。因此chunkserver不会将损坏数据传播到其他机器上去。如果一个块的校验和与记录中的不一致，chunkserver会向请求者返回一个错误，同时向master报告这个不匹配。之后，请求者会向其他副本读取数据，而master则会用其他副本来clone这个chunk。当这个合法的新副本创建成功后，master向报告不匹配的那个chunkserver发送指令删除它的副本。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;校验和对于读性能的影响很小，因为：我们大部分的读操作至少跨越多个块，我们只需要读取相对少的额外数据来进行验证。GFS client代码通过尽量在校验边界上对齐读操作大大降低了开销。另外在chunkserver上校验和的查找和比较不需要任何的IO操作，校验和的计算也可以与IO操作重叠进行。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;校验和计算对于append文件末尾的写操作进行了特别的优化。因为它们在工作负载中占据了统治地位。我们仅仅增量性的更新最后一个校验块的校验值，同时为那些append尾部的全新的校验块计算它的校验值。即使最后一个部分的校验块已经损坏，而我们现在无法检测出它，那么新计算出来的校验和将不会与存储数据匹配，那么当这个块下次被读取时，就可以检测到这个损坏。(也就是说这里并没有验证最后一个块的校验值，而只是更新它的值，也就是说这里省去了验证的过程，举个例子假设最后一个校验块出现了错误，由于我们的校验值计算时是增量性的，也就是说下次计算不会重新计算已存在的这部分数据的校验和，这样该损坏就继续保留在校验和里，关键是因为这里采用了增量型的校验和计算方式)</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;与之相对的，如果一个写操作者覆盖了一个现有chunk的边界，我们必须首先读取和验证操作边界上的第一个和最后一个块，然后执行写操作，最后计算和记录新的校验和。如果在覆盖它们之前不验证第一个和最后一个块，新的校验和就可能隐藏掉那些未被覆盖的区域的数据损坏。(因为这里没有采用增量计算方式，因为它是覆盖不是append所以现有的检验和就是整个块的没法从中取出部分数据的校验和，必须重新计算)。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;在空闲期间，chunkserver可以扫描验证处在非活动状态的trunk的内容。这允许我们检测到那些很少被读取的数据的损失。一旦损坏被发现，master就可以创建一个新的未损坏副本并且删除损坏的副本。这就避免了一个不活跃的坏块骗过master，让之以为块有足够的好的副本。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;5.3诊断工具</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">全面而详细的诊断性的日志以很小的成本，带来了在问题分解，调试，性能分析上不可估量的帮助。没有日志，就很难理解那些机器间偶然出现的不可重复的交互。GFS生成一个诊断日志用来记录很多重要事件(比如chunkserver的启动停止)以及所有RPC请求和应答。这些诊断日志可以自由的删除而不影响系统的正常运行。然而，只要磁盘空间允许，我们会尽量保存这些日志。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;RPC日志包含了所有的请求和响应信息，除了读写的文件数据。通过匹配请求和响应，整理不同机器上的RPC日志，我们可以重新构建出整个交互历史来诊断一个问题。这些日志也可以用来进行负载测试和性能分析。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;因为日志是顺序异步写的，因此写日志对于性能的影响是很小的，得到的好处却是大大的。最近的事件也会保存在内存中，可以用于持续的在线监控。&nbsp;</p></div><img src ="http://www.cppblog.com/knzeus/aggbug/162111.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/knzeus/" target="_blank">knzeus</a> 2011-12-14 17:33 <a href="http://www.cppblog.com/knzeus/articles/162111.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】Google文件系统(上)  </title><link>http://www.cppblog.com/knzeus/articles/162110.html</link><dc:creator>knzeus</dc:creator><author>knzeus</author><pubDate>Wed, 14 Dec 2011 09:18:00 GMT</pubDate><guid>http://www.cppblog.com/knzeus/articles/162110.html</guid><wfw:comment>http://www.cppblog.com/knzeus/comments/162110.html</wfw:comment><comments>http://www.cppblog.com/knzeus/articles/162110.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/knzeus/comments/commentRss/162110.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/knzeus/services/trackbacks/162110.html</trackback:ping><description><![CDATA[<div><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; ">转载请注明：<a href="http://duanple.blog.163.com/blog/static/7097176720109145829346/" style="text-decoration: none; color: #189a0f; ">http://duanple.blog.163.com/blog/static/7097176720109145829346/</a><a href="http://duanple.blog.163.com/blog/static/70971767201091102339246/" style="text-decoration: none; color: #189a0f; "></a>&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; ">作者&nbsp;<a rel="nofollow" href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#112;&#104;&#121;&#108;&#105;&#112;&#115;&#64;&#98;&#109;&#121;" style="text-decoration: none; color: #189a0f; ">phylips@bmy</a>&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">摘要</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">我们设计实现了google文件系统，一个面向大规模分布式数据密集性应用的可扩展分布式文件系统。它运行在廉价的商品化硬件上提供容错功能，为大量的客户端提供高的整体性能。&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">尽管与现有的分布式文件系统具有很多相同的目标，我们的设计更多的来源于对于我们的具体应用的负载类型以及当前甚至未来技术环境的观察，这就使得它与早期的文件系统表现出明显的不同。这也使得我们重新审视传统上的设计选择，探索出一些在根本上不同的设计观点。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;这个文件系统成功的满足了我们的存储需求。伴随这研究和开发的努力，在google内部，它已经作为那些需要大数据集服务的数据生成处理的基础存储平台而广泛部署。迄今为止，最大的集群可以通过超过一千台机器的数千块硬盘提供数百T的存储，这些存储空间可以由数百个客户端并发访问。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;在本论文中，我们将描述为了支持分布式应用的文件系统扩展接口设计，讨论很多我们的设计观点，展示来自于beachmark和现实世界的一些测量数据。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;分类和主题描述：分布式文件系统&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;常用词：设计，可靠性，性能，测量</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;关键词：容错，可扩展，数据存储，集群存储</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;导引</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">为了满足google快速增长的数据处理需求，我们设计实现了google文件系统(GFS)。GFS与传统的分布式文件系统具有很多相同的目标比如性能，可扩展性，可靠性，可用性。然而，它的设计是由我们的具体应用的负载类型以及当前甚至未来技术环境的观察驱动的，所以与早期文件系统的设计假设具有明显的区别。我们重新审视传统上的设计选择，探索出一些在根本上不同的设计观点。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;一，组件失败成为一种常态而不是异常。文件系统是由成百上千台通过廉价的商品化部件组装起来的存储机器构成，可以被大量的客户端访问。组件的数量和质量在本质上决定了在某一时间有一些是不可用的，有一些无法从当前的失败中恢复过来。我们观察到，应用程序的bug，操作系统bug，人为的错误，硬盘的失败，内存，连接器，网络，电力供应都可以引起这样的问题。因此经常性的监控，错误检测，容错和自动恢复必须集成到系统中。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;二，与传统的标准相比，文件是巨大的。在这里，好几个G的文件是很普通的。每个文件通常包含很多的应用程序处理的对象比如网页文档。当我们日常处理的快速增长的数据集合总是达到好几个TB的大小，包含数十亿的对象时，去处理数十亿个KB级别的文件即使文件系统支持也会显得很笨重。这样设计中的一些假设和参数，比如IO操作和块大小就必须重新定义。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">三，大部分的文件更新模式是通过在尾部追加数据而不是覆盖现有数据。文件内部的随机写操作几乎是不存在的。一旦写完，文件就是只读的，而且通常是顺序读。大量的数据都具有这样的特点。有些可能是被数据分析程序扫描的库组成，有些可能是由运行中的应用程序持续生成的数据流，有些可能是档案数据，有些可能是数据需要由一台机器产生，然后由另一台机器处理而产生的中间结果。假设在大文件上数据访问具有这样的模式，那么当当缓存数据在客户端失效后，append操作就成为性能优化和原子性的关键。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;四，应用程序和文件系统api的协同设计，增加了整个系统的灵活性。比如我们通过放松了GFS的一致性模型大大简化了文件系统，同时也没有给应用程序带来繁重的负担。我们也提供了一个原子性的append操作，这样多个客户端就可以对同一个文件并行的进行append操作而不需要彼此间进行额外的同步操作。这些都会在后面进行详细的讨论。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;目前已经有多个GFS集群为了不同的目的而部署起来。最大的那个具有1000个存储节点，超过300T的磁盘空间，被来自不同机器的数百个客户端持续访问着。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2 设计概览</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">2.1 假设</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">在设计一个满足我们需求的文件系统时，我们以一些充满了挑战和机遇的假设作为指南，之前我们曾间接的提到过一些关键的点，现在我们把这些假设再详细的列出来。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;系统是由廉价的经常失败的商品化组件构建而来。必须进行经常性的监控和检测，容错，并且能够从组件失败中迅速的恢复，这些都应该像是例行公事。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;系统存储了适度个数的大文件。我们期望有数百万个文件，每个100mb或者更大。上GB的文件大小应该是很普通的情况而且能被有效的管理。小文件也应该被支持，但我们不需要为它们进行优化。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;工作负载主要由两种类型的读组成：大的顺序流式读取和小的随机读取。在大的流式读取中，单个操作通常要读取数百k，甚至1m或者更大的数据。来自于同一个客户端的连续读取，通常读完文件的一个连续区域。小的随机读取通常在某个任意的偏移位置读取几kb的数据。具有性能意识的应用程序会把这些小的随机读取按批次，排序使得读取可以稳步的穿越整个文件而不是来回读取。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;工作负载有很多大的对文件数据的append操作，通常操作的大小类似与读操作。一旦写完，件就很少改变，在文件内部的随机写操作可以被支持，但是性能不必是很高的。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;系统对于良好定义的多个客户端对相同文件的并行append操作必须提供有效的实现。我们的文件通常是用来作为生产者消费者队列或者进行多路归并。数百个生产者(每个机器上运行一个)将会对同一个文件进行append。因此具有最小化同步开销的原子性是必要的。文件之后可能会被读取，或者消费者可能并行的读取这个文件。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;高的持续带宽比低延时更重要。我们大部分的目标应用都希望得到高速的批量数据处理速度，很少有对于单个的读写有严格的响应时间需求。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2.2接口</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">GFS提供一个熟悉的文件系统接口，尽管它并没有实现一个诸如POSIX那样的标准API。文件通过目录进行层次化组织，通过路径来标识文件。支持常见的那些文件操作：create,delete,open,close,read,write。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;另外，GFS还具有快照和append操作。快照以很低的开销创建一个文件或者目录数的拷贝。Append操作允许多个客户端向同一个文件并发的操作，同时保证每个独立客户端的append操作的原子性。这对于实现多路归并的结果以及生成者消费者队列很有帮助，这样客户端不需要额外的锁操作就可以并行的append。我们发现这种文件类型，对于构建大型的分布式应用简直就是无价之宝。快照和append操作将会在3.4和3.3分别讨论。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2.3架构</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">一个GFS集群由一个master和多个chunkserver组成，可以被多个client访问，如图1所示。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; "></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">它们都是一个运行着用户级服务进程的商品化linux机器。可以很容易的在同一台机器上运行一个chunkserver和client，只要机器资源允许以及由于运行可能的片状应用程序代码带来的低可靠性是可以接受的。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;文件被划分成固定大小的chunk。每个chunk是由chunk创建时由master分配的一个不可变的全局唯一的64bit句柄来标识。Chunkserver将chunk作为linux文件存储在本地，对于chunk数据的读写通过chunk的handle和字节边界来表示。为了可靠性，每个chunk存储在多个chunkserver上。尽管用户可以为不同文件名字空间区域指定不同的备份级别，默认地我们存储三个备份。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Master维护所有的文件系统元数据。包括名字空间，访问控制信息，文件与chunk的映射信息，chunk的当前位置。它也控制系统范围内的一些活动，比如chunk租赁管理，僵死chunk的垃圾回收，chunkserver间的chunk迁移。Master与chunkserver通过心跳信息进行周期性的通信，以发送指令和收集chunkserver的状态。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">应用程序链接的GFS客户端代码实现了文件系统API以及代表应用程序与master和chunkserver进行通信以读写数据。客户端如果需要操作元数据则需要与master通信，但是所有的纯数据通信直接与chunksever通信。我们没有提供POSIX API，因此也就不需要与linux vnode layer关联。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;客户端或者chunkserver都不会进行文件数据缓存。客户端缓存只能得到很少的好处，因为大部分的应用需要直接读取整个大文件或者工作集合太大根本无法缓存。没有cache简化了客户端和整个系统，因为不需要考虑缓存一致性问题(实际上客户端会缓存元数据)。Chunkserver不需要进行文件数据缓存，是因为chunk是作为本地文件存储，这样linux自身会将那些经常访问的数据进行缓存。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2.4 单Master</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;只有一个master大大简化了我们的设计，而且使得master可以利用全局信息对chunk的放置和备份进行更好的判断。然而，我们必须最小化它在读写中的参与性，使得它不会成为一个瓶颈。Client永远不会通过master读取文件数据，它只是问master它应该同哪个chunkserver联系。并且client将这些信息在有限的时间段内进行缓存，直接与chunksever交互进行很多后续的操作。</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; "><img alt="【google论文一】Google文件系统 - 星星 - 银河里的星星" src="http://img775.ph.126.net/4IwRdyPfQXXdVp91zt7Q3g==/4853191548445388985.png" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; " /></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;根据图1，我们简单解释一些一个读操作的交互过程：首先，通过固定大小的chunk，客户端将应用程序中标识的文件名和offset转换为chunk的index。然后给master发送一个包含文件名和chunk index的请求，master返回相应的chunk的handle和所有备份的位置。客户端以文件名和chunk index为key将这条信息进行缓存。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;然后客户端给其中一个备份发送一个请求，通常是最近的那个。请求标识了chunk 的handle以及在那个chunk内的字节边界。直到缓存信息过期或者重新打开文件之前，对于相同chunk的后续读操作就不需要client-master的通信了。事实上，客户端通常在一个请求中查询多个chunk的信息，master也可以将这些被请求的多个chunk的信息包裹在一块进行返回。这种特别的信息，并没有额外的花费就避免了未来的client-master的多次通信。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2.5 chunk大小</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">Chunk大小是一个关键的设计参数。我们选择了64mb，远远大于现有的文件系统块。每个chunk的副本作为普通的linux文件存储在chunkserver上，如果需要才会进行扩展。Lazy空间分配避免了内部碎片造成的空间浪费，很可能最大的碎片有向一个chunk那么大。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;大的chunk size提供了几个重要的优势。首先，降低了client与sever的交互需求，因为在相同chunk上的读写只需要一个初始化请求就可以从master得到chunk的位置信息。这个减少对于我们的应用负载是非常明显的，因为我们应用大部分需要顺序的读写整个大文件。即使对于小的随机读取，客户端也可以很容易的缓存一个几TB工作集的所有chunk的位置信息。其次，由于chunk很大，那么客户端就很有可能在一个给定的chunk上执行更多的操作，这样可以将一个与chunkserver的TCP连接保持更长的时间，这就减少了网络开销。再者，降低了存储在master上的元数据大小。这样就允许我们将元数据存放在内存中，反过来就带来了我们将在2.6.1中讨论的其他优势。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;另一方面，大的chunk size，即使采用了lazy空间分配，也有它的缺点。小的文件可能只有少数几个chunk，或许只有一个。如果很多的client都需要访问这个文件，这样那些存储了这些chunk的chunkserver就会变成热点。实际中，热点还没有成为一个主要的考虑点因为我们的应用绝大部分都是在顺序读大的多chunk文件。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;然而，当GFS第一次使用在一个批处理队列系统时，热点确实出现了：一个可执行文件作为一个chunk的文件写到GFS，然后同时在数百台机器上开始执行。存储了该可执行文件的那些chunkserver被数百个并发请求瞬间变成超载。我们通过更高的备份级别存储这样的可执行文件以及减慢队列系统的应用程序启动时间解决了这个问题。一个潜在的长远的解决方案是在这种情况下，允许客户端从其他客户端读取数据。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2.6元数据</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">Master存储了三个主要类型的元数据：文件和chunk名字空间，文件到chunk的映射信息，每个chunk的备份的位置。所有的元数据都保存在master的内存中。前两种类型还通过将更新操作的日志保存在本地硬盘和备份在远程机器来保持持久化。使用log允许我们简单可靠地更新master的状态，不用担心当master crash的时候的不一致性。Master并没有永久保存chunk的位置信息，而是在master启动或者某个chunkserver加入集群时，它会向每个chunkserver询问它的chunks信息。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2.6.1 内存数据结构</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">由于元数据存储在内存里，master的操作是很快的。因此对于master来说，可以简单有效地对在后台整个状态进行周期性扫描。这个周期性的扫描是用来实现chunk垃圾回收，chunkserver出现失败时进行的重复制，以及为了平衡负载和磁盘空间在chunkserver间的chunk 迁移。4.3 4.4将进一步讨论这些活动。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;这样全内存策略存在一个潜在的限制就是chunk的数目，因此整个系统的容量取决于master有多少可用内存。实际中这不是一个很严重的限制。Master为每个64MB的chunk维护少于64byte的数据。大部分的chunk是满的，因为大部分的文件包含多个chunk，只有最后一个chunk可能是未慢的。类似的，每个文件名字空间数据通常需要少于64byte因为文件名称存储时会使用前缀压缩算法进行压缩。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;如果需要支持更大的文件系统，只需要往master里添加内存。这点开销与通过将元数据存储到内存所得到简单性，可靠性，性能和灵活性，将是很小的一笔花费。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2.6.2 chunk location</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Master并没有提供一个永久性的存储保存对于一个给定的chunk都是那些chunkserver保存了它的副本。它只是在启动时，简单地从chunkserver那里把这些信息拉过来。Master能够保证它自己是更新过的，因为是由它来控制chunk的放置，以及通过周期性的心跳信息来监控chunkserver。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;起初，我们尝试将chunk位置信息永久保存在master，但是我们发现在启动时去chunkserver请求这些数据更简单。这样避免了当chunkserver在加入或者离开集群，改名，失败，重启等待时需要的master与chunkserver间的同步。在一个数百台机器的集群中，这样的事件太经常了。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;理解这个设计决定的另一个方式是chunkserver对于自己有还是没有某个chunk具有最终的发言权。在master上维护一个这些信息一致性视图是没有意义的，因为发生在chunkserver上的错误可能使得一些chunk突然间不见了(比如硬盘可能会坏掉或者不可用)，一个操作可能将chunkserver重命名。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2.6.3操作日志</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">操作日志包含了关键元数据改变的历史记录。它是GFS的核心。它不仅是元数据的唯一一致性记录，而且它也定义了那些并发操作的逻辑上的时间表。文件和chunk的版本都是唯一和永恒地由它们创建时的逻辑时间来标识的。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;因此操作日志是很关键的，我们必须可靠地保存它，在任何元数据变更被持久化之前不应当被客户端看到。否则，我们将丢失整个文件系统或者最近的客户端操作即使chunckserver自己保存了它们。因此我们将它备份在多个远程机器上，对于一个客户端操作只有当该操作对应的日志记录被刷新到本地和远程的磁盘上时才会发出响应。Master将几个操作日志捆在一块刷新，从而降低刷新和复制对于整个系统吞吐率的影响。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;Master通过重新执行操作日志来恢复它的文件系统。为了最小化启动时间，我们必须将日志是保持在很小的规模。当日志增长超过一定的大小后，Master给它的状态设置检查点，它可以通过从本地磁盘加载最新的检查点进行恢复，然后重新执行那些在该检查点之后的日志记录。检查点保存了一个压缩的类B树的结构，不需要额外的解析就可以直接映射到内存用于名字空间查找。这大大提高了恢复的速度和可用性。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;因为建立一个检查点会花费一些时间，master的内部状态的结构设计使得一个新的检查点可以不需要延时那些接受到的变化就可以被创建。Master会启动一个新的线程切换到一个新的日志文件然后创建新的检查点。这个新的检查点包含在切换之前的所有变更。对于一个包含几百万文件的集群大概需要几分钟就可以完成。结束后，它将会被写回本地和远程的磁盘。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;恢复只需要最新完全的检查点和后来的日志文件。更老的检查点和日志文件可以自由的删除，当然我们会保存了一些来应对某些突发情况。在创建检查点的时候发生的失败不会影响系统的正确性，因为恢复代码会检测和跳过不完全的检查点。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2.7一致性模型</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;GFS使用的一个放松的一致性模型不但很好的支持了我们的高度分布式的应用，而且实现起来也相对简单和有效率。我们现在讨论GFS所提供的保证以及它们对应用程序的意味着什么。我们也会讲述GFS如何维护这些保证，但是会将具体的细节留到其他论文里讲述。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;2.7.1 GFS提供的保证</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">文件名字空间的改变(比如文件创建)是原子性的。它们只由master进行处理：名字空间锁来保证原子性和正确性(4.1节)。Master的操作日志定义了这些操作的全局性的排序。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;当数据变更后，文件区域的状态取决于变更的类型，变更是否成功以及是否是并发进行的。表1是对结果的一个概述。</p><p align="center" style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; background-color: #ffffff; "><img alt="【google论文二】Google文件系统(上) - 星星 - 银河里的星星" src="http://img775.ph.126.net/F0_0Kk-xE5g634xhCo8fxw==/4853191548445389018.png" width="329" height="155" style="border-top-width: 0px; border-right-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-color: initial; max-width: 100%; margin-top: 0px; margin-right: 10px; margin-bottom: 0px; margin-left: 0px; " /></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; "></p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">如果所有的客户端无论从哪个副本读取数据总是看到相同的数据，那么我们就说文件区域是一致的。如果文件数据变更后是一致的，同时客户端可以看到它所有的变更，那么我们就说文件区是已定义的。当一个变更成功后，且没有受到其他并发写者的影响，那么被影响的区域就是定义良好的(肯定是一致性的)：所有的客户端将会看到所做的变更。并发的成功的变更，会使区域进入未定义的状态但是还是一致的：所有的客户端可以看到一致的数据，但是它可能无法看到所有的变更(如果变更是针对相同的数据写这样有的变更就会被新的变更所覆盖，这样用户就无法看到最先的变更了，同时发生在跨chunk的操作会被拆分成两个操作，这样这个操作的一部分可能会被其他操作覆盖，而另一部分则保留下来，如3.1节末尾所述)。通常它看到的是多个变更组合后的结果。一个失败的变更会使区域进入非一致的状态(因此也是未定义的状态)：不同的客户端在不同的访问中可能看到不同的数据。我们下面描述下我们的应用程序如何区分定义良好的区域和未定义的区域。应用程序不需要进一步区分未定义区域的各种不同的类型。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;数据变更可能是写或者记录append。写操作会使数据在应用程序指定的偏移位置写入。记录append操作会使数据原子性的append，如果是并发性的话则至少会被append一次，但是偏移位置是由GFS决定的(然而，通常的理解可能是在客户端想写入的那个文件的尾部)。偏移位置会被返回给客户端，同时标记包含这条记录的那个定义良好的文件区域的起始位置。另外GFS可能会在它们之间插入一些padding或者记录的副本。它们会占据那些被认为是不一致的区域，通常它们比用户数据小的多。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;在一系列成功的变更之后，变更的文件区域被保证是已定义的，同时包含了最后一次变更的数据写入。GFS通过两种方式来实现这种结果a.将这些变更以相同的操作顺序应用在该chunk的所有的副本上，b.使用chunk的版本号来检测那些老旧的副本可能是由于它的chunkserver挂掉了而丢失了一些变更。陈旧的副本永远都不会参与变更或者返回给那些向master询问chunk位置的client。它们会优先参与垃圾回收。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;因为客户端会缓存chunk的位置，在信息更新之前它们可能会读到陈旧的副本。时间窗口由缓存值的超时时间以及文件的下一次打开而限制，文件的打开会清楚缓存中该文件相关的chunk信息。此外，由于我们的大部分操作都是记录的append，因此一个陈旧副本通常会返回一个过早结束的chunk而不是过时的数据。当读取者重试并与master联系时，它会立即得到当前的chunk位置。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;成功的变更很久之后，组件失败仍有可能破坏或者污染数据。GFS通过周期性的在master和所有chunkserver间握手找到那些失败的chunkserver，同时通过校验和(5.2节)来检测数据的污染。一旦发现问题，会尽快的利用正确的副本恢复(4.3节)。只有一个块的所有副本在GFS做出反应之前，全部丢失，这个块才会不可逆转的丢失，而通常GFS的反应是在几分钟内的。即使在这种情况下，块不可用，而不是被污染：应用程序会收到清晰的错误信息而不是被污染的数据。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">2.7.2 对于应用程序的影响</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">GFS应用程序可以通过使用简单的技术来适应这种放松的一致性模型，这些技术已经为其他目的所需要：依赖与append操作而不是覆盖，检查点，写时自我验证，自己标识记录。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;实际中，我们所有的应用程序都是通过append而不是覆盖来改变文件。在一个典型应用中，一个写操作者会从头至尾生成一个文件。当写完所有数据后它自动的将文件重命名为一个永久性的名称，或者通过周期性的检查点检查已经有多少数据被成功写入了。检查点可能会设置应用级的校验和。读取者仅验证和处理最后一个检查点之前的文件区域，这些区域处于已定义的状态。无论什么样的并发和一致性要求，这个方法都工作的很好。Append操作比随机写对于应用程序的失败处理起来总是要更加有效和富有弹性。检查点允许写操作者增量性的重启(不需要重新从头写)，允许读取者可以处理那些已经成功写入的数据，虽然在应该程序的看来仍然是不完全的。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;另一种典型的应用中，很多写者同时向一个文件append为了归并文件或者是作为一个生产者消费者队列。记录的append的append-at-least-once语义保证了每个写者的输出。读取者这样处理偶然的padding和重复数据。写者为每条记录准备一些额外信息比如校验和，这样它的合法性就可以验证。如果不能容忍重复的数据(比如它们可能触发非幂等操作)，可以通过在记录中使用唯一标识符来过滤它们，很多时候都需要这些标识符命名相应的应用程序实体，比如网页文档。这些用于record输入输出的功能函数是以库的形式被我们的应用程序共享的，同时应用于gongle其他的文件接口实现。所以，相同系列的记录，加上一些罕见的重复，总是直接被分发给记录读取者。</p><p style="line-height: 25px; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; font-family: Arial, Helvetica, simsun, u5b8bu4f53; text-align: left; background-color: #ffffff; text-indent: 2em; ">&nbsp;在以上的描述中，存在一个基本的假定：数据是以record形式存储的，而且通常这些record都是可以重复的，比如一个网页文档我们可以重复存，这对于数百亿的网页文档来说，存储少数多余的很正常，也就是说这些数据通常是文本，而不是二进制，所以我们才可以在append或者写时用记录的副本来覆盖非一致的区域，所以提供了append的appen<span class="Apple-style-span" style="font-family: verdana, 'courier new'; line-height: 21px; ">d-at-least-once语义，因为append二次也是可以的。如果我们要保证唯一性，可以在应用层增加逻辑。</span></p></div><img src ="http://www.cppblog.com/knzeus/aggbug/162110.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/knzeus/" target="_blank">knzeus</a> 2011-12-14 17:18 <a href="http://www.cppblog.com/knzeus/articles/162110.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【转载】面向星球的网络搜索：google集群架构  </title><link>http://www.cppblog.com/knzeus/articles/162106.html</link><dc:creator>knzeus</dc:creator><author>knzeus</author><pubDate>Wed, 14 Dec 2011 07:37:00 GMT</pubDate><guid>http://www.cppblog.com/knzeus/articles/162106.html</guid><wfw:comment>http://www.cppblog.com/knzeus/comments/162106.html</wfw:comment><comments>http://www.cppblog.com/knzeus/articles/162106.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/knzeus/comments/commentRss/162106.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/knzeus/services/trackbacks/162106.html</trackback:ping><description><![CDATA[<div><p style="text-indent: 2em;">转载请注明：<a href="http://duanple.blog.163.com/blog/static/70971767201091102339246/">http://duanple.blog.163.com/blog/static/70971767201091102339246/</a>&nbsp;作者 <a rel="nofollow" href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#112;&#104;&#121;&#108;&#105;&#112;&#115;&#64;&#98;&#109;&#121;">phylips@bmy</a></p>  <p style="text-indent: 2em;">为 了能够支持可扩展的并行化，google的网络搜索应用让不同的查询由不同的处理器处理，同时通过划分全局索引，使得单个查询可以利用多个处理器处理。针 对所要处理的工作负载类型，google的集群架构由15000个普通pc机和容错软件组成。这种架构达到了很高的性能，同时由于采用了普通pc机，也节 省了采用昂贵的高端服务器的大部分花费。&nbsp;</p>  <p style="text-indent: 2em;">很少有网络服务的单个请求像搜索引擎 占用那样多的计算资源。平均来看，在google上的每次查询需要读取数百m的数据耗费数10亿的cpu指令循环。为了能够支持峰值在每秒数千次请求流， 需要与世界上最大的超级计算机规模相当的硬件设施。通过容错性软件将15000个普通pc机联合起来，提供了一种比使用高端服务器更廉价的解决方案。&nbsp;</p>  <p style="text-indent: 2em;">本文我们将介绍google的集群架构，讨论那些影响到设计方案的最重要的因素：能效和性价比。在我们的实际操作中，能效实际上是一个关键的度量标准，因为数据中心的电力是有限的，因此电力耗费和制冷成为运作中的关键。&nbsp;</p>  <p style="text-indent: 2em;">我 们的应用本身可以很容易进行并行化：不同的查询可以运行在不同的处理器上，同时全局索引也划分的使得单个查询可以使用多个处理器。因此，处理器的性价比比 峰值性能变得更重要。同时，google的应用是面向吞吐率的，可以更有效的利用处理器提供的并行化，比如并行多线程(SMT)，或者多核处理器 (CMP)。&nbsp;</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">Google架构概览&nbsp;</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">Google 的软件架构来源于两个基本的观点。首先我们需要在软件层面提供可靠性，而不是通过硬件，这样我们就可以使用普通的pc构建廉价的高端集群。其次，我们不断 的裁剪设计是为了达到最好的总体请求吞吐率，不是为了提高服务器的峰值响应时间， 因为我们可以通过并行化独立的请求来控制响应时间。&nbsp;</p>  <p style="text-indent: 2em;">我 们相信使用不可靠的廉价pc来构建可靠的计算设施可以达到最好的性价比。通过在不同的机器上备份服务，以及自动化的故障检测和错误处理，为我们的环境提供 软件级的可靠性。这种软件级的可靠性在我们的系统设计中几乎随处可见。检查一下一次查询处理的控制流程，有助于理解这种高级的查询服务系统，同时也有助于 对于可靠性考虑的理解。</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">Google的一次查询</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">当用户在google中输入一次查询，用户浏览器首先通过DNS进行域名解析，将<a rel="nofollow" href="http://www.google.com/">www.google.com</a>转 换为ip地址。为了对查询可以进行更有效的处理，我们的服务由分布在世界各地的多个集群组成。每个集群大概有数千个机器，这种地理上的分布可以有效的应付 灾难性的数据中心失败比如地震，大规模的停电。基于DNS的负载平衡系统，会计算用户的与每一个物理集群地理上的距离来选择一个合适的物理集群。负载平衡 系统，需要最小化请求往返时间，同时要考虑各个集群的可用容量。&nbsp;</p>  <p style="text-indent: 2em;">用户浏览器然 后给这些集群中的一个发送一个http请求，之后，对于该集群来说，所有的处理都变成了本地化的。在每个集群中有一个基于硬件的负载平衡器监控当前可用的 google web  servers(GWS)集合，并在这个集合上将本地的请求处理进行负载平衡。收到一个请求之后，GWS协调这个查询的执行，并将结果格式化为html语 言。图1表示了这个过程。&nbsp;</p>  <p align="center"><img style="margin: 0px 10px 0px 0px;" alt="面向星球的网络搜索：google集群架构 - 星星 - 银河里的星星" src="http://img763.ph.126.net/1Xkml6perMaIhK6pgHQcxw==/4827295850588008953.png" /></p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">查询执行由两个主要阶段组成，第一个阶段，索引服务器查阅倒排索引(将每个查询词映射到匹配的文档列表)。索引服务器然后决定相关的文档集合，通过对每个查询词匹配的文档列表求交集，为每个文档计算出一个相关性的分值，这个分值决定了在输出结果中的排序。</p>  <p style="text-indent: 2em;">&nbsp;搜 索的过程非常具有挑战性，因为需要处理海量数据：原始网页文档通常具有数十T的未压缩数据，从原始数据中导出的倒排索引本身也有好几T的数据。幸运的是， 通过将索引划分到不同的片段，可以将搜索高度并行化，每个片段具有从全布索引中随机选择的一个文档子集。一组机器负责处理对于一个索引片段的请求，在整个 集群中每个片段都会有这样的一组机器与之对应。每个请求通过中间负载平衡器选择组内机器中的一个，换句话说每个查询将会访问分配到每个片段的一台机器(或 者是一组机器的子集)。如果一个片段的备份坏了，负载平衡器将会避免在查询时使用它，我们的集群管理系统的其他组件将会尝试修复它，实在不行就用另一台机 器来取代它。停工期间，系统的容量需要减去那台坏掉的机器所代表的容量。然而，服务仍然是未中断的的，索引仍然是可用的。&nbsp;</p>  <p style="text-indent: 2em;">第 一阶段的查询执行最终输出一个排过序的文档标识符列表。第二阶段则通过获取这个文档列表，然后计算出所有文档的标题和url以及面向查询内容的文档摘要。 文档服务器处理这项任务，从硬盘中获取文档，抽取标题以及查询关键词在文档中的出现片段。像索引查找阶段，这里的策略也是对文档进行划分，主要通过：随机 分布文档到不同的小片段；针对每个片段的处理具有多个服务器作为备份；通过一个负载平衡器分发请求。文档服务器必须能够访问一个在线的低延时的整个网络的 网页的副本。实际上由于对于这个副本的访问需要性能及可用性，所以google实际上在集群中存储了整个web的多个副本。&nbsp;</p>  <p style="text-indent: 2em;">除了索引和文档服务阶段，GWS在收到查询时还会初始化几个其他的辅助任务，比如将查询发送给拼写检查系统，广告系统生成相关广告。当所有阶段完成后，GWS生成html输出页面，然后返回给用户浏览器。</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">使用备份进行容量扩充和容错</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">我们对系统进行了一些结构化以保证对于索引和其他响应查询相关的数据结构是只读的：更新是相对不频繁的，这样我们就能通过将查询转移到一个服务备份来安全的进行更新。 这条原则，使得我们避免了很多在通用数据库中出现的一致性问题。&nbsp;</p>  <p style="text-indent: 2em;">我 们也尽力挖掘出大量应用中的固有的并行性：比如我们将在一个大索引中的匹配文档的查询转化为针对多个片段中的匹配文档的多个查询加上开销相对便宜的归并步 骤。类似的，我们将查询请求流划分为多个流，每个由一个集群来处理。增加为每个处理索引片段的机器组增加机器来增加系统容量，伴随着索引的增长增长片段的 个数。通过将搜索在多个机器上并行化，我们降低了响应一个查询的必需的平均延时，将整个计算任务划分在多个cpu和硬盘上。因为独立的片段相互之间不需要 通信，所以极速比几乎是线性的。换句话说，单个索引服务器的cpu速度不会影响整个搜索的整体性能，因为我们可以增加片段数来适应慢的cpu，因此我们的 硬件选择主要关注那些可以为我们的应用提供出色的请求吞吐率的机器，而不是可以提供最高的单线程性能的那些。</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">简单来说，google的集群主要遵循下面三个主要设计原则：</p>  <p style="text-indent: 2em;">软件可靠性。我们没有选择硬件性容错，比如采用冗余电源，RAID，高质量组件，而是专注于软件可靠性。&nbsp;</p>  <p style="text-indent: 2em;">使用备份得到更好的吞吐率和可用性。因为机器本身是不可靠的，我们备份我们的内部服务在很多机器上。通过备份我们得到了容量，与此同时也得到了容错，而这种容错几乎是免费的。&nbsp;</p>  <p style="text-indent: 2em;">性价比重于峰值性能。我们购买当前最具性价比的cpu，而不是那些具有最高绝对性能的cpu。&nbsp;</p>  <p style="text-indent: 2em;">使用普通pc降低计算花费。这样我们可以为每一个查询提供更多的计算资源，在ranking算法中使用更昂贵的技术，可以搜索文档的更大的索引。</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">使用商业化部件</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">Google 的机柜是专门定制的，由两面组成，总共放置了40到80个基于80x86的服务器(每侧包含20个20u或者40个10u服务器)  。我们对于性价比的偏爱，使得我们选择自己组装的桌面pc通过它们的组件，除了选择大的硬盘驱动器。目前的服务中使用了好几个cpu产品，从533m  intel-celeron 到双核1.4G  Intel奔三服务器。每个服务器包含一个或者多个IDE硬盘，每个80g空间。与文档服务器相比，索引服务器通常具有更少的磁盘空间，因为它的负载类型 是对cpu更敏感。在机柜一侧的服务器通过一个100m  以太网交换机相连，该交换机通过一个或者两个GB级的链路与一个核心的GB交换机相连，该核心交换机连接所有的机柜。&nbsp;</p>  <p style="text-indent: 2em;">我 们的根本选择标准是单次查询花费，可以表示为性能/资金花费总和(包括折旧)+管理花费(主机，系统管理，维修)。实际来看，一个服务器的寿命通常不会超 过2，3年，因为与新机器相比，无法在性能上保持一致。三年前的机子性能上要远远落后于当前的机子，对于包含这两类机器的集群，很难达到合适的负载分布和 配置。有了这个相对的短期分摊周期，可以看到设备的花费在总的开销中占到相当的一部分。&nbsp;</p>  <p style="text-indent: 2em;">因 为google服务器是专门定制的，我们可以使用基于pc的服务器机柜价格做一个展示。比如在2002年，一个88 个双核cpu 2G intle  xeon，2G内存，80G硬盘的机柜在RackSaver.com上的价格大概是27800$,转换成三年周期每月的花费将是7700$。剩下的主要花 费是人力和hosting。</p>  <p style="text-indent: 2em;">&nbsp;设备花费的相对重要性使得采用传统的服务器解决方 案并不适合解决我们的问题，因为虽然它们可以提高性能，但是降低了性价比。比如4处理器的主板是很昂贵的，由于我们的应用已经进了很好的并行化，这样的主 板不需要额外的花费就能获得很好的性能.类似的，尽管SCSI硬盘更快也更可靠，但是它们通常比同样容量的IDE硬盘贵2-3倍。&nbsp;</p>  <p style="text-indent: 2em;">使 用基于基于廉价的PC的集群比使用高端多处理器服务器在成本上的优势是非常明显的，至少对于像我们这样的高并行化应用来说。上面例子中的27800$的机 柜包含176个2G Xeon  CPU，176G内存，7T硬盘空间。与此相比，一个典型的x86服务器具有8个2GCPU，64G内存，8T硬盘空间，花费大概758000$。换句话 说，这个服务器贵了3倍，但是cpu只是原来的1/22，内存是1/3,硬盘空间稍微多了点。价格的差距，主要是源于更高的互联网络带宽和可靠性。但是 google的高度冗余架构并不依赖于这两个中的任何一个。&nbsp;</p>  <p style="text-indent: 2em;">管理数千台中型 PC机和一些高端多处理器服务器将会带来完全不同的系统管理和维修费用。然而，对于一个相对同构的的应用来说，大部分的服务器只用来运行少数应用中的一 个，这些开销是可管理的。加速安装和更新工具都是可用的，维护1000台和100台服务器所需的时间和成本相差并不大，因为所有的机器都具有相同的配置。 类似的，通过使用可扩展的应用监控系统，监控的花费伴随这集群的大小增长也不会有太大的增长。另外，我们可以通过批处理修复将修复的开销保持在一个较低的 水平，同时保证我们可以很容易的替换掉那些具有高损坏率的组件，比如硬盘和电源。</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">电力问题</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">如 果没有特殊的高密度包装，电力消耗和制冷设备将会成为一个挑战。一个中型的1.4G奔三服务器在有负载情况下，通常要耗费90w直流电：55w给 cpu，10w给硬盘，25w给DRAM加电和主板，对于一个ATX电源，通常具有75%的效率，这样转化成交流电就是120w，每个机柜就需要 10kw。一个机柜需要25 ft2  的空间，这样算下来，用电的密度就是400w/ft2。如果采用高端处理器，一个机柜的用电密度可以超过700w/ft2。</p>  <p style="text-indent: 2em;">&nbsp;不 幸的是，通常的商业数据中心电力密度在70-150w/ft2之间，远远低于pc集群的需求。这样，如果低端pc集群使用相对直接的包装方式，就需要特殊 的制冷和额外的空间来降低用电密度使得与标准数据中心兼容。因此只要机柜还存放在标准数据中心中，如果想要往机柜里增加服务器就会在实际部署中收到限制。 这种情况就使得我们考虑降低单服务器的电力使用是否是可能的。 </p>  <p style="text-indent: 2em;">&nbsp;对于大规模集 群来说，低功耗服务器是非常具有吸引力的。但是我们必须要牢记以下几点：降低电力是迫切的，但是对于我们的应用来说，不能带来性能上的惩罚，我们关心的是 每单元性能的瓦特，不是单纯的瓦特，第二，低功耗的服务器必须不能太过昂贵，因为服务器的折旧花费通常要超过电力的花费。前面提到的10kw机柜，每月大 概消耗10mwh的电力(包括制冷的开销)，假设每kwh电15美分，这样每月需要花费1500$，与折旧的7700$相比并不算大。因此低功耗服务器不 能够多于我们通过采用常规pc节省下的那部分花费。</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">硬件级别的应用特点</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">检 查我们应用的各种架构特点可以帮助我们搞清楚哪种硬件平台可以为我们的索引查询系统提供最高的性价比。我们着重于索引服务器的特点，该模块的性价比对于整 体的性价比起着主导性的作用。索引服务器的任务主要包括：对倒排索引中的压缩数据进行解压，找到与一个查询相匹配的文档集合。表1展示了一些索引服务器程 序的基本的指令级别的度量，程序运行在一个1G双核奔三系统上。</p>  <p align="center"><img style="margin: 0px 10px 0px 0px;" alt="面向星球的网络搜索：google集群架构 - 星星 - 银河里的星星" src="http://img.ph.126.net/t4v4Vh3om3GD1tvmr9PIxA==/3280027903610834230.png" /></p>  <p style="text-indent: 2em;">&nbsp;&nbsp; </p>  <p style="text-indent: 2em;"></p>  <p style="text-indent: 2em;">&nbsp;考 虑到奔三每个circle基本上可以处理三条指令，可以看到该应用程序的CPI(cycles per  instruction)稍微偏高。我们可以预见到这种行为，考虑到我们的应用程序使用了很多动态数据结构而控制流是依赖于数据的，这样就会产生大量的很 难预测的分支。事实上，如果相同的工作负载在奔四处理器上运行时，CPI几乎增长了2倍，分支预测几乎相同，尽管奔四具有更强大的指令并行和分支预测功 能。可见，在这种工作负载类型下，并没有太多的指令级并行性可供挖掘。测量结果显示，对于我们的应用来说，出现在处理器中的大量的乱序不确定性执行成为降 低程序性能的关键点。</p>  <p style="text-indent: 2em;">&nbsp;对于像索引服务器这样的应用来说，更合适的挖掘并行性的 方式应该是提高平凡的计算并行性。系统在处理每个查询时共享只读数据，建立只需要很少通信的工作单元。我们在集群级别上通过部署大量的廉价节点取代少量的 昂贵节点来发挥这个优势，  挖掘在微架构级别上的线程级并行性看起来也是可行的。并行多线程(SMT)和多处理器架构(CMP)都是面向线程级的并行性，都可以大大提高我们的服务器 的性能。一些针对Intel Xeon处理器的早期实验表明通过使用两个上下文的SMT比单个上下文具有30%的性能提升。</p>  <p style="text-indent: 2em;">&nbsp;我 们相信对于CMP系统提升的潜力应该是更大的。在CMP的设计中，采用多个简单的，按序执行的，短流水线核取代复杂的高性能核。如果我们的应用具有很少的 指令级并行性(ILP)，那么由于按序执行所带来的性能惩罚也是很小的。同时短流水线将可以减少甚至排除分支预测失败所造成的影响。线程级的并行性伴随着 核的增长可以呈现出接近线性的加速比，同时一个合理大小的共享L2 cache将会加速处理器间的通信。</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">内存系统</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">表 1  也描述了主存系统的性能参数，我们可以观察到对于指令cache和指令tlb具有良好的性能，由于使用了较小的内层循环代码。索引数据块不具有时间局部 性，因为索引数据大小变化剧烈同时对于索引的数据块访问模式是不可预测的。然而对于一个索引数据块的访问可以从空间局部性上获益，这种局部性能够通过硬件 预取或者大的缓存line开拓出来。这样如果使用相对合适cache大小就可以得到好的全局cache命中率。</p>  <p style="text-indent: 2em;">&nbsp;内 存带宽看起来并不会成为一个瓶颈。我们估计奔腾系列处理器系统的内存带宽使用率可以很好的控制在20%以下。主要是由于对于索引数据的每个缓存行，需要放 到处理器cache里，这需要大量的计算资源，此外在数据获取中还存在天然的依赖关系。在很多情况下，索引服务器的内存系统行为正如TPC- D(Transaction Processing Performance Counicil&#8217;s benchmark  D)所报告的那样。对于这种工作负载类型，采用一个相对合适的L2cache大小，短的L2 cache和内存延时，长的(比如128字节)cache  line可能是最有效的。</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">大规模多处理</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">正 如前面提到的，我们的设备是一个由大量廉价pc组成的庞大集群，而不是少数大规模的共享内存机组成的。大规模共享内存机主要用于在计算通信比很低的时候， 通信模式或者数据划分是动态或者难预测的，或者总的花费使得硬件花费显得很少的时候(由于管理日常费用和软件许可证价格)。在这些情况下，使得它们的高价 格变得合理。</p>  <p style="text-indent: 2em;">&nbsp;在google，并不存在这样的需求，因为我们通过划分索引数据 和计算来最小化通信和服务器间的负载平衡。我们自己开发需要的软件，通过可扩展的自动化和监控来降低系统管理的日常费用，这些使得硬件花费成为整个系统开 销中显著的一块。另外，大规模的共享内存机器不能很好的处理硬件或者软件的失败。这样大部分的错误可能导致整个系统的crash。通过部署大量的多处理器 机，我们可以将错误的影响控制在一个小的范围内。总的来看，这样的一个集群通过明显的低成本解决了我们的服务对于性能和可用性的需求。</p>  <p style="text-indent: 2em;">&nbsp;初 看起来，好像很少有应用具有像google这样的特点，因为很少有服务需要数千台的服务器和数pb的存储。然而可能有很多的应用需要使用基于pc的集群架 构。一个应用如果关注性价比，能够运行在不具有私有状态的多个服务器上(这样服务器就可以被复制)，它都有可能从类似的架构中获益。比如一个高容量的 web服务器或者一个  计算密集型的应用服务器(必须是无状态的)。这些应用具有大量的请求级并行性(请求可以划分在在独立的服务器上处理)。事实上，大的web站点已经采用这 样的架构。</p>  <p style="text-indent: 2em;">&nbsp;在google规模上，大规模服务器的并行化的一些限制确实变得明 显起来，比如商业数据中心在制冷容量上的限制，当前的cpu对于面向吞吐率的应用所做的优化还远远不够。虽然如此，通过使用廉价pc，明显地提高了我们可 以为单个查询所能支付的计算量。因此有助于帮助我们提高成千上万的用户的网络搜索体验。</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">致谢</p>  <p style="text-indent: 2em;">&nbsp;</p>  <p style="text-indent: 2em;">在这些年里，很多人为google的硬件架构做出了重要的贡献。在这里，特别感谢Gerald Aigner ，Ross Biro,Bogdan Cocosel 和Larry Page所做的工作。</p></div><img src ="http://www.cppblog.com/knzeus/aggbug/162106.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/knzeus/" target="_blank">knzeus</a> 2011-12-14 15:37 <a href="http://www.cppblog.com/knzeus/articles/162106.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>