在这个实验中你需要构建一个MapReduce程序库,从中学习Go语言编程以及分布式系统中的错误处理方法。第一部分你需要编写一个简单的MapReduce程序,第二部分你需要完成master的代码,用于将任务分发给worker并且处理worker的故障。程序库的接口和错误处理的方法和原始的MapReduce论文中描述的类似。
所有提交到6.824的代码都必须是你自己写的,除了我们给出的部分。不允许看别人的解答以及过去几年的作业。你可以和其他的同学讨论,但是你不能看也不能拷贝别人的代码。请不要将你的代码发布,也不要把你的代码送给未来6.824的学生,例如不要把你的代码提交到github上。
你需要用Go实现这个实验(以及后面的所有实验)。Go官方网页包含了很多教程资料,你需要去看一下。
我们提供的是一份非分布式的MapReduce实现以及一部分分布式的实现(just the boring bits)。你需要用git(一种版本控制系统)获取初始的实验代码。想要了解更多关于git的知识,请阅读git用户手册,或者如果你已经熟悉了其他的版本控制系统,你可能觉得这篇论文面向CS的git概览值得进一步阅读。
本课程的git仓库的URL为git://g.csail.mit.edu/6.824-golabs-2015。为了在你的Athena账户中安装文件,你需要克隆课程的代码仓库,命令如下。必须使用x86或者x86_64的Athena机器,即uname -a应该输出i386 GNU/Linux or i686 GNU/Linux或者x86_64 GNU/Linux。你可以使用athena.dialup.mit.edu登录到公共的i686 Athena主机。
$ add git
$ git clone git://g.csail.mit.edu/6.824-golabs-2015 6.824
$ cd 6.824
$ ls
Makefile src
$
git允许你追踪我们对代码的修改。例如如果你想记录的你的进度,你可以提交你的修改:
$ git commit -am 'partial solution to lab 1'
$
输入文件为~/6.824/src/main中的kjv12.txt,它从这里下载,编译我们提供的初始代码,然后使用下载的文件运行:
$ add 6.824
$ export GOPATH=$HOME/6.824
$ cd ~/6.824/src/main
$ go run wc.go master kjv12.txt sequential
# command-line-arguments
./wc.go:11: missing return at end of function
./wc.go:15: missing return at end of function
编译器产生两个错误,因为Map和Reduce函数还没有实现。
修改Map和Reduce使得wc.go可以以字母的顺序输出每个单词出现的次数:
$ go run wc.go master kjv12.txt sequential
输出结果在文件mrtmp.kjv12.txt中,正确的输出结果中出现次数最多的前10个单词出现的次数如下:
$ sort -n -k2 mrtmp.kjv12.txt | tail -10
unto: 8940
he: 9666
shall: 9760
in: 12334
that: 12577
And: 12846
to: 13384
of: 34434
and: 38850
the: 62075
运行下面的脚本可以使得测试更简答,并且它会报告你的程序是否正确。
$ sh ./test-wc.sh
在编写程序前,请阅读MapReduce论文的第二部分,你的Map和Reduce函数和论文中有些不同,这里Map函数输入的是文件中的文本,因此需要将它分割成单词,并且返回一个K/V对的列表,元素类型为mapreduce.KeyValue,列表类型为list.List;reduce函数对于每个key都会被调用,值为map产生的K/V对中,键为key的所有value的列表,reduce函数需要返回一个单独的输出值。
mapreduce包中的mapreduce.go是我们提供的MapReduce代码,阅读它有助于你编写程序,特别是RunSingle()函数以及它调用的函数,这有助于你理解MapReduce以及学习Go编程的例子。
如果你理解了这个代码,请实现wc.go中的Map和Reduce。
提示:你可以使用strings.FieldsFunc将字符串划分为部分;为了这个实验,你可以认为单词是任意连续的字母序列,字母通过unicode.IsLetter来判定,Go Blog on strings中的内容有助于你理解Go中string的实现;strconv包用于将字符串转换为整数。
删除输出文件以及所有的中间结果文件:
$ rm mrtmp.*
这一部分你会完成一个MapReduce,用于把工作派发到一组worker线程中,这里使用多线程是为了利用多核提高效率。master线程分发任务到worker线程,然后等待任务完成。master和worker通过RPC通信,启动worker的代码位于mapreduce/worker.go中,处理RPC消息的代码位于mapreduce/common.go。
你的任务是完成mapreduce包中的master.go,特别是master.go中的RunMaster()函数,它用于将map和reduce工作分发给worker,当所有的工作完成时返回。
mapreduce.go中的Run()函数:首先调用了Split()将输入文件划分成每个map任务对应的分片,然后调用RunMaster()运行map和reduce任务,最后调用Merge()把所有reduce任务的输出合并为一个文件。RunMaster只需要告诉worker原始输入文件名以及任务编号,每个worker知道从哪里读取输入以及将输出写入到哪个文件。
每个worker在启动时向master发送一个注册RPC,mapreduce.go已经实现了MapReduce.Register的注册RPC,mr.registerChannel中保存了新的worker信息,你的RunMaster需要读取这个channel并处理新的worker注册信息。
MapReduce任务的信息保存在mapreduce.go文件的MapReduce结构体中。修改这个结构体可以跟踪任何新增的状态(例如可用的worker集合),新增的状态在InitMapReduce()函数中初始化,master不需要知道任务使用的map和reduce函数,worker负责执行正确的map或reduce代码。
使用Go的单元测试系统运行你的代码,我们在test_test.go中提供了一些测试用例,在包目录下运行测试用例,如下:
$ cd mapreduce
$ go test
如果你的代码通过了mapreduce包中的test_test.go第一个测试用例(基本MapReduce测试),Part II就完成了。你不需要考虑worker的故障。
master向worker并行的发送RPC,以便于任务可以并发的执行。你会发现在Go的RPC文档中的go语句(原文:statement)在这种情况下是很有用的。
master可能需要等待一个worker完成才能分发更多的任务,你可以考虑使用channel来同步等待master回复的线程。Concurrency in Go文档解释了channel的用法。
追踪bug最简单的方式是插入log.Printf()语句,把输出收集到文件中,然后考虑是否实际输出和你对于代码的执行结果是否匹配,其中最后一步(思考)是最重要的。
我们给你的代码在单个UNIX进程中以线程的方式运行worker,并且可以充分利用单机上的多核资源。如果想要把程序运行在通过网络连接的多台机器上,需要对代码进行一定的修改:RPC应该使用TCP而不是UNIX域套接字;需要以某种方法启动所有机器上的worker进程;所有的机器需要通过某种方式共享存储例如NFS。
在这一部分,你会实现master处理worker的错误,MapReduce中由于worker不保存持久的状态,因此使得故障的处理极其简单。如果worker故障了,master发送到那个worker的RPC都会失败(比如超时)。另外,如果master发送到worker的RPC失败了,master应该把失败的worker的任务重新派发给其他的worker。
任何RPC的失败并不意味着worker故障:比如worker可能正在忙于计算,没来得及响应,或者两个worker接收到了同一个任务并执行它。然而,由于任务是幂等的,如果一个任务被执行了两次,没什么关系,因为两次的输出一定是相同的。所以,对于这种情况,什么也不用做。我们的测试不会出现worker在任务中途崩溃的情况,所以你也不需要担心几个worker写同一个输出文件。
你不需要处理master故障,我们假定它永远不会故障。master的容错性比worker复杂,因为master故障后,master需要保持持久的状态并且在故障后恢复。后面的实验会尝试挑战这一点。
你的实现必须传递test_test.go中的两个剩余的测试用例,第一个测试了一个worker故障,第二个测试了许多worker故障。测试用例周期性的启动新worker,master使用它们推进整个计算过程,但是这些worker会在处理一些任务后失败。
通过课程的提交网页提交你的代码,地址为:https://6824.scripts.mit.edu:444/submit/handin.py/
第一次登录时,需要使用MIT认证或者通过邮件获取一个API key。你的API key会在你登录后显示,它可以用于通过控制台上传lab1。
$ cd ~/6.824
$ echo XXX > api.key
$ make lab1
你可以登录提交网页检查你的提交是否成功。如果你的代码在我们的机器上运行通过了test_test.go,你会得到全部分数。我们使用你的最后一次提交的时间戳用于计算迟交的时间。
疑问请发送至Pizza。