*** Starting wc test.
--- wc test: PASS
*** Starting indexer test.
--- indexer test: PASS
*** Starting map parallelism test.
--- map parallelism test: PASS
*** Starting reduce parallelism test.
--- reduce parallelism test: PASS
*** Starting job count test.
--- job count test: PASS
*** Starting early exit test.
--- early exit test: PASS
*** Starting crash test.
--- crash test: PASS
*** PASSED ALL TESTS
6.824 Schedule: Spring 2021
根据课程规定不能公开代码
前置知识:
MapReduce paper
the way to go
goroutine,chan,mutex,IO,json,rpc,data race
其中rpc在project已经有写好的架构了,mutex data race在课程也有提到,json lab提示里有
不懂的地方多看project已经有的源码
设计思路:
coordinator 分发task,记录task完成情况,shuffle
worker 询问task,map/reduce,报告完成
coordinator为rpc主节点,worker向其询问task,向其报告完成
一个文件作为一个task
shuffle时,按照ihash将key分为nReduce个文件,每个文件为一个task
按照以上思路应该可以完成除了crash test以外的test。(注意不要同时运行多个test)
crash test crash.go
func maybeCrash() {
max := big.NewInt(1000)
rr, _ := crand.Int(crand.Reader, max)
if rr.Int64() < 330 {
// crash!
os.Exit(1)
} else if rr.Int64() < 660 {
// delay for a while.
maxms := big.NewInt(10 * 1000)
ms, _ := crand.Int(crand.Reader, maxms)
time.Sleep(time.Duration(ms.Int64()) * time.Millisecond)
}
}
在map reduce中运行如上函数,使得有 1/3几率直接退出,1/3几率等到0-10s,1/3几率直接开始
所以在coordinator分发task时,需考虑将超时task重新分发。
所以综上二者考虑,我们将超过15s的task视为woker宕机,将task重新发放
在这里,我使用了一种类似LRU、LFU的机制。设定每个task的生命为15,启动一个线程,每隔1s,将task生命-1,当task生命为0,则task重新分配。
另外,需要注意的是,如果worker1超过15s,但它可能仍旧存活,worker2此时分配得到相同task,他们可能会抢夺相同命名文件。所以lab提示里有:使用tmp文件。使worker1、work2使用不同文件。由coordinator在记录task完成情况时,将文件名修改
不过此时,运行crash test还是会出问题,我发现原因在测试脚本中
( while [ -e $SOCKNAME -a ! -f mr-done ]
do
timeout -k 2s 180s ../mrworker ../../mrapps/crash.so
sleep 1
done ) &
( while [ -e $SOCKNAME -a ! -f mr-done ]
do
timeout -k 2s 180s ../mrworker ../../mrapps/crash.so
sleep 1
done ) &
while [ -e $SOCKNAME -a ! -f mr-done ]
do
timeout -k 2s 180s ../mrworker ../../mrapps/crash.so
sleep 1
done
-e -f检测文件是否存在,-a AND
这段代码是想让worker数量保持在3个,但是我运行的时候发现,worker数量无法保持在3个。所以我将这部分直接修改为30需根据具体任务数修改
for i in `seq 1 30`
do
{
timeout -k 2s 180s ../mrworker ../../mrapps/crash.so
}&
done
这样修改应该没有改变本意吧
然后就能通过测试了
遇到的一些问题
// worker.go
for{
go AskForTask(ch1,ch2)
select {
case u := <-ch1:
// ch1为Map
case u := <-ch2:
// ch2为Reduce
}
}
func AskForTask(ch1 chan *Reply, ch2 chan *Reply) {
args := Args{}
reply := Reply{}
call("Coordinator.Task", &args, &reply)
if reply.TaskType == -1 {// 无任务
} else if reply.TaskType == 0 {// Map
ch1 <- &reply
} else if reply.TaskType == 2 {// Reduce
ch2 <- &reply
}
}
如果worker没询问到任务,就会一直卡在select中,应改为
// worker.go
for{
go AskForTask(ch1,ch2)
select {
case u := <-ch1:
// ch1为Map
case u := <-ch2:
// ch2为Reduce
default:
}
time.Sleep(time.Duration(1) * time.Second)
}