MapReduce 框架
MapReduce框架用于大量数据的并行运算,框架的使用者只需要实现自己的Map和Reduce函数就可以使用。例如实验中进行的词频统计
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
}
func Reduce(key string, values []string) string {
// return the number of occurrences of this word.
return strconv.Itoa(len(values))
}
Map函数使用一个key和一个value作为参数。入参中,key是输入文件的名字,通常会被忽略,因为我们不太关心文件名是什么,value是输入文件的内容。Map函数的输出是一个key-value对的列表。
Reduce函数的入参是某个特定key的所有实例(Map输出中的key-value对中,出现了一次特定的key就可以算作一个实例)。所以Reduce函数也是使用一个key和一个value作为参数,其中value是一个数组,里面每一个元素是Map函数输出的key的一个实例的value。在词频统计中,只需要统计单词的数量,因此Reduce只需要统计传入参数的长度。
而实验中要做的就是能够为Map和Reduce函数提供正确的输入并将输出保存到输出文件,并保证任务并行。
Worker
首先完成Worker的Map和Reduce任务的执行。
Map任务只需读取对应的文件内容,将内容传递给Map()函数即可。重点是处理输出,需要将输出进行排序并写入到对应的中间文件,以便后续Reduce处理。为了有序高效的处理,还需要使用hash将不同的key放入不同的桶中,实验要求NReduce=10,即桶的数量为10,Reduce阶段时,会以桶为单位进行处理。所以每个Map任务执行过后,会输出NReduce个中间文件。
Reduce任务,每个任务处理指定的桶,根据桶编号读取所有对应的中间文件,执行Reduce函数并写入到输出文件。
func Worker(mapf func(string, string) []KeyValue,
reducef func(string, []string) string) {
log.Printf("Worker started\n")
for {
taskReply := TaskReply{}
CallTask(&TaskArgs{}, &taskReply)
switch taskReply.TaskType {
case TaskMap:
Map(taskReply.MapInfo, mapf)
finishMapArgs := FinishArgs{
TaskId: taskReply.MapInfo.TaskId,
TaskType: TaskMap,
}
finishMapReply := FinishReply{}
CallFinish(&finishMapArgs, &finishMapReply)
case TaskReduce:
Reduce(taskReply.ReduceInfo, reducef)
finishReduceArgs := FinishArgs{
TaskId: taskReply.ReduceInfo.TaskId,
TaskType: TaskReduce,
}
finishReduceReply := FinishReply{}
CallFinish(&finishReduceArgs, &finishReduceReply)
case TaskWait:
time.Sleep(time.Second)
case TaskExit:
default:
log.Printf("Worker exiting\n")
return
}
}
}
Coordinator
Coordinator发布给Worker的任务要包含有任务类型,Worker需要根据不同的任务类型,选择不同的操作。要注意的是如果暂时没有任务,Worker需要等待一段时间再向Coordinator获取任务,这里的等待时间不宜过长,否则可能会导致并行测试失败,即所有任务都被一个Worker完成了,另一个Worker还在sleep
Coordinator负责任务的发布,因此数据结构中需要有一个数组来记录所有需要处理的文件。可以使用channel来进行任务的发布,任务发布协程和负责超时重传的定时器均可以向channel中写入任务(生产)。在收到Worker的任务请求时从channel中接收任务并发送即可(消费),若channel中暂时没有任务则将任务类型设为等待(这里可以使用select来分别处理这两种情况)。
Coordinator同时还需要一个定时器数组来保存每个任务的定时器,以便在收到Worker的任务完成通知时,取消超时任务。
Coordinator也需要一个状态字段,来记录当前阶段是Map还是Reduce以便发布对应的任务。
同时使用finishNum来记录当前已经完成的任务数,当任务全部完成时切换状态。
使用finishNum时,遇到了一个问题,当任务超时会调度新的Worker来执行,这时原本负责的Worker执行结束通知Coordinator,finishNum数量加一。但是新的Worker会再次重复执行该任务,完成后,finishNum再次加一。这显然是不允许的。所以需要一个数组来记录每个任务的状态,Coordinator收到任务完成通知时,检查该任务的状态,若本来就是已完成,则不会对finishNum加一,防止重复任务执行带来的影响。
另外对于fiishNum和状态字段,需要加锁来进行保护,避免同时修改。
Coordinator最终的数据结构如下
type Coordinator struct {
mu sync.Mutex
files []string
taskStatus []bool
finishNum int
taskCh chan int
//通知任务发布协程,开始发送下一阶段任务
stateCh chan struct{}
nReduce int
status int
timeout time.Duration
taskTimeouts []*time.Timer
}
另外Coordinator在任务全部完成退出时,可以先sleep,让Worker先退出。
752

被折叠的 条评论
为什么被折叠?



