1. MapReduce论文
关于这片论文没啥好说的,有地方不懂得可以看课程的视频( B站有 ), MapReduce论文翻译。具体工作过程可参见下图:
2. Lab 大致内容
整个Lab要完成的代码文件有三个:
1. Master.go 即负责分配Map和Reduce任务的主进程
2. RPC.go 由于 Worker 需要通过RPC 与 Master 进程 通信,故在此放置 RPC调用的参数与响应的结构体定义
3. Worker.go 干活的进程, 从Master获取任务并执行
整个程序大致执行流程为
1. 启动 Master 进程,监听 Worker 发过来的 RPC 请求
2. 启动一个或若干Worker进程
3. Worker 进程从 Master 获取 任务,如为 Map / Rduce 任务,则调用对应的Map / Reduce 函数处理,并将结果写到文件中;
如暂时无任务(任务都在执行或完成), 则等待一定时间;如任务都已完成,则退出
对于 Master 进程,需要自己定义管理任务状态的一些数据结构,并且 由于可能有多个Worker产生RPC请求,RPC请求会并发访问或修改这些数据结构,所以一定要加锁Mutex
对于Worker进程,RPC请求可以细分为 1. 请求分配任务 2. 完成分配任务
3. 需注意的细节
1. 整个 Master.go 文件中只有 server函数中才会出现 go xxx(xxx), 而 Worker.go 中也不会出现 go xxx(xxx)。Worker 进程代表着真实分布式系统中的一台主机,我们运行时会启动多个 Worker 进程来模拟。
2. 对每个任务要记录下分配的时间,以便运行超过一定的时间就可以将它分配给另一个Worker
3. 严格注意 RPC 函数的格式问题,可参考下述代码或go的rpc库说明
- 对于RPC响应函数,最好两个参数均为指针,其中第二个一定要是指针, 返回值一定为 error,调用无错误可以返回 nil。 如不符合上述格式 会出现 "rpc :can't find method xxx" 或者 "rpc :can't find service xxx"
- 对于RPC调用参数, 参数中的字段名字首字母必须大写,否则写不进去,收到的结果统统为默认值
// 以下代码为Master.go中的RPC响应函数
func (m *Master) Test(args *TestArgs, reply *TestReply) error {
// 从args中获取调用参数 并将返回结果写回到reply
// 如 reply.Data = 1
return nil
}
// 以下代码为RPC.go中的RPC调用参数
type TestArgs struct {
Data int
}
type TestReply struct {
Data int
}
// 以下代码为Worker.go中的RPC调用封装函数
func CallTest(...) TestReply {
// 构造调用参数和返回值
args := TestArgs{}
reply := TestReply{}
// 定义在Worker.go中 调用结果在reply中
call("Master.Test", &args, &reply)
return reply
}
4. Worker 中的 Map任务收到的任务信息为要处理的文件名,需要先从文件名中读取所有内容再喂给Map函数(这部分可参考mrsequential.go)。Map函数处理的结果排序后按Key归档成组,并用ihash(key) % reduce_num(要分成的reduce任务数,提前从Master获取)将其分配给不同的reduce任务,并写入到不同的中间文件中。如分成3 个Map任务和4个Reduce任务,中间文件采用
mr-X-Y的文件名格式(X = 0 / 1 / 2, Y = 0 / 1 / 2 /3 ),则对于 Map 0 号任务的 Map函数输出,需要将其分割为 mr-0-0,mr-0-1,mr-0-2, mr-0-4四个文件,注意必须使用 ioutil.TempFile("", "mr-*")先 创建临时文件进行写入,后面再用os.Rename() 修改为最终的文件名,最后调用完成任务的RPC。写入KeyValue 可参考下面
import "encoding/json"
enc := json.NewEncoder(file)
for _, kv := ... {
err := enc.Encode(&kv)
5. Worker 中的Reduce 任务收到要处理的文件名数组。仍以上例说明,对于Reduce 0号任务,会收到 mr-0-0, mr-1-0, mr-2-0, mr-3-0 四个文件名,将收到的文件名对应的文件内容读入汇总到一个数组,再按Key进行排序,遍历数组,将同一个key对应的value数组和Key 交给Reduce函数处理,依次处理并汇总所有输出,采用第四点的临时文件再改名的方法写入,最后调用完成任务RPC。其中从文件读入KeyValue可以参考下面
import "encoding/json"
dec := json.NewDecoder(file)
for {
var kv KeyValue
if err := dec.Decode(&kv); err != nil {
break
}
kva = append(kva, kv)
}
写入最终数组到文件可以参考下面 (不要自作聪明在最后一行就不加换行符,否则最后的Crash Test过不去,都是从泪中总结出的)
for i := 0; i < len(output); i++ {
str = output[i].Key + " " + output[i].Value + "\n"
if _, err := file.WriteString(str); err != nil {
fmt.Println(err)
}
}
6. 将KeyValue数组按Key排序可以参考mrsequential.go 或直接使用下面的代码
// 放在 Worker.go 中
// for sorting by key.
type ByKey []KeyValue
// for sorting by key.
func (a ByKey) Len() int { return len(a) }
func (a ByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByKey) Less(i, j int) bool { return a[i].Key < a[j].Key }
7. Master 进程没什么要特别注意的,规规矩矩写就可以了
以上就是个人总结的东西和想起来要注意的点,如有疑问,可在下方评论