MIT 6.824第一个lab完成记录、心得和完整代码

MIT 6.824是分布式系统课程,在听完两节课之后用时几天进行了lab1的实现,相关的中文翻译文档在CSDN等各个平台都可以搜索得到,仅借此文章分享下个人完成的心得及体会。

必须保持清晰的头脑

看到实验描述后整个人完全是懵逼的状态,需要改哪个地方?这个函数是干嘛的?what‘s up?这些介绍都是干啥的?看不懂,看不下去,没耐心,导致前期很多时间都浪费了,其实在看过实验要求的论文之后,其中的内容很多都很简单,跟着lab的介绍进行一步步的完成,包括hints部分的很多内容可以很直观的指导实验的进行,而自己在看完课程的前几天完全就是在磨时间,不愿意一条一条的看,心浮气躁,导致起码两天的时间都浪费了,所以首先强调,在之后的三次实验中保持耐心,将实验要求及提示一条条看清楚看明白!

Go语言基础

这个实验对于Go语言也有相应训练的课程,老师在上课的时候也讲了很多相关实现的问题,语言永远不是做实验的障碍,大部分都是从c/c++开始的编程语言的学习,很多东西都是相同的,Go语言也不会对实验造成太大的阻碍。
Go语言的RPC其实自己也没有太过了解,只是根据他上面所给的例子进行更改和添添加。

1 入手

万事开头难

1.1 Your job分析

实验中给的mrsequential.go给出了map操作和reduce操作的流程,主要是wc.go文件以plugin的模式进行编译,并且生成wc.so程序函数库,并在运行时调用其中的map函数和reduce函数进行map和reduce操作,这两个操作实验中给出,其实我们后面实现的主要就是分布式MapReduce的逻辑。对于mrsequential.go中的map和reduce函数可以看一下实现可能有助于后面,并且在map程序中输出的文件要求是“mr-X-Y”,注意!!!这里是mr-X-Y,reduce的输出才是mr-out-X,我这个脑子因为当时以为是mr-out-X-Y,导致后面测试不过,很多地方也要重新进行更改文件的读取格式,挺麻烦的。后续给出的test-mr.sh和test-mr-many.sh都是后续使用的测试工具。
Go RPC给出的错误我没有遇到。

1.2 Rules分析

规则其实说的很清晰,这边注意的首先是输出格式,其次在我实验过程中我打开文件,写入文件之后,输出的只有一行原因是打开方式覆盖写入,这边网上找相关的续写就行。

1.3 Hints分析

在这里我会挑出来几个我在实验时遇到的这一模块相关问题做以介绍。
···第2条给出了实验可以从哪里开始,我确实也是从work的Worker()入手的,要考虑worker需要做的工作,要从coordinator获取什么,内容怎么保存,这些都可以在Example函数里面得到。
···第7条给出了用json包的建议,我这里并没有使用这种格式,而是直接输出了要求的格式,在读取时按行按单词读取,每行只有两个内容,用一个进行标记,第一个都读取key,第二个读取value,从而进行插入
···第8条ihash函数,我自己当时不知道在犯什么神经,居然忘了%可以求余从而获得相应的数字,后来是又看了一遍提示才想起来,但是这本来就应该想到的
···第10条RPC服务器是并发的,实现的时候直接一把大锁保平安,啊哈哈,这个也是看到一个帖子上面这样说的,这样可能会影响并发性,但是确实安全,之后有时间可能会尝试一下用chan
···第12条在使用时我是一直让worker进行循环请求工作,请求不到就进行休眠,但是这里有一个问题,我开始是让他休眠10秒进行下次请求,这样间隔时间太长了,我在测试的时候直观影响就是,reduce工作的无法并发,因为10秒对于我们来说可能不太长,但是对于计算机可以做很多工作了,这就导致很多worker在请求不到工作后待机过长,所有的reduce工作都被一个worker给干完了

第13-16条

···第13-16条是一个逻辑的重点,可能处理不当会导致文件的多次读取写入或者没有读写,因此这里用大标题来介绍下自己的实现方法。首先看下Coordinator结构体

type Coordinator struct {
	// Your definitions here.
	mutex        sync.Mutex        //a lock keep data's consistence
	files        []string          //load the work files
	workerNumber int               //keep the number of the worker
	nReduce      int               //remember the nReduce
	fileNumber   int               //remember the number of assign file
	worker       map[int]FileReply //if the worker can be used to work
	mapwork      map[string]bool   //if map work have been assigned and done
	reducework   map[string]bool   //if reduce work have been assigned and done
	mapover      bool              //if map work is over
	emap         []string          //store the error map work
	ereduce      []string          //store the error reduce work
	reduceNumber int               //store the number of reduce work
	legal        []string          //legal map work used by reduce work
}

map工作的唯一性

保证这个工作肯定完成则专门实现checkmap函数进行监控,checkmap会单独线程进行执行,首先休眠10秒,如果10秒之后这个工作还没完成那就将这个工作添加到字段emap中去,在worker请求时会查询emap是否是空的,如果不是空的则执行里面的工作。
在Coordinator中设立了legfal字段用来存储有效的输出的map工作结果文件编号,在每次worker请求工作时,会对worker进行重新编号,从而在输出中间文件时会有相应的文件,在工作完成后,worker会向coordinator发送自己的工作内容完成请求,如果这时候Coordinator结构体里面显示这个工作还没完成,就将他的标号放进legal里面做记录,如果显示这个工作已经完成,则不加入,表示这个worker输出的文件无用,不会在reduce工作过程中使用,这样就可以保证map工作的唯一性。另外第16条的建议也很有用,这里ioutil好像被弃用了,但是有替代的os.CreateTemp来创建临时文件,以防止worker工作到一半被中断导致的错误。

legal        []string          //legal map work used by reduce work

···第19条需要额外注意,RPC中间通信用到的中间变量,都需要开头字母大写,否则在交互过程中无法读取和修改。

2 具体实现

该Lab其实主要分为两个部分:Map过程和Reduce过程,二者会有所关联,但是基本可以分开考虑,Reduce读取Map的过程中按照输出格式读取即可,在这里我作处理时设置了标志位tag,tag为1时为Map过程,为2时为Reduce。在所有的过程中都是worker向coordinator发送请求申请工作,coordinator再进行分配。

2.1 工作请求结构

type FileReply struct {
	Filename string
	Id       int
	NReduce  int
	Ifwrite  bool
	Legal    []string //字母一定要大写要不然无法更改!!!TMD
	Tag      int      //tag == 0 :can be used to work
	// tag==1: doing mapwork
	// tag == 2: doing reducework
	// tag == 3: be killed
}

worker向coordinator进行请求时会返回需要worker处理的文件名Filename、分配的Id,标记位Tag,用来向coordinator询问是否写入的Ifwrite(用以保证reduce结果唯一性,如果有工作完成了,后面的worker就不用将上述的临时文件改名成要求的文件了),传入的NReduce,以及在Reduce过程中会用到的合法的worker编号Legal

2.2 Map过程

2.2.1 worker

worker需要做的工作很少,只需要向coordinator申请工作,根据coordinator反馈回来的标记位以及文件名进行读取文件-处理-输出文件即可

2.2.2 coordinator

协调者需要考虑的地方比较多,这里秉承着一把大锁保平安保证数据一致性。

worker序号分配

不为固定的worker线程设置自始至终的编号,而是根据请求次序进行分配序号,第几次申请工作序号就是多少,序号用来标记worker对应工作的输出

工作文件的分配

首次分配会根据文件个数k进行分配,分配时将tag置为1,并把除legal外所有信息补充完整回复给worker,同时启动检查是否完成的协程,如果在10还没有完成,就把分配的工作放到自己的emap中,当次序为k+1的worker请求工作时,coordinator会查询emap是否为空,如果不为空就将该文件分配给worker,为空就默认不回复,因为Go语言int类型初始为0,所以worker接收到reply不会进行Map和Reduce操作。
对应的代码如下

if len(c.files) > c.fileNumber {
			reply.Filename = c.files[c.fileNumber]
			reply.NReduce = c.nReduce
			reply.Tag = 1
			reply.Id = c.workerNumber
			reply1 := *reply
			c.worker[c.workerNumber] = reply1
			go c.checkmap(c.workerNumber, reply.Filename)
			c.workerNumber++
			c.fileNumber++
		} else if len(c.emap) != 0 {
			reply.Filename = c.emap[0]
			reply.NReduce = c.nReduce
			reply.Tag = 1
			reply.Id = c.workerNumber
			reply1 := *reply
			c.worker[c.workerNumber] = reply1
			go c.checkmap(c.workerNumber, reply.Filename)
			c.workerNumber++
			c.emap = c.emap[1:]
		} else {
		}
		
	func (c *Coordinator) checkmap(id int, file string) {
	time.Sleep(10 * time.Second)
	c.mutex.Lock()
	defer c.mutex.Unlock()
	switch c.worker[id].Tag {
	case 0:
		return
	case 1:
		if c.worker[id].Filename != file {
			return
		}
		c.emap = append(c.emap, c.worker[id].Filename)
	default:
		return
	}
}

2.2.3 工作完成

在worker对应的map工作完成后,会调用coordinator的WorkOver,在这里coordinator会检查该工作是否已经完成,如果没有完成就会把这次的工作对应的mapwork置为true,并且把该worker的ID放入legal中。并且在每一个工作完成后检查整个map工作是否完成,如果完成,那么置mapwork为true,后续的work请求会分配work进入Reduce阶段。对应代码如下,工作完成调用时同样分Map阶段(tag=1),和Reduce阶段(tag=2)。对应的Map阶段

case 1:
		c.mutex.Lock()
		defer c.mutex.Unlock()
		if c.mapwork[send.Filename] != true {
			c.legal = append(c.legal, strconv.Itoa(send.Id))
		}
		c.mapwork[send.Filename] = true
		send.Tag = 0
		send.Filename = ""
		*reply = *send
		c.worker[send.Id] = *reply
		tag := 0
		for _, w := range c.mapwork {
			if w == false {
				tag = 1
				break
			}
		}
		if tag == 0 {
			c.mapover = true
		}
		return nil

2.3 Reduce过程

在上述Map过程处理结束后,Reduce过程相对简单。

2.3.1 worker

worker同样向coordinator请求工作,此阶段tag被置为2,负责传入的Filename结尾的reduce工作,这里通过打开本地文件对文件名进行切割,确定中间部分的map过程的Id是在legal里面的,从而确定每一个要处理的文件并进行reduce操作,reduce操作的代码在示例代码中已经给了。

2.3.2 coordinator

与Map过程类似,首次分配nReduce个worker,并启动检查程序checkreduce,这里检查会直接查询文件是否已经存在,如果存在就确定完成,不存在就将对应工作添加入ereduce中。第nReduce+1个woker进行工作请求就会查询ereduce是否为空,不为空则处理其中的工作。对应代码如下:

else if c.reduceNumber < c.nReduce {
		reply.Id = c.workerNumber
		reply.Tag = 2
		reply.Filename = strconv.Itoa(c.reduceNumber)
		reply.NReduce = c.nReduce
		reply.Legal = c.legal
		reply1 := *reply
		c.worker[c.workerNumber] = reply1
		go c.checkreduce(c.workerNumber, reply.Filename)
		c.reduceNumber++
		c.workerNumber++
	} else if len(c.ereduce) != 0 {
		reply.Id = c.workerNumber
		reply.Tag = 2
		reply.Filename = c.ereduce[0]
		reply.Legal = c.legal
		reply.NReduce = c.nReduce
		reply1 := *reply
		c.worker[c.workerNumber] = reply1
		go c.checkreduce(c.workerNumber, reply.Filename)
		c.ereduce = c.ereduce[1:]
		c.workerNumber++
	} else {

	}
func (c *Coordinator) checkreduce(id int, file string) {
	time.Sleep(10 * time.Second)
	switch c.worker[id].Tag {
	case 2:
		c.mutex.Lock()
		defer c.mutex.Unlock()
		check := "mr-out-" + file
		_, err := os.Stat(check)
		if err == nil {
			return
		}
		c.ereduce = append(c.ereduce, c.worker[id].Filename)
	default:
		return
	}
}

2.3.3 Reduce工作结束

在每一个work工作结束后,会向coordinator发送请求是否将临时文件写入到系统中,coordinator会检查是否已经有对应的文件产生,如果有的话回复不写入,否则写入以保证单次写入。代码如下

case 2:
		c.mutex.Lock()
		defer c.mutex.Unlock()
		check := "mr-out-" + send.Filename
		_, err := os.Stat(check)
		send.Tag = 0
		if err == nil {
			send.Ifwrite = false
			*reply = *send
		} else {
			send.Ifwrite = true
			*reply = *send
		}
		return nil
	default:
		return nil
	}

3 实验结果

下面是单个测试的截图请添加图片描述
在测试的过程中我最多进行了100次测试,因为太慢了,还要等,反正通过100次测试是没有什么问题的。

总结

总体来说要考虑的很多,不经意的失误就会导致程序无法跑通,分布式程序也无法打断点,只能通过printf来进行判断调试,确实累人!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值