1.简介
Lecture 1主要是介绍了MIT6.824这门课程的课程安排,分布式的概念、为什么要使用分布式(水平和垂直维度提高系统的可用性与性能、高效利用廉价硬件集群的性能来快速完成海量数据处理任务)以及实现分布式过程中的挑战(CAP)。最后介绍了MapReduce以及它的优缺点。课后需要完成lab1,自己构建一个MapReduce系统。
课程的视频(B站就可以看):www.bilibili.com/video/BV1R7…
Lecture1的讲义:pdos.csail.mit.edu/6.824/notes…
MapReduce论文:pdos.csail.mit.edu/6.824/paper…
Lecture1课后lab1的要求:pdos.csail.mit.edu/6.824/labs/…
2.Lab1实现
实现lab1前,要求先读完MapReduce的论文,并仔细阅读lab1的要求文档。
lab要求使用Go语言。目标是实现MapReduce模型,需要分别实现Master和Worker线程。worker线程可以读写文件,调用Map和Reduce功能。Master线程,能将任务分发给worker,而且可以处理运行失败的worker,以及当某个worker运行的比较慢的时候,可以将同一个任务下发个另一个空闲的worker
2.0.GO语言入门学习
之前没用过GO,现学吧,把Go 语言基础语法 | 菜鸟教程给看完,知道GO的基础语法,就可以上手lab1开发了,有遇到问题就google下。
2.1.GO环境安装
- 按照lab1上的要求,下载1.3版本的免安装版的golang:golang.google.cn/dl/go1.13.w…
- 下载完成后解压zip包,配置golang的环境变量,将go下面的bin目录添加到path中,同时配置GOROOT和GOPATH。
2.2.IDE
折腾了下用sublime安装godef和gosublime,太折腾了,而且也不是很好用。还是用GoLand(破解补丁)吧。
2.3.Go代码编写时要注意的
-
GO rpc方法的格式:
func (m *Master) RegisterWorker(mrworkerUUID string, ret *MRWorkerRegisterReply) error { idleWorkers := m.workerMap[WorkerIdle] idleWorkers = append(idleWorkers, MRWorker{uuid: mrworkerUUID}) for i, idleWorker := range m.workerMap[WorkerIdle] { log.Printf("m.workerMap[WorkerIdle][%d] = %s\n", i, idleWorker.uuid) } ret.ret = true return nil } -
结构体struct中小写开头的成员变量表示protected级别,不能被其他package下的代码访问也不会参与序列化,所以在进行RPC调用的时候,如果有用到某个变量,应该将它首字母设置为大写:
type MRWorkerRegisterReply struct { Ret bool } -
数组元素拷贝时,如果不使用指针默认是元素值(不是内存地址值,是元素实际的值)拷贝:
package main import "fmt" type MRWorker struct { uuid string status int } func main() { var workerA = [2]MRWorker{{ uuid: "1", status: 1, },{ uuid: "2", status: 1, }} var workerIdelArr []MRWorker for i := range workerA { if workerA[i].status == 1 { workerIdelArr = append(workerIdelArr, workerA[i]) } } worker := workerIdelArr[0] workerIdelArr = workerIdelArr[1:] worker.status = 2 for _, mrWorker := range workerA { fmt.Printf("uid = %s, status = %d \n ", mrWorker.uuid, mrWorker.status) } }使用指针来操作数组:
package main import "fmt" type MRWorker struct { uuid string status int } func main() { var workerA = [2]MRWorker{{ uuid: "1", status: 1, },{ uuid: "2", status: 1, }} var workerIdelArr []*MRWorker // TODO 不能使用这种遍历 for _, worker := range m.workerA for i := range workerA { if workerA[i].status == 1 { workerIdelArr = append(workerIdelArr, &workerA[i]) } } var worker *MRWorker = workerIdelArr[0] workerIdelArr = workerIdelArr[1:] (*worker).status = 2 for _, mrWorker := range workerA { fmt.Printf("uid = %s, status = %d \n ", mrWorker.uuid, mrWorker.status) } } // 输出 uid = 1, status = 2 uid = 2, status = 1 -
string和int类型转换正确姿势:
reduceNum := 1 √ strconv.Itoa(reduceNum) × string(reduceNum) -
RPC接口必须要返回error,否则调用RPC接口的时候会报错找不到方法
func (mrworker *MRWorker) Exit(reduceReq *MapReduceRequest, ret *RpcReply) error { mrworker.Stop = true ret.Ret = true return nil } -
shell脚本不识别windows换行符,解决方式
$ sh ./test-mr.sh ./test-mr.sh: line 2: $'\r': command not found ./test-mr.sh: line 6: $'\r': command not found ./test-mr.sh: line 8: $'\r': command not found ./test-mr.sh: line 11: $'\r': command not found -
跑test-mr.sh前,看下这个shell脚本里的内容,看看是怎么测试的,这样子测试不通过的时候自己也能知道怎么去debug和处理。
2.5.成功的喜悦XD
lab1还是挺简单的,读一到两遍MapReduce的论文,然后仔细看下lab1的要求(一定要仔细看,很多细节要注意),基本上lab1对于有编程基础的人应该是没啥难度的。
完成lab1之后,跑测试的时候发现一个问题,每次跑完一个测试案例之后,在test-mr.sh中将map生成的中间文件删除了,再执行下一个测试,没问题,能全部跑通,但是如果不删除map生成的中间文件的话 reduce parallelism和crash两个test会跑的很慢,crash必定超时。。。
感觉还是前面测试案例产生的中间数据文件会对后面的测试有影响,在文件命名的时候,应该以每次测试为粒度,让相互之间的任务不收到影响,解决方式:通过给master添加了一个uuid来标识每次测试,顺利通过test-mr.sh。如果master在整个测试期间都不停止的话,就无法将uuid添加到master上,可以换种方式,在每个测试任务中都生成一个uuid并添加到这次测试任务中的的map job和reduce job上。修改完毕之后,测试全部跑通~。源码就不分享出来,一是第一次用Go写的有点糙,二是这个课程希望学生们不要将lab代码开源放到github上(如果将仓库设置成private是没问题的。老师还是担心学生们去github上搜线程的源码,自己不去动手搞lab,也就失去了这个lab的意义。)