6.824 Spring 2021 -- Lab 1: MapReduce

介绍:https://pdos.csail.mit.edu/6.824/labs/lab-mr.html

前期工作

使用windows vscode远程linux来做,这样我们可以在windows debug,也可以直接在remote  server运行我们的测试脚本

remote development 开发配置参考:

  • https://www.jianshu.com/p/0f2fb935a9a1
  • https://blog.csdn.net/weixin_42397613/article/details/114983147
  • https://blog.csdn.net/weixin_39523456/article/details/113998296

阅读 6.824 Lab 1: MapReduce 的介绍,我们先把mrsequential的例子使用我们的环境运行起来


# remote server上
git clone git://g.csail.mit.edu/6.824-golabs-2021 6.824
cd src/main
# 加-gcflags的原因可以看:https://www.coder.work/article/7497554
go build -race -buildmode=plugin -gcflags="all=-N -l"../mrapps/wc.go

在我们本地的环境运行vscode,同时远程连接上remote server ,如下:

打开我们的项目,打开main/mrsequential.go文件,点击Run->Add Configuration...,设置如下的内容

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch Package",
            "type": "go",
            "request": "launch",
            "mode": "auto",
            "program": "${file}",
            // "program": "${fileDirname}",
            "args": ["wc.so", "pg-being_ernest.txt"]
        }
    ]
}

args里面原本应该是["wc.so", "pg*.txt"],但是vscode里面模式无法识别出来正则,就直接填入文件名就可以了

配置完成后,我们可以 Run -> start without debugging,可以看到main下面出现了一个mr-out-0的文件

 我们先分析下 wc.go里面的内容,其实这个里面就是用户实现的、独立于mapreduce框架的map和reduce函数

// 输入文本内容,返回一系列的<word, 1>
func Map(filename string, contents string) []mr.KeyValue {
	// function to detect word separators.
	ff := func(r rune) bool { return !unicode.IsLetter(r) }

	// split contents into an array of words.
	words := strings.FieldsFunc(contents, ff)

	kva := []mr.KeyValue{}
	for _, w := range words {
		kv := mr.KeyValue{w, "1"}
		kva = append(kva, kv)
	}
	return kva
}

// 计算每个单词的个数,计算的方式就是统计有几个values,因为每个values都是1,其和就是这个values的长度
func Reduce(key string, values []string) string {
	// return the number of occurrences of this word.
	return strconv.Itoa(len(values))
}

mrsequential.go的内容也比较简单,就是加载上面的map和reduce函数,然后串行的处理输入的文本文件,每个文件都调用map函数,并把最后的汇总结果做一次排序,统计每个单词对应的列表,然后调用reduce函数就可以了

对应到论文, k1就是文件名,v1是文件的内容;k2是一个个单词,比如good,v2就是1(代表遍历到k2单词一次),而list(v2)就是一个只有1的列表;

目标介绍

我们的目标是实现一个分布式的mapreduce框架,实现与上面串行运行相同的效果。

整个分布式的mapreduce框架流程如论文所描述

这里面有几个概念描述如下:

  1. split,也就是切分,一般就是指将文件的内容进行切分成合适的块;如果文件不大,每个文件就是一个split
  2. map任务,负责执行Map阶段,map的任务是将split之后的键值内容进行映射成一种中间的键值内容,并将键值内容根据键对reduce任务的数量的哈希结果,放入不同的桶中,每个桶都对应一个reduce任务的输入列表之一
  3. combiner function:就是在Intermediate files里面做的一个操作,主要就避免传输量太大,把可以合并的数据给合并了,比如把两个<good:1>合并为<good:2>
  4. reduce任务,负责执行reduce阶段,每个reduce的任务,都是去读取对应每个map任务生成的键值内容(也就是对应自己的桶),然后将这些去键值内容进行整合
  5. worker,也就是执行机,负责请求任务和执行任务,任务有两种类型,分别是map类型任务和reduce类型任务
  6. master,负责分派任务和管理任务(超时等)

为了更加方便理解,我们以论文中的word count进行说明,这个例子的目的在于统计一堆文件中每个单词的个数,

  1. split,进行切分,如果文件内容不是很大,我们直接就使用各个文件做为一个块,不做任何处理,也就是split之后就是<filename, content>,那么如果我们有m个文件,也就对应有m个map任务了,这些任务是可以并发进行处理的
  2. map,每个map任务都会有一个属于自己的编号x,接收一个<filename, content>内容,初始化一个大小为nReduce(reduce的任务数)的桶buckets,读取content内容,每遇到一个单词就生成一个<word, 1>(映射规则),然后根据word与nReduce(reduce的任务数)进行哈希,得到一个桶序号y,并将这个单词放入buckets[y]中,等处理完后,将buckets内容保存至文件中,每个bucket对应一个文件,文件名为mr-x-y
  3. combiner function,这个我们可以把同个中间文件里面的多个相同kv合并,比如n个<word,1> 合并为<word,n>
  4. reduce,在上述m个map任务全部执行完成后,我们会得到m*n个中间文件(每个map任务会生成n个文件,当然有可能为空),编号为y的reduce任务,会依次读取mr-x-y文件的内容,其中x为迭代变量,从0到m-1,读取每个mr-x-y的内容之后,对内容进行排序,然后把文件的内容根据key进行整合,整合的规则是统计每个key出现的频次,得到<word,count>,其中count表示每个key的数量,然后把内容保存到文件名为mr-out-y的文件中
  5. 最后,我们只需要把所有mr-out-y的内容合并成一个文件就是我们需要的内容了

lab给我们提供了基本的代码框架,入口文件分别是main/mrcoordinator.go和main/mrworer.go,这不需要我们修改,我们主要修改mr/coordinator.go, mr/worker.go,和 mr/rpc.go.

我们的运行命令是

go build -race -buildmode=plugin  -gcflags="all=-N -l" ../mrapps/wc.go
rm mr-out*
# 运行 master
go run -race mrcoordinator.go pg-*.txt
# 另一个窗口运行 worker(需要几个worker就开几个窗口)
go run -race mrworker.go wc.so
# 当上面的master和workers执行完毕后,查看mr-out-*文件
cat mr-out-* | sort | more
A 509
ABOUT 2
ACT 8
...

为了完整的测试,lab提供了一个test脚本,我们需要保证可以通过全部的测试用例(建议通读下test-mr.sh文件,脚本写得很清晰,我们可以知道我们的环境、输入和输出是怎么样的)

总的来说,脚本测试:

  • 两个测试是测试结果的准确性
  • 第三个是测试map task的并行性,也就是你的程序是否支持不同的map任务在不同的worker上执行
  • 第四个是测试reduce task的并行性,也就是你的程序是否支持不同的reduce任务在不同的worker上执行
  • 最后 一个是测试程序的容错性,当你的worker会延迟或者失败时,你的master是否可以重新进行任务的分配
bash test-mr.sh

设计实现

流程描述

整体的流程如上面的时序图描述所示,有几点需要注意的:

  1. 为了减少worker和master之间的服务调用,在实际实现的时候,把ACK请求和Apply请求给合并了,每次去请求新任务的售后都会把上次自己执行的请求的响应给带过去
  2. 如果返回回来的任务的task的编号是-1,表示没有获取到任务,此时master是出于等待其他worker执行结果的阶段,比如把map任务都派发完了
  3. 目前应为测试量小,所以其实reduce的过程中,worker是把自己需要reduce的内容都加载到内存里面的。这个其实是不合理的
  4. worker的退出时机:When the job is completely finished, the worker processes should exit. A simple way to implement this is to use the return value from call(): if the worker fails to contact the coordinator, it can assume that the coordinator has exited because the job is done, and so the worker can terminate too. Depending on your design, you might also find it helpful to have a "please exit" pseudo-task that the coordinator can give to workers.
  5. master的退出时机:全部的job任务都完成

类图设计

​​​​​​​

 类图设计如上,解释:

  1. 我们把每个map-reduce的过程都定义为一个Job,每个Job会有一些Task,分为map任务和reduce任务(不会同时出现这两种任务,因为reduce任务是在map阶段结束后才产生的)
  2. Coordinator负载整体的执行,将job的执行委托给了Scheduler类,Scheduler负载Job的执行(具体就是task的调度,包括分配task、接收task的处理结果、超时重试task、阶段推进,比如Map任务都结束了,就开始reduce阶段)

测试运行

项目里面提供了一次性测试和多次测试的脚本

# 一次
bash test-mr.sh
# 多次,比如测试5次
base test-mr-many.sh 5

如下是多次测试的效果截图

附录

RPC的使用

在实验里面使用了unix的通信方式,具体的代码如下,可以学习下

rpct/rpc.go代码

package rpct

import (
	"os"
	"strconv"
)

//
// example to show how to declare the arguments
// and reply for an RPC.
//

type ExampleArgs struct {
	X int
}

type ExampleReply struct {
	Y int
}

// Add your RPC definitions here.

// Cook up a unique-ish UNIX-domain socket name
// in /var/tmp, for the coordinator.
// Can't use the current directory since
// Athena AFS doesn't support UNIX-domain sockets.
func CoordinatorSock() string {
	s := "/var/tmp/rpct-mr-"
	s += strconv.Itoa(os.Getuid())
	return s
}

 server/server.go

package main

import (
	"log"
	"net"
	"net/http"
	"net/rpc"
	"os"
	"rpcunixt/rpct"
)

type Coordinator struct {
}

func (c *Coordinator) Example(args *rpct.ExampleArgs, reply *rpct.ExampleReply) error {
	reply.Y = args.X + 1
	return nil
}

func (c *Coordinator) server() {
	rpc.Register(c)
	rpc.HandleHTTP()
	sockname := rpct.CoordinatorSock()
	os.Remove(sockname)
	l, e := net.Listen("unix", sockname)
	if e != nil {
		log.Fatal("listen error: ", e)
	}
	go http.Serve(l, nil)
}

func main() {
	c := Coordinator{}
	c.server()
	select {}
}

client/client.go

package main

import (
	"log"
	"net"
	"net/http"
	"net/rpc"
	"os"
	"rpcunixt/rpct"
)

type Coordinator struct {
}

func (c *Coordinator) Example(args *rpct.ExampleArgs, reply *rpct.ExampleReply) error {
	reply.Y = args.X + 1
	return nil
}

func (c *Coordinator) server() {
	rpc.Register(c)
	rpc.HandleHTTP()
	sockname := rpct.CoordinatorSock()
	os.Remove(sockname)
	l, e := net.Listen("unix", sockname)
	if e != nil {
		log.Fatal("listen error: ", e)
	}
	go http.Serve(l, nil)
}

func main() {
	c := Coordinator{}
	c.server()
	select {}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值