MapReduce是Google在2004年发表的论文《MapReduce: Simplified Data Processing on Large Clusters》中提出的一个用于分布式的用于大规模数据处理的编程模型。

原理

MapReduce将数据的处理分成了两个步骤,Map和Reduce。Map将输入的数据集拆分成一批KV对并输出,对于每一个<k1, v1>,Map将输出一批<k2, v2>;Reduce将Map对Map中产生的结果进行汇总,对于每一个<k2, list(v2)>list(v2)是所有key为k2的value),Reduce将输出结果<k3, v3>

以单词出现次数统计程序为例,map对文档中每个单词都输出<word, 1>,reduce则会统计每个单词对应的list的长度,输出<word, n>

  1. map(String key, String value):
  2. // key: document name
  3. // value: document contents
  4. for each word w in value:
  5. EmitIntermediate(w, 1″);
  6. reduce(String key, Iterator values):
  7. // key: a word
  8. // values: a list of counts
  9. int result = 0;
  10. for each v in values:
  11. result += ParseInt(v);
  12. Emit(AsString(result));

流程

MapReduce的流程如下:

  1. 将输入拆分成M个段,产生M个Map任务和R个Reduce任务。
  2. 创建1个master和n个worker,master会将Map和Reduce分派给worker执行。
  3. 被分配了Map任务的worker从输入中读取解析出KV对,传递给用户提供的Map函数,得到中间的一批KV对。
  4. 将中间的KV对使用分区函数分配到R个区域上,并保存到磁盘中,当Map任务执行完成后将保存的位置返回给master。
  5. Reduce worker根据master传递的参数从文件系统中读取数据,解析出KV对,并对具有相同key的value进行聚合,产生<k2, list(v2)>。如果无法在内存中进行排序,就需要使用外部排序。
  6. 对于每一个唯一的key,将<k2, list(v2)>传递给用户提供的Reduce函数,将函数的返回值追加到输出文件中。
  7. 当所有任务都完成后,MapReduce程序返回

MapReduce的整个流程并不复杂,就是将数据分片后提交给map执行,执行产生的中间结果经过处理后再交给reduce执行,产生最终结果。

容错

当worker发生故障时,可以通过心跳等方法进行检测,当检测到故障之后就可以将任务重新分派给其他worker重新执行。

当master发生故障时,可以通过检查点(checkpoint)的方法来进行恢复。然而由于master只有一个,比较难进行恢复,因此可以让用户检测并重新执行任务。

对于输出文件来说,需要保证仍在写入中的文件不被读取,即保证操作的原子性。可以通过文件系统重命名操作的原子性来实现,先将结果保存在临时文件中,当执行完成后再进行重命名。使用这种方法就可以将有副作用的write变为幂等(总是产生相同结果的运算,如a = 2就是幂等的,而a += 2则不是)的重命名。

落伍者

影响任务的总执行时间的重要因素就是落伍者:在运算中某个机器用了很长时间才完成了最后的几个任务,从而增加了总的执行时间。对于这种情况,可以在任务即将完成时,将剩余的任务交给备用者进程来执行,无论是最初的worker完成了任务还是备用者完成了,都可以将任务标记为完成。

分区函数

对于map产生的结果,通过分区函数来将相同key的KV对分配给同一个reduce来执行。默认的分区函数是hash(key) % R,但在某些情况下也可以选择其他分区函数。如key为URL时,希望相同主机的结果在同一个输出中,那么就可以用hash(hostname(key)) % R作为分区函数。

实现

实现部分是基于MIT 6.824的实验完成的。

  1. type Coordinator struct {
  2. mapJobs []Job
  3. reduceJobs []Job
  4. status int
  5. nMap int
  6. remainMap int
  7. nReduce int
  8. remainReduce int
  9. lock sync.Mutex
  10. }
  11. func MakeCoordinator(files []string, nReduce int) *Coordinator {
  12. c := Coordinator{}
  13. c.status = MAP
  14. c.nMap = len(files)
  15. c.remainMap = c.nMap
  16. c.nReduce = nReduce
  17. c.remainReduce = c.nReduce
  18. c.mapJobs = make([]Job, len(files))
  19. c.reduceJobs = make([]Job, nReduce)
  20. for idx, file := range files {
  21. c.mapJobs[idx] = Job{[]string{file}, WAITTING, idx}
  22. }
  23. for idx := range c.reduceJobs {
  24. c.reduceJobs[idx] = Job{[]string{}, WAITTING, idx}
  25. }
  26. c.server()
  27. return &c
  28. }
  29. func (c *Coordinator) timer(status *int) {
  30. time.Sleep(time.Second * 10)
  31. c.lock.Lock()
  32. if *status == RUNNING {
  33. log.Printf("timeout\n")
  34. *status = WAITTING
  35. }
  36. c.lock.Unlock()
  37. }
  38. func (c *Coordinator) AcquireJob(args *AcquireJobArgs, reply *AcquireJobReply) error {
  39. c.lock.Lock()
  40. defer c.lock.Unlock()
  41. fmt.Printf("Acquire: %+v\n", args)
  42. if args.CommitJob.Index >= 0 {
  43. if args.Status == MAP {
  44. if c.mapJobs[args.CommitJob.Index].Status == RUNNING {
  45. c.mapJobs[args.CommitJob.Index].Status = FINISHED
  46. for idx, file := range args.CommitJob.Files {
  47. c.reduceJobs[idx].Files = append(c.reduceJobs[idx].Files, file)
  48. }
  49. c.remainMap--
  50. }
  51. if c.remainMap == 0 {
  52. c.status = REDUCE
  53. }
  54. } else {
  55. if c.reduceJobs[args.CommitJob.Index].Status == RUNNING {
  56. c.reduceJobs[args.CommitJob.Index].Status = FINISHED
  57. c.remainReduce--
  58. }
  59. if c.remainReduce == 0 {
  60. c.status = FINISH
  61. }
  62. }
  63. }
  64. if c.status == MAP {
  65. for idx := range c.mapJobs {
  66. if c.mapJobs[idx].Status == WAITTING {
  67. reply.NOther = c.nReduce
  68. reply.Status = MAP
  69. reply.Job = c.mapJobs[idx]
  70. c.mapJobs[idx].Status = RUNNING
  71. go c.timer(&c.mapJobs[idx].Status)
  72. return nil
  73. }
  74. }
  75. reply.NOther = c.nReduce
  76. reply.Status = MAP
  77. reply.Job = Job{Files: make([]string, 0), Index: -1}
  78. } else if c.status == REDUCE {
  79. for idx := range c.reduceJobs {
  80. if c.reduceJobs[idx].Status == WAITTING {
  81. reply.NOther = c.nMap
  82. reply.Status = REDUCE
  83. reply.Job = c.reduceJobs[idx]
  84. c.reduceJobs[idx].Status = RUNNING
  85. go c.timer(&c.reduceJobs[idx].Status)
  86. return nil
  87. }
  88. }
  89. reply.NOther = c.nMap
  90. reply.Status = REDUCE
  91. reply.Job = Job{Files: make([]string, 0), Index: -1}
  92. } else {
  93. reply.Status = FINISH
  94. }
  95. return nil
  96. }

Coordinator中保存所有的任务信息以及执行状态,worker通过AcquireJob来提交和申请任务,要等待所有map任务完成后才能执行reduce任务。这里就简单的将每一个文件都作为一个任务。

  1. func doMap(mapf func(string, string) []KeyValue, job *Job, nReduce int) (files []string) {
  2. outFiles := make([]*os.File, nReduce)
  3. for idx := range outFiles {
  4. outFile, err := ioutil.TempFile("./", "mr-tmp-*")
  5. if err != nil {
  6. log.Fatalf("create tmp file failed: %v", err)
  7. }
  8. defer outFile.Close()
  9. outFiles[idx] = outFile
  10. }
  11. for _, filename := range job.Files {
  12. file, err := os.Open(filename)
  13. if err != nil {
  14. log.Fatalf("cannot open %v", filename)
  15. }
  16. content, err := ioutil.ReadAll(file)
  17. if err != nil {
  18. log.Fatalf("cannot read %v", filename)
  19. }
  20. file.Close()
  21. kva := mapf(filename, string(content))
  22. for _, kv := range kva {
  23. hash := ihash(kv.Key) % nReduce
  24. js, _ := json.Marshal(kv)
  25. outFiles[hash].Write(js)
  26. outFiles[hash].WriteString("\n")
  27. }
  28. }
  29. for idx := range outFiles {
  30. filename := fmt.Sprintf("mr-%d-%d", job.Index, idx)
  31. os.Rename(outFiles[idx].Name(), filename)
  32. files = append(files, filename)
  33. }
  34. return
  35. }
  36. func doReduce(reducef func(string, []string) string, job *Job, nMap int) {
  37. log.Printf("Start reduce %d", job.Index)
  38. outFile, err := ioutil.TempFile("./", "mr-out-tmp-*")
  39. defer outFile.Close()
  40. if err != nil {
  41. log.Fatalf("create tmp file failed: %v", err)
  42. }
  43. m := make(map[string][]string)
  44. for _, filename := range job.Files {
  45. file, err := os.Open(filename)
  46. if err != nil {
  47. log.Fatalf("cannot open %v", filename)
  48. }
  49. scanner := bufio.NewScanner(file)
  50. for scanner.Scan() {
  51. kv := KeyValue{}
  52. if err := json.Unmarshal(scanner.Bytes(), &kv); err != nil {
  53. log.Fatalf("read kv failed: %v", err)
  54. }
  55. m[kv.Key] = append(m[kv.Key], kv.Value)
  56. }
  57. if err := scanner.Err(); err != nil {
  58. log.Fatal(err)
  59. }
  60. file.Close()
  61. }
  62. for key, value := range m {
  63. output := reducef(key, value)
  64. fmt.Fprintf(outFile, "%v %v\n", key, output)
  65. }
  66. os.Rename(outFile.Name(), fmt.Sprintf("mr-out-%d", job.Index))
  67. log.Printf("End reduce %d", job.Index)
  68. }
  69. //
  70. // main/mrworker.go calls this function.
  71. //
  72. func Worker(mapf func(string, string) []KeyValue,
  73. reducef func(string, []string) string) {
  74. CallExample()
  75. var status int = MAP
  76. args := AcquireJobArgs{Job{Index: -1}, MAP}
  77. for {
  78. args.Status = status
  79. reply := AcquireJobReply{}
  80. call("Coordinator.AcquireJob", &args, &reply)
  81. fmt.Printf("AcReply: %+v\n", reply)
  82. if reply.Status == FINISH {
  83. break
  84. }
  85. status = reply.Status
  86. if reply.Job.Index >= 0 {
  87. // get a job, do it
  88. commitJob := reply.Job
  89. if status == MAP {
  90. commitJob.Files = doMap(mapf, &reply.Job, reply.NOther)
  91. } else {
  92. doReduce(reducef, &reply.Job, reply.NOther)
  93. commitJob.Files = make([]string, 0)
  94. }
  95. // job finished
  96. args = AcquireJobArgs{commitJob, status}
  97. } else {
  98. // no job, sleep to wait
  99. time.Sleep(time.Second)
  100. args = AcquireJobArgs{Job{Index: -1}, status}
  101. }
  102. }
  103. }

worker通过RPC调用向Coordinator.AcquireJob申请和提交任务,之后根据任务类型执行doMapdoReduce

doMap函数读取目标文件并将<filename, content>传递给map函数,之后将返回值根据hash(key) % R写入到目标中间文件中去。

doReduce函数则从目标文件中读取KV对并加载到内存中,对相同的key进行合并(这里我是用map来做的,但是之后看论文发现是用排序来做的,这样可以保证在每个输出文件中的key是有序的)。合并之后就将<key, list(value)>交给reduce函数处理,最后把返回值写入到结果文件中去。

MapReduce原理及简单实现的更多相关文章

  1. HBase笔记:对HBase原理的简单理解

    早些时候学习hadoop的技术,我一直对里面两项技术倍感困惑,一个是zookeeper,一个就是Hbase了.现在有机会专职做大数据相关的项目,终于看到了HBase实战的项目,也因此有机会搞懂Hbas ...

  2. MapReduce原理及其主要实现平台分析

    原文:http://www.infotech.ac.cn/article/2012/1003-3513-28-2-60.html MapReduce原理及其主要实现平台分析 亢丽芸, 王效岳, 白如江 ...

  3. MapReduce原理讲解

    简介 本文主要介绍MapReduce V2的基本原理, 也是笔者在学习MR的学习笔记整理. 本文首先大概介绍下MRV2的客户端跟服务器交互的两个协议, 然后着重介绍MRV2的核心模块MRAppMast ...

  4. Hapoop原理及MapReduce原理分析

    Hapoop原理 Hadoop是一个开源的可运行于大规模集群上的分布式并行编程框架,其最核心的设计包括:MapReduce和HDFS.基于 Hadoop,你可以轻松地编写可处理海量数据的分布式并行程序 ...

  5. Hadoop学习记录(4)|MapReduce原理|API操作使用

    MapReduce概念 MapReduce是一种分布式计算模型,由谷歌提出,主要用于搜索领域,解决海量数据计算问题. MR由两个阶段组成:Map和Reduce,用户只需要实现map()和reduce( ...

  6. hadoop笔记之MapReduce原理

    MapReduce原理 MapReduce原理 简单来说就是,一个大任务分成多个小的子任务(map),并行执行后,合并结果(reduce). 例子: 100GB的网站访问日志文件,找出访问次数最多的I ...

  7. MapReduce 原理与 Python 实践

    MapReduce 原理与 Python 实践 1. MapReduce 原理 以下是个人在MongoDB和Redis实际应用中总结的Map-Reduce的理解 Hadoop 的 MapReduce ...

  8. 大数据 --> MapReduce原理与设计思想

    MapReduce原理与设计思想 简单解释 MapReduce 算法 一个有趣的例子:你想数出一摞牌中有多少张黑桃.直观方式是一张一张检查并且数出有多少张是黑桃? MapReduce方法则是: 给在座 ...

  9. MapReduce原理

    MapReduce原理 WordCount例子 用mapreduce计算wordcount的例子: package org.apache.hadoop.examples; import java.io ...

随机推荐

  1. jQuery——样式与动画

    通过jQuery,不仅能够轻松地为页面操作添加简单的视觉效果,甚至能创建更精致的动画. ###修改内联CSS jQuery提供了.css()方法. 这个方法集getter(获取方法)和setter(设 ...

  2. Java——方法

    Java 方法 Systom.out.println():其中,println()是一个方法(Method),而System是系统类(Class),out是标准输出对象(Object).这句话的意思是 ...

  3. Spring Boot的进阶和高级

    一.Repository接口 二.Repository子接口 三.@Query注解 四.更新及删除操作整合事物 五.CrudRepository接口 六.PagingAndSortingReposit ...

  4. HDOJ 1078

    标准的DAG上的DP,其实之前一直不大想得明白为什么dp[i][j]为什么一定是在(i,j)状态上的局部最优解了呢? 其实仔细想想和我一般所做的DP是一个道理,因为运用dfs的方法,因此可以确定的是, ...

  5. P1541 乌龟棋(DP)

    题目背景 小明过生日的时候,爸爸送给他一副乌龟棋当作礼物. 题目描述 乌龟棋的棋盘是一行NNN个格子,每个格子上一个分数(非负整数).棋盘第1格是唯一的起点,第NNN格是终点,游戏要求玩家控制一个乌龟 ...

  6. Codeforces Round #653 (Div. 3) D. Zero Remainder Array (数学,模拟)

    题意:有一组数,刚开始时\(x=0\),每次可以让\(x\)++或让某一个元素+=\(x\)后\(x\)++,每个元素只能加一次\(x\),问最少操作多少次使得所有元素能被\(k\)整除. 题解:每个 ...

  7. Centos 7 安装nginx指定版本

    官方版本列表:http://nginx.org/download/ 1.安装 wget http://nginx.org/download/nginx-1.10.3.tar.gz tar -zxvf ...

  8. SQL Server 远程连接配置

    打开sql server配置工具 SQL Server网络配置→SQLEXPRESS的协议→启用TCP/IP→右键属性→IP地址→IPALL端口修改为1433→重启SQL Server服务 https ...

  9. 动态链接库(DLL)的创建和使用

    最近想做个记录日志的C++库,方便后续使用.想着使用动态库,正好没用过,学习下.概念这里不赘述.学习过程中碰到的几点,记录下来.学习是个渐进的过程,本文也是一个逐渐完善的过程. 一.Static Li ...

  10. java调用http接口的几种方式总结

    本文参考: https://blog.csdn.net/fightingXia/article/details/71775516 https://www.cnblogs.com/jeffen/p/69 ...