【分布式】MapReduce论文笔记

372 阅读10分钟

概述

源自谷歌,是处理和产生大数据集的编程模型

MapReduce是一个抽象,用来表达简单的计算,隐藏乱七八糟的并行、容错、数据分发和负载均衡都放到一个lib里面,这个抽象是受到Lisp等函数语言中map和reduce原语的启发

重新执行为容错的主要机制

MapReduce库使用c++实现的

一个mapreduce的例子,统计单词出现次数:
应用举例:

每台机器的词向量:

map:接收每个doc的<word, frequency>的对,输出<host_name, term_vector>

reduce: 把每个hostname对应的term vector相加

倒排索引:

map: 接收每个doc, 输出<word, document_id>

reduce: 接收<word, document_id>, 输出<word, list(document_id)>

分布式排序:

map:从record里面抽取key,输出<key, record>对

reduce: 接收什么就输出什么

需要借助partition工具和排序特性

实现
计算环境

1.x86双处理器,linux系统,每台机器2~4GB的内存

2.网卡每台100Mb/s或者1Gb/s,平均值要低得多

3.很多台机器构成一个集群

4.存储是机器上的IDE硬盘提供。文件系统使用复制提供可用性和可靠性

5.用户将任务提交到调度系统。一个job是由一组task组成,由调度器映射到集群的一组可用机器上去

运行时

Map输入数据自动分区到M个split,Reduce通过一个分区函数(比如hash(key) Mod R)分成R个分区(partition),R和分区函数由用户指定

如图1所示,用户在调用MapReduce函数时候发生的过程:

● 把用户输入文件分成M个pieces,每个piece为16~64MB,用户可以通过修改参数控制。然后会在这个集群的多台机器上启动程序拷贝

● 这些进程里面包含一个master和多个worker,master负责给空闲的worker分配map或者reduce的任务,M个map任务,R个reduce任务

● worker从input解析key/value对,发送给map函数,map函数产生中间结果后缓存在内存里

● key/value对被周期性刷写到本地磁盘,并由partition函数分区,把其位置发给master,master负责转发这些位置给reduce worker

● reduce worker从master拿到这些位置之后,会通过rpc从硬盘上读取数据,全部读完之后按key进行一个sort,需要排序是因为一个reduce task上可能有多个key。如果数据量太大的话需要使用外部排序(这个外部排序是什么样子的?)

● reduce worker遍历这些中间结果,对每个key,把key和value集合传给用户自定义reduce函数,reduce函数的输出会作为这个分区的结果追加到一个output文件,文件名用户可以自定义

● 当所有的map和reduce任务都处理完成,master会唤醒用户进程。到此,这个MapReduce的调用就完成了

一个reduce任务对应一个输出文件,一般不需要合并这些文件,因为一般后续还会有其他分布式任务使用

master数据结构

存储每个map和reduce任务:状态、id

每个map计算完成后,存储每个map任务的R个中间文件的位置和大小。map计算完成后会更新这个数据,然后增量推送给reduce任务(这里有点不太懂是说这个map worker会计算好几轮,每轮结束发一个位置吗?)

容错

重新执行为容错的主要机制

worker失败:

master会定期去ping这些worker,如果在一定时间内没有响应,就会被标记为idle,然后这个worker会被标记为失败,它所负责的map任务的状态会被重置到初始状态并由其他worker重新执行。reduce任务同

已经完成的map任务如果失败了,需要重跑一下,因为存储在本地的文件没法访问了;但是reduce任务不需要,因为他的output文件是存储在全局的文件系统

map任务失败的话,会把这个信息通知到所有的reduce,还没有从这个失败的map任务读数据的reduce任务会从重新执行的map任务读数据

因为网络维护导致的一批机器失联,处理方式跟单个的一样,也是重跑

master失败:

现在的实现是master失败的话就终止这个MapReduce任务。因为只有一个master,不太可能失败

如果用户需要的话,可以master在每个检查点把相关的数据结构记录下来,用户自己在客户端实现重新开启任务,从检查点开始重跑

语义可能存在的故障:

● 依赖map和reduce任务的原子性提交到master,来达到跟串行执行相同的正确结果

● 如果有两个相同的reduce任务同时完成,临时文件改名过程原子性依赖底层文件系统

● 如果map和reduce的操作是非确定的,是弱语义可能存在问题,弱语义的情况比如map任务M,和两个reduce任务R1和R2,可能是R1依赖的M的一个执行输出结果,R2依赖M的另一个不同的执行输出结果

Locality

计算环境里网络带宽比较稀缺

解决方案是,利用GFS谷歌文件系统,GFS把每个文件分成64MB大小的block,每个block在机器上保存多个副本(一般是3个)。master会试图在包含输入数据副本的机器上跑map,如果失败的话,就尝试在与有副本的机器同一个网络交换机的worker机器上跑

当在一个集群上执行大型MapReduce操作的时候,数据一般都是本地读取,不消耗网络带宽

任务粒度

把一个MapReduce分为M个map片,一个reduce分为R片,一般这个M和R是远大于worker机器总数的。一台worker处理多个任务可以提升动态负载均衡,也能加快一个worker故障恢复的速度:已经完成的map任务可以快速分散到其他机器

实验出了一个M和R的边界,master需要处理O(M+R)个任务分发,以及在内存中保存 O(M*R)个任务状态数据结构(每个map/reduce 任务对平均近似一个字节的数据)

由于输出到分开的文件,R的大小通常受限但是M可以用一个比较大的值。一般2,000台worker机器,设置为M=200,000 R=5,000

备份任务

一般导致任务时间很长的一个原因是straggler,即某一台机器在处理某几个map或者reduce任务时异常耗时,straggler出现的原因很多:比如磁盘问题导致的可处理的error使得读性能下降严重;集群在这台机器上跑了其他任务导致了cpu/贷款等的竞争。经历的一个问题是机器初始化代码bug导致处理器缓存被禁用,受影响的机器上处理速度慢了100多倍

一个通用的解决straggler问题的机制。在一个MapReduce任务快要结束的时候,master对还在处理中的任务开启一个备份执行。无论主任务和备份任务哪一个完成了这个任务都会被标记为完成。这能有效减少MapReduce任务操作时间,通过调参,后面描述的sort程序,如果关了backup任务的话,执行时间会增减44%

改进

一些有用的扩展

分区函数:

hash(key) Mod R

hash(HostName(urlkey)) Mod R

有序保证:

同一个partition的中间值kv对是以key升序的顺序来处理的

输出查找排序更快捷

combiner函数:

可选项

上面的sort例子,词频遵循齐普夫分布,每个map产生很多<the, 1>通过网络发送

允许指定combiner函数在发到网络之前做局部merge,一般情况就使用reduce函数做combiner。reduce和combiner的不同就是output不同

输入输出类型:

text格式,一行是一个kv对,key是offset,value是这一行的内容

用户可以通过reader接口自定义输入函数

可以从文件/数据库/内存读数据

副作用:

如果有需要产生额外的文件,依赖用户自己保证原子性和幂等性

不支持同一个任务的原子性两段提交,所以跨文件一致性得是有确定性的

跳过坏的记录:

每个worker有一个信号处理器来捕获段错误和bus错误,执行map/reduce操作之前保存一个序列号的全局变量,如果用户代码产生了信号,信号处理器通过UDP发送一个“last gasp“的包给master,master在启动任务重执行时跳过这条记录

本地执行:

用于debug/profile/小范围测试

本地机器串行执行

可以限制只用于一个map

状态信息:

master提供一个http服务提供状态信息给用户,可以查看进度,比如多少任务完成了、输入的字节数、进度比例、每个任务的标准输入输出和错误的链接等

首页会显示哪个worker失败了,以及关联的哪个map/reduce任务,用于用户诊断错误

计数器:

使用方式:定义一个命名counter,在map/reduce里面增加他的值。

counter的值会作为ping响应的附加数据传给master,master聚合数据,计算完成时返回给用户,同时状态页也会显示。重跑任务有去重

一些计数器是自动添加的,比如已处理的输入/输出kv对的数量

性能

使用两个例子测试性能:

一个是在1TB的数据中搜索特定模式(Grep)(提取关键信息)

一个是对1TB的数据进行排序(Sort)(shuffle)

集群配置

1800台机器的集群,每台机器配备2个2GB的Intel Xeon多线程处理器,4G内存,2个160G的IDE硬盘,千兆以太网链路。2层树形交换机,近似100~200Gb的聚合带宽。所有机器在同一个机房,两台机器之间往返时间小于1ms。4G内存里有1~1.5G是用于集群上其他应用,在周末下午跑的任务,处理器比较空闲

Grep

总耗时150s,包含1min的启动时间,包含跟GFS交互打开文件的时间

Sort

10^10个100字节的记录,用TeraSort基准(?)建模

只需要不到50行代码

截屏2023-04-10 下午1.44.44.png

输入速率比shuffle和输出速率高是因为本地化Locality的优化,输出速率比shuffle低是因为写了两份数据拷贝。

如果底层文件系统使用erasure coding(纠偏编码?)而不是复制的话,网络带宽会少很多

Backup任务的影响

把backup任务关掉之后,多消耗了44%时间

机器失败

1746个worker杀掉200个,重跑只增加了5%运行时间

经验

一些应用场景:

MapReduce的问题和局限性

参考:

pdos.csail.mit.edu/6.824/paper… 论文