docs: add Lab1 MapReduce experiment report
This commit is contained in:
277
docs/answers/lab1-mapreduce.md
Normal file
277
docs/answers/lab1-mapreduce.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# MIT 6.824 Lab1: MapReduce 实验报告
|
||||
|
||||
> 📅 日期:2026-02-26
|
||||
> 🔗 代码仓库:https://git.rc707blog.top/rose_cat707/6.824-golabs-2021-6.824
|
||||
> 🌿 分支:answer/20260226
|
||||
|
||||
---
|
||||
|
||||
## 1. 实验概述
|
||||
|
||||
本实验要求实现一个分布式 MapReduce 系统,包括 Coordinator(协调者)和 Worker(工作者)两个核心组件。系统需要能够:
|
||||
|
||||
- 将 Map 和 Reduce 任务分配给多个 Worker 并行执行
|
||||
- 处理 Worker 崩溃的情况(故障容错)
|
||||
- 确保输出文件的原子性写入
|
||||
|
||||
## 2. 系统架构
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ Coordinator │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
|
||||
│ │ Map Tasks │ │ Reduce Tasks│ │ Timeout Checker │ │
|
||||
│ │ (8 files) │ │ (10 tasks) │ │ (10 seconds) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
│ RequestTask/ReportTask (RPC)
|
||||
▼
|
||||
┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ Worker1 │ │ Worker2 │ │ Worker3 │
|
||||
└─────────┘ └─────────┘ └─────────┘
|
||||
```
|
||||
|
||||
## 3. 核心实现
|
||||
|
||||
### 3.1 RPC 定义 (`rpc.go`)
|
||||
|
||||
定义了 Worker 和 Coordinator 之间的通信协议:
|
||||
|
||||
```go
|
||||
// 任务类型
|
||||
type TaskType int
|
||||
const (
|
||||
MapTask TaskType = 0 // Map 任务
|
||||
ReduceTask TaskType = 1 // Reduce 任务
|
||||
WaitTask TaskType = 2 // 等待(无可用任务)
|
||||
ExitTask TaskType = 3 // 退出(全部完成)
|
||||
)
|
||||
|
||||
// 任务状态
|
||||
type TaskStatus int
|
||||
const (
|
||||
Idle TaskStatus = 0 // 空闲
|
||||
InProgress TaskStatus = 1 // 进行中
|
||||
Completed TaskStatus = 2 // 已完成
|
||||
)
|
||||
```
|
||||
|
||||
### 3.2 Coordinator (`coordinator.go`)
|
||||
|
||||
Coordinator 负责任务调度和状态管理:
|
||||
|
||||
#### 数据结构
|
||||
|
||||
```go
|
||||
type Coordinator struct {
|
||||
mu sync.Mutex // 保护并发访问
|
||||
|
||||
files []string // 输入文件列表
|
||||
nReduce int // Reduce 任务数量
|
||||
|
||||
mapTasks []Task // Map 任务列表
|
||||
mapDone int // 已完成的 Map 任务数
|
||||
mapFinished bool // Map 阶段是否完成
|
||||
|
||||
reduceTasks []Task // Reduce 任务列表
|
||||
reduceDone int // 已完成的 Reduce 任务数
|
||||
reduceFinished bool // Reduce 阶段是否完成
|
||||
}
|
||||
```
|
||||
|
||||
#### 任务分配策略
|
||||
|
||||
1. **两阶段执行**:先完成所有 Map 任务,再开始 Reduce 任务
|
||||
2. **超时重试**:任务分配 10 秒后未完成则重新分配(处理 Worker 崩溃)
|
||||
3. **状态追踪**:每个任务有 Idle → InProgress → Completed 的状态转换
|
||||
|
||||
```go
|
||||
func (c *Coordinator) RequestTask(args *RequestTaskArgs, reply *RequestTaskReply) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
// 检查超时任务
|
||||
c.checkTimeouts()
|
||||
|
||||
// 优先分配 Map 任务
|
||||
if !c.mapFinished {
|
||||
for i := range c.mapTasks {
|
||||
if c.mapTasks[i].Status == Idle {
|
||||
// 分配任务并记录开始时间
|
||||
c.mapTasks[i].Status = InProgress
|
||||
c.mapTasks[i].StartTime = time.Now()
|
||||
reply.TaskType = MapTask
|
||||
reply.TaskID = i
|
||||
reply.Filename = c.mapTasks[i].Filename
|
||||
return nil
|
||||
}
|
||||
}
|
||||
reply.TaskType = WaitTask // 所有任务都在进行中
|
||||
return nil
|
||||
}
|
||||
|
||||
// Map 完成后分配 Reduce 任务
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Worker (`worker.go`)
|
||||
|
||||
Worker 负责执行具体的 Map 和 Reduce 任务:
|
||||
|
||||
#### 主循环
|
||||
|
||||
```go
|
||||
func Worker(mapf func(string, string) []KeyValue,
|
||||
reducef func(string, []string) string) {
|
||||
|
||||
for {
|
||||
reply := requestTask()
|
||||
|
||||
switch reply.TaskType {
|
||||
case MapTask:
|
||||
doMapTask(mapf, reply)
|
||||
case ReduceTask:
|
||||
doReduceTask(reducef, reply)
|
||||
case WaitTask:
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
case ExitTask:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Map 任务执行
|
||||
|
||||
```go
|
||||
func doMapTask(mapf func(string, string) []KeyValue, task RequestTaskReply) {
|
||||
// 1. 读取输入文件
|
||||
content, _ := ioutil.ReadFile(task.Filename)
|
||||
|
||||
// 2. 调用 Map 函数
|
||||
kva := mapf(task.Filename, string(content))
|
||||
|
||||
// 3. 按 key 的 hash 值分桶
|
||||
buckets := make([][]KeyValue, task.NReduce)
|
||||
for _, kv := range kva {
|
||||
bucket := ihash(kv.Key) % task.NReduce
|
||||
buckets[bucket] = append(buckets[bucket], kv)
|
||||
}
|
||||
|
||||
// 4. 写入中间文件 mr-X-Y (X=mapID, Y=reduceID)
|
||||
for reduceID, bucket := range buckets {
|
||||
tmpFile, _ := ioutil.TempFile("", "mr-map-*")
|
||||
enc := json.NewEncoder(tmpFile)
|
||||
for _, kv := range bucket {
|
||||
enc.Encode(&kv)
|
||||
}
|
||||
tmpFile.Close()
|
||||
// 原子重命名
|
||||
os.Rename(tmpFile.Name(), fmt.Sprintf("mr-%d-%d", task.TaskID, reduceID))
|
||||
}
|
||||
|
||||
// 5. 报告完成
|
||||
reportTask(MapTask, task.TaskID, true)
|
||||
}
|
||||
```
|
||||
|
||||
#### Reduce 任务执行
|
||||
|
||||
```go
|
||||
func doReduceTask(reducef func(string, []string) string, task RequestTaskReply) {
|
||||
// 1. 读取所有相关的中间文件 mr-*-Y
|
||||
var kva []KeyValue
|
||||
for mapID := 0; mapID < task.NMap; mapID++ {
|
||||
filename := fmt.Sprintf("mr-%d-%d", mapID, task.TaskID)
|
||||
// 读取并解码 JSON
|
||||
}
|
||||
|
||||
// 2. 按 key 排序
|
||||
sort.Sort(ByKey(kva))
|
||||
|
||||
// 3. 对每个 key 调用 Reduce 函数
|
||||
for i < len(kva) {
|
||||
// 收集相同 key 的所有 value
|
||||
values := collectValues(kva, i, &j)
|
||||
output := reducef(kva[i].Key, values)
|
||||
fmt.Fprintf(tmpFile, "%v %v\n", kva[i].Key, output)
|
||||
i = j
|
||||
}
|
||||
|
||||
// 4. 原子重命名为 mr-out-Y
|
||||
os.Rename(tmpFile.Name(), fmt.Sprintf("mr-out-%d", task.TaskID))
|
||||
|
||||
// 5. 报告完成
|
||||
reportTask(ReduceTask, task.TaskID, true)
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 关键设计决策
|
||||
|
||||
### 4.1 故障容错
|
||||
|
||||
- **超时机制**:任务分配后 10 秒未完成,自动重置为 Idle 状态
|
||||
- **原子写入**:使用临时文件 + rename 确保崩溃时不产生部分写入的文件
|
||||
- **幂等性**:相同任务可以被多次执行,最终结果一致
|
||||
|
||||
### 4.2 并发控制
|
||||
|
||||
- 使用 `sync.Mutex` 保护 Coordinator 的共享状态
|
||||
- Worker 是无状态的,可以随时崩溃和重启
|
||||
|
||||
### 4.3 中间文件格式
|
||||
|
||||
- 文件名:`mr-X-Y`(X = Map 任务 ID,Y = Reduce 任务 ID)
|
||||
- 编码:JSON(便于调试和兼容性)
|
||||
|
||||
## 5. 测试结果
|
||||
|
||||
```
|
||||
*** 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 ✅
|
||||
```
|
||||
|
||||
### 测试说明
|
||||
|
||||
| 测试名称 | 测试内容 |
|
||||
|---------|---------|
|
||||
| wc | 基本的 word count 功能 |
|
||||
| indexer | 倒排索引功能 |
|
||||
| map parallelism | Map 任务并行执行 |
|
||||
| reduce parallelism | Reduce 任务并行执行 |
|
||||
| job count | 确保每个 Map 任务只执行一次 |
|
||||
| early exit | Worker 在任务完成前不会退出 |
|
||||
| crash | Worker 崩溃时的容错恢复 |
|
||||
|
||||
## 6. 总结与收获
|
||||
|
||||
### 实现要点
|
||||
|
||||
1. **两阶段同步**:Map 阶段必须全部完成才能开始 Reduce 阶段
|
||||
2. **超时重试**:处理 Worker 崩溃的核心机制
|
||||
3. **原子操作**:确保输出文件的一致性
|
||||
|
||||
### 学到的知识
|
||||
|
||||
- 分布式系统中的任务调度
|
||||
- RPC 通信机制
|
||||
- 故障容错设计模式
|
||||
- Go 语言的并发编程
|
||||
|
||||
---
|
||||
|
||||
> 💡 这是 MIT 6.824 分布式系统课程的第一个 Lab,为后续的 Raft 和分布式 KV 存储奠定了基础。
|
||||
Reference in New Issue
Block a user