6.5840 - 2026 年春季

6.5840 实验 1:MapReduce

协作政策 // 提交实验 // 配置Go // 指导 // Piazza


介绍

在这个实验中,你将构建一个 MapReduce 系统。 你将实现一个调用应用程序 Map 和 Reduce 函数并处理文件读写的 worker 进程, 以及一个将任务分配给 worker 并处理 worker 故障的 coordinator 进程。 你将构建类似于 MapReduce 论文 中的系统。 (注意:本实验使用 "coordinator" 代替论文中的 "master"。)

入门

你需要 配置 Go 来完成实验。

使用 git(一个版本控制系统)获取初始实验软件。 要了解更多关于 git 的知识,请查看 Pro Git 书git 用户手册

$ git clone git://g.csail.mit.edu/6.5840-golabs-2026 6.5840
$ cd 6.5840
$ ls
Makefile src
$

我们在 src/main/mrsequential.go 中提供了一个简单的串行 mapreduce 实现。它在单个进程中依次运行 map 和 reduce。我们还提供了几个 MapReduce 应用程序:mrapps/wc.go 中的词频统计,以及 mrapps/indexer.go 中的文本索引器。你可以按如下方式串行运行词频统计:

$ cd ~/6.5840
$ cd src/main
$ go build -buildmode=plugin ../mrapps/wc.go
$ rm mr-out*
$ go run mrsequential.go wc.so pg*.txt
$ sort mr-out-0
A 509
ABOUT 2
ACT 8
ACTRESS 1 
...
(你可能需要设置 LC_COLLATE=C 环境变量以使 sort 产生上述输出:LC_COLLATE=C sort mr-out-0

mrsequential.go 将其输出留在文件 mr-out-0 中。输入来自名为 pg-xxx.txt 的文本文件。

你可以随意从 mrsequential.go 中借用代码。你还应该查看 mrapps/wc.go 以了解 MapReduce 应用程序代码的样子。

在本实验及所有其他实验中,我们可能会对我们提供的代码进行更新。为了确保你能获取这些更新并使用 git pull 轻松合并,最好将我们提供的代码保留在原始文件中。你可以按照实验说明在我们提供的代码基础上添加内容;只是不要移动它。你可以将自己的新函数放在新文件中。

你的任务

你的任务是实现一个分布式 MapReduce,由两个程序组成:coordinator 和 worker。只会有一个 coordinator 进程,以及一个或多个并行执行的 worker 进程。在实际系统中,worker 会运行在一组不同的机器上,但在本实验中,你将它们全部运行在同一台机器上。worker 将通过 RPC 与 coordinator 通信。每个 worker 进程将在循环中向 coordinator 请求任务,从一个或多个文件读取任务输入,执行任务,将一个或多个文件的任务输出写入,然后再次向 coordinator 请求新任务。coordinator 应该能检测到一个 worker 是否在合理时间内没有完成任务(本实验中为十秒),并将同一任务分配给另一个 worker。

我们提供了一些初始代码。coordinator 和 worker 的 "main" 例程在 main/mrcoordinator.gomain/mrworker.go 中;不要修改这些文件。你应该将实现放在 mr/coordinator.gomr/worker.gomr/rpc.go 中。

以下是在词频统计 MapReduce 应用程序上运行你的代码的方法。首先,构建词频统计插件:

$ cd main
$ go build -buildmode=plugin ../mrapps/wc.go
在一个窗口中,运行 coordinator:
$ rm mr-out*
$ go run mrcoordinator.go sock123 pg-*.txt
sock123 参数指定了 coordinator 接收 worker RPC 的 socket。mrcoordinator.gopg-*.txt 参数是输入文件;每个文件对应一个 "split",是一个 Map 任务的输入。

在一个或多个其他窗口中,运行一些 worker:

$ go run mrworker.go wc.so sock123
当 worker 和 coordinator 完成后,查看 mr-out-* 中的输出。当你完成实验后,输出的排序并集应该与串行输出匹配,如下所示:
$ cat mr-out-* | sort | more
A 509
ABOUT 2
ACT 8
ACTRESS 1 
...

我们提供了所有用于评分的测试。测试的源代码在 mr/mr_test.go 中。你可以在 src 目录中运行测试:

$ cd src
$ make mr
...
这些测试检查 wcindexer MapReduce 应用程序在给定 pg-xxx.txt 文件作为输入时是否产生正确的输出。测试还检查你的实现是否并行运行 Map 和 Reduce 任务,以及你的实现是否能从运行任务时崩溃的 worker 中恢复。

如果你现在运行测试,它们会在第一个测试中挂起:

$ cd ~/6.5840/src
$ make mr
...
cd mr; go test -v -race 
=== RUN   TestWc
...

你可以在 mr/coordinator.go 的 Done 函数中将 ret := false 改为 true,使 coordinator 立即退出。然后:

$ make mr
...
=== RUN   TestWc
2026/01/22 14:56:24 reduce created no mr-out-X output files!
exit status 1
FAIL    6.5840/mr       4.516s
make: *** [Makefile:44: mr] Error 1
$

测试期望在名为 mr-out-X 的文件中看到输出,每个 reduce 任务一个。mr/coordinator.gomr/worker.go 的空实现不会生成这些文件(也基本不做其他任何事情),所以测试失败。

当你完成后,测试输出应该如下所示:

$ make mr
...
=== RUN   TestWc
--- PASS: TestWc (8.64s)
=== RUN   TestIndexer
--- PASS: TestIndexer (5.90s)
=== RUN   TestMapParallel
--- PASS: TestMapParallel (7.05s)
=== RUN   TestReduceParallel
--- PASS: TestReduceParallel (8.05s)
=== RUN   TestJobCount
--- PASS: TestJobCount (10.04s)
=== RUN   TestEarlyExit
--- PASS: TestEarlyExit (6.05s)
=== RUN   TestCrashWorker
2026/01/22 14:58:14 *re*-starting map ../../main/pg-tom_sawyer.txt 0
2026/01/22 14:58:14 *re*-starting map ../../main/pg-metamorphosis.txt 2
2026/01/22 14:58:39 *re*-starting map ../../main/pg-metamorphosis.txt 2
2026/01/22 14:58:40 map 2 already done
2026/01/22 14:58:45 *re*-starting reduce 0
--- PASS: TestCrashWorker (40.18s)
PASS
ok      6.5840/mr       86.932s
$

根据你终止 worker 进程的策略,你可能会看到如下错误:

2026/02/11 16:21:32 dialing:dial unix /var/tmp/5840-mr-501: connect: connection refused
每个测试看到少量这些消息是正常的;它们出现在 coordinator 退出后 worker 无法联系 coordinator RPC 服务器时。

一些规则:

提示

不计分挑战练习

实现你自己的 MapReduce 应用程序(参见 mrapps/* 中的示例),例如分布式 Grep(MapReduce 论文的第 2.3 节)。

让你的 MapReduce coordinator 和 worker 在单独的机器上运行,就像实际部署中那样。你需要设置 RPC 通过 TCP/IP 而不是 Unix socket 进行通信(参见 Coordinator.server() 中被注释掉的行),并使用共享文件系统读写文件。例如,你可以在 MIT 的多个 Athena 集群机器上使用 ssh,它们使用 AFS 共享文件;或者你可以租用几个 AWS 实例并使用 S3 进行存储。