MIT6.824-LAB1(MapReduce)

枯木逢春不在茂
年少且惜镜边人

写在前面

从大二下开始,便再也没有写过博客,多半以学习笔记的形式存在于我的机械硬盘。回顾大学的两年半,发现自己学的知识大多都忘记的比较快,真正掌握的也少之又少。这期间也曾迷失过自我,也曾拥有过许多,但是这都不重要,重要的是,五年前说的镜边人,还是现在的镜边人。
大三上学期的目标也都没有切实的实现,须记在以后的学习中切勿眼高手低,切勿操之过急,切勿过度膨胀。应不论在什么时候都应该认清自我,应常以“菜鸡“自称,切勿再去说学了什么,应思考自己能干什么!哦,对了!刷力扣题是灰常重要的。往以后的自己能坚持下来
----------------------若能以不自由之事为常,则不觉不足以---------------------------
这里也写下自己眼下的一些目标吧

  1. 6级得过啊(本人可以说对英语一点天赋都没有)
  2. 用大三上剩下的时间看完DDIA
  3. 大三上别挂科
  4. 准备考研
  5. 五年的镜边人,希望三年后也是

实现过程

这里给出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,加油,耀瑶要努力变好啊!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MIT 6.824 课程的 Lab1 是关于 Map 的实现,这里单介绍一下实现过程。 MapReduce 是一种布式计算模型,它可以用来处理大规模数据集。MapReduce 的核心想是将数据划分为多个块,每个块都可以在不同的节点上并行处理,然后将结果合并在一起。 在 Lab1 中,我们需要实现 MapReduce 的基本功能,包括 Map 函数、Reduce 函数、分区函数、排序函数以及对作业的整体控制等。 首先,我们需要实现 Map 函数。Map 函数会读取输入文件,并将其解析成一系列键值对。对于每个键值对,Map 函数会将其传递给用户定义的 Map 函数,生成一些新的键值对。这些新的键值对会被分派到不同的 Reduce 任务中,进行进一步的处理。 接着,我们需要实现 Reduce 函数。Reduce 函数接收到所有具有相同键的键值对,并将它们合并成一个结果。Reduce 函数将结果写入输出文件。 然后,我们需要实现分区函数和排序函数。分区函数将 Map 函数生成的键值对映射到不同的 Reduce 任务中。排序函数将键值对按键进行排序,确保同一键的所有值都被传递给同一个 Reduce 任务。 最后,我们需要实现整个作业的控制逻辑。这包括读取输入文件、调用 Map 函数、分区、排序、调用 Reduce 函数以及写入输出文件。 Lab1 的实现可以使用 Go 语言、Python 或者其他编程语言。我们可以使用本地文件系统或者分布式文件系统(比如 HDFS)来存储输入和输出文件。 总体来说,Lab1 是一个比较简单的 MapReduce 实现,但它奠定了 MapReduce 的基础,为后续的 Lab 提供了良好的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值