资料

课程相关,建议看掉 Lecture 1 和 Lecture2,也就是 B 站课程的 P1 和 P2。了解一下 MapReduce 的基本概念,以及 Golang 并发编程和 RPC 的演示。

在 Lecture6,也就是 B 站课程的 P6 中,有这次 Lab 的相关 Q&A,可以小小的借鉴一下。

按理来说不应该寻找解析博客,但既然搜到本篇博客了,这里就推荐一篇解析详细的博客:MIT 6.824 Lab 1: MapReduce 实验
本篇博客基于上述博客,内容更精简,包含了自己一些的踩坑历程。

任务: task.go

CO (coordinator) 和 C (Client) 都需要使用到Task,有必要定义相同的Task数据结构。

二者RPC通信使用Task,为了减少流量,提高吞吐量(throughput)应当减少Task类的大小,仅仅应当包含二者都必须的内容,不应该包含仅仅需要单方维护的信息。Task是系统框架分配资源的最小单元。

Task包含的字段包括:

  • TaskType
  • TaskId: 本文的设计是不同Type的Task的Id可以是相同的
  • ReduceTaskNum
  • File 每个Map任务对应一个文件,每个Reduce可能对应多个文件。

这里本文讲Task类放在一个新的文件中,也可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type TaskType int

const (
MapTask TaskType = iota
ReduceTask
ExitTask
WaitTask
)

type Task struct {
Type TaskType
TaskId int
ReducerTaskNum int
Files []string
}

func NewTask(taskType TaskType, taskId int, nReduce int, files []string) *Task {
return &Task{
Type: taskType,
TaskId: taskId,
ReducerTaskNum: nReduce,
Files: files,
}
}


  • 所有Map完成后再开始Reduce任务。
  • 创建channel时需要指定缓冲区大小,否则当写入第一个数据后在写入第二个数据时就会阻塞。
  • 另外由于文件系统的特性,当我们使用 os.Rename 覆盖一个文件时,如果被覆盖的文件被打开正在读写,那读写操作不会受影响,只有 inode 和文件名之间发生了 unlink,inode 在文件关闭后才会回收,因此不用担心某个 map 任务运行由于过于缓慢被认定失败但是在 reduce 运行到某个阶段再次覆盖了中间文件。
  • const 内的 iota是golang语言的常量计数器,只能在常量的表达式中使用,,即const内。
    iota在const关键字出现时将被重置为0(const内部的第一行之前),const中每新增一行常量声明将使iota计数一次。
  • 当刚被创建出来的时候,这个任务是处于在等待状态的,由于还没有开始运行,因此时间戳也暂时无需设置,直到这个任务从队列中被取出我们才需要进行设置。
  • type ByKey []KeyValue 必须要这样写吗