MIT 6.824 学习(一)【MapReduce】

文章目录

  • 1. 理论
    • 1.1 概述
    • 1.2 执行流程
  • 2. 实验
    • 2.1 概述
    • 2.2 思路

1. 理论

理论内容来自 Google 论文 MapReduce: Simplified Data Processing on Large Clusters

1.1 概述

MapReduce是用于处理和生成大型数据集的编程模型和相关实现。

我认为主要包括两个部分:

  • MapReduce Framework :帮助实现一些具体流程的封装,包括任务分发、数据 shuffle、错误处理、负载均衡等等细节。用户无需掌握这些细节,更能关注于业务逻辑。
  • Map / Reduce 函数:这两个函数需要自己实现,包含对业务逻辑的具体处理。
    • Map:入参是一个 出参也是一组经过处理的中间变量 。用于中间逻辑的处理,类似于数据处理的一个子任务。
    • Reduce:入参是 ,出参是一组值,用于合并同一个 key 关联的中间值。

1.2 执行流程

MIT 6.824 学习(一)【MapReduce】_第1张图片

  1. MapReduce 库会将输入的文件分割成 M 个片段(通常在16 MB到64 MB),然后启动集群的机器来运行程序副本(也就是 MapReduce 的具体操作)。
  2. 集群中会有一个为 master 节点,来具体分配 map 或者 reduce 任务给空闲的 worker。
  3. 分配到 map 任务的进程就会读入对应分片的内容,解析成 kv,调用用户实现的 Map 函数,处理得到中间值缓存在内存中。
  4. 缓存在内存中的中间值会被 MapReduce 库分割成 R 个分片(对应 reduce 个数),周期性的写入磁盘,之后将具体存储位置通知 master,master 分配给对应的 reduce 任务给 worker。
  5. 被分配的 reduce worker 就会向对应的 map worker 节点发送 RPC 请求读取对应的数据,并会进行排序和聚合相同的 key。
  6. 然后将处理完的 kv 传送到用户实现的 Reduce 函数,进行最终的聚合业务处理,生成结果到 一个全局文件中(一个 reduce 对应一个文件分片)。
  7. 当所有 map 和 reduce 任务都完成了,整个 MapReduce 工作也完成了。

最后生成的 R 个文件就是 MapReduce 的结果,一些大型复杂的项目可能会有一个很长的调用 pipline,继续将这 R 个文件作为下一个 MapReduce 的入参进行处理。

论文中还包括一些优化点和选举容错(例如 master 的选举,worker 挂了后的重试以及worker 速度太慢的优化)等,可以自己阅读。

2. 实验

2.1 概述

实验内容来自 MIT 6.824 lab 6.824 Lab 1: MapReduce

可以先看看 lab 的内容,然后 clone 实验代码

git clone git://g.csail.mit.edu/6.824-golabs-2022 6.824

原版代码中的 src/main/mrsequential.go 实现了一个简易的单线程并且单 Map Worker 单 Reduce Worker 的简易版 MapReduce,阅读后基本可以对整体流程框架有了大概的了解。

func main() {
	// 三个参数 mrsequential.go wc.so pg*.txt
	if len(os.Args) < 3 {
		fmt.Fprintf(os.Stderr, "Usage: mrsequential xxx.so inputfiles...\n")
		os.Exit(1)
	}
	// 从「wc.so」得到 Map 和 Reduce 函数
	mapf, reducef := loadPlugin(os.Args[1])

	// --------------------------------------Map Worker 操作-----------------------------------------------
	// 中间值
	intermediate := []mr.KeyValue{}
	// 遍历「pg*.txt」所有文件
	for _, filename := range os.Args[2:] {
		file, err := os.Open(filename)
		if err != nil {
			log.Fatalf("cannot open %v", filename)
		}
		content, err := ioutil.ReadAll(file)
		if err != nil {
			log.Fatalf("cannot read %v", filename)
		}
		file.Close()
		// Map 函数   -> mr.KeyValue
		kva := mapf(filename, string(content))
		intermediate = append(intermediate, kva...)
	}

	// --------------------------------------Reduce Worker 操作-----------------------------------------------
	// 排序key,便于聚合
	sort.Sort(ByKey(intermediate))

	// 创建文件
	oname := "mr-out-0"
	ofile, _ := os.Create(oname)

	// 循环调用 Reduce
	i := 0
	for i < len(intermediate) {
		// shuffle 数据,聚合key
		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)
		}
		// 调用 Reduce 函数
		output := reducef(intermediate[i].Key, values)

		// 写文件
		fmt.Fprintf(ofile, "%v %v\n", intermediate[i].Key, output)

		i = j
	}

	ofile.Close()

	// 整个流程看起来是 MapReduce 的单线程简易版,少了一些文件存储,共享互斥,RPC 调用
	// 整体就是单 Map Worker 单 Reduce Worker。
}

// 获取 Map 和 Reduce 函数
func loadPlugin(filename string) (func(string, string) []mr.KeyValue, func(string, []string) string) {
	p, err := plugin.Open(filename)
	if err != nil {
		log.Fatalf("cannot load plugin %v", filename)
	}
	xmapf, err := p.Lookup("Map")
	if err != nil {
		log.Fatalf("cannot find Map in %v", filename)
	}
	mapf := xmapf.(func(string, string) []mr.KeyValue)
	xreducef, err := p.Lookup("Reduce")
	if err != nil {
		log.Fatalf("cannot find Reduce in %v", filename)
	}
	reducef := xreducef.(func(string, []string) string)

	return mapf, reducef
}

了解了大概流程之后就可以实现自己的多线程版本,操作的入口在 src/main/mrcoordinator.go (master 入口) 以及 src/main/mrworker.go (Worker 入口)。具体需要自己实现的内容,在 mr 包下的三个文件。

MIT 6.824 学习(一)【MapReduce】_第2张图片

2.2 思路

  1. 按照 lab 提示,从 Worker 入手,不断向 Coordinator 发送心跳包,并根据返回结果来进行具体的操作,处理哪种任务,或者等待,或者结束。(需要提供心跳 RPC 以及完成任务的RPC)
  2. Coordinator 接收心跳包,根据具体情况来判断这个 Worker 应该处理什么逻辑,返回具体操作的类型和一些数据。总体上先进行 Map 再进行 Reduce,或者需要等待或退出。
  3. 需要对任务的状态进行维护,包括任务ID,完成状况,开始时间来做超时控制,任务类型等。

你可能感兴趣的:(分布式,golang,分布式,mapreduce)