MapReduce实现分析思路方法一

一、整体思路

这种方法几乎同步的地方都是使用channel来控制的(channel 的整个发送过程和接收过程都使用 runtime.mutex 进行加锁。runtime.mutex 是 runtime 相关源码中常用到的一个轻量级锁。整个过程并不是最高效的 lockfree 的做法。)。
Coordinator是master,负责任务的分配,开始创建Coordinator时,新建两个channel,代表map和reduce的任务队列,worker通过RPC任务从channel中取任务。另外用两个Fin channel,代表已完成的任务计数,通过统计Fin channel长度,来修改Coordinator状态,决定是发送map任务还是reduce任务。
本方法7个测试用例通过了5个,失败分析在另一篇文章中。

(注:channel是一种进程内的通信方式,不支持跨进程通信,所以我们下面的coordinator和worker之间访问channel都是通过RPC调用)

二、具体实现

1. Coordinator的实现

构造Coordinator和task结构体
在这里插入图片描述
在这里插入图片描述

初始化Coordinator的同时,初始化四个channel
在这里插入图片描述
在Coordinator中实现给worker分发task的RPC方法GetTask
在这里插入图片描述
和worker发送完成任务信号的RPC方法TaskFin,属性State表示Coordinator的状态,根据任务的完成情况修改State

在这里插入图片描述

2. worker的实现

worker的主要执行逻辑在下面方法中:通过获取任务返回的State判断当前任务是map还是reduce,分别进行处理
map任务:读取输入文件,根据插件中提供的mapf方法,解析成对应内容,根据哈希值分桶,每一个map任务(如id为i)生成nReduce个中间文件mr-i-[0~nReduce-1],一共numMap个任务(numMap=输入文件个数),共生成numMap*nReduce个中间文件。
reduce任务:每个reduce任务读取自己任务id(如j)对应的中间文件,读取mr-[0~numMap-1]-j共numMap个。根据插件中的reducef方法,执行后生成输出文件mr-out-j,最后将输出文件整合到同一个文件mr-wc-all中,与mr-correct-wc.txt对比,一样则通过。
(注:以上是根据第一个测试用例wc.so分析的)

func Worker(mapf func(string, string) []KeyValue,
   reducef func(string, []string) string) {

   // Your worker implementation here.
   // 每一个worker循环执行以下逻辑
   for {
      // 先取任务
      args := TaskRequest{}
      reply := TaskResponse{}
      CallGetTask(&args, &reply)
      state := reply.State
      //根据返回任务的不同状态,分别处理
      if state == 0 {
         id := strconv.Itoa(reply.XTask.IdMap)
         fileName := reply.XTask.FileName
         file, err := os.Open(fileName)
         if err != nil {
            log.Fatalf("cannot open mapTask %s", fileName)
         }
         content, err := ioutil.ReadAll(file)
         if err != nil {
            log.Fatalf("cannot read %s", fileName)
         }
         file.Close()
         // 解析mrapp/wc.go中的map方法,将content字符串切分成key/value键值对数组,key为拆分出的每个关键字,value为1(这里fileName没什么用)
         kva := mapf(fileName, string(content)) // 接下来要把kva写到中间文件中去
         // 生成与reduce任务个数相同的中间文件
         numReduce := reply.NumReduceTask
         bucket := make([][]KeyValue, numReduce)
         //接下来是论文Figure1的local write阶段, 即将读入的文件内容,按照hash值分类保存到bucket中
         for _, kv := range kva {
            num := ihash(kv.Key) % numReduce
            bucket[num] = append(bucket[num], kv) // key的hash值为num的kv,分到了bucket[num]
         }
         //分完桶后根据reduce的个数写入临时文件tmpFile
         for i := 0; i < numReduce; i++ {
            tmpFile, error := ioutil.TempFile("", "mr-map-*")
            if error != nil {
               fmt.Printf("error is : %+v\n", error)
               log.Fatal("cannot open map tmpFile")
            }
            //参考课程提示,用json向tmpFile中写bucket
            //enc为 *json.Encoder格式,代表以json格式编码往tmpFile文件中写
            enc := json.NewEncoder(tmpFile)
            // 把bucket[i]的内容传递给enc
            err := enc.Encode(bucket[i])
            if err != nil {
               log.Fatal("encod bucket error")
            }
            tmpFile.Close()
            //根据课程提示,把中间文件rename
            outFileName := `mr-` + id + `-` + strconv.Itoa(i)
            os.Rename(tmpFile.Name(), outFileName)
         }
         //map任务完成后向MapTaskFin中发送一个true
         CallTaskFin()
      } else if state == 1 {
         //否则就是reduce
         //每一个reduce的任务取numMap个中间文件,取对应的最后一个数字为自己task id的中间文件,因为中间文件的名字最后一个数字是取哈希值得到的
         numMap := reply.NumMapTask
         id := strconv.Itoa(reply.XTask.IdReduce)
         //说明所有的map任务完成,开始reduce
         //kva保存从中间文件中读出的key value对
         intermediate := []KeyValue{}
         //reduce开始读中间文件
         for i := 0; i < numMap; i++ {
            mapFileName := "mr-" + strconv.Itoa(i) + "-" + id
            // inputFile为 *os.File格式,读中间文件
            inputFile, err := os.OpenFile(mapFileName, os.O_RDONLY, 0777)
            if err != nil {
               log.Fatalf("cannot open reduceTask %s\n", mapFileName)
            }
            // 将inputFile按照json格式解析
            dec := json.NewDecoder(inputFile)
            for {
               var kv []KeyValue
               if err := dec.Decode(&kv); err != nil {
                  break
               }
               //以上把中间文件中读出的键值对保存到了intermediate中
               intermediate = append(intermediate, kv...)
            }
            //将intermediate强转为ByKey类型,排序
            sort.Sort(ByKey(intermediate))

            //准备整合,去重

            //创建一个tmpFile,存放reduce输出结果
            outFileName := "mr-out-" + id
            tmpFile, err := ioutil.TempFile("", "mr-reduce-*")
            if err != nil {
               log.Fatalf("cannot open reduce tmpFile")
            }

            i := 0
            for i < len(intermediate) {
               j := i + 1
               for j < len(intermediate) && intermediate[j].Key == intermediate[i].Key {
                  j++
               }
               //values累积了所有key相同的kv对的value值,单词例子中为很多个1
               values := []string{}
               for k := i; k < j; k++ {
                  values = append(values, intermediate[k].Value)
               }
               // reducef为插件解析的mrapp/wc.go中的reduce方法,返回values的数组长度,即values有多少个1(这里intermediate[i].Key没有用)
               output := reducef(intermediate[i].Key, values)

               //将每个key和对应的有多少个数按格式拼好,写入到tmpFile
               fmt.Fprintf(tmpFile, "%v %v\n", intermediate[i].Key, output)
               //再遍历下一批key相等的键值对
               i = j
            }
            tmpFile.Close()
            //改名字
            os.Rename(tmpFile.Name(), outFileName)
         }
         CallTaskFin()
      } else {
         // state == 2的情况
         break
      }
   }

}

3.RPC的实现

同时还要实现worker访问Coordinator的RPC方法,有两个,分别用来获取task和向Fintask channel中发送完成信号。

//参考下面的CallExample方法实现获取任务的方法
func CallGetTask(args *TaskRequest, reply *TaskResponse) {
   ok := call("Coordinator.GetTask", &args, &reply)
   if ok {
      fmt.Printf("CallGetTask method - reply.FileName %s\n", reply.XTask.FileName)
   } else {
      fmt.Printf("CallGetTask method failed!\n")
   }
}

func CallTaskFin() {
   // 这两个参数没有用,但是call方法必须要给参数
   args := ExampleArgs{}
   reply := ExampleReply{}
   ok := call("Coordinator.TaskFin", &args, &reply)
   if ok {
      fmt.Printf("CallTaskFin method ok\n")
   } else {
      fmt.Printf("CallTaskFin method failed\n")
   }
}

RPC就是将worker请求任务需要的内容封装在response结构体中返回即可

type TaskRequest struct {
}

type TaskResponse struct {
   XTask         Task
   NumMapTask    int
   NumReduceTask int
   State         int // 0 map 1 reduce 2 finish
}

这里的State对应的是Coordinator中的State,表示当前的阶段。

三、结果

运行结果是7个测试案例可以通过5个,reduce parallelism test失败, crush test失败。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

分析失败原因见另一博客lab1(1)问题调试
(参考资料:go中channel的实现https://www.cyhone.com/articles/analysis-of-golang-channel/)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值