Lab1
lab链接:http://nil.csail.mit.edu/6.82...。
mapreduce论文链接:http://nil.csail.mit.edu/6.82...
您的任务是实现一个分布式MapReduce,它由两个程序组成,master和worker。只有一个master和一个或多个并行执行的worker。
在一个真实的系统中,工作人员将在一堆不同的机器上运行, 但在这个实验室中,
你将在一台机器上运行所有的工作人员,worker将通过RPC与master进行对话。
每个worker将向master请求一个任务,从一个或多个文件中读取任务的输入,
执行任务,并将任务的输出写入一个或多个文件。
如果一个工人没有在合理的时间内完成他的任务(在这个实验中使用10秒),
master应该注意,并把相同的任务给不同的worker。
master和worker的主要程序在main/mrmaster和main/mrworker.go;不要更改这些文件。
您应该将您的实现放在mr/master、mr/worker、mr/rpc.go。
运行示例:
运行master:
$ rm mr-out*
$ go run mrmaster.go pg-*.txt
运行worker
$ go run mrworker.go wc.so
一些规则:
- map阶段应该将中间键划分为nReduce个reduce任务的bucket,其中nReduce是main/mrmaster的参数。
- go传递给MakeMaster()。worker运行时应将第x个reduce任务的输出放在mr-out-x文件中。一个mr-out-x文件应该在每个Reduce函数输出中包含一行。这一行应该用Go的“%v %v”格式生成,用键和值调用,在main/mrsequential.go里看一看。
- 你可以修改mr/worker.go、mr/master、mr/rpc.go。您可以临时修改其他文件以进行测试,但要确保您的代码与原始版本兼容;我们将用原始版本进行测试。
- worker应该将中间Map输出放在当前目录中的文件中,稍后您的worker可以将它们作为Reduce任务的输入读取。main/mrmaster.go希望mr/master去实现Done()方法,当MapReduce任务完成时返回true,mrmaster.go 将退出去。当任务完全完成时,worker应该退出。实现这一点的一个简单方法是使用call()的返回值:如果worker无法联系到master,它可以假设master已经退出,因为作业已经完成,所以worker也可以终止。
可以这样进行简单测试:
$ go build -buildmode=plugin ../mrapps/wc.go
$ go run mrmaster.go pg-*.txt
// 打开一个master进程,输入参数是一系列文件
$ go run mrworker.go wc.so
// 打开以恶worker进程,wc.so是包含map、reduce的链接文件
$ cat mr-out-* | sort | more
// 合并并排序结果文件然后打印
这里是实现一个mapreduce
框架,我们先分析一下运行流程:
这里是用动态链接的方式来运行特定的map
、reduce
函数,整个lab我们不需要写map
和reduce
函数,只需要开发mapreduce
框架。
首先看main/mrmaster.go
中做了什么:
func main() {
log.SetFlags(log.Lshortfile | log.LstdFlags)
if len(os.Args) < 2 {
fmt.Fprintf(os.Stderr, "Usage: mrmaster inputfiles...\n")
os.Exit(1)
}
// 启动master进程
m := mr.MakeMaster(os.Args[1:], 10)
for m.Done() == false {
time.Sleep(time.Second)
}
time.Sleep(time.Second)
}
func MakeMaster(files []string, nReduce int) *Master {
m := Master{}
// your code
m.server()
return &m
}
func (m *Master) Done() bool {
// your code
return ret
}
两个关键函数,mr.MakeMaster
、m.Done
,这两个函数分别负责创建一个master的实例以及等待master进程工作完成,内部逻辑需要自己补充。进入m.server我们可以发现,这里注册了一个Master的service(lab希望我们通过RPC进行通信)。
然后我们看一下work进程的工作(mr/mrworker.go
文件):
func main() {
log.SetFlags(log.Lshortfile | log.LstdFlags)
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: mrworker xxx.so\n")
os.Exit(1)
}
mapf, reducef := loadPlugin(os.Args[1])
mr.Worker(mapf, reducef)
}
func Worker(mapf func(string, string) []KeyValue,
reducef func(string, []string) string) {
}
这里是调用了Worker,并且讲map函数和reduce函数传给了Worker函数。这里信息不多,看完也不知道要做什么。再看一下rpc相关的代码:
func call(rpcname string, args interface{}, reply interface{}) bool {
// c, err := rpc.DialHTTP("tcp", "127.0.0.1"+":1234")
sockname := masterSock()
c, err := rpc.DialHTTP("unix", sockname)
if err != nil {
log.Fatal("dialing:", err)
}
defer c.Close()
err = c.Call(rpcname, args, reply)
if err == nil {
return true
}
return false
}
func (m *Master) server() {
err := rpc.Register(m)
if err != nil {
log.Fatalf("Rpc register master error=%v\n", err)
}
rpc.HandleHTTP()
// l, e := net.Listen("tcp", ":1234")
sockname := masterSock()
os.Remove(sockname)
l, e := net.Listen("unix", sockname)
if e != nil {
log.Fatal("listen error:", e)
}
go http.Serve(l, nil)
}
可以发现Master进程是服务端,监听的地址是masterSock的返回值,call函数会像masterSock的地址执行rpc调用。所以我理解这里应该是Master作为服务端,提供rpc接口,然后worker主动调用rpc接口完成工作。
基于以上理解,需要定义这些数据结构:Master
、Task
,Master
需要提供这些rpc函数(GetTask
、TaskDone
)。Worker
进程通过调用GetTask
获取任务,执行doMap/doReduce
函数(自己实现)完成task
,并调用TaskDone
通知Master
该任务已完成。Master
需要自己维持任务的执行情况,调用Done
函数轮询任务是否全部完成。
大致框架如上,具体实现参考:https://github.com/wujiah251/...。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。