dec := json.NewDecoder(file) // json.Decoder 实例,解码为 json 格式
for {
var kv KeyValue
if err := dec.Decode(&kv); err != nil {
break
}
kva = append(kva, kv)
}
使用 mrapps/crash.go 插件测试崩溃恢复
Go复制代码
go build -race -buildmode=plugin crash.go // 编译插件文件
go run -race mrcoordinator.go pg-*.txt // 根据输入文件,启动 MapReduce 作业
go run -race mrworker.go crash.so // 运行 worker 进程,使用插件故意崩溃
package mr
import (
"log"
"net"
"os"
"net/rpc"
"net/http"
)
// Coordinator 负责管理和分配任务
type Coordinator struct {
// Your definitions here.
}
// RPC handlers for the worker to call.
// an example RPC handler.
// the RPC argument and reply types are defined in rpc.go
// rpc 调用的参数和返回值,在 rpc.go 中定义
func (c *Coordinator) Example(args *ExampleArgs, reply *ExampleReply) error {
reply.Y = args.X + 1
return nil // rpc 调用成功
}
// start a thread that listens for RPCs from worker.go
func (c *Coordinator) server() {
// 注册协调者实例,处理 RPC 调用
rpc.Register(c)
// 允许使用 HTTP 协议进行 RPC 通信
rpc.HandleHTTP()
// 协调者 socket 文件名
sockname := coordinatorSock()
// 监听前移除已存在的 socket 文件,避免监听失败
os.Remove(sockname)
// 监听 UNIX socket,准备接收来自 worker 的连接
l, e := net.Listen("unix", sockname)
if e != nil {
log.Fatal("listen error:", e)
}
// 新的 goroutine 中启动 HTTP 服务,以处理 RPC 请求
go http.Serve(l, nil)
}
// main/mrcoordinator.go 会定期调用 Done() 函数来检查整个作业是否已完成。
func (c *Coordinator) Done() bool {
ret := false
// Your code here to implement the check for completion of all tasks
// 在这里实现检查所有任务是否完成的逻辑,例如检查所有 Map 和 Reduce 任务的状态
return ret // 作业是否完成
}
// create a Coordinator
// main/mrcoordinator.go calls this function
// nReduce is the number of reduce tasks to use.
// The returned value is a pointer to the newly created Coordinator instance.
func MakeCoordinator(files []string, nReduce int) *Coordinator {
c := Coordinator{}
// Your code here to initialize the Coordinator, e.g., load input files, setup tasks, etc
// 启动 RPC 服务器线程,以便监听和处理来自 worker 的 RPC 请求
c.server()
// 返回指向新创建的协调者实例的指针,这样调用者就可以通过这个指针来访问和操作协调者实例
return &c
}
mr/worker.go
KeyValue 结构体
ihash():返回 reduce 任务编号(用于发送 Map 输出的数据)
Worker():调用插件中的 map() 和 reduce() 函数
CallExample():展示 rpc 调用的完整流程,需要借助 call()
call():建立 rpc 连接,再发送 rpc 请求
Go复制代码
// package mr - 定义了MapReduce作业的工作者包,包含实现MapReduce算法所需的结构和函数
// import语句 - 日志记录、rpc远程过程调用、哈希计算
package mr
import (
"fmt"
"log"
"net/rpc"
"hash/fnv"
)
// 定义 MapReduce 中的键值对
type KeyValue struct {
Key string
Value string
}
// 自定义的哈希函数,用于确定Map输出的键值对,应该发送到哪个Reduce任务
// Map阶段输出的键分配到不同的Reduce任务
func ihash(key string) int {
h := fnv.New32a() // 创建FNV-1a哈希生成器
// 字符串 key 转为 []byte 字节切片,因为 Wirte() 需要操作字节数据
h.Write([]byte(key)) // 将键的字节序列写入哈希生成器
// 使用按位与操作确保结果是一个非负整数,适合作为索引使用
// 0x7fffffff 就是 0111 1111 ... 1111,符号位为正,其他不变
return int(h.Sum32() & 0x7fffffff)
}
// Worker 函数 - 是MapReduce工作者的主要工作函数
// 它调用用户提供的map和reduce函数
// main/mrworker.go calls this function.
// 传入的两个参数是 mapf() 和 reducef()
func Worker(mapf func(string, string) []KeyValue,
reducef func(string, []string) string) {
// 工作者实现细节将在这里编写,包括从协调者接收任务和发送结果
}
// example function to show how to make an RPC call to the coordinator.
func CallExample() {
// {X: 99} 结构体字面量, X 初始化为 99
args := ExampleArgs{X: 99} // rpc通信中传递的参数
reply := ExampleReply{} // 用于存储响应的返回值
// 发送RPC请求到协调者,等待回复
// 服务名称.方法名称,rpc包会根据这个字符串,找到对应的服务和方法进行调用
call("Coordinator.Example", &args, &reply)
fmt.Printf("reply.Y %v\n", reply.Y)
}
// send an RPC request to the coordinator, wait for the response.
func call(rpcname string, args interface{}, reply interface{}) bool {
sockname := coordinatorSock() // 获取协调者socket名称
c, err := rpc.DialHTTP("unix", sockname) // 建立RPC连接
if err != nil {
log.Fatal("dialing:", err)
}
defer c.Close()
// Call 方法是 net/rpc 包中的 *rpc.Client 类型的一个实例方法
err = c.Call(rpcname, args, reply) // 发送RPC请求
if err == nil {
return true
}
fmt.Println(err)
return false
}
mr/rpc.go
ExampleArgs 和 ExampleReply,表示 rpc 参数和 rpc 返回值两种类型
coordinatorSock():为协调者生成 socket 文件名
Go复制代码
package mr
// RPC definitions
// remember to capitalize(大写) all names
import "os" // 操作系统功能,获取用户ID
import "strconv" // 字符串转换
// example to show how to declare the arguments(参数)
// and reply(返回值) for an RPC
type ExampleArgs struct {
X int
}
type ExampleReply struct {
Y int
}
// Add your RPC definitions here
// Cook up a unique-ish UNIX-domain socket name
// in /var/tmp, for the coordinator
// Can't use the current directory since
// Athena AFS doesn't support UNIX-domain sockets.
// 这里指定的是一个UNIX域socket的文件路径前缀,它位于/var/tmp目录下
// 并且以"824-mr-"作为前缀,以确保socket文件名的唯一性
// 用于获取协调者的socket文件名,以便建立RPC连接
func coordinatorSock() string {
// 定义UNIX域socket的基础路径,前缀为"/var/tmp/824-mr-"
s := "/var/tmp/824-mr-"
// 将当前用户的UID转换为字符串并追加到基础路径之后,创建一个唯一的socket文件名
s += strconv.Itoa(os.Getuid())
return s // 协调者监听的socket文件的路径
}