2017 6.824学习笔记 Lecture 1: Introduction

什么是分布式系统?

  • 多个计算机进行协作
  • 大规模数据库,P2P文件共享,MR,DNS等等
  • 许多重要的基础设施是分布式的

为什么要使用分布式?

  • 连接物理隔离的实体
  • 通过隔离取得安全性
  • 通过副本机制容忍故障
  • 可水平扩展资源提高生产力

实现中的困难?

  • 许多并发问题
  • 处理局部故障
  • 难以实现理论性能

主题


关于分布式程序背后的三大抽象方面:

  • 存储
  • 通信
  • 计算

话题:实现

RPC ,threads,concurrency control

话题:性能

目标:可伸缩的吞吐量,n台服务器提供对应n台对应的性能总量(cpu,disk,net)
困难点: 难以实现线性提升至N倍,原因如下:

1.负载均衡, stragglers(可能是负载过高的某一节点)
2.非平行化的代码: 初始化关系,或有相互作用
3.由于共享资源产生瓶颈:例如网络

话题:故障容忍

当集群的规模很大,网络结构很复杂时,总会有一些节点故障,而我们需要让应用能够隐藏这些故障,通常需要:
1.可用性:整个集群仍可提供服务,使用存储的数据
2.耐久性:当故障节点恢复时,相关的数据也会随之恢复

一个好的解决方案: 冗余的服务。当一个实例崩溃时,客户端可执行另一个副本实例

话题:一致性

通用的基础架构需要提供明确的行为,例如Get操作产生的值要来自于最近的Put操作,但是达到这样好的行为时困难的。
冗余的(副本)服务之间很难保证(eg.数据)完全相同,如:

1.客户端可能中途崩溃在由多步组成的更新操作
2.服务端可能崩溃在一个“尴尬”点:例如在计算完成和上报结果之间
3.网络可能使活着的服务看起来是故障的.严重"脑裂"问题

一致性和性能是矛盾的,达到一致性需要进行通信(例如去获得最近的put操作值)。
强一致往往使得服务变慢
高性能得服务通常是弱一致
人们在这个领域追求很多设计点

案例分析 MapReduce


综述

设计背景:对上TB得数据集进行多个小时的计算获取结果,然而这通常需要一定规模的服务器集群进行处理,进而需要分布式编程,但通常分布式编程需要考虑很多问题,是很困难的。
总体目标:使非专业的程序员能够很容易的处理分离在多台机器上的数据并达到可接受的效率,程序员来编写Map和Reduce函数,而顺序执行的代码通常是相当简单的。
MR将上文中的Map Reduce函数“同时”运行在上千台具有大量输入的机器上,并且隐藏背后的分布式细节

抽象展示mapreduce

  Input1 -> Map -> a,1 b,1 c,1
  Input2 -> Map ->     b,1
  Input3 -> Map -> a,1     c,1
                    |   |   |
                        |   -> Reduce -> a,2 c,2
                        -----> Reduce -> b,2
                   

M是分配Map任务的数量,R是分配Reduce任务数量
整体输入被划分成了M份并分别输入给各map任务,map函数执行后会生成<k2,v2>值(由程序指定),称之为“中间值”,并对k2进行hash(size=R)写入指定的文件中,MR程序会让各Reduce程序去拉去对应自己hash值的文件(相同的k2值会发送给同一个reduce任务),进行reduce作业后生成最终的结果<k2,k3>。这些结果reduce直接写入自己的reduce结果文件中(共R份结果)

例子:单词统计

  input is thousands of text files
  Map(k, v)
    split v into words
    for each word w
      emit(w, "1")
  Reduce(k, v)
    emit(len(v))

mapreduce隐藏许多困难的细节问题:

  • 在服务器上启动软件
  • 追踪具体任务的完成情况
  • 数据传输
  • 从故障中恢复

Mapreduce 扩展性很好:

N倍的计算机给你提供N倍的吞吐力

  • 假定M和R的数量大于等于N,Maps()因为任务之间没有互相影响所以能够平行化,Reduce()也相同
  • 所以你可以购买更多的机器提高更大的吞吐,而不是通过高效平行化编程提高指定应用性能(机器要比程序员更便宜)

什么会限制性能表现:

我们会关心几个方面去优化
CPU 内存 磁盘 网络
在2004年的论文中,限制主要存在于跨域网络带宽:
在Map->Reduce的Shuffle阶段数据是会通过网络传输,论文中的主交换机是100-200GB的带宽,总共1800台服务器 所以每个机器只有55MB的带宽,这要比磁盘或者内存的速度慢的多。

所以他们关注在网络中数据的最小化移动。(当前的数据中心已经比那时快的多了)

工作流程:

figure 1
  • master:指派任务给worker,记录中间值的位置
  • M个Map任务,R个Reduce任务
  • 每个服务器运行MR的owrker和GFS,每个Map的输入文件由3个副本
  • 通常输入的任务大于总worker数,老的Map执行后,该worker继续执行新的task
  • Map的worker 对中间值的key进行hash写入到R个partitions(worker的本地磁盘),直到所有的map任务结束后reduce操作开始
  • master告知reduce worker从指定的map worker获取中间数据
  • reduce worker将最终的结果值写入GFS(每个reduce worker一个结果文件)

如何设计使得缓解慢网络的影响?

  • Map的输入从GFS的副本中读取,这通常是在本地磁盘而不是通过网络拉取
  • 中间数据只在网络中传输一次,map的会将中间结果写在本地磁盘而不是GFS中
  • 许多key的中间结果存在同一个partition文件中,因为大文件传输效率更高

如何取得好的负载均衡

关键点: N-1个服务可能在等待1个worker完成,而有的任务可能会比其他任务完成的慢

解决方案:将任务拆分,使得总任务数大于workers数量
master将新任务交给刚完成了任务的worker,这样使得每个任务都比较小不会占用相对太长的时间,性能强劲的worker会处理更多的任务

如何容错?

隐含解决故障问题会使编程更加容易
MR会重启失败了的map或是reduce任务,而不是整体重启
因为这些任务都是纯函数的:

  • 他们不会在调用过程中保存状态
  • 他们不会读或写MR规定以外的文件
  • 这些任务之间没有隐藏的通信

所以重新调用这些任务会得到相同的结果
这也是MR相对于其他的平行化编程模型的最大限制,但这也保证了MR的简单性

故障恢复细节

Map Worker:
当master接收不到某worker的ping结果时,该worker可能崩溃了,他的中间结果可能已经丢失,而很多reduce任务可能需要这些数据。master会重新执行这个任务在这份输入文件的其他GFS副本worker上。
有时reduce操作在该map worker崩溃前已经读完了他生成的中间结果,所以会在重启该map任务,除非这个reduce也发生了故障

Reduce Worker:
完成的reduce任务会将结果存储在GFS中,如果某个reduce失败则只需要重新启动这个reduce任务在其他的worker上。GFS会保证输出结果在完成前时不可见的,当完成后原子性的重命名该文件。所以master时很安全的重启reduce任务,无论在那个worker上

其他一些关于故障的问题?

如果master启动了两个相同的Map()任务?

可能由于master误判了一个map失败,但reduce worker只会得到其中一个中间结果

如果master启动了两个相同reduce任务?

他们可能会尝试写入“相同的一个结果文件”,由GFS的原子性保证只有一个完成结果会被重命名成最终结果文件

如何解决某个非常慢的任务---straggler问题?

master会为最后的一些任务启动第二个副本task共同执行

如何解决由于硬件或软件导致的错误结果?

MR会采取失败停止,终止整个任务告知用户

什么样的应用不适合MR?

不是所有的任务都适合 map/suffle/reduce 这样的模式

  • 数据量较小
  • 对大数据集进行非常小的更新操作
  • 不可预期的读内容
  • 需要多个shuffles阶段

总结:

Mapreduce 通常用于一个大规模的集群

  • 并不总是很高效或灵活
  • 扩展性很好
  • 很容易去编程(隐藏了很多分布式问题)

他是生产中进行很好的折中产物

推荐阅读更多精彩内容