最近终于抽出时间开始学习MIT 6.824,本文为我看MapReduce论文和做lab后的总结。

[MapReduce英文论文]

lab要用到go语言,这也是我第一次接触。可以参考go语言圣经学习基本语法。

[Go语言圣经]

MapReduce 简介

MapReduce描述了一种编程模型,由处理数据的map函数生成中间键值对(Key/Value),再由Reduce函数处理中间键值对生成输出文件。根据用户自定义的map和reduce函数不同,可以实现不同的功能。下面我简单总结了个人觉得比较关键的部分。

执行概述

在MapReduce中存在两种程序:master (也即lab中的coordinator) 和worker。master负责分配任务和接受反馈,更新任务列表。worker负责完成map和reduce任务。

在程序运行前,mapreduce库会将输入的数据切分成M个片段,即M个map任务,然后启动master和worker。master会对每个空闲的worker分配map或者reduce任务。

被分配了map任务的worker读取相应任务的数据,解析出键值对,生成中间键值对存入本地磁盘。这些键值对根据key的不同,被分区函数划分到R个区域内。worker将这些数据的位置传回master,master会将这些位置转发给执行reduce操作的worker。

执行reduce任务的worker在接受任务后,使用RPC的方式读取数据,并根据key进行排序,然后调用reduce函数生成R个输出文件。

容错

由于数百上千台机器同时运行,发生网络故障/设备中断是常有的事情,因此需要应对故障的方案。

worker故障

master会周期性的ping下每个worker,如果在一定时间内收不到来自某个worker的响应,master就会将该worker标记为failed,该worker正在执行的任务会被重置为【待执行】。master会将这些任务交给其他worker重新执行。

对于已经执行完的任务。如果是已完成的map任务,由于中间数据储存在发生故障的worker磁盘中,无法读取,因此需要重新执行该任务。如果是reduce任务则无须再执行,因为完成时输出文件已经储存在全局系统中。

master故障

一种解决方案是,将master上的数据周期性地写入磁盘,发生故障后从最新的checkpoint创建出一个新的备份,重启master进程。但往往需要人工干预。

Master的数据结构

在Master中包含了一些数据结构。它保存了每个Map任务和每个Reduce任务的状态(闲置,正在运行,以及完成),以及非空闲任务的worker机器的ID。

备用任务

在MapReduce计算中,一台机器花费了异常长的时间去完成最后几个Map或者Reduce任务会导致执行总时间延长很多。因此当一个MapReduce任务接近完成时,master可以调度一个备用(backup)任务来执行正在执行的任务。无论是主任务还是备用任务完成,都视为整个计算完成。可以显著减少大型计算花费的时间。

Lab1 总结

虽然看论文的时候感觉自己对MapReduce的执行过程了解的比较透彻,但是在实际实现全过程的时候才发现有很多地方没有注意到。果然是实践出真知。

在过程中遇到的一个比较大的坑是我对go的struct不够了解。go中struct用变量名的首字母大小写来区分public和private(可导出和不可导出),习惯了驼峰命名法的我并没有注意到。因此在测试的时候才需要全盘修改,花费了一些精力。

在lab中我们要实现的是一个在本地机器执行的mapreduce任务。和论文中介绍的不同,这个mapreduce没有实现对worker的周期检测,也不需要储存每个worker的状态,而是当worker在一段时间(lab中为10s)内没有完成任务时,直接将该worker视为故障,重新分配任务。并且任务时由worker主动申请再由master进行分配。这对于小任务是可行的,但对于无法预测时间的大型任务,应当按论文中进行实现。

在执行过程中,必须要先将map任务全部执行完,才能执行reduce任务。因为reduce任务要读取全部数据进行排序。当map任务已经分配完但没有全部完成时,部分没有任务可以执行的worker可能会空转。

  1. for {
  2. switch reply.State{
  3. case 0:
  4. //map任务
  5. case 1:
  6. //reduce任务
  7. case 2:
  8. continue //暂时没有任务,等待下一次申请
  9. case 3:
  10. break //所有任务均已完成,worker停止工作
  11. }

在任务分配上,我简单的采用了数组初始化所有任务,在分配任务时从数组中寻找【待执行】的任务(即state为0),更优化的方式可以考虑任务队列。将任务依次入队,对已经执行的任务出队,如果任务执行失败(超时),则重新入队。这样免去了遍历的过程。

因为在mrcoordinator.go中我们可以看到,每隔1s中会执行一次c.Done(),因此可以在c.Done()中增加每次任务的运行时间。

  1. m := mr.MakeCoordinator(os.Args[1:], 10)
  2. for m.Done() == false {
  3. time.Sleep(time.Second)
  4. }

论文中提到通过写入临时文件并重命名它的方式,可以避免在崩溃生成部分写入的文件,造成混乱。ioutil库可以创建临时文件,并在写入结束后重命名为标准文件格式。

在实际上手时,可以先从worker开始,根据程序中给的example,分析执行过程,再在程序中添加对应的实现。RPC调用的函数必须要有返回值,否则运行时会报错找不到该函数。

实现代码

以下为实现代码,通过了全部测试。

  1. //worker.go
  2. package mr
  3. import "fmt"
  4. import "log"
  5. import "net/rpc"
  6. import "hash/fnv"
  7. import (
  8. "time"
  9. "os"
  10. "sort"
  11. "io/ioutil"
  12. "strconv"
  13. "encoding/json"
  14. )
  15. //
  16. // Map functions return a slice of KeyValue.
  17. //
  18. type KeyValue struct {
  19. Key string
  20. Value string
  21. }
  22. type ByKey []KeyValue
  23. // for sorting by key.
  24. func (a ByKey) Len() int { return len(a) }
  25. func (a ByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  26. func (a ByKey) Less(i, j int) bool { return a[i].Key < a[j].Key }
  27. //
  28. // use ihash(key) % NReduce to choose the reduce
  29. // task number for each KeyValue emitted by Map.
  30. //
  31. func ihash(key string) int {
  32. h := fnv.New32a()
  33. h.Write([]byte(key))
  34. return int(h.Sum32() & 0x7fffffff)
  35. }
  36. //
  37. // main/mrworker.go calls this function.
  38. //
  39. func Worker(mapf func(string, string) []KeyValue,
  40. reducef func(string, []string) string) {
  41. // Your worker implementation here.
  42. // uncomment to send the Example RPC to the coordinator.
  43. // CallExample()
  44. for {
  45. time.Sleep(time.Second) //睡眠一秒再接任务
  46. args := ASKArgs{}
  47. reply := ASKReply{}
  48. callAskTask(&args, &reply)
  49. taskNumber := reply.TaskNumber
  50. switch reply.State{
  51. case 0:
  52. file, err := os.Open(reply.FileName)
  53. if err != nil {
  54. log.Fatalf("cannot open mapTask file", reply.FileName)
  55. }
  56. content, err := ioutil.ReadAll(file)
  57. if err != nil {
  58. log.Fatalf("cannot read mapTask file", reply.FileName)
  59. }
  60. file.Close()
  61. kva := mapf(reply.FileName, string(content))
  62. //写入mr-taskNumber-y文件中
  63. WriteMiddleFile(kva, taskNumber, reply.NReduce)
  64. case 1:
  65. intermediate := []KeyValue{}
  66. nmap := reply.NMap
  67. for i:=0; i<nmap ; i++ {
  68. mapFile := "mr-" + strconv.Itoa(i) + "-" + strconv.Itoa(taskNumber)
  69. inputFile, err := os.OpenFile(mapFile, os.O_RDONLY, 0666)
  70. if err != nil {
  71. log.Fatalf("can not open reduceTask", mapFile)
  72. }
  73. dec := json.NewDecoder(inputFile)
  74. for {
  75. var kv []KeyValue
  76. if err := dec.Decode(&kv); err != nil {
  77. break
  78. }
  79. intermediate = append(intermediate, kv...)
  80. }
  81. }
  82. sort.Sort(ByKey(intermediate))
  83. outFile := "mr-out-" + strconv.Itoa(taskNumber)
  84. tempReduceFile, err := ioutil.TempFile("", "mr-reduce-*")
  85. if err != nil {
  86. log.Fatalf("cannot open", outFile)
  87. }
  88. i := 0
  89. for i < len(intermediate) {
  90. j := i + 1
  91. for j < len(intermediate) && intermediate[j].Key == intermediate[i].Key {
  92. j++
  93. }
  94. values := []string{}
  95. for k := i; k < j; k++ {
  96. values = append(values, intermediate[k].Value)
  97. }
  98. output := reducef(intermediate[i].Key, values)
  99. // this is the correct format for each line of Reduce output.
  100. fmt.Fprintf(tempReduceFile, "%v %v\n", intermediate[i].Key, output)
  101. i = j
  102. }
  103. tempReduceFile.Close()
  104. os.Rename(tempReduceFile.Name(), outFile)
  105. case 2:
  106. continue //暂时没有任务,等待下一次申请
  107. case 3:
  108. break //所有任务均已完成,worker停止工作
  109. }
  110. Args := FinishAgrs{State: reply.State, TaskNumber:taskNumber}
  111. Reply := FinishReply{}
  112. callFinishTask(&Args, &Reply)
  113. if Reply.State == 1 {
  114. break
  115. }
  116. }
  117. }
  118. func WriteMiddleFile(kva []KeyValue, taskNumber int, nReduce int) bool {
  119. buffer := make([][]KeyValue, nReduce)
  120. for _, value := range(kva) {
  121. area := (ihash(value.Key)) % nReduce
  122. buffer[area] = append(buffer[area], value)
  123. }
  124. for area, output := range(buffer) {
  125. outputFile := "mr-" + strconv.Itoa(taskNumber) + "-" + strconv.Itoa(area)
  126. tempMapFile, err := ioutil.TempFile("", "mr-map-*")
  127. if err != nil {
  128. log.Fatalf("cannot open tempMapFile")
  129. }
  130. enc := json.NewEncoder(tempMapFile)
  131. err = enc.Encode(output)
  132. if err != nil {
  133. return false
  134. }
  135. tempMapFile.Close()
  136. os.Rename(tempMapFile.Name(), outputFile) //通过原子地重命名避免写入时崩溃,导致内容不完整
  137. }
  138. return true
  139. }
  140. func callAskTask(args *ASKArgs, reply *ASKReply){
  141. call("Coordinator.ASKTask", &args, &reply)
  142. }
  143. func callFinishTask(args *FinishAgrs, reply *FinishReply){
  144. call("Coordinator.FinishTask", &args, &reply)
  145. }
  146. //
  147. // send an RPC request to the coordinator, wait for the response.
  148. // usually returns true.
  149. // returns false if something goes wrong.
  150. //
  151. func call(rpcname string, args interface{}, reply interface{}) bool {
  152. // c, err := rpc.DialHTTP("tcp", "127.0.0.1"+":1234")
  153. sockname := coordinatorSock()
  154. c, err := rpc.DialHTTP("unix", sockname)
  155. if err != nil {
  156. log.Fatal("dialing:", err)
  157. }
  158. defer c.Close()
  159. err = c.Call(rpcname, args, reply)
  160. if err == nil {
  161. return true
  162. }
  163. fmt.Println(err)
  164. return false
  165. }
  1. //rpc.go
  2. package mr
  3. //
  4. // RPC definitions.
  5. //
  6. // remember to capitalize all names.
  7. //
  8. import "os"
  9. import "strconv"
  10. //
  11. // example to show how to declare the arguments
  12. // and reply for an RPC.
  13. //
  14. type ASKArgs struct {
  15. //申请时不需要任何信息
  16. }
  17. type ASKReply struct {
  18. State int //0-map 1-reduce 2-wait 3-shutdown
  19. FileName string //文件名
  20. TaskNumber int //任务号
  21. NReduce int //reduce任务中的分区数
  22. NMap int //Map任务的总数
  23. }
  24. type FinishAgrs struct{
  25. State int //同reply,用于更新Coordinator状态
  26. TaskNumber int
  27. }
  28. type FinishReply struct{
  29. State int //0-继续接受任务 1-任务全部完成,关闭worker
  30. }
  31. // Add your RPC definitions here.
  32. // Cook up a unique-ish UNIX-domain socket name
  33. // in /var/tmp, for the coordinator.
  34. // Can't use the current directory since
  35. // Athena AFS doesn't support UNIX-domain sockets.
  36. func coordinatorSock() string {
  37. s := "/var/tmp/824-mr-"
  38. s += strconv.Itoa(os.Getuid())
  39. return s
  40. }
  1. //coordinator.go
  2. package mr
  3. import "log"
  4. import "net"
  5. import "os"
  6. import "net/rpc"
  7. import "net/http"
  8. import (
  9. "sync"
  10. )
  11. //缺少检测故障,不能主动分配任务
  12. //由设备主动申请任务,不需要轮训检查设备是否响应,因此不需要机器号
  13. type Coordinator struct {
  14. State int //0-map 1-reduce 2-finish
  15. NMap int //map任务总数
  16. NReduce int //reduce分区数
  17. MapTask map[int]*mapTask //map任务数组
  18. ReduceTask map[int]*reduceTask //reduce任务数组
  19. Mu sync.Mutex
  20. }
  21. type mapTask struct {
  22. FileName string
  23. State int //0-待做 1-进行中 2-已完成
  24. RunTime int
  25. }
  26. type reduceTask struct {
  27. State int //0-待做 1-进行中 2-已完成
  28. RunTime int
  29. }
  30. func (c *Coordinator) TickTick() {
  31. if c.State == 0 {
  32. for TaskNumber, task := range(c.MapTask){
  33. if task.State == 1 {
  34. c.MapTask[TaskNumber].RunTime += 1
  35. if c.MapTask[TaskNumber].RunTime>=10 {
  36. c.MapTask[TaskNumber].State = 0
  37. }
  38. }
  39. }
  40. } else if c.State == 1 {
  41. for TaskNumber, task := range(c.ReduceTask){
  42. if task.State == 1 {
  43. c.ReduceTask[TaskNumber].RunTime += 1
  44. if c.ReduceTask[TaskNumber].RunTime>=10 {
  45. c.ReduceTask[TaskNumber].State = 0
  46. }
  47. }
  48. }
  49. }
  50. }
  51. func (c *Coordinator) ASKTask(args *ASKArgs, reply *ASKReply) error{
  52. c.Mu.Lock()
  53. defer c.Mu.Unlock()
  54. reply.State = 2
  55. reply.NMap = c.NMap
  56. reply.NReduce = c.NReduce
  57. switch c.State {
  58. case 0:
  59. for TaskNumber, task := range(c.MapTask) {
  60. if task.State == 0 {
  61. reply.FileName = task.FileName
  62. reply.State = 0
  63. reply.TaskNumber = TaskNumber
  64. c.MapTask[TaskNumber].State = 1
  65. break
  66. }
  67. }
  68. case 1:
  69. for TaskNumber, task := range(c.ReduceTask) {
  70. if task.State == 0 {
  71. reply.State = 1
  72. reply.TaskNumber = TaskNumber
  73. c.ReduceTask[TaskNumber].State = 1
  74. break
  75. }
  76. }
  77. case 2:
  78. reply.State = 3
  79. }
  80. return nil
  81. }
  82. func (c *Coordinator) FinishTask(args *FinishAgrs, reply *FinishReply) error{
  83. c.Mu.Lock()
  84. defer c.Mu.Unlock()
  85. reply.State = 0
  86. if args.State == 0 {
  87. c.MapTask[args.TaskNumber].State = 2
  88. c.CheckState()
  89. } else {
  90. c.ReduceTask[args.TaskNumber].State = 2
  91. c.CheckState()
  92. if c.State == 2 {
  93. reply.State = 1
  94. }
  95. }
  96. return nil
  97. }
  98. func (c *Coordinator) CheckState() {
  99. for _, task := range(c.MapTask) {
  100. if task.State == 0 || task.State == 1 {
  101. c.State = 0
  102. return
  103. }
  104. }
  105. for _, task := range(c.ReduceTask) {
  106. if task.State == 0 || task.State == 1 {
  107. c.State = 1
  108. return
  109. }
  110. }
  111. c.State = 2
  112. }
  113. //
  114. // start a thread that listens for RPCs from worker.go
  115. //
  116. func (c *Coordinator) server() {
  117. rpc.Register(c)
  118. rpc.HandleHTTP()
  119. //l, e := net.Listen("tcp", ":1234")
  120. sockname := coordinatorSock()
  121. os.Remove(sockname)
  122. l, e := net.Listen("unix", sockname)
  123. if e != nil {
  124. log.Fatal("listen error:", e)
  125. }
  126. go http.Serve(l, nil)
  127. }
  128. //
  129. // main/mrcoordinator.go calls Done() periodically to find out
  130. // if the entire job has finished.
  131. //
  132. func (c *Coordinator) Done() bool {
  133. c.Mu.Lock()
  134. defer c.Mu.Unlock()
  135. ret := false
  136. c.TickTick() //在每次检查是否完成时,增加任务时间
  137. if c.State == 2 {
  138. ret = true
  139. } else {
  140. ret = false
  141. }
  142. return ret
  143. }
  144. //
  145. // create a Coordinator.
  146. // main/mrcoordinator.go calls this function.
  147. // nReduce is the number of reduce tasks to use.
  148. //
  149. func MakeCoordinator(files []string, nReduce int) *Coordinator {
  150. maptask := make(map[int]*mapTask)
  151. reducetask := make(map[int]*reduceTask)
  152. for i, filename := range(files) {
  153. maptask[i] = &mapTask{FileName: filename, State: 0, RunTime: 0}
  154. }
  155. for j := 0; j < nReduce; j++ {
  156. reducetask[j] = &reduceTask{State: 0, RunTime: 0}
  157. }
  158. c := Coordinator{State: 0, NMap: len(files), NReduce: nReduce, MapTask: maptask, ReduceTask: reducetask, Mu: sync.Mutex{}}
  159. c.server()
  160. return &c
  161. }

Distributed | MapReduce的更多相关文章

  1. MapReduce的核心资料索引 [转]

    转自http://prinx.blog.163.com/blog/static/190115275201211128513868/和http://www.cnblogs.com/jie46583173 ...

  2. MapReduce C++ Library

    MapReduce C++ Library for single-machine, multicore applications Distributed and scalable computing ...

  3. MapReduce剖析笔记之七:Child子进程处理Map和Reduce任务的主要流程

    在上一节我们分析了TaskTracker如何对JobTracker分配过来的任务进行初始化,并创建各类JVM启动所需的信息,最终创建JVM的整个过程,本节我们继续来看,JVM启动后,执行的是Child ...

  4. MapReduce剖析笔记之六:TaskTracker初始化任务并启动JVM过程

    在上面一节我们分析了JobTracker调用JobQueueTaskScheduler进行任务分配,JobQueueTaskScheduler又调用JobInProgress按照一定顺序查找任务的流程 ...

  5. MapReduce剖析笔记之二:Job提交的过程

    上一节以WordCount分析了MapReduce的基本执行流程,但并没有从框架上进行分析,这一部分工作在后续慢慢补充.这一节,先剖析一下作业提交过程. 在分析之前,我们先进行一下粗略的思考,如果要我 ...

  6. [MapReduce] Google三驾马车:GFS、MapReduce和Bigtable

    声明:此文转载自博客开发团队的博客,尊重原创工作.该文适合学分布式系统之前,作为背景介绍来读. 谈到分布式系统,就不得不提Google的三驾马车:Google FS[1],MapReduce[2],B ...

  7. 使用MapReduce实现join操作

     在关系型数据库中,要实现join操作是非常方便的,通过sql定义的join原语就可以实现.在hdfs存储的海量数据中,要实现join操作,可以通过HiveQL很方便地实现.不过HiveQL也是转化成 ...

  8. 分布式系统(Distributed System)资料

    这个资料关于分布式系统资料,作者写的太好了.拿过来以备用 网址:https://github.com/ty4z2008/Qix/blob/master/ds.md 希望转载的朋友,你可以不用联系我.但 ...

  9. hadoop 学习笔记:mapreduce框架详解

    开始聊mapreduce,mapreduce是hadoop的计算框架,我学hadoop是从hive开始入手,再到hdfs,当我学习hdfs时候,就感觉到hdfs和mapreduce关系的紧密.这个可能 ...

随机推荐

  1. Android低功耗蓝牙(蓝牙4.0)——BLE开发(上)

    段时间,公司项目用到了手机APP和蓝牙设备的通讯开发,这里也正好对低功耗蓝牙(蓝牙4.0及以后标准)的开发,做一个总结. 蓝牙技术联盟在2010年6月30号公布了蓝牙4.0标准,4.0标准在蓝牙3.0 ...

  2. FileReader, readAsText

    readastext filereader FileReader.readAsText() https://developer.mozilla.org/zh-CN/docs/Web/API/FileR ...

  3. CSS前端性能优化

    1.Google 资深web开发工程师Steve Souders对CSS选择器的效率从高到低做了一个排序: 1. id选择器(#myid) 2. 类选择器(.myclassname) 3. 标签选择器 ...

  4. SSL/TLS协议详解(下)——TLS握手协议

    本文转载自SSL/TLS协议详解(下)--TLS握手协议 导语 在博客系列的第2部分中,对证书颁发机构进行了深入的讨论.在这篇文章中,将会探索整个SSL/TLS握手过程,在此之前,先简述下最后这块内容 ...

  5. 【HTB靶场系列】靶机Carrier的渗透测试

    出品|MS08067实验室(www.ms08067.com) 本文作者:大方子(Ms08067实验室核心成员) Hack The Box是一个CTF挑战靶机平台,在线渗透测试平台.它能帮助你提升渗透测 ...

  6. Mybatis【20】-- Mybatis延迟加载怎么处理?

    注:代码已托管在GitHub上,地址是:https://github.com/Damaer/Mybatis-Learning ,项目是mybatis-16-lazyload,需要自取,需要配置mave ...

  7. Spring-03 依赖注入(DI)

    Spring-03 依赖注入(DI) 依赖注入(DI) 依赖注入(Dependency Injection,DI). 依赖 : 指Bean对象的创建依赖于容器,Bean对象的依赖资源. 注入 : 指B ...

  8. Redis操作指南

    目录 Redis安装与使用教程 一.Redis介绍 1.redis安装 2.redis与mysql的异同 3.redis与memcache的异同 二.Redis操作 1.启动服务 2.密码管理 3.连 ...

  9. 判断app是否安装

    Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.fromPar ...

  10. 共享内存与存储映射(mmap)

    [前言]对这两个理解还是不够深刻,写一篇博客来记录一下. 首先关于共享内存的链接:共享内存.里面包含了创建共享内存区域的函数,以及两个进程怎么挂载共享内存通信,分离.释放共享内存. 共享内存的好处就是 ...