LAB1

任务地址
源码地址

实现思路

Lab1主要有两个角色:coordinatorworkerworker 主动的向 coordinator 请求任务。总共两个任务: map 与 reduce,并且 reduce 依赖于 map 的结果。所以我们可以自然的让 coordinator 将整个流程分为两个模块来进行处理:一个是处理 map 任务,一个是处理 reduce 任务。

在有了大致的思路之后,下一步显然是具体的去处理任务。那处理任务又分为两个步骤,分配任务与校验任务是否完成。在这个lab 中,分配任务与校验任务都是被动的,只能依靠 worker 的请求来进行处理。所以对于 map 阶段来说,我们显然可以设计两个 RPC 请求,一个是请求分配 map 任务,一个是告知任务已完成。

有了分配任务和校验的思路之后,下一步就是如何分配与校验。根据任务要求,coordinator 主要校验的是任务是否超时,那显然我们在分配任务的时候要有一个 record 来记录某个任务被分配出去的时间,然后等到 worker 主动报备任务完成时,coordinator 来校验这个时候任务是否已经超时。但显然还有另一种情况存在,那就是 worker 接收到任务之后宕机或者因为某些原因拖延完成时间,没有办法给 coordinator 及时的反馈,那 coordinator 就需要一个主动的机制去校验一个任务何时失败,让一个任务没有反馈或者超时的时候,coordinator 可以自行的控制这个 task 接下来的行为。需要注意一点:既然我们要具体的判断某一个任务,那我们就需要对这个任务进行唯一标识,这样才能更好的存储与判断任务的状态。

说完了 coordinator 的要做的事情,worker 的工作流程就相对简单,不管是正常完成,超时完成,接收到了但是不完成。coordinator 都已经有对应的机制了,那需要做的就是在请求到任务之后去做然后给 coordinator 反馈就可以了。唯一需要注意的就是,worker 是需要转变的,比如现在请求 map 任务,过一会儿请求 reduce 任务,最后如果没有任务可做的话,还需要能够退出进程。那这里我们再结合前面的不同阶段引入一个 period(时期)的概念,为了避免混乱,我们规定时期的轮转由 coordinator 来控制,那我们可以简单的让每一次 rpc请求的 response 都带上 coordinator 的period,让 worker 改变自己的 period,当 period 是退出(全部任务完成)的时候,worker 也跟着退出就好了。

以上就是大致的思路,具体的实现就是遵循着上述的思路进行展开,需要注意的就是并发导致的数据竞争问题,一种粗暴而简单的方法就是在并发访问的地方加上一把大锁,更细致的思路是针对可能并发读写的资源进行保护,再细致就是利用 go 的 channel 以及原子化变量包括锁来实现更加细微的控制,尽可能的减少锁的粒度。

文件内容概述

本 lab 所修改的内容都在 src/mr 中,并且文件中含有详细的注释

.
├── xxx
├── mr
│   ├── constants.go		   // 主要定义一些常量和 format 模板来控制整体的行为
│   ├── coordinator.go	   // coordinator 设计上的主要实现,包括时期迭代,任务过期与再消费等机制
│   ├── coordinator_rpc.go   // 集中于 rpc 的实现,尽可能的减少于 coordinator.go 的耦合
│   ├── rpc.go			   // 定义 rpc 请求的参数和方法 
│   ├── task.go			   // 定义了 task 接口与结构体,方便整个流程的控制 
│   ├── util.go			   // 一些工具函数
│   └── worker.go 		   // worker 的实现位置
└── xxx

启动测试

cd src/main
bash test-mr.sh

如果你想的话,你也可以进行多次测试

bash test-mr-many.sh 5