说明
本系列文章是对大名鼎鼎的 MIT6.824分布式系统课程 的翻译补充和学习总结,算是自己一边学习一边记录了。
如有疏漏错误,还请指正:)
持续更新ing。。。
翻译&补充
内容
本课程主要涉及应用的基础架构,包括:
- 存储
- 通信
- 计算
终极目标
隐藏分布式复杂性的抽象。
在我们的研究中,还有大量的主题重复出现。
主题:实现
RPC,线程,并发控制。
【多个实验】
主题:性能
目标:可扩展的吞吐量
N倍服务器 -> N倍吞吐量,通过并行的CPU、硬盘、网络。
【图表:用户,应用服务器,存储服务器】
所以承担更多的负载只需要购买更多的机器,而不是花费昂贵的人力成本重新设计。
如果切分任务不需要太多通信成本,则是高效的。
当N增加时扩展变得更难:
负载不均衡,落后者,N中最长的延迟。
无法并行化的代码:初始化,交互。
共享资源造成的瓶颈,比如:网络。
有些性能问题在扩展时并不容易处理
例如:减小单个用户请求的相应时间
例如:所有用户想更新相同的数据
这些问题通常需要更好的设计,而不是更多的机器。
【实验 4】
主题:容错
千级台服务器,大网络 -> 总是会出现部件损坏。
我们希望向应用隐藏这些错误。
我们通常需要:
- 可用性 —— 应用可以不管错误,继续工作;
- 可恢复 —— 当错误被修复时,应用可以重新工作。
好主意
机器备份,如果一台服务器宕机,可以再其他机器上执行。
【实验 1, 2, 3】
主题:一致性
通用的基础架构需要明确定义的行为
例如:“Get(k)获得最近一次Put(k,v)的值。”
能够明确定义行为是很困难的!
多个备份机器很难保持一致。
客户端可能会在多步骤执行的中途崩溃。
机器可能会宕机,比如:在执行请求后但回复之前。
网络分区可能会导致活跃的服务器看起来已经死机;脑裂的风险。
一致性和性能不可兼得
强一致性需要通讯,例如:Get()需要检查最近一次的Put()。
大多情况下的设计仅提供弱一致性,用来提高执行效率。例如:Get()不需要检查最近的Put()!对于应用开发程序员很痛苦,但可能是一种好的权衡。
在一致性和性能的权衡上,有很多可行的设计方法!
案例学习:MapReduce
我们将讨论MapReduce(MR),并作为一个案例学习
在6.824的多个主题中是一个良好实现;
巨大的影响力;
重点在实验1。
MapReduce概览
场景
在TB级的数据集上进行多小时的计算,例如:计算搜索用的索引,排序,分析网络结构。
在千级数目的计算机上进行。
没有分布式系统专家来编写应用。
总体目标
对于非专业的程序员操作简单。
程序员只需要通过定义Map和Reduce函数,通常是很短小的面向过程的代码。
MR负责管理、隐藏分布式的所有内容!
MapReduce Job的逻辑视图
输入已经切分为M个文件。
MR对每个输入文件调用Map(),产生成(k2,v2)键值对,这是“临时”的数据。每个Map()调用时一个“任务”。
MR收集指定k2的所有临时v2,传递所有键值对到Reduce调用。
最终的输出是来自所有Reduce()的<k2,v3>键值对。
示例:单词统计
输入是千级数量的文本文件
Map(k, v)
split v into words
for each word w
emit(w, "1")
Reduce(k, v)
emit(len(v))
优势及缺陷
MapReduce易于扩展
N个“worker”计算机可以带来N倍的吞吐量。
- Map函数可以并行运行,因为它们不需要通信;
- Reduce函数也是一样。
所以你可以通过购买更多的机器来获得更高的吞吐量。
MapReduce隐藏了许多细节
发送应用代码到服务器;
跟踪已经完成的任务;
从Map到Reduce传递数据;
服务器间的负载均衡;
从失败中恢复。
然而MapReduce也限制了应用可以做的事情
没有通信和状态(除了通过中间临时数据);
没有迭代,没有多阶段的管道;
没有实时处理或流处理。
实现细节
输入和输出都存储在GFS集群文件系统上
MR需要大量并行的输入和输出吞吐量。
GFS以64MB的chunk来切分文件,存储在多个服务器上。Map并行读,Reduce并行写。
GFS还会在2-3台服务器上备份每个文件。
使用GFS对于MapReduce来说是一个巨大的成功。
什么有可能影响到性能?
我们需要考虑下可以优化的地方:CPU?内存?硬盘?网络?
在2004年,作者们受限于网络的容量:
- MR将什么发送到网络中?Map从GFS读取输入;Reduce读取Map的输出,可能和输入一样大,比如,排序;Reduce向GFS写入输出文件。【图表:服务器,网络交换机的树形图】
- 在MR中所有节点之间随机调度,一般的网络请求都会通过核心交换机。
- 论文中的核心交换机:总共100-200 Gb/s;1800台机器,所有每台机器 55 Mb/s。55这个数值是小的,比磁盘或内存的速度还要慢。
今天,网络和核心交换机相对于CPU/硬盘要快得多
一些细节(论文的图表1)
一个master,向worker分发工作并记录进展。
- master发送Map任务给worker,直到所有Map完成。Map输出到本地磁盘(中间临时数据);Map通过hash切分输出,每个Reduce任务一个文件。
- 在所有Map完成以后,master发送Reduce任务。每个Reduce获取来自所有Map worker的中间临时数据;每个Reduce任务将输出写到GFS上的一个不同文件。
MR如何最小化网络使用?
master试图在GFS服务器上执行Map任务,可以存储自己的输入。所有的机器运行GFS和MR worker,因此输入是从GFS的本地磁盘读取,不是通过网络。
中间临时数据只在网络上传递一次。Map worker写到本地磁盘,Reduce worker直接从所有map worker读取,不是通过GFS。
中间临时数据通过不同的key划分为不同文件。R 的数目比key的数目小很多,大的网络转发会更有效率。
MR是如何获得良好的负载均衡?
如果N-1台服务器在等待1台服务器,是浪费和缓慢的。
但是一些任务可能会比其他的任务需要更久。
解决方案
任务数目大于worker数目。
- master向已经完成之前任务的worker发送新的任务;
- 因此没有太大的任务时需要占用所有完成时间的(但愿如此);
- 所以快服务器可以比慢服务器执行更多的任务,使得所有服务器可以在差不多的时间完成。
容错
如何容错呢?
也就是说,如果worker在执行MR job的途中崩溃了,怎么办?
我们想要对应用开发者完全隐藏失败!
MR需要从头开始重新执行整个job吗?为什么不呢?
MR重新执行失败的Map和Reduce。
假如MR执行Map两次,一个Reduce看见之前执行的结果,另一个Reduce看到第二次执行的结果,怎么办?
只有重新执行输出相同的结果,才可以保证正确。
所以Map和Reduce需要是完全确定性的函数:
- 它们只依赖于输入参数。
- 没有状态,没有文件I/O,没有交互,没有其他的通信。
如果你需要运行非函数的map或reduce,怎么办?
worker失败需要整个job重新执行,或者你自己创建同步的全局检查点。
worker崩溃后恢复的细节:
Map worker崩溃
master发现worker很久没有回应ping。
master知道worker上执行的任务,这些任务的之间临时输出已经丢失,需要重新创建;master告诉其他worker重新执行这些任务。
如果reduce已经获得中间临时数据,可以不用重新执行
Reduce worker崩溃
已经完成的任务是ok的 —— 在GFS上存储,有备份。
master在其他worker上重新开始未完成的任务。
其他失败/问题:
如果master发送给两个worker相同的Map任务,怎么办?
或许master是错误地认为其中一个worker已经死掉。
它只会告诉所有Reduce worker其中一个Map worker。
如果master发送给两个worker相同的Reduce任务,怎么办?
它们将会同时写到GFS上相同的输出文件!
原子的GFS重命名操作可以防止文件内容混淆;只有一个完整的文件是可见的。
如果某个worker很慢——“落伍者”?
也许是因为硬件容易出故障。
master对于剩下的最后一小部分任务开启第二个副本。
如果一个worker由于硬件或软件错误,计算出错误的输出,怎么办?
太糟了!MR只能识别CPU和软件的停止错误(fail-stop)
如果master崩溃呢?
现在的情况?
巨大的影响力(Hadoop,Spark等等)。
可能在Google内部不再使用:
- 被Flume / FlumeJava替代(看 Chambers等人的论文);
- GFS被Colossus(没有好的论文描述)和BigTable替代。
总结
MapReduce只手将大集群计算发扬光大。
缺点
不是最有效率或者最灵活的。
优点
- 扩展性良好;
- 容易编程 —— 错误和数据的移动都是隐藏的。
这些都是在实践中良好的权衡。
我们会在后续课程中看到更多的成功的后继者。
有疑问加站长微信联系(非本文作者)