【MIT 6.824】Lab1

这篇博客主要记录了Lab1的实现,MapReduce论文阅读的笔记。

Lab1

1, MapReduce是什么?

MapReduce是一个编程的模型,可以说是一个框架,用来处理和生成大规模数据,开发者只需要提供Map和Reduce函数就可以利用分布式计算。MapReduce框架隐藏了底层的细节,让开发者可以更加专注于某一个任务的编程而不用考虑分布式计算的处理细节。这套框架隐藏的细节有:数据分割,任务调度,错误处理,通信。
Map的输入是一个键值对;输出是一组键值对。Reduce的输入是一个键,这个键对应的所有值;输出是将这个键的值合并起来更小的集合。

2, 举个例子。

WordCount。从开发者的角度来讲,需要提供Map和Reduce函数。

Map函数,输入的是键值对<文件名,文件内容>,将文本分割成一个个单词,逐个扫描,每遇到一个单词就输出一个键值对<单词,’1’>。
Reduce函数,输入是键值对<单词,这个键对应的所有值>,输出长度。

func Map(filename string, contents string) []mr.KeyValue {
	// function to detect word separators.
	ff := func(r rune) bool { return !unicode.IsLetter(r) }

	// split contents into an array of words.
	words := strings.FieldsFunc(contents, ff)

	kva := []mr.KeyValue{}
	for _, w := range words {
		kv := mr.KeyValue{w, "1"}
		kva = append(kva, kv)
	}
	return kva
}

func Reduce(key string, values []string) string {
	// return the number of occurrences of this word.
	return strconv.Itoa(len(values))
}

3, 论文中的流程是如何描述的?

  1. 将输入文件分割成M份,一份16MB ~ 64MB。在集群上启动MapReduce程序,其中一个是Master,其他是Worker。
  2. Master负责挑选一个任务分配给一个空闲的主机。
  3. 当Worker收到了Map任务,Worker读取对应的数据分割,调用用户定义的Map函数来处理数据,处理产生的键值对保存在内存中。
  4. 将键值对分割成R份,定期存储到硬盘,并将存储的位置告知Master。
  5. Master将存储的位置告诉Reduce Worker,Reduce Worker通过RPC读取远程的Map Worker上对应的文件,读取完毕,按键对中间结果排序。
  6. Reduce遍历排好序的中间结果,每个键都是连续的一块,将一个键和这个键对应的所有中间结果传给Reduce函数进行处理。将结果写入到这个Reduce任务的文件尾。(并不是一个Reduce Worker一个output file,而是每个Reduce任务一个output file)
  7. 当所有任务处理完成,Master唤醒用户程序。

4, Lab1中,你的流程是怎么样的?

Lab1实现一个简易版本的MapReduce,只需要实现Master和Worker。

(1) 这个简易版本的MapReduce和论文中描述的有哪些区别?
文件:不需要分割文件;文件本地读取;
流程:分阶段进行,当所有Map完成了之后,才开始Reduce

(2) Worker的实现
启动Worker的时候:第一,和Master交换信息,比如从Master那里获得NReduce。第二,开启RPC服务,准备好了之后,告诉Master RPC服务的地址,在Master那里注册成为可用的Worker。
Worker的RPC服务接收三个调用:Map任务;Reduce任务;中止任务。
当Worker接收到Map任务:读文件,Map函数处理,使用散列和NReduce取余计算每个键值对的Reduce任务号,保存到本地,将结果返回给Master。
当Worker接收到Reduce任务:读取中间结果,Reduce函数处理,保存,返回文件名字。

(3) Master的实现
启动Master的时候:开启RPC服务,准备接收Worker的注册请求。
分配任务:开一个线程,不断循环,选未完成的任务,选空闲的Worker,选不到就从头开始循环。选到了,设置任务为in-progress,设置Worker为in-progress,开一个线程分配任务(因为RPC会阻塞线程,所以要开线程),然后继续寻找任务和Worker。
当RPC调用返回时:如果成功,修改任务的信息和Worker的信息,记得加锁。Map任务还需要保存中间结果文件。如果失败,认为这个Worker不可用,修改任务的信息即可。
超时检测:在选任务之前,进行一个检测,如果超时,设置任务为未完成。需要在任务开始的时候,设置任务的开始时间。

5, Master需要哪些数据结构?

Worker列表,保存每个Worker的状态和RPC调用地址
Task列表,保存任务类型,任务id,状态,开始时间,文件列表
Master,还需要一个Mutex。

6, 如何逐步实现?

Master的服务和Worker 注册;Worker的Map和Reduce调用;Master找任务、找Worker;超时检测。

7, 遇到了哪些坑?

(1) Go的多线程和闭包。如果多线程不采用传参的方式,多线程外面的函数是构成一个闭包的,多线程的函数会访问外部变量。具体来说是在实现Master找任务和找Worker的时候,用指针指向了Task和Worker,没有使用传参,开启线程之后,访问的Task和Worker指针是在改变的;后来改成了传指针变量,还是有问题,这个问题是什么还没找到;最后改成了传数组下标,问题解决。
(2) 中间结果输出。Reduce的样例一直通过不了,最后问题定位到了Map。在Go中,如果文件已经存在,那么会从文件头开始覆盖内容,而不是将内容全部删除,再开始写入。Reduce测试用例中每个中间结果都要sleep 1秒,因为没有删除掉前面任务的中间结果,导致了中间结果文件很长,Reduce根本完不成。解决办法:调用os.Remove。
(3) 超时检测。一开始觉得,如果还有任务没有开始(0状态),那么根本没有必要去理会超时任务。所以将超时检测放到了找任务后面,如果找不到一个任务,那么进行超时检测。后来代码写得一团糟,问题也不知道出在了哪里,一直过不了最后一个TEST。解决办法:git checkout,从头写过,这次将超时检测放到了找任务之前,一切顺利。但问题在哪却无从定位了。

8, 编程环境配置是怎样的?

使用WSL+VSCode,设置好http.proxy,安装插件。
debug配置:似乎要使用WSL2才可以,WSL1不行。不折腾,debug用打印或日志的方式。

9, mrsequential.go在做什么?

mrsequential.go是lab1的串行化版本,单处理器,单进程,单线程,整个处理过程由一个主线程来完成。主要做的事情是,读取插件和函数,调用map逐个处理文件,将map的结果合并起来,调用reduce,输出到文件。

10, Reduce的结果如何合并?在哪里合并?

不用合并。在脚本中会帮助合并,MapReduce的结果只需要每一个Reduce的输出即可。脚本中调用
sort mr-out-* | grep . > wc-all.txt来合并文件

MapReduce阅读笔记

1, 一个分布式系统,需要考虑哪些问题呢?

并行,容错,数据,负载均衡(parallelization, fault-tolerance, data distribution and load balancing)

2, MapReduce提出的动机?

数据量很大,为了获得一个合理的运行时间,需要分散在上千台主机上并行计算。并行计算,数据分配,错误处理使得原本很简单的计算变得复杂起来。因此,可以考虑将这些东西抽象出来,提供一个接口,不需要考虑底层的这些细节如何实现,只需要专注于开发数据处理的函数。

3, Job和Task的区别?

用户提交的是一个Job,Job包含一系列Task。比如Map和Reduce就是Task,处理整个数据就是Job。

4, Map Task和Reduce Task的数目如何决定?

Map Task的数目取决于输入文件的分割数,Reduce Task的数目可以由用户决定。Map的结果分成R份,每一份是Reduce Task的数据的一部分。

5, Master数据结构?

Task状态、Worker状态、中间文件位置(Map完成了之后可以更新中间文件位置,同时也可以给正在运行的Reduce Worker增加中间文件位置)

6, 错误处理需要考虑哪些因素?

Worker出错:
Master去ping Worker,如果没有响应,将主机标记为失败。
对于执行了Map任务的Worker,其上执行过的Map任务都要重新执行。即使是执行完了Map任务也要重新执行,因为Reduce Worker是通过RPC来读取Map Worker上的中间文件,而这个Map Worker已经不可用,不能RPC。
对于执行了Reduce任务的Worker,如果任务完成了,那么不需要重新执行,因为输出文件保存在GFS中。如果任务没有完成,需要重新执行。
重新执行了Map任务,会对Reduce Worker产生影响,如果Reduce Worker还没有读取失败的Map Worker文件,那么可以改为读取重新执行任务的Map Worker的文件;如果Reduce Worker读取了失败的Map Worker文件,那么需要重新执行。

Master出错:
客户端定期去检查Master。
检查点,保存好Master的数据结构,一旦失败就重新启动一个Master;或者放弃这个Job

7, 论文中说Map和Reduce有deterministic和non-deterministic之分,确定和不确定的意思究竟指的是?什么样的函数称之为deterministic,什么样的函数称之为non- deterministic呢?

区别是:给定一个输入,你是否总是可以得到同一个输出!
参考链接:https://stackoverflow.com/questions/3570416/what-does-deterministic-mean

8, 对于deterministic,什么样的运行结果是正确的?

对于deterministic:分布式计算的结果和串行计算的结果是一样的,这也是Lab1中TEST的方法,顺序执行来获得正确的结果,通过比对结果来返回正确与否。

9, 如何利用局部性?

GFS将文件分割,在不同的机器上储存一部分,每个部分允许重复几份。Master在分配任务的时候,将位置信息考虑进来。有那个文件的Worker优先考虑,没有就优先考虑距离近的Worker(比如连接在同一个交换机上)。

10, 任务数量远多于主机数量的目的是?

负载均衡。

11, straggler问题?

有Worker耗费很长的时间去执行最后的几个任务,拖慢了整体的速度。解决办法,当只剩下最后几个in-progress任务的时候,去将这个几个任务重新分发去执行一下。同个任务,只要有一个Worker完成了,就标记为完成。

12, Refinement

需求 改进方法
Partitioning Function: 将Map的中间结果的某些键映射到一起。比如键是URL,但是想要将这些URL的按照主机映射到一起。 允许用户提供分割函数。Hash(Hostname(key))%NReduce
Ordering Guarantees: 中间结果或者输出结果保持有序 有序输出
Combiner Function: 存在大量重复的中间键值对,合并部分中间结果,以减少网络传输。 用户自定义一个Combiner函数,Map结束后,将结果合并起来之后保存下来。
Input and Output Types: 自定义从输入文件读入键值对的方式;自定义输出到文件的格式。
Skipping Bad Records: 跳过造成错误的记录 每个Worker安装一个handler捕捉错误,在调用Map/Reduce之前,先记录下序列号,如果发生了错误,handler检测到了,发一个UDP包给Master。之后Master分配任务的时候,告诉Worker这些记录需要跳过。
Local Execution: 串行执行代码,用于调试Map和Reduce函数 实现一个串行版本的MapReduce
Status Information: 显示执行状态,用于分析
Counters: 给某些事件计次,用户可以在Map/Reduce的函数中,检测某些事件,计次。 计数器的值定期传给Master,当所有任务都完成了之后,Master合计所有次数,返回给用户程序。当有backup-task或者重新执行的时候,Master不能重复合计次数。

猜你喜欢

转载自www.cnblogs.com/zzk0/p/12933376.html