参考多篇文档、博客,仅供学习记录。
MapReduce用于大规模数据集(大于1TB)的并行运算。概念"Map(映射)“和"Reduce(归约)”,是它们的主要思想,都是从函数式编程语言里借来的,还有从矢量编程语言里借来的特性。 当前的软件实现是指定一个Map(映射)函数,用来把一组键值对映射成一组新的键值对,指定并发的Reduce(归约)函数,用来保证所有映射的键值对中的每一个共享相同的键组。
2004年谷歌提出了MapReduce, 在此之前谷歌程序员面对的大规模数据集,常常需要编程实现:
在MapReduce的关键是两个功能:Map和Reduce。不同Map并行执行,Map和Reduce之间串行执行,所有的Map执行完毕,执行Reduce。
输入数据首先被分成更小的块。然后将每个块分配给一个mapper进行处理。map函数实现要给key排序。
例如,如果一个文件有 100 条记录要处理,则 100 个mapper可以一起运行,每个mapper处理一条记录。或者也许 50 个mapper可以一起运行,每个mapper处理两条记录。Hadoop 框架根据要处理的数据大小和每个mapper服务器上可用的内存块来决定使用多少个映射器。
在所有mapper完成处理后,框架会在将结果传递给reducer之前对结果进行混合和排序。当mapper仍在进行中时,reducer无法启动。具有相同key的所有map输出值都分配给单个reducer,然后聚合该key的值。
RPC(Remote Procedure Call)是指远程过程调用,该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC是一种服务器-客户端(Client/Server)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。
也就是说两台服务器A、B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
在MapReduce的shuffle阶段,将相同的key拉取到一个Reduce上计算,若某一种key的数据量远远大于其他的key的数据量,造成某一个Reduce Task计算量过大,计算耗时较长,导致整个任务的计算速度及效率大大下降。
数据倾斜一般的两种表现:
① 大部分Reduce Task执行完成特别快,某一个Reduce Task执行特别慢,耗时长;
② 某一个Reduce Task出现OOM,内存溢出,任务失败。
MapReduce中的数据倾斜解决方案:
①自定义combiner类。在map shuffle阶段对数据压缩,相当于做一次Reduce,相同的key先进行聚合运算。从而减少流向Reduce端的文件数量和数据量。
②分析导致数据倾斜的key,在Map阶段将造成倾斜的key分成多组,例如aaa 这个 key,map 时随机在aaa 后面加上 1,2,3,4 这四个数字之一,把key 先分成四组,先进行一次运算,之后再恢复 key 进行最终运算。
1.用户程序中的MapReduce库首先将输入文件切分为M块,每块的大小从16MB到64MB(用户可通过一个可选参数控制此大小)。然后MapReduce库会在一个集群的若干台机器上启动程序的多个副本。
2.程序的各个副本中有一个是特殊的——主节点,其它的则是工作节点。主节点将M个map任务和R个reduce任务分配给空闲的工作节点,每个节点一项任务。
3.被分配map任务的工作节点读取对应的输入区块内容。它从输入数据中解析出key/value对,然后将每个对传递给用户定义的map函数。由map函数产生的中间key/value对都缓存在内存中。
4.缓存的数据对会被周期性的由划分函数分成R块,并写入本地磁盘中。这些缓存对在本地磁盘中的位置会被传回给主节点,主节点负责将这些位置再传给reduce工作节点。
5.当一个reduce工作节点得到了主节点的这些位置通知后,它使用RPC调用去读map工作节点的本地磁盘中的缓存数据。当reduce工作节点读取完了所有的中间数据,它会将这些数据按中间key排序,这样相同key的数据就被排列在一起了。同一个reduce任务经常会分到有着不同key的数据,因此这个排序很有必要。如果中间数据数量过多,不能全部载入内存,则会使用外部排序。
6.reduce工作节点遍历排序好的中间数据,并将遇到的每个中间key和与它关联的一组中间value传递给用户的reduce函数。reduce函数的输出会写到由reduce划分过程划分出来的最终输出文件的末尾。
7.当所有的map和reduce任务都完成后,主节点唤醒用户程序。此时,用户程序中的MapReduce调用返回到用户代码中。
8.主节点维持多种数据结构。它会存储每个map和reduce任务的状态(空闲、处理中、完成),和每台工作机器的ID(对应非空闲的任务)。
9.主节点是将map任务产生的中间文件的位置传递给reduce任务的通道。因此,主节点要存储每个已完成的map任务产生的R个中间文件的位置和大小。位置和大小信息的更新情况会在map任务完成时接收到。这些信息会被逐步发送到正在处理中的reduce任务节点处。
分布式Grep:map函数在匹配到给定的pattern时输出一行。reduce函数只是将给定的中间数据复制到输出上。
URL访问频次统计:map函数处理网页请求的日志,对每个URL输出〈URL, 1〉。reduce函数将相同URL的所有值相加并输出〈URL, 总次数〉对。
倒转Web链接图:map函数在source页面中针对每个指向target的链接都输出一个〈target, source〉对。reduce函数将与某个给定的target相关联的所有source链接合并为一个列表,并输出〈target, list(source)〉对。
每个主机的关键词向量:关键词向量是对出现在一个文档或一组文档中的最重要的单词的概要,其形式为〈单词, 频率〉对。map函数针对每个输入文档(其主机名可从文档URL中提取到)输出一个〈主机名, 关键词向量〉对。给定主机的所有文档的关键词向量都被传递给reduce函数。reduce函数将这些关键词向量相加,去掉其中频率最低的关键词,然后输出最终的〈主机名, 关键词向量〉对。
倒排索引:map函数解析每个文档,并输出一系列〈单词, 文档ID〉对。reduce函数接受给定单词的所有中间对,将它们按文档ID排序,再输出〈单词, list(文档ID)〉对。所有输出对的集合组成了一个简单的倒排索引。用户可以很轻松的扩展这个过程来跟踪单词的位置。
分布式排序:map函数从每条记录中提取出key,并输出〈key, 记录〉对。reduce函数不改变这些中间对,直接输出。
https://www.talend.com/resources/what-is-mapreduce/
https://hardcore.feishu.cn/docs/doccnxwr1i2y3Ak3WXmFlWLaCbh
https://corbinhu.github.io/2020/04/25/Data-Tilt-Reasons-and-Solutions-in-MapReduce/
您的工作是完成一个由master和worker两个程序组成的分布式MapReduce。只有一个master进程和并行执行的一个或多个的worker进程。在现实系统中,workers会执行在不同的机器上,但在本实验中,您只需把它们都运行在一台机器。workers会通过RPC和master通信。每个worker进程会向master请求一个任务,从一或多个文件中读取任务的输入,执行该任务,然后把任务的输出写入到一或多个文件。master应该注意检查worker是否在合理的时间内没有完成任务(在本实验中,使用10s)。
我们已经给你一些代码供你起步。master 和 worker 的“main”例程在main/mrmaster.go
和main/mrworker.go
中;不要更改这些文件。您应该将您的实现放在mr/master.go
、 mr/worker.go
和mr/rpc.go
中。
以下是在字数统计 MapReduce 应用程序上运行代码的方法。首先,确保 word-count plugin是全新构建的:
$ go build -buildmode=plugin ../mrapps/wc.go
在main目录中,运行master。
$ rm mr-out*
$ 去运行 mrmaster.go pg-*.txt
mrmaster.go
的pg-*.txt
参数是输入文件;每个文件对应一个“split”,并且是一个 Map 任务的输入。
在一个或多个其他窗口中,运行一些工作程序:
$ go run mrworker.go wc.so
当workers和master都完成后,查看mr-out-*
中的输出
。完成实验后,输出文件的排序联合应与顺序输出匹配,如下所示:
$ cat mr-out-* | sort | more
A 509
ABOUT 2
ACT 8
...
我们在main/test-mr.sh 中为
您提供了一个测试脚本。测试检查wc
和indexer
MapReduce 应用程序在将pg-xxx.txt
文件作为输入时是否产生正确的输出。这些测试还会检查您的实现是否并行运行 Map 和 Reduce 任务,以及您的实现是否从在运行任务时崩溃的工作线程中恢复。
如果您现在运行测试脚本,它将挂起,因为 master 永远不会完成:
$ cd ~/6.824/src/main
$ sh test-mr.sh
*** Starting wc test.
您可以在mr/master.go
的 Done 函数中将ret := false
更改为 true, 以便 master 立即退出。然后:
$ sh ./test-mr.sh
*** Starting wc test.
sort: No such file or directory
cmp: EOF on mr-wc-all
--- wc output is not the same as mr-correct-wc.txt
--- wc test: FAIL
$
测试脚本希望在名为mr-out-X 的
文件中看到输出,每个reduce 任务一个。mr/master.go
和mr/worker.go
的空实现不生成这些文件(或做很多其他事情),所以测试失败。
完成后,测试脚本输出应如下所示:
$ sh ./test-mr.sh
*** Starting wc test.
--- wc test: PASS
*** Starting indexer test.
--- indexer test: PASS
*** Starting map parallelism test.
--- map parallelism test: PASS
*** Starting reduce parallelism test.
--- reduce parallelism test: PASS
*** Starting crash test.
--- crash test: PASS
*** PASSED ALL TESTS
$
您还会看到 Go RPC 包中的一些错误,如下所示
2019/12/16 13:27:09 rpc.Register: method "Done" has 1 input parameters; needs exactly three
忽略这些消息。
几条规则:
nReduce
缩减任务的桶 ,其中nReduce
是 main/mrmaster.go
传递给MakeMaster() 的参数
。mr-out-X 中
。MR-out-X
文件应包含每个Reduce函数输出一行。该行应使用 Go "%v %v"
格式生成,使用键和值调用。查看main/mrsequential.go
中注释“this is the correct format”的行。如果您的实现与此格式偏差太大,则测试脚本将失败。mr/worker.go
、mr/master.go
和mr/rpc.go
。您可以临时修改其他文件进行测试,但请确保您的代码适用于原始版本;我们将使用原始版本进行测试。main/mrmaster.go
期望mr/master.go
实现一个 Done()
方法,该方法在 MapReduce 作业完全完成时返回 true;届时,mrmaster.go
将退出。call()
的返回值:如果 worker 无法联系 master,则可以假设 master 已退出,因为作业已完成,因此 worker 也可以终止。根据您的设计,您可能还会发现拥有一个“please exit”的伪任务(master 可以给worker)很有帮助。一种入门方法是修改mr/worker.go
的 Worker()
以向 master 发送 RPC 请求任务。然后修改 master 以使用尚未启动的 map 任务的文件名进行响应。然后修改 worker 以读取该文件并调用应用程序 Map 函数,如在mrsequential.go 中
。
应用程序 Map 和 Reduce 函数在运行时使用 Go 插件包从名称以.so
结尾的文件中加载。
如果您更改了mr/
目录中的任何内容,您可能需要重新构建您使用的任何 MapReduce 插件,例如go build -buildmode=plugin ../mrapps/wc.go
该实验室依赖于共享文件系统的工作人员。当所有工作人员都在同一台机器上运行时,这很简单,但如果工作人员在不同的机器上运行,则需要像 GFS 这样的全局文件系统。
中间文件的合理命名约定是mr-XY
,其中 X 是 Map 任务编号,Y 是 reduce 任务编号。
worker 的 map 任务代码需要一种在文件中存储中间键/值对的方法,这种方式可以在 reduce 任务期间正确读回。一种可能性是使用 Go 的
encoding/json
包。将键/值对写入 JSON 文件:
enc := json.NewEncoder(file)
for _, kv := ... {
err := enc.Encode(&kv)
并读回这样的文件:
dec := json.NewDecoder(file)
for {
var kv KeyValue
if err := dec.Decode(&kv); err != nil {
break
}
kva = append(kva, kv)
}
worker 的 map 部分可以使用ihash(key)
函数(在worker.go 中
)为给定的键选择 reduce 任务。
您可以从mrsequential.go
借鉴一些代码,用于读取 Map 输入文件、对 Map 和 Reduce 之间的中间键/值对进行排序,以及将 Reduce 输出存储在文件中。
Master作为RPC服务器,将是并发的;不要忘记锁定共享数据。
使用 Go 的race检测器,使用go build -race
和go run -race
。 test-mr.sh
有一条评论,向您展示如何为测试启用竞态检测器。
worker有时需要等待,例如,在最后一个map完成之前,reduce 无法启动。一种可能性是worker定期向master请求工作,在每次请求之间与time.Sleep()
一起休眠。另一种可能性是 master 中的相关 RPC 处理程序具有等待的循环,使用time.Sleep()
或sync.Cond
。Go 在其自己的线程中为每个 RPC 运行处理程序,因此一个处理程序正在等待这一事实不会阻止主进程处理其他 RPC。
master 无法可靠地区分崩溃的 worker、活着但由于某种原因停止的 worker,以及正在执行但太慢而无用的 worker。你能做的最好的事情是让主人等待一段时间,然后放弃并将任务重新发布给不同的工人。对于这个实验,让 master 等待 10 秒;在那之后,主人应该假设工人已经死了(当然,它可能没有)。
要测试崩溃恢复,您可以使用mrapps/crash.go
应用程序插件。它在 Map 和 Reduce 函数中随机退出。
为了确保没有人在出现崩溃的情况下观察到部分写入的文件,MapReduce 论文提到了使用临时文件并在完全写入后自动重命名的技巧。您可以使用ioutil.TempFile
创建一个临时文件,并使用 os.Rename
对其 进行原子重命名。
test-mr.sh
运行子目录mr-tmp
中的所有进程 ,因此如果出现问题并且您想查看中间文件或输出文件,请查看那里。