MIT6.824 lab1-MapReduce

介绍

环境安装网上都有,而且很简单,就不多说了,我使用的是Linux+vscode,安装golang的教程网上一搜就有。
本文从一个小白的角度来简述思考与设计过程,以供自己回顾和复习,仅写出伪代码,不展示实现代码,方便在以后回顾时能经过思考得出结果——只有经过自己思考过后写出来并通过的代码才能真正让人感受到代码的乐趣!

实验前提

如果有其他语言基础的话,学习golang的基本语法及使用只需要几天时间就可以了,剩下的遇到不会的函数和代码,看看golang的文档就能理解了
强烈建议在开始实验之前阅读MapReduce论文:

https://pdos.csail.mit.edu/6.824/papers/mapreduce.pdf

一定要理解什么是Map、什么是Reducemasterworker分别是做什么的

实验要求

实现分布式MapReduce,由一个master和一个或多个worker组成:

  • 真实环境中将在不同机器上运行,本实验在一台机器上完成
  • 通过RPC远程调用完成masterworker之间的通信
  • worker可以向master请求任务或汇报一个任务结果
  • master负责分配mapreduce任务给worker
  • 如果worker没有在合理时间内完成任务,master应该认为此worker无效了,并将对应的任务交给其他worker来完成

在官方lab1文档中给出了一些规则与提示,在此,不再一一赘述了,接下来,我将给出我在做lab时的思考与设计

一路想到底

MapReduce是一个按顺序完成的多阶段过程:
MapRdeuce流程图

先从简单的开始,在代码中给出了建立RPC通信的例子,我们只需要依葫芦画瓢,在rpc.go文件中也设计两个结构体,先不用思考结构体中该有什么内容:

type RPCArgs struct{
}
type RPCReply struct{
}

worker.go的代码中已经给出了使用call函数进行RPC通信,同样的,继续画瓢,写出workermaster请求的函数:

func acquireTask() *RPCReply{
	初始化两个RPC结构体
	然后调用call函数从而调用到master中的请求任务函数
}

在上面的函数中,我们通过call函数来调用master中的请求方法,因此,接下来,我们需要在master中,写一个workermaster请求的函数:

func (m *Master) RequestTask(args *RPCargs, reply *RPCreply) error {
}

接下来,就要想几个问题:

  • master如何应答worker的请求呢?
  • master如何知道该分配map还是reduce任务或者不分配呢?
  • master中添加什么变量或结构体来完成分配?

思考:

  • 看看本文开头的流程图,分了那么多阶段,表明,我们需要在master中应该添加一个状态标志位,这样master才能知道该如何应答请求。主要的状态有以下四种:
const (
	DoMap int = iota
	DoReduce
	TaskWait
	AllDone
)

标志清晰易懂,应该无需再做多余说明

  • master知道该分配什么任务了,那不同的任务该怎么表示呢?当然需要两个结构体,一个表示Map任务,一个表示Reduce任务:
type MapTask struct {
	Id   int    //当前map任务id
	File string //一个map任务执行一个文件
}

type ReduceTask struct {
	Id    int      //当前reduce任务id
	Files []string //一个reduce任务执行多个文件
}
  • master该如何存储这些任务,以便分配给worker呢?两个任务可以使用两个数组来存放,不过,由于可能有多个worker同时请求,因此,这里采用channel更方便:
QueMap    chan MapTask   
QueReduce chan ReduceTask

刚才也说了可能有race condition,因此,再加一个锁对象,方便后续使用:

mutex sync.Mutex
  • 此时master结构体中有:
type Master struct {
	// Your definitions here.
	NReduce int //reduce任务量,指定的
	NMap    int //map任务量,传入的文件数
	Status  int //当前master的状态 0-DoMap 1-DoReduce 2-TaskWait 3-AllDone

	QueMap    chan MapTask    //存储map任务的队列,可保证线程安全
	QueReduce chan ReduceTask //存储reduce任务的队列,可保证线程安全
	
	mutex sync.Mutex //防止race condition
}

有了以上的结构体,master就基本可以回应worker的请求了。

  • 接下来该思考,要传给worker消息的话,RPCRelpy结构体中应该记录什么内容呢?首先,肯定是当前master的状态,这样,worker才能知道我该干嘛。如果是DoMap则要传给worker一个map任务,如果是DoReduce则是一个reduce任务,因此:
type RPCreply struct {
	Status int

	MapInfo    MapTask    //master给worker分配一个map任务
	ReduceInfo ReduceTask //master给worker分配一个reduce任务
}

有了以上的东西,我们终于可以开始写func (m *Master) RequestTask(args *RPCargs, reply *RPCreply) error函数的主要代码了


好!
我们终于完成了请求函数,现在,我们需要完成汇报函数,即workermaster汇报结果:

func (m *Master) ReportTask(args *RPCargs, reply *RPCreply) error{
}

RPCArgs结构体中应该有哪些内容,才能告诉master结果呢?第一个,当然是状态标志位:当前汇报的worker完成了哪种任务,第二个当然就是任务结果啊!因此:

type RPCargs struct {
	Status int      //worker传递给master的状态只能是DoMap和DoReduce两个状态其中之一
	Files  []string //map任务完成会传递NReduce个中间文件,reduce任务完成会传递一个临时结果文件即Files[0]
}

此时,master该如何接收这些结果呢?我们使用的是string类型的数组:

IntermediateFiles [][]string //每个map任务完成后,worker传递给master NReduce个中间文件
TempResultFiles   []string   //每个reduce任务完成后,worker传递给master一个结果文件
  • 第一个是由map任务生成的中间文件,由于,一个map任务会生成NReduce个中间文件,所以需要一张表才能记录每个map任务的结果
  • 第二个是由reduce任务生成的临时结果文件,在实验文档中提到,为了确保worker在崩溃时没有人观察到部分写入的文件,可以采用临时文件(ioutil.TempFile),并在汇报给master后,由master进行重命名

现在我们可以开始写func (m *Master) ReportTask(args *RPCargs, reply *RPCreply) error函数的代码了


好!好!
我们终于完成了汇报函数。但还有一个问题,从DoMap转到DoReduce时,我们现在的中间文件存在IntermediateFiles这个表中,但与reduce任务需要的输入格式不一样,因此,我们得转化一下:

//将map生成的M×R个中间文件按照对应的reduce编号将map文件表格的每一列放入一个reduce任务中
func (m *Master) MakeReduceTask() {
}

好!好!好!
我们终于完成了masterworker之间的通信。接下来,我们需要在worker.go文件中,完成任务的处理。
func Worker(mapf func(string, string) []KeyValue, reducef func(string, []string) string) 函数中,使用轮询机制向master请求或报告,根据请求函数传回来的内容,去完成对应的任务:

  1. DoMap状态,则需要处理map任务:
//做map任务,返回一组中间文件,以及处理结果状态
func doMapTask(mapf func(string, string) []KeyValue, mtask *RPCreply) ([]string, bool) {
}
  1. DoReduce状态,则需处理reduce任务:
//做reduce任务,返回临时结果文件,以及处理状态
func doReduceTask(reducef func(string, []string) string, rtask *RPCreply) (string, bool) {
}

好!好!好!好!
我们终于完成了worker中对任务的处理。
现在,我们运行sh ./test-mr-sh,前四个测试都可以正确通过了,还差最后一个:

  • worker在执行任务中突然crash或者timeout了怎么办?

lab文档中说了,在本实验中,让master等待10秒,之后,无论worker是因为执行慢了还是崩溃了,master都认为此worker无法在规定时间完成原本的任务,重新将任务分配给其他的worker

  • 那么,我们就要想,master该如何知道worker的状态以及是否超时呢?

我们设计一个结构体来管理worker状态,那么,结构体中应该有什么东西呢?

  • 第一个当然是分配给worker任务时的时间
  • 第二个当然是分配的什么任务,这样,master才能在后续重新分配此任务
type workerState struct {
	Status     int
	StartTime  time.Time
	MapInfo    MapTask
	ReduceInfo ReduceTask
}

设计好管理的结构后,在master中该如何存储每个任务状态呢?可以注意到,每个map任务或reduce任务都有一个Id号,我们可以将Id作为键,任务状态作为值,使用golang中的map类型来记录:

workermap map[int]workerState //记录每个分配了任务的worker状态

准备就绪,接下来我们需要在请求函数以及汇报函数中添加相关代码来完成对worker的状态记录,主要有以下几点要注意:

  • master什么时候记录worker的状态?
    在请求函数中,当master分配任务时要进行记录
  • master什么时候检查worker的状态?
    在请求函数中,masterTaskWait状态时,说明,任务都分配出去了,但是master还未收到结果,因此,需要遍历当前还在进行任务的worker,看是否超时了并进行相应处理
  • master什么时候更新worker状态?
    在汇报函数中,worker完成了任务,不用再记录对应的状态星系了,因此需要删除其对应任务的状态信息

好!好!好!好!好!
恭喜!我们终于完成了lab1的所有实验要求!
all passed

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nobugnolife

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值