枯木逢春不在茂
年少且惜镜边人
写在前面
从大二下开始,便再也没有写过博客,多半以学习笔记的形式存在于我的机械硬盘。回顾大学的两年半,发现自己学的知识大多都忘记的比较快,真正掌握的也少之又少。这期间也曾迷失过自我,也曾拥有过许多,但是这都不重要,重要的是,五年前说的镜边人,还是现在的镜边人。
大三上学期的目标也都没有切实的实现,须记在以后的学习中切勿眼高手低,切勿操之过急,切勿过度膨胀。应不论在什么时候都应该认清自我,应常以“菜鸡“自称,切勿再去说学了什么,应思考自己能干什么!哦,对了!刷力扣题是灰常重要的。往以后的自己能坚持下来
----------------------若能以不自由之事为常,则不觉不足以---------------------------
这里也写下自己眼下的一些目标吧
- 6级得过啊(本人可以说对英语一点天赋都没有)
- 用大三上剩下的时间看完DDIA
- 大三上别挂科
- 准备考研
- 五年的镜边人,希望三年后也是
实现过程
这里给出MIT6.824分布式入门课的网址(点我)
本文只作为个人复习所用,大佬们,有错误尽管指出。由于本人的代码比较丑,本文将只是抽取其中的重点代码进行一些展示。在做lab1
之前搜索了大量的资料,建议先看完MapReduce的论文,并且看一下b站上的视频,我觉得复习是学习中非常重要的一部分,这点之前老师老说,我却当是笑话。现在,悔不当初。
网上对于lab1的讲解也很多,当然自己在做的时候也参考了许多,
首先明确一下MapReduce的任务(总的来说弄懂下面这张图就OK了)
实现分布式mr,一个coordinator,多个worker。worker通过rpc和coordinator交互。worker请求任务,进行运算,写出结果到文件。coordinator需要关心worker的任务是否完成,在超时情况下将任务重新分配给别的worker。
type MapReduceTask struct {
//need 1.tasktype 2.taskstatus 3.tasknum
TaskType string //map,reduce,wait
TaskStatus string //unassigned,assigned,finished
TaskNum int
//need 1.input of map task 2.input of reduce reduce
MapFile string //input of map task
ReduceFiles []string //input of reduce reduce
NumReduce int
NumMap int
TimeStamp int64
}
然后定义RPC(远程过程调用)需要的相关参数
//rpc Arguments
type MapReduceArgs struct {
MessageType string //request / finish
Task MapReduceTask
}
//rpc return values
type MapReduceReply struct {
Task MapReduceTask
}
MessageType类型
- request
- finish
下面是集群Coordinator的结构体
type Coordinator struct {
// Your definitions here..
mu sync.Mutex
NumMap int
NumMApFinished int
NumReduce int
NumReduceFinished int
MapTasks []MapReduceTask
ReduceTasks []MapReduceTask
MapFinihed bool
ReduceFinish bool
}
其中MapFinished和ReduceFinish表示所有的mapreduce任务是否完成了,若都是true,则结束。
然后就是过程的具体实现函数,首先看worker.go中的内容
func Worker(mapf func(string, string) []KeyValue,
reducef func(string, []string) string) {
// Your worker implementation here.
for {
args := MapReduceArgs{MessageType: "request"}
reply := MapReduceReply{}
resp := call("Coordinator.MapReduceHandeler", &args, &reply)
//resp ---- bool
if !resp {
break
}
switch reply.Task.TaskType {
case "Map":
mapTask(mapf, reply.Task)
case "Reduce":
reduceTask(reducef, reply.Task)
case "Wait":
waitTask()
}
}
// uncomment to send the Example RPC to the coordinator.
// CallExample()
}
func waitTask() {
time.Sleep(time.Second)
}
这里Worker的实现是发送了一个RPC请求,请求的是Coordinator.MapReduceHandeler这个函数,这个函数待会说,现在只需要知道这个函数给worker分配工作,来觉得这个work是去做什么工作(map或着reduce,或着wait(直接让该worker沉睡,然后重新分配))
func mapTask(mapf func(string, string) []KeyValue, task MapReduceTask) {
filename := task.MapFile
file, err := os.Open(filename)
if err != nil {
log.Fatalf("mapTask cannot open %v", filename)
}
content, err := ioutil.ReadAll(file) //var content []byte
if err != nil {
log.Fatalf("connot read %v", filename)
}
file.Close()
kva := mapf(filename, string(content)) //var kva []KeyValue
kvaa := make([][]KeyValue, task.NumReduce)
for _, kv := range kva {
idx := ihash(kv.Key) % task.NumReduce
kvaa[idx] = append(kvaa[idx], kv)
}
for i := 0; i < task.NumReduce; i++ {
// if len(kvaa[i]) == 0 {
// continue
// }
storeIntermediateFile(kvaa[i], intermediateFilename(task.TaskNum, i))
}
defer finishTask(task)
}
可以看到这里我们maptask只不过是把拿到的文件内容解析出来然后,交给穿入的函数mapf进行处理,处理结果是var kva []KeyValue,
type KeyValue struct {
Key string
Value string
}
这里是通过key值计算hash值,然后存入一个双层切片,这个切片可以这样理解
确实有点丑,但是大家看个乐就行,用二级切片是因为讲一个任务切分成多个碎片,然后存储,最后交给reduce处理,对这一部分论文中有详细介绍,并且做了分析。
这里涉及两个函数
func intermediateFilename(numMapTask int, numReduceTask int) string {
return fmt.Sprintf("mr-%v-%v", numMapTask, numReduceTask)
}
func storeIntermediateFile(kva []KeyValue, filename string) {
file, err := os.Create(filename)
if err != nil {
log.Fatalf("cannot create %v\n", filename)
}
defer file.Close()
enc := json.NewEncoder(file) //var enc *json.Encoder
if err != nil {
log.Fatal("cannot create encoder")
}
for _, kv := range kva {
err := enc.Encode(&kv)
if err != nil {
log.Fatalf("cannot encode")
}
}
//这里为了防止 worker 中途挂掉,先将结果写到零时文件中,最后通过 Rename 函数重新命名
// ofile, _ := ioutil.TempFile("./", "tmp_")
// enc := json.NewEncoder(ofile)
// for _, kv := range kva {
// err := enc.Encode(&kv)
// if err != nil {
// log.Fatalf("Json encode error: Key-%s, Value-%s", kv.Key, kv.Value)
// }
// }
//defer ofile.Close()
//os.Rename(ofile.Name(), filename)
}
这里面就是一个是命名加存储函数,看代码解析和注释就可
func finishTask(task MapReduceTask) {
args := MapReduceArgs{MessageType: "finish", Task: task}
reply := MapReduceReply{}
//fmt.Printf("%v %v has been finish\n", task.TaskType, task.TaskNum)
call("Coordinator.MapReduceHandeler", &args, &reply)
}
map和ruduce任务完成后便调用finishTAsk告诉管理者,以便给自己分配新的任务
func loadIntermediateFile(filename string) []KeyValue {
var kva []KeyValue
file, err := os.Open(filename)
if err != nil {
log.Fatalf("reduce cannot open %v", filename)
}
defer file.Close()
dec := json.NewDecoder(file)
for {
kv := KeyValue{}
if err := dec.Decode(&kv); err != nil {
break
}
kva = append(kva, kv)
}
return kva
}
这里提前看一下加载函数,在reduce中有用到,思路也是简单的把文件中的内容提取出来。
func reduceTask(reducef func(string, []string) string, task MapReduceTask) {
//fmt.Printf("%v %v has been begin/n", task.TaskType, task.TaskNum)
var intermediate []KeyValue
for _, filename := range task.ReduceFiles {
intermediate = append(intermediate, loadIntermediateFile(filename)...)
}
sort.Sort(ByKey(intermediate))
outname := fmt.Sprintf("mr-out-%v", task.TaskNum)
outfile, err := os.Create(outname)
if err != nil {
log.Fatalf("cannot create %v", outname)
}
// ofile, _ := ioutil.TempFile("./", "tmp_")
// i := 0
// for i < len(intermediate) {
// j := i + 1
// for j < len(intermediate) && intermediate[j].Key == intermediate[i].Key {
// j++
// }
// values := []string{}
// for k := i; k < j; k++ {
// values = append(values, intermediate[k].Value)
// }
// output := reducef(intermediate[i].Key, values) //string []string (k list<v>)
// fmt.Fprintf(ofile, "%v %v/n", intermediate[i].Key, output)
// i = j
// }
// ofile.Close()
// os.Rename(ofile.Name(), outname)
i := 0
for i < len(intermediate) {
j := i + 1
for j < len(intermediate) && intermediate[j].Key == intermediate[i].Key {
j++
}
values := []string{}
for k := i; k < j; k++ {
values = append(values, intermediate[k].Value)
}
output := reducef(intermediate[i].Key, values)
// this is the correct format for each line of Reduce output.
fmt.Fprintf(outfile, "%v %v\n", intermediate[i].Key, output)
i = j
}
outfile.Close()
defer finishTask(task)
}
reduce的任务就是整合map的内容变成一个 string [] string 然后发送给reducef函数去处理。
这里的for循环其实在做一个这样的事情
比如keyvalue中的内容是
a 1 a 1 a 1 b 2 b 3
则生成的结果就是 a 1 1 ; b 2 3 然后去做处理
然后关于work,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 }
//
// use ihash(key) % NReduce to choose the reduce
// task number for each KeyValue emitted by Map.
//
func ihash(key string) int {
h := fnv.New32a()
h.Write([]byte(key))
return int(h.Sum32() & 0x7fffffff)
}
这些东西在实验指导和代码中都能找到,直接cv工程就行
最后说一下coordinate的内容
// Your code here -- RPC handlers for the worker to call.
func (c *Coordinator) MapReduceHandeler(args *MapReduceArgs, reply *MapReduceReply) error {
c.mu.Lock()
defer c.mu.Unlock()
if args.MessageType == "request" {
if !c.MapFinihed {
for index, task := range c.MapTasks {
if task.TaskStatus == "Unassigned" {
c.MapTasks[index].TaskStatus = "Assigned"
reply.Task = c.MapTasks[index]
go c.checkTimeout("Map", index, 10)
return nil
}
}
reply.Task.TaskType = "Wait"
return nil
} else if !c.ReduceFinish {
for index, task := range c.ReduceTasks {
if task.TaskStatus == "Unassigned" {
c.ReduceTasks[index].TaskStatus = "Assigned"
reply.Task = c.ReduceTasks[index]
go c.checkTimeout("Reduce", index, 10)
return nil
}
}
reply.Task.TaskType = "Wait"
return nil
} else {
return nil
}
} else if args.MessageType == "finish" {
if args.Task.TaskType == "Map" {
c.NumMApFinished = c.NumMApFinished + 1
c.MapTasks[args.Task.TaskNum].TaskStatus = "Finished"
if c.NumMApFinished == c.NumMap {
c.MapFinihed = true
}
} else {
c.NumReduceFinished = c.NumReduceFinished + 1
c.ReduceTasks[args.Task.TaskNum].TaskStatus = "Finished"
if c.NumReduceFinished == c.NumReduce {
c.ReduceFinish = true
}
}
return nil
}
//fmt.Println("map/reduce has been done")
return nil
}
这里其实的看发过来的请求,如果有map没有分配完成,优先分配map任务,然后设置超时。如果所有的work都在运行map ,还有未处理完成的map任务,则让其等待,如果有map任务完成则给他finish数量+1,并且检查是不是完成所有的,方便之后的结束检查
func (c *Coordinator) checkTimeout(taskType string, num int, timeout int) {
time.Sleep(time.Second * time.Duration(timeout))
c.mu.Lock()
defer c.mu.Unlock()
if taskType == "Map" {
if c.MapTasks[num].TaskStatus == "Assigned" {
c.MapTasks[num].TaskStatus = "Unassigned"
}
} else {
if c.ReduceTasks[num].TaskStatus == "Assigned" {
c.ReduceTasks[num].TaskStatus = "Unassigned"
}
}
}
如果该work在处理该任务的时候超时了,直接把对应的任务状态置为未完成,等待重新进行
func MakeCoordinator(files []string, nReduce int) *Coordinator {
c := Coordinator{}
// Your code here.
c.NumMap = len(files)
c.NumReduce = nReduce
c.MapFinihed = false
c.ReduceFinish = false
for index, file := range files {
var tempTask MapReduceTask
tempTask.NumMap = c.NumMap
tempTask.NumReduce = c.NumReduce
tempTask.TaskType = "Map"
tempTask.TaskStatus = "Unassigned"
tempTask.TaskNum = index
tempTask.MapFile = file
c.MapTasks = append(c.MapTasks, tempTask)
}
for i := 0; i < c.NumReduce; i++ {
var tempTask MapReduceTask
tempTask.NumMap = c.NumMap
tempTask.NumReduce = c.NumReduce
tempTask.TaskType = "Reduce"
tempTask.TaskStatus = "Unassigned"
tempTask.TaskNum = i
for j := 0; j < c.NumMap; j++ {
tempTask.ReduceFiles = append(tempTask.ReduceFiles, intermediateFilename(j, i))
}
c.ReduceTasks = append(c.ReduceTasks, tempTask)
}
c.server()
return &c
}
这里就是一些初始化工作
然后就是
//
// main/mrcoordinator.go calls Done() periodically to find out
// if the entire job has finished.
//
func (c *Coordinator) Done() bool {
c.mu.Lock()
defer c.mu.Unlock()
// Your code here.
return c.ReduceFinish
}
到这里也就结束了,这个mapreduce写的有点早,今天更新了一下test,(╯﹏╰)b ,发现新添加的测试有一个通过不了,官方文档中也没有对该测试讲解,寄了,明天计组考试,我晚上改不出代码,不改了,等以后重新写一遍mapreduce吧
其他的测试点可以通过,明天再来写2A,后台2B,大后天2C,大大后天3A ,大大大后天3B,加油,耀瑶要努力变好啊!