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

相关推荐
hai40587几秒前
Spring Boot中的响应与分层解耦架构
spring boot·后端·架构
Adolf_19931 小时前
Flask-JWT-Extended登录验证, 不用自定义
后端·python·flask
叫我:松哥1 小时前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
海里真的有鱼1 小时前
Spring Boot 项目中整合 RabbitMQ,使用死信队列(Dead Letter Exchange, DLX)实现延迟队列功能
开发语言·后端·rabbitmq
喜欢猪猪2 小时前
深度解析ElasticSearch:构建高效搜索与分析的基石原创
分布式
工业甲酰苯胺2 小时前
Spring Boot 整合 MyBatis 的详细步骤(两种方式)
spring boot·后端·mybatis
新知图书2 小时前
Rust编程的作用域与所有权
开发语言·后端·rust
蘑菇蘑菇不会开花~3 小时前
分布式Redis(14)哈希槽
redis·分布式·哈希算法
wn5313 小时前
【Go - 类型断言】
服务器·开发语言·后端·golang
希冀1234 小时前
【操作系统】1.2操作系统的发展与分类
后端