Mit 6.824 Lab:1 MapReduce 实现历程

实验原址:mit 6.824 Lab1 MapReduce

中文翻译:mit 6.824 实验1 MapReduce

建议先了解一下MapReduce他可以用来做什么。可以看看我的上一篇文章MapReduce 介绍 ,不过最好还是阅读原论文,这样你会更加清楚。

前言

分布式理论接触的挺多的,例如:

  • CAP理论
  • BASE理论

但是关于分布式理论实践,我也找不到很好的简单的入门课程。不过,感谢互联网让我找到了Mit的公开课,为我打开了分布式的新的大门~~

实现历程

本次实验需要我们自己补充完整课程所提供的代码,完成一个用来单词计数的MapReduce程序

环境搭建

我们跟着实验搭建环境。

  1. 首先确认自己的环境是否是Linux。本人是使用虚拟机搭建Ubantu的Linux环境。
  2. 安装VS Code,因为VS Code轻量便捷,即便是你电脑性能不好也不会太卡。VSCode自带Git,我们就不用再配置了。
  3. 安装go Sdk。注意!!!请务必和实验提供的环境一致,否则可能会有莫名其妙的Bug。
  4. 使用Ctrl + Alt + t在Linux下打开命令行,使用code命令启动VSCode。
  5. 在VSCode中,新建一个终端,在里面使用Git将实验代码clone下来。
  6. 此时检查自己是否搭好环境,以跟着下面的测试检查程序运行是否正常

实验目录结构

本次实验,我们需要用到的这三个目录。

main目录

mr目录

包含三个文件 master rpc worker 我们的代码需要编写在这三个文件中

mrapps

测试程序所需要使用的,用来检测我们编写代码的健壮性。

本次实验一共有五个测试

  1. wc test:验证编写的程序是否得到正确的单词统计结果
  2. indexer test:验证编写的程序结果是否来自正确的文件
  3. map parallelism test:验证Map任务是否多线程执行
  4. reduce parallelism test:验证Reduce任务是否多线程执行
  5. crash test:健壮性测试,测试worker崩溃或执行太慢时,本程序依然可以获得正确的结果

在测试的时候,咱们有时候可以一个一个来,也可以直接全部进行测试。

实验目的

实现一个分布式MapReduce,它由两个程序(master程序和worker程序)组成。只有一个 master进程,一个或多个worker进程并行执行。在真实的系统中,工作人员将在一堆不同的机器上运 行,但是对于本实验,您将全部在单个机器上运行它们。worker将通过RPC与master服务器对话。每个 工作进程都会向主服务器请求一个任务,从一个或多个文件中读取任务的输入,执行任务,并将任务 的输出写入一个或多个文件。master应注意一个工人是否在合理的时间内没有完成任务(在本实验 中,使用十秒钟),并将同一任务交给另一个worker。

实验要求

  • Map阶段应将中间键划分为用于nReduce reduce任务的存储桶 ,其中nReduce是 main/mrmaster.go传递给MakeMaster()的参数。
  • 工作程序实现应将第X个reduce任务的输出放入文件mr-out-X中。
  • 一个mr-out-X文件的每个Reduce函数输出应包含一行。该行应以Go "%v %v" 格式生成,并 使用键和值进行调用。在main/mrsequential.go中 查看注释为"这是正确的格式"的行。如 果您的实现不按该格式输出,则测试脚本将失败。
  • 您可以修改mr/worker.go,mr/master.go,和mr/rpc.go。您可以临时修改其他文件以进行 测试,但是请确保您的代码可以与原始版本一起使用;我们将使用原始版本进行测试。
  • worker应将中间Map输出放置在当前目录中的文件中,您的worker以后可以在其中读取它们, 作为Reduce任务的输入。
  • main/mrmaster.go期望mr/master.go实现 Done()方法,该方法在MapReduce作业完全完成时 返回true;届时,mrmaster.go将退出。
  • 全部完成后,工作进程应退出。一种简单的实现方法是使用call()的返回值:如果worker程序 无法与master服务器联系,则可以假定worker服务器由于作业完成而退出,因此worker程序也 可以终止。根据您的设计,您可能还会发现拥有主人可以交给工作人员的"请退出"伪任务会 很有帮助。

实验实现

小技巧

  • 这种多线程调试比较麻烦,所以需要用到两个以上的终端。一个终端用于启动Master,另外的一些终端用来启动Worker,这样双方的日志不会混合。
  • 实验提供了一个Word-Count插件,该插件也提供了Map函数与Reduce函数,因此每次运行的时候,我们都要编译他,我们就可以将编译与运行Worker弄成一个简单的shell脚本。

这样我们就可以一行命令执行一堆命令了。

bash 复制代码
sh master.sh
sh worker.sh

下面涉及一点点实现思路与核心代码,请你谨慎观看。我更希望下面是在你做了实验后,当做一个交流。

---------------------------------------------分割线---------------------------------------------

整体思路

注意:在RPC中定义的各个参数一定要用大驼峰 ,否则将报错。

在Master中,还有必要的存储的数据结构,个人定义如下

go 复制代码
//任务状态
const (
	WAITING =  0
	WORKING = 1
	FINISHED = 2 
)

type Master struct {
	// Your definitions here.
	filesCnt              int
	nReduce               int     
	files              [] string  //所有的文件名称
	mapStatus          [] int     //0 - 等待中 1 - 处理中 2 - 成功
	mapDone               bool    //所有的mapTask是否完成
	reduceStatus       [] int     //0 - 等待中 1 - 处理中 2 - 成功
	reduceDone            bool    //所有的reduceTask是否完成
}

实现步骤

  1. 在每一个Worker中,存在一个死循环,不断向Master要任务。这边也需要定义好双方通信的约定。
go 复制代码
//任务类型
const(
	MAP_TASK = 0    //map任务
	REDUCE_TASK = 1 // reduce任务
)
//请求类型
const(
	ASK_FOR_TASK = 0 //请求任务
	NOTICE = 1       // 通知
)

//请求
type Request struct {
	RequestType int // 0 - 请求任务 1- 通知
	Seq int // 完成的任务序号 
	TaskType int // 完成的任务类型
}
//回复
type Response struct {
	TaskType int // 0-Map 1-Reduce
	Seq int //任务序号
	Filename string // 文件名称
	FileContent string // 文件内容
	NReduce int //
}
  1. 在Master中,有一个不断监听Woker任务的线程,Master将会把任务发给Worker。下面是派发任务的逻辑代码
go 复制代码
func selectTask  (m * Master) (int,int)  {
	if !m.mapDone  {
		for i,j := range m.mapStatus {
			if j == WAITING{
				m.mapStatus[i] = WORKING
				return MAP_TASK, i
			}
		}
		for _,j := range m.mapStatus {
			if j != FINISHED{
				return 2, 0
			}
		}
		m.mapDone = true
		//fmt.Println("All Map Task is Done")
	} else if  m.mapDone  &&  !m.reduceDone {
		for i,j := range m.reduceStatus {
			if j == WAITING{
				m.reduceStatus[i] = WORKING
				return REDUCE_TASK, i
			}
		}
		for _,j := range m.reduceStatus {
			if j != FINISHED{
				return 2, 0
			}
		}
		m.reduceDone = true
	//	fmt.Println("All Reduce Task is Done")
	}else{
		//fmt.Println("All task were done!" )
		os.Exit(0)
		return 2,0
	}
	return 2,0
}
  1. Worker拿到Map任务后,把所有的文件都调用一遍Map函数,并且遍历每一个Key,按照ihash()函数将该Key分到对应的Reduce任务中。我个人倾向将Reduce序号分个文件夹,就像下面这样 如此以来,后续分发Reduce任务时,读取数据比较方便。
  2. 等待Map任务完成之后,Master将会进行Reduce任务的分发,Worker拿到Reduce任务后,可以从mrsequential.go借鉴一些代码,以读取Map输入文件,对Map和Reduce之间的中间键/值对进行排序,以及将Reduce输出存储在文件中。
  3. 最后,为了应对最后的Crash Test,对于每派发出的一个任务,我设置了一个监听队列以及一个新的线程来判断该任务是否超时。一些核心代码定义如下
go 复制代码
type Monitor struct{
	taskType int    //监听任务类型
	seq int         //任务序号
	startTime time.Time//任务派发时间
	overTime string //结束时间
}
var moitorTaskList = make([] Monitor,0)

var lockSelect sync.RWMutex
var lockMonitor sync.RWMutex

func (m * Master) moitor() {
	for true {
            lockMonitor.Lock()
            var t [] Monitor
            for i,j  :=  range moitorTaskList  {
                    if time.Now().After(j.startTime) {
                            if j.taskType == MAP_TASK {
                                    if m.mapStatus[j.seq] == WORKING {
                                            m.mapStatus[j.seq] = WAITING
                                    }else{
                                            t = append(moitorTaskList[:i],moitorTaskList[i+1:]...)
                                    }
                            }else{
                                    if m.reduceStatus[j.seq] == WORKING {
                                            m.reduceStatus[j.seq] = WAITING
                                    }else{
                                            t  = append(moitorTaskList[:i],moitorTaskList[i+1:]...)
                                    }
                            }
                    }
            }
            moitorTaskList = t
            lockMonitor.Unlock()
            time.Sleep(3*time.Second)
	}
}

一些小细节:

  • 在修改共享数据时,一定要加锁
  • 有时候,Worker需要进行等待任务,所以任务类型可以新增一个等待类型
  • 当所有的任务结束后,避免无用程序一直执行,可以在Done函数中,告知Worker任务执行完成可下线做到shutdown gracefully

总结

在这我想说几点

  • 其实实验最难的不是实现,也不是使用另一种语言,而是开始做。这个才是最难的,万事开头难。
  • 在本实验中最烦的应该就是调试了,所以在调试的过程中,最好每一步都打个日志,这样你的思路就会很清晰。
  • 最后,真的难遇到这种可以有检验自己学习成果的课程,希望你且做且珍惜。这篇也算是记录一下自己的实现思路吧,真的很喜欢这种创造感。祝你们都能看到最后的PASS ALL TEST

附上本人简陋实现:mit6.824 lab1 mapreduce

相关推荐
也无晴也无风雨24 分钟前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
斑驳竹影3 小时前
【RabbitMQ】之高可用集群搭建
分布式·rabbitmq
2401_857610034 小时前
多维视角下的知识管理:Spring Boot应用
java·spring boot·后端
代码小鑫4 小时前
A027-基于Spring Boot的农事管理系统
java·开发语言·数据库·spring boot·后端·毕业设计
floret*4 小时前
Kafka高频面试题详解
分布式·kafka
Wlq04154 小时前
分布式技术缓存技术
分布式·缓存
明达技术5 小时前
LVDS高速背板总线:打造分布式I/O高效数据传输新境界
分布式·物联网·自动化·lvds
java1234_小锋5 小时前
分布式环境下宕机的处理方案有哪些?
分布式
颜淡慕潇5 小时前
【K8S问题系列 | 9】如何监控集群CPU使用率并设置告警?
后端·云原生·容器·kubernetes·问题解决
大数据魔法师5 小时前
Hadoop生态圈框架部署(六)- HBase完全分布式部署
hadoop·分布式·hbase