lab 地址 : https://pdos.csail.mit.edu/6.824/labs/lab-mr.html

1. 介绍

准备工作

阅读 MapReduce

做什么

实现一个分布式的 Map - Reduce 结构,在原先的代码结构中 6.824/src/main/mrsequential.go 实现了单机版的 Map - Reduce ,我们需要将其改造为多进程版本的 Map - Reduce 。一个经典的 Map - Reduce 结构如下

2. 思路

该 lab 中,主要的进程分为 WorkerCoordinatorCoordinator 主要负责分发任务,Worker 负责执行任务。

Coordinator

Coordinator 负责管理协调任务,本身具有状态(Map phase 和 Reduce phase),根据不同的 phase 分发不同的任务。
其结构设计如下:

  1. type Coordinator struct {
  2. // Your definitions here.
  3. phase CoordinatorPhase // 当前处于哪个阶段 (Map or Reduce)
  4. MRTasks []MRTask // 记录当前阶段所有任务
  5. lock sync.Mutex
  6. RunningTasks chan MRTask // channel, 用来做任务队列
  7. nReduce int // reduce 任务的数量
  8. nMap int // map 任务的数量
  9. }

Coordinator 默认执行流程如下,只是一个简单的 For Loop,worker 通过 rpc 来请求获取任务:

Coordinator 需要提供两个 RPC 调用,RequestTaskRequestTaskDone ,分别用来处理请求任务和提交任务,下图为 RequestTask 的处理流程

RequestTaskDone 主要在 Worker 执行完 Task 后向 Coordinator 汇报其工作完成了,由 Coordinator 进行最后的确认,将中间文件 Rename 为目标结果文件。

参考了 Google MapReduce 的做法,Worker 在写出数据时可以先写出到临时文件,最终确认没有问题后再将其重命名为正式结果文件,区分开了 Write 和 Commit 的过程。Commit 的过程可以是 Coordinator 来执行,也可以是 Worker 来执行:

  • Coordinator Commit:Worker 向 Coordinator 汇报 Task 完成,Coordinator 确认该 Task 是否仍属于该 Worker,是则进行结果文件 Commit,否则直接忽略
  • Worker Commit:Worker 向 Coordinator 汇报 Task 完成,Coordinator 确认该 Task 是否仍属于该 Worker 并响应 Worker,是则 Worker 进行结果文件 Commit,再向 Coordinator 汇报 Commit 完成

这里两种方案都是可行的,各有利弊。

Worker

Worker 的逻辑比较简单,主要根据 RPC 返回的任务类型,进行 Map/Reduce 任务,并将中间结果输出到文件中,再通过 RPC 向 Coordinator 通知任务完成。
Worker 本身无限循环,一直请求 Map/Reduce 任务,其退出的条件是请求任务时,收到的消息中 phase 已经切换为结束。
两种 RPC 的结构如下:

  1. // 通知任务完成
  2. type NotifyArgs struct {
  3. TaskID int
  4. TaskType CoordinatorPhase
  5. WorkerID int
  6. }
  7. type NotifyReplyArgs struct {
  8. Confirm bool
  9. }
  10. // 请求任务
  11. type RequestArgs struct {
  12. }
  13. type ReplyArgs struct {
  14. FileName string // map task
  15. TaskID int
  16. TaskType CoordinatorPhase
  17. ReduceNum int
  18. MapNum int
  19. }

3. 实现

Coordinator 初始化

  1. //
  2. // create a Coordinator.
  3. // main/mrcoordinator.go calls this function.
  4. // nReduce is the number of reduce tasks to use.
  5. //
  6. func MakeCoordinator(files []string, nReduce int) *Coordinator {
  7. c := Coordinator{}
  8. c.nReduce = nReduce
  9. // Your code here.
  10. c.phase = PHASE_MAP
  11. c.RunningTasks = make(chan MRTask, len(files)+1)
  12. c.nMap = len(files)
  13. fmt.Printf("start make coordinator ... file count=%d\n", len(files))
  14. for index, fileName := range files {
  15. task := MRTask{
  16. fileName: fileName, // task file
  17. taskID: index, // task id
  18. status: INIT,
  19. taskType: PHASE_MAP,
  20. }
  21. c.MRTasks = append(c.MRTasks, task)
  22. fmt.Printf("[PHASE_MAP]Add Task %v %v\n", fileName, index)
  23. c.RunningTasks <- task
  24. }
  25. c.server()
  26. return &c
  27. }

Coordinator 任务超时机制

lab 中要求任务有一定超时时间,当 worker 超过 10s 没有上报任务成功,则将任务重新放回 RunningTasks 队列

  1. // 任务超时检查
  2. func (c *Coordinator) CheckTimeoutTask() bool {
  3. /*
  4. 1. 如果没有超时,则直接 return,等待任务完成 or 超时
  5. 2. 有超时,则直接分配该任务给 worker
  6. */
  7. TaskTimeout := false
  8. now := time.Now().Unix()
  9. for _, task := range c.MRTasks {
  10. if (now-task.startTime) > 10 && task.status != DONE {
  11. fmt.Printf("now=%d,task.startTime=%d\n", now, task.startTime)
  12. c.RunningTasks <- task
  13. TaskTimeout = true
  14. }
  15. }
  16. return TaskTimeout
  17. }

Coordnator 处理请求任务 RPC

由于设计了只要存在 worker,就会一直请求任务,因此将超时检查放在申请任务的前置检查中。

  1. // Your code here -- RPC handlers for the worker to call.
  2. // worker 申请 task
  3. func (c *Coordinator) RequestTask(args *RequestArgs, reply *ReplyArgs) error {
  4. if len(c.RunningTasks) == 0 {
  5. fmt.Printf("not running task ...\n")
  6. // 先检查是否所有任务都已完成
  7. if c.AllTaskDone() {
  8. fmt.Printf("All Task Done ... \n")
  9. c.TransitPhase() // 任务结束,则切换状态
  10. } else if !c.CheckTimeoutTask() { // 检查是否有任务超时
  11. // 没有任务超时,则返回当前状态, 让 worker 等待所有任务完成
  12. fmt.Printf("waiting task finish ... \n")
  13. reply.TaskType = PHASE_WAITTING
  14. return nil
  15. }
  16. }
  17. if c.phase == PHASE_FINISH {
  18. fmt.Printf("all mr task finish ... close coordinator\n")
  19. reply.TaskType = PHASE_FINISH
  20. return nil
  21. }
  22. task, ok := <-c.RunningTasks
  23. if !ok {
  24. fmt.Printf("task queue empty ...\n")
  25. return nil
  26. }
  27. c.lock.Lock()
  28. defer c.lock.Unlock()
  29. c.setupTaskById(task.taskID)
  30. reply.FileName = task.fileName
  31. reply.TaskID = task.taskID
  32. reply.TaskType = c.phase
  33. reply.ReduceNum = c.nReduce
  34. reply.MapNum = c.nMap
  35. return nil
  36. }

Coordnator 处理阶段流转

阶段流转只存在两种情况:

  • map 阶段切换到 reduce 阶段
  • reduce 阶段切换到结束

主要关注第一种情况,当 map 阶段切换到 reduce 阶段时,清空记录的任务列表 Coordinator.MRTask ,resize RunningTasks channel,因为 reduce 任务数量可能比 map 任务数量要多,需要重新 resize,否则 channel 可能会阻塞。

  1. // 阶段流转
  2. func (c *Coordinator) TransitPhase() {
  3. // 生成对应阶段 task
  4. c.lock.Lock()
  5. newPhase := c.phase
  6. switch c.phase {
  7. case PHASE_MAP:
  8. fmt.Printf("TransitPhase: PHASE_MAP -> PHASE_REDUCE\n")
  9. newPhase = PHASE_REDUCE
  10. c.MRTasks = []MRTask{} // 清空 map task
  11. c.RunningTasks = make(chan MRTask, c.nReduce+1) // resize
  12. for i := 0; i < c.nReduce; i++ {
  13. task := MRTask{
  14. taskID: i, // task id
  15. status: INIT,
  16. taskType: PHASE_REDUCE,
  17. }
  18. c.MRTasks = append(c.MRTasks, task)
  19. fmt.Printf("[PHASE_REDUCE]Add Task %v\n", task)
  20. c.RunningTasks <- task
  21. }
  22. case PHASE_REDUCE:
  23. fmt.Printf("TransitPhase: PHASE_REDUCE -> PHASE_FINISH\n")
  24. newPhase = PHASE_FINISH
  25. }
  26. c.phase = newPhase
  27. c.lock.Unlock()
  28. }

Coordnator 处理提交任务 RPC

主要根据当前阶段,对任务的中间输出结果进行确认(即把 tmp file rename 为 final file)

  1. func (c *Coordinator) CommitTask(args *NotifyArgs) {
  2. switch c.phase {
  3. case PHASE_MAP:
  4. fmt.Printf("[PHASE_MAP] Commit Task %v\n", args)
  5. for i := 0; i < c.nReduce; i++ {
  6. err := os.Rename(tmpMapOutFile(args.WorkerID, args.TaskID, i),
  7. finalMapOutFile(args.TaskID, i))
  8. if err != nil {
  9. fmt.Printf("os.Rename failed ... err=%v\n", err)
  10. return
  11. }
  12. }
  13. case PHASE_REDUCE:
  14. fmt.Printf("[PHASE_REDUCE] Commit Task %v\n", args)
  15. err := os.Rename(tmpReduceOutFile(args.WorkerID, args.TaskID),
  16. finalReduceOutFile(args.TaskID))
  17. if err != nil {
  18. fmt.Printf("os.Rename failed ... err=%v\n", err)
  19. return
  20. }
  21. }
  22. }
  23. func (c *Coordinator) RequestTaskDone(args *NotifyArgs, reply *NotifyReplyArgs) error {
  24. for idx := range c.MRTasks {
  25. task := &c.MRTasks[idx]
  26. if task.taskID == args.TaskID {
  27. task.status = DONE
  28. c.CommitTask(args)
  29. break
  30. }
  31. }
  32. return nil
  33. }

Worker 初始化

根据请求的任务类型(MAP,REDUCE,FINISH,WAITING),做不同处理

  • MAP :执行 Map 任务
  • REDUCE : 执行 Reduce 任务
  • WAITTING :等待,这种情况意味 Coordinator 没有空闲任务,也没有完成所有任务,有任务还在运行当中
  • FINISH :表示所有任务已经完成,可以退出 Worker
  1. //
  2. // main/mrworker.go calls this function.
  3. //
  4. func Worker(mapf func(string, string) []KeyValue,
  5. reducef func(string, []string) string) {
  6. // Your worker implementation here.
  7. for {
  8. args := RequestArgs{}
  9. reply := ReplyArgs{}
  10. ok := call("Coordinator.RequestTask", &args, &reply)
  11. if !ok {
  12. fmt.Printf("call request task failed ...\n")
  13. return
  14. }
  15. fmt.Printf("call finish ... file name %v\n", reply)
  16. switch reply.TaskType {
  17. case PHASE_MAP:
  18. DoMapTask(reply, mapf)
  19. case PHASE_REDUCE:
  20. DoReduceTask(reply, reducef)
  21. case PHASE_WAITTING: // 当前 coordinator 任务已经分配完了,worker 等待一会再试
  22. time.Sleep(5 * time.Second)
  23. case PHASE_FINISH:
  24. fmt.Printf("coordinator all task finish ... close worker")
  25. return
  26. }
  27. }
  28. }

Worker 处理 Map

参考 6.824/src/main/mrsequential.go


  1. func DoMapTask(Task ReplyArgs, mapf func(string, string) []KeyValue) bool {
  2. fmt.Printf("starting do map task ...\n")
  3. file, err := os.Open(Task.FileName)
  4. if err != nil {
  5. fmt.Printf("Open File Failed %s\n", Task.FileName)
  6. return false
  7. }
  8. content, err := ioutil.ReadAll(file)
  9. if err != nil {
  10. fmt.Printf("ReadAll file Failed %s\n", Task.FileName)
  11. return false
  12. }
  13. file.Close()
  14. fmt.Printf("starting map %s \n", Task.FileName)
  15. kva := mapf(Task.FileName, string(content))
  16. hashedKva := make(map[int][]KeyValue)
  17. for _, kv := range kva {
  18. hashed := ihash(kv.Key) % Task.ReduceNum
  19. hashedKva[hashed] = append(hashedKva[hashed], kv)
  20. }
  21. for i := 0; i < Task.ReduceNum; i++ {
  22. outFile, _ := os.Create(tmpMapOutFile(os.Getpid(), Task.TaskID, i))
  23. for _, kv := range hashedKva[i] {
  24. fmt.Fprintf(outFile, "%v\t%v\n", kv.Key, kv.Value)
  25. }
  26. outFile.Close()
  27. }
  28. NotifiyTaskDone(Task.TaskID, Task.TaskType)
  29. return true
  30. }

Worker 处理 Reduce

参考 6.824/src/main/mrsequential.go


  1. func DoReduceTask(Task ReplyArgs, reducef func(string, []string) string) bool {
  2. /*
  3. 1. 先获取所有 tmp-{mapid}-{reduceid} 中 reduce id 相同的 task
  4. */
  5. fmt.Printf("starting do reduce task ...\n")
  6. var lines []string
  7. for i := 0; i < Task.MapNum; i++ {
  8. filename := finalMapOutFile(i, Task.TaskID)
  9. file, err := os.Open(filename)
  10. if err != nil {
  11. log.Fatalf("cannot open %v", filename)
  12. }
  13. content, err := ioutil.ReadAll(file)
  14. if err != nil {
  15. log.Fatalf("cannot read %v", filename)
  16. }
  17. /*
  18. 2. 将所有文件的内容读取出来,合并到一个数组中
  19. */
  20. lines = append(lines, strings.Split(string(content), "\n")...)
  21. }
  22. /*
  23. 3. 过滤数据,将每行字符串转成 KeyValue, 归并到数组
  24. */
  25. var kva []KeyValue
  26. for _, line := range lines {
  27. if strings.TrimSpace(line) == "" {
  28. continue
  29. }
  30. split := strings.Split(line, "\t")
  31. kva = append(kva, KeyValue{
  32. Key: split[0],
  33. Value: split[1],
  34. })
  35. }
  36. /*
  37. 4. 模仿 mrsequential.go 的 reduce 操作,将结果写入到文件,并 commit
  38. */
  39. sort.Sort(ByKey(kva))
  40. outFile, _ := os.Create(tmpReduceOutFile(os.Getpid(), Task.TaskID))
  41. i := 0
  42. for i < len(kva) {
  43. j := i + 1
  44. for j < len(kva) && kva[j].Key == kva[i].Key {
  45. j++
  46. }
  47. var values []string
  48. for k := i; k < j; k++ {
  49. values = append(values, kva[k].Value)
  50. }
  51. output := reducef(kva[i].Key, values)
  52. fmt.Fprintf(outFile, "%v %v\n", kva[i].Key, output)
  53. i = j
  54. }
  55. outFile.Close()
  56. NotifiyTaskDone(Task.TaskID, Task.TaskType)
  57. return true
  58. }

Worker 通知任务完成


  1. func NotifiyTaskDone(taskId int, taskType CoordinatorPhase) {
  2. args := NotifyArgs{}
  3. reply := NotifyReplyArgs{}
  4. args.TaskID = taskId
  5. args.TaskType = taskType
  6. args.WorkerID = os.Getpid()
  7. ok := call("Coordinator.RequestTaskDone", &args, &reply)
  8. if !ok {
  9. fmt.Printf("Call Coordinator.RequestTaskDone failed ...")
  10. return
  11. }
  12. if reply.Confirm {
  13. fmt.Printf("Task %d Success, Continue Next Task ...", taskId)
  14. }
  15. }

4. 各种异常情况

  • worker crash 处理

coordinator 有超时机制,只有 worker 完成并且成功 commit ,才会标记任务结束,因此 crash 之后,当前处理中的任务最后会重新返回到 task channel 中

  • rpc call 需要参数名首字符大写,否则可能无法正确传输

2022-6.824-Lab1:Map&Reduce的更多相关文章

  1. MIT 6.824 lab1:mapreduce

    这是 MIT 6.824 课程 lab1 的学习总结,记录我在学习过程中的收获和踩的坑. 我的实验环境是 windows 10,所以对lab的code 做了一些环境上的修改,如果你仅仅对code 感兴 ...

  2. MapReduce剖析笔记之三:Job的Map/Reduce Task初始化

    上一节分析了Job由JobClient提交到JobTracker的流程,利用RPC机制,JobTracker接收到Job ID和Job所在HDFS的目录,够早了JobInProgress对象,丢入队列 ...

  3. python--函数式编程 (高阶函数(map , reduce ,filter,sorted),匿名函数(lambda))

    1.1函数式编程 面向过程编程:我们通过把大段代码拆成函数,通过一层一层的函数,可以把复杂的任务分解成简单的任务,这种一步一步的分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计的基本单元. ...

  4. 记一次MongoDB Map&Reduce入门操作

    需求说明 用Map&Reduce计算几个班级中,每个班级10岁和20岁之间学生的数量: 需求分析 学生表的字段: db.students.insert({classid:1, age:14, ...

  5. filter,map,reduce,lambda(python3)

    1.filter filter(function,sequence) 对sequence中的item依次执行function(item),将执行的结果为True(符合函数判断)的item组成一个lis ...

  6. map reduce

    作者:Coldwings链接:https://www.zhihu.com/question/29936822/answer/48586327来源:知乎著作权归作者所有,转载请联系作者获得授权. 简单的 ...

  7. python基础——map/reduce

    python基础——map/reduce Python内建了map()和reduce()函数. 如果你读过Google的那篇大名鼎鼎的论文“MapReduce: Simplified Data Pro ...

  8. Map/Reduce 工作机制分析 --- 作业的执行流程

    前言 从运行我们的 Map/Reduce 程序,到结果的提交,Hadoop 平台其实做了很多事情. 那么 Hadoop 平台到底做了什么事情,让 Map/Reduce 程序可以如此 "轻易& ...

  9. Map/Reduce个人实战--生成数据测试集

    背景: 在大数据领域, 由于各方面的原因. 有时需要自己来生成测试数据集, 由于测试数据集较大, 因此采用Map/Reduce的方式去生成. 在这小编(mumuxinfei)结合自身的一些实战经历, ...

  10. 用通俗易懂的大白话讲解Map/Reduce原理

    Hadoop简介 Hadoop就是一个实现了Google云计算系统的开源系统,包括并行计算模型Map/Reduce,分布式文件系统HDFS,以及分布式数据库Hbase,同时Hadoop的相关项目也很丰 ...

随机推荐

  1. 第六章:Django 综合篇

    前面五章,已经将Django最主要的五大系统介绍完毕,除了这些主要章节,还有很多比较重要的内容,比如开发流程相关.安全.本地化与国际化.常见工具和一些框架核心功能.这些内容的篇幅都不大,但整合起来也是 ...

  2. Elasticsearch:foreach 摄入处理器介绍---处理未知长度数组中的元素

    转载自:https://blog.csdn.net/UbuntuTouch/article/details/108621206 foreach processor 用于处理未知长度数组中的元素.这个有 ...

  3. Bootstrap5 如何创建多媒体对象

    一.在Bootstra5中使用媒体对象 Bootstrap 媒体对象在版本 5 中已经停止支持了.但是,我们仍然可以使用 flex 和 margin 创建包含左对齐或右对齐媒体对象(如图像或视频)以及 ...

  4. n维偏序 方法记录

    题解 首先我们要对一个地点能否到达建立认知:一个地点能到达不仅仅是能从它的上一个点或上上个点跳到,而是能从第一个点开始跳一路跳到.就好比说,咱吃了6个包子吃饱了,但咱不能只付第6个包子的钱. 方法一: ...

  5. Vue学习之--------组件嵌套以及VueComponent的讲解(代码实现)(2022/7/23)

    欢迎加入刚建立的社区:http://t.csdn.cn/Q52km 加入社区的好处: 1.专栏更加明确.便于学习 2.覆盖的知识点更多.便于发散学习 3.大家共同学习进步 3.不定时的发现金红包(不多 ...

  6. 6.-Django设计模式及模版层

    一.MVC (java等其他语言) MVC代表Model-view-Contorller(模型-视图-控制器)模式 M模型层主要用于对数据库层的封装 V视图层用于向用户展示结果 C控制器用于处理请求. ...

  7. k8s运维之pod排错

    k8s运维之pod排错 K8S是一个开源的,用于管理云平台中多个主机上的容器化应用,Kubernetes的目标是让部署容器化变得简单并且高效 K8S的核心优势: 1,基于yaml文件实现容器的自动创建 ...

  8. Python基础之模块:1、模块的导入和使用

    目录 一.模块 1.简介 2.模块的表现形式 二.模块的分类 1.自定义模块 2.内置模块 3.第三方模块 三.导入模块的句式 学前须知: 1.import句式 2.from...import...句 ...

  9. 云原生之旅 - 9)云原生时代网关的后起之秀Envoy Proxy 和基于Envoy 的 Emissary Ingress

    前言 前一篇文章讲述了基于Nginx代理的Kuberenetes Ingress Nginx[云原生时代的网关 Ingress Nginx]这次给大家介绍下基于Envoy的 Emissary Ingr ...

  10. 带你了解S12直播中的“黑科技”

    摘要:让精彩更流畅.让较量更清晰.让参与更沉浸.让体验更有趣,幕后的舞台,从来都是技术的战场,S12背后的名场面同样场场高能. 本文分享自华为云社区<用硬核方式打开S12名场面>,作者:华 ...