【Mit6.824】Lab1 MapReduce

Lab1 MapReduce

MapReduce 简介

MapReduce的思想是,应用程序设计人员和分布式运算的使用者,只需要写简单的Map函数和Reduce函数,而不需要知道任何有关分布式的事情,MapReduce框架会处理剩下的事情。
Map函数使用一个key和一个value作为参数。入参中,key是输入文件的名字,value是输入文件的内容。
Reduce函数的入参是某个特定key的所有实例(Map输出中的key-value对中,出现了一次特定的key就可以算作一个实例)。所以Reduce函数也是使用一个key和一个value作为参数,其中value是一个数组,里面每一个元素是Map函数输出的key的一个实例的value。

MapReduce实现

Coordinator

mr/coodinator.go
Coordinator充当一个协调者或者master的角色,主要负责将任务分配给worker
Coordinator 主要维护了两个任务列表,Maper任务和Reducer任务,以及两种任务的完成状态,我们来看一下Coordinator的结构体:

type Coordinator struct {
	// Your definitions here.
	mu              sync.Mutex
	MapperFinished  bool           // mapper是否全部完成
	ReducerFinished bool           // reducer是否全部完成
	Mappers         []*MapperTask  // mapper任务
	Reducers        []*ReducerTask // reducer任务
}

其中MapperTask和ReducerTask都维护了可以完整代表一个map任务和reduce任务的属性:

type MapperTask struct {
	Index        int       // 任务编号
	Assigned     bool      // 是否分配
	AssignedTime time.Time // 分配时间
	IsFinished   bool      // 是否完成

	InputFile    string // 输入文件
	ReducerCount int    // 有多少路reducer

	timeoutTimer *time.Timer // 任务超时
}

type ReducerTask struct {
	Index        int       // 任务编号
	Assigned     bool      // 是否分配
	AssignedTime time.Time // 分配时间
	IsFinished   bool      // 是否完成

	MapperCount int //	有多少路mapper

	timeoutTimer *time.Timer // 任务超时
}

Coodinator要暴露出两个方法给worker通过rpc进行调用,分别是FetchTask()和UpdateTask()

Go RPC 的函数只有符合下面的条件才能被远程访问,不然会被忽略,详细的要求如下:

  1. 函数必须是导出的 (首字母大写)
  2. 必须有两个导出类型的参数
  3. 第一个参数是接收的参数,第二个参数是返回给客户端的参数,第二个参数必须是指针类型的
  4. 函数还要有一个返回值 error
    正确的 RPC 函数格式如下:
    func (t *T) MethodName(argType T1, replyType *T2) error

FetchTask为worker分配任务,当worker通过rpc调用FetchTask,coordinator会检查map任务是否全部完成,若没完成便分配map任务,若完成则同理检查reduce任务。

func (c *Coordinator) FetchTask(request *FetchTaskRequest, response *FetchTaskResponse) (err error) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if !c.MapperFinished {   //如果mapper没完成
		for _, mapper := range c.Mappers {   //遍历mappers任务列表
			if mapper.Assigned || mapper.IsFinished {
				continue
			}
			c.startMapper(mapper)
			task := *mapper // 副本
			response.MapperTask = &task
			return
		}
		return // 所有mapper任务都分配出去了,那么暂时没有工作了
	}
	if !c.ReducerFinished {   
		for _, reducer := range c.Reducers {
			if reducer.Assigned || reducer.IsFinished {
				continue
			}
			c.startReducer(reducer)
			task := *reducer
			response.ReducerTask = &task
			return
		}
		return // 所有reducer任务都分配出去了,那么暂时没有工作了
	}
	response.AllFinished = true
	return
}

UpdateTask主要是来更新任务状态的,拿到worker的请求后,如果是Mapper任务,则遍历coordinator的Mapper任务列表,找到此mapper任务,然后更改任务状态;reducer任务同理。
注意:每次修改一个任务的状态后要检查对应类型的任务是否全部完成

func (c *Coordinator) UpdateTask(request *UpdateTaskRequest, response *UpdateTaskResponse) (err error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if request.Mapper != nil {
		MapperFinished := true
		for _, mapper := range c.Mappers {
			if mapper.Index == request.Mapper.Index && mapper.Assigned && !mapper.IsFinished {
				c.finishMapper(mapper)
			}
			MapperFinished = MapperFinished && mapper.IsFinished
		}
		c.MapperFinished = MapperFinished   //检查mapper任务是否全部完成
	}
	if request.Reducer != nil {
		ReducerFinished := true
		for _, reducer := range c.Reducers {
			if reducer.Index == request.Reducer.Index && reducer.Assigned && !reducer.IsFinished {
				c.finishReducer(reducer)
			}
			ReducerFinished = ReducerFinished && reducer.IsFinished
		}
		c.ReducerFinished = ReducerFinished   //检查reducer任务是否完成
	}
	return
}

Worker

mr/worker.go
worker 的逻辑相对简单一些主要就是不断轮训做任务即可,遇到Map任务调用doMapperTask(),遇到Reduce任务就调用doReduceTask().

func Worker(mapf func(string, string) []KeyValue,
	reducef func(string, []string) string) {
	for {
		resp := CallFetchTask()
		if resp == nil {
			continue
		}
		if resp.AllFinished {
			return
		}
		if resp.MapperTask != nil {
			// 做mapper的事情
			doMapperTask(resp.MapperTask, mapf)
		}
		if resp.ReducerTask != nil {
			// 做reducer的事情
			doReducerTask(resp.ReducerTask, reducef)
		}
	}
}

doMapperTask():
在doMapperTask()方法中,进行Mapper的shuffle,经过Mapper后,输出的是Key/Value形式的,将这个键值对的key做hash%nReduce,每个mapper输出nReduce个中间文件来存储这些键值对。

func doMapperTask(mapperTask *MapperTask, mapf func(string, string) []KeyValue) {
	file, err := os.Open(mapperTask.InputFile)
	if err != nil {
		log.Fatalf("cannot open %v", mapperTask.InputFile)
	}
	content, err := ioutil.ReadAll(file)
	if err != nil {
		log.Fatalf("cannot read %v", mapperTask.InputFile)
	}
	file.Close()
	kva := mapf(mapperTask.InputFile, string(content))

	// mapper的shuffle
	// 10个reducer
	// 每1个mapper输出10路文件,对key做hash%10
	reducerKvArr := make([][]KeyValue, mapperTask.ReducerCount)

	for _, kv := range kva {
		reducerNum := ihash(kv.Key) % mapperTask.ReducerCount
		reducerKvArr[reducerNum] = append(reducerKvArr[reducerNum], kv)
	}

	for i, kvs := range reducerKvArr {
		sort.Sort(ByKey(kvs))

		filename := fmt.Sprintf("mr-%d-%d", mapperTask.Index, i)
		file, err := os.Create(filename + ".tmp")
		if err != nil {
			log.Fatalf("cannot write %v", filename+".tmp")
		}
		enc := json.NewEncoder(file)
		for _, kv := range kvs {
			err := enc.Encode(&kv)
			if err != nil {
				log.Fatalf("cannot jsonencode %v", filename+".tmp")
			}
		}
		file.Close()
		os.Rename(filename+".tmp", filename)
	}

	CallUpdateTaskForMapper(mapperTask)
}

doReducerTask():
在doReducerTask()方法中,reducer拿到自己的中间文件后,将自己对应的所有中间文件中的键值对全部取出后通过key进行排列,程序遍历排序后的中间数据,对于每一个唯一的中间 key 值,Reduce程序将这个 key 值和它相关的中间 value 值的集合传递给用户自定义的 Reduce 函数。Reduce 函数的输出被追加到所属分区的输出文件。

func doReducerTask(reducerTask *ReducerTask, reducef func(string, []string) string) {
	kvs := make([]KeyValue, 0)
	for i := 0; i < reducerTask.MapperCount; i++ {
		filename := fmt.Sprintf("mr-%d-%d", i, reducerTask.Index)
		fp, err := os.Open(filename)
		if err != nil {
			log.Fatalf("cannot open %v", filename)
		}
		dec := json.NewDecoder(fp)
		for {
			var kv KeyValue
			if err := dec.Decode(&kv); err != nil {
				break
			}
			kvs = append(kvs, kv)
		}
	}

	sort.Sort(ByKey(kvs)) // 按key排序的k-v列表

	ofileName := fmt.Sprintf("mr-out-%d", reducerTask.Index)
	ofile, err := os.Create(ofileName + ".tmp")
	if err != nil {
		log.Fatalf("cannot open %v", ofileName+".tmp")
	}

	// [i,j]
	i := 0
	for i < len(kvs) {
		j := i + 1
		for j < len(kvs) && kvs[j].Key == kvs[i].Key {
			j++
		}
		values := []string{}
		for k := i; k < j; k++ {
			values = append(values, kvs[k].Value)
		}
		output := reducef(kvs[i].Key, values)
		fmt.Fprintf(ofile, "%v %v\n", kvs[i].Key, output)
		i = j
	}
	ofile.Close()
	os.Rename(ofileName+".tmp", ofileName)
	CallUpdateTaskForReducer(reducerTask)
}
  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值