前言
- 此项目来源于mit6.824Lab1
- 链接:pdos.csail.mit.edu/6.824
- 论文链接:https://pdos.csail.mit.edu/6.824/papers/mapreduce.pdf
- 建议大家做lab的时候先看下代码和注释,之后再确定思路
- 点进去之后能看到下面这个,就是实验指导手册
一、MapReduce是什么?
- MapReduce 是一个编程模型,用来处理大数据的。
- 假设现在有一个场景,需要统计客户最爱吃的水果,有西瓜,苹,柿子等等,哪种水果出现的次数多,客户就是最爱吃哪种水果。
- 如果是单机运行的话,只能是遍历一边,然后找到那个出现最多次数的。数据很多,遍历很慢,不划算。
- 单机使用mapreduce
- map,如下图
- reduce(统计词频),如下图
- 单机使用mapreduce的话还是太慢了,因为我们知道多线程么,很多任务一起执行肯定很快,所以这里可以使用多线程技术来提高速度,但其实多线程是指从软件或者硬件上实现多个线程并发执行的技术,它更多的是解决CPU调度多个进程的问题,从而让这些进程看上去是同时执行,注意:是看上去像是同时执行,事实上不是同时执行。
- 分布式则是为了解决单个物理服务器容量和性能瓶颈问题而采用的优化手段。这个是真的在不同机器上同时执行,所以这个效率高。
二.编程模型
- 这里直接用mit6.824的例子来讲下其编成模型。
- 这个例子就是官网上面的(如下图):
- 首先,mapreduce的模型之前简单叙述过,这里从几个命令开始讲起:
//看着名字就是知道,就是根据mrapps/目录下的wc.go文件生成一个插件
//这个插件是wc.so
go build -buildmode=plugin ../mrapps/wc.go
//这个意思是运行mrsequential.go ,wc.go是上面生成的插件,后面的是数据集
go run mrsequential.go wc.so pg*.txt
//查看数据集
more mr-out-0
- 讲代码
- wc.go
//map方法 filename:文件名字 contents:文件内容 输出在mr包里面
/*
type KeyValue struct {
Key string
Value string
}
*/
func Map(filename string, contents string) []mr.KeyValue {
//这两行代码是将单词按照空格分开,变成数组
//比如:西瓜 苹果 这是一个字符串
//经过下面两行代码之后:[西瓜 苹果],变一个字符数组了
ff := func(r rune) bool { return !unicode.IsLetter(r) }
words := strings.FieldsFunc(contents, ff)
//初始化kva
kva := []mr.KeyValue{}
//key:单词(西瓜 苹果),Value:1,都是1
for _, w := range words {
kv := mr.KeyValue{w, "1"}
kva = append(kva, kv)
}
//words这个字符数组经过上面的for循环之前(西瓜 苹果 西瓜)
//words这个字符数组经过上面的for循环之后[{"西瓜",“1”}{"苹果",“1”}{"西瓜",“1”}]
return kva
}
func Reduce(key string, values []string) string {
// strconv函数将int转化为string返回
return strconv.Itoa(len(values))
}
- mrsequential.go
// for sorting by key.
//中间结果集,用来排序的
type ByKey []mr.KeyValue
// for sorting by key.
func (a ByKey) Len() int { return len(a) }
func (a ByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByKey) Less(i, j int) bool { return a[i].Key < a[j].Key }
func main() {
// 在go run mrsequential.go ../mrapps/wc.so pg*.txt中
// 参数0:mrsequential.go
// 参数1:wc.so
// 参数2:pg*.txt(这里其实输入文件算是多个参数)
if len(os.Args) < 3 {
fmt.Fprintf(os.Stderr, "Usage: mrsequential xxx.so inputfiles...\n")
os.Exit(1)
}
//加载map,reduce程序
mapf, reducef := loadPlugin(os.Args[1])
//开始map过程
intermediate := []mr.KeyValue{}
//读取文件名字pg*.txt
for _, filename := range os.Args[2:] {
file, err := os.Open(filename)
if err != nil {
log.Fatalf("cannot open %v", filename)
}
//获取文件内容
content, err := ioutil.ReadAll(file)
if err != nil {
log.Fatalf("cannot read %v", filename)
}
file.Close()
//使用map方法
kva := mapf(filename, string(content))
//在后面追加
intermediate = append(intermediate, kva...)
}
//排序
//[{"西瓜",“1”}{"苹果",“1”}{"西瓜",“1”}] -> [{"西瓜",“1”} {"西瓜",“1”} {"苹果",“1”}]
sort.Sort(ByKey(intermediate))
oname := "mr-out-0"
ofile, _ := os.Create(oname)
//把key相等的对应的value进行叠加
i := 0
for i < len(intermediate) {
j := i + 1
for j < len(intermediate) && intermediate[j].Key == intermediate[i].Key {
j++
}
values := []string{}
for k := i; k < j; k++ {
values = append(values, intermediate[k].Value)
}
//输出其长度,就是词频
output := reducef(intermediate[i].Key, values)
fmt.Fprintf(ofile, "%v %v\n", intermediate[i].Key, output)
i = j
}
ofile.Close()
}
//加载方法
func loadPlugin(filename string) (func(string, string) []mr.KeyValue, func(string, []string) string) {
p, err := plugin.Open(filename)
if err != nil {
log.Fatalf("cannot load plugin %v", filename)
}
xmapf, err := p.Lookup("Map")
if err != nil {
log.Fatalf("cannot find Map in %v", filename)
}
mapf := xmapf.(func(string, string) []mr.KeyValue)
xreducef, err := p.Lookup("Reduce")
if err != nil {
log.Fatalf("cannot find Reduce in %v", filename)
}
reducef := xreducef.(func(string, []string) string)
return mapf, reducef
}
自己实现MapReduce
- 只说思路(参考各位大佬的,这里实现的是多线程版,集群版等找完工作在弄下吧)
- 程序入口:mrmaster.go.
- 程序入口:mrworker.go
- mrmaster.go.
import "../mr"
import "time"
import "os"
import "fmt"
func main() {
if len(os.Args) < 2 {
fmt.Fprintf(os.Stderr, "Usage: mrmaster inputfiles...\n")
os.Exit(1)
}
//这里是创建了一个Master
//输入的参数:文件名字 reduce任务的个数
m := mr.MakeMaster(os.Args[1:], 10)
//通过Done来判断任务是否结束
for m.Done() == false {
time.Sleep(time.Second)
}
time.Sleep(time.Second)
}
- mrworker.go
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: mrworker xxx.so\n")
os.Exit(1)
}
//加载了两个方法(这两个方法就是wc.go里面的map和reduce)
mapf, reducef := loadPlugin(os.Args[1])
//启动worker
mr.Worker(mapf, reducef)
}
func loadPlugin(filename string) (func(string, string) []mr.KeyValue, func(string, []string) string) {
p, err := plugin.Open(filename)
if err != nil {
log.Fatalf("cannot load plugin %v", filename)
}
xmapf, err := p.Lookup("Map")
if err != nil {
log.Fatalf("cannot find Map in %v", filename)
}
mapf := xmapf.(func(string, string) []mr.KeyValue)
xreducef, err := p.Lookup("Reduce")
if err != nil {
log.Fatalf("cannot find Reduce in %v", filename)
}
reducef := xreducef.(func(string, []string) string)
return mapf, reducef
}
- 根据实验说明应该还有个rpc.go需要我们来写
- 所以,这里主要有三个东西,master worker rpc
master里面要写的东西
- func (m *Master) server():监听worker的请求
- func (m *Master) Done() bool :判断任务是否完成
- func MakeMaster(files []string, nReduce int) *Master :初始化master
- 以上三个函数都是文件里面自带的,而且有注释
- 在写master时可以根据上述代码进行扩展
worker里面要写的东西
- func Worker(mapf func(string, string) []KeyValue,reducef func(string, []string) string)
- 这个是worker工作的主线程,用来向master要任务并且返回结果的
- func call(rpcname string, args interface{}, reply interface{}) bool
- 这个是用来进行远程调用的,因为我们的任务肯定是在master里面初始化好的,然而worker要处理任务,所以这里会用到远程调用,调用master里面的东西
rpc里面要写的东西
- 因为这个案例比较简单,rpc里面不涉及到接口逻辑,只是一个数据的中间存储而已
思路
整体思路
-
说完了上面的,大胆猜想下,应该是有思路的
-
就是master先生成任务,worker去找master要任务嘛,就这么简单,然后rpc里面就存一些和master,worker关系不大的中间变量。
-
下面这个是个详细点的图