论文:
MapReduce: Simplified Data Processing on Large Clusters
Jeffrey Dean and Sanjay Ghemawat
https://pdos.csail.mit.edu/6.824/papers/mapreduce.pdf
MapReduce 是一种分布式系统中处理大数据方法。他提出是在 2004, jeff dean 和 Sanjay Ghemawat 的作品,和 GFS、BigTable 并列 Google 分布式系统的三驾马车。后面,基于 mapreduce 的框架由于种种问题已经很少使用了(2014 Google I/O),但是 map reduce 的思想(感觉也是人很自然想到的分治思想)不过时。
mapreduce 框架淘汰的原因是,对于简单的操作,map reduce 的严格框架方便了下层,但是上层写协同的处理时候必须很复杂。而底层的实现上仍然涉及很多部署和运维、整体的性能优化等问题,并不是写一个 map + reduce 函数就能成功了。结果就是,反而这层隔离抽象又好像没有了,仍然需要技术团队根据不同的业务数据特征折腾不同的性能优化和配置(属于是超参数)。
未来的层次抽象可能希望集中上层业务注意力到写 OLAP 的 DML 上,分布式的 HTAP、OLAP 数据库负责搞定下层的各种脏活(spanner/f1)?
整体论文看了我好久,而且最后感觉我自己的笔记好像也没做到什么。但是在读论文之前看别人的笔记或者简略的话我又感觉不知道他在说什么,缺失了某种上下文信息。
看完论文之后再看别人的笔记,好像的确是这么回事,而且比我打的精美多了。
看来看的细节的东西确实有用,变成某种常识了。但是又不能直接一上来就给你丢几个概念,这样是去掉了分析法的过程,只剩下综合法不知道他怎么来的,最近用不同的材料复习数学也有同感。
以后可以直接找别人写好的笔记,自己只是读一遍过理解细节之后,这样效率可能高一点,因为其实笔记也做不了什么,反而分心,利用前人的笔记其实均摊下来别人写笔记的花费也分摊了。不过对一些细节没弄懂的地方,思考和阅读其他资料的过程是不能丢掉的了。
更新:后来想了一下,原论文没有 motivation 和 background,所以以问题的形式存在的笔记效果会好一点,因为这样强迫你去回答要点,而不是含糊的暧昧跳过。所以回来补充以下关键的一些点。我的一句话总结就是,map - shuffle - reduce = select - group by - aggregation。
总结一下:2022/5/30 14:50
MapReduce 是什么概念? |
他是一个编程模型,然后基于这个严格的编程模型,可以隔离上层和下层的逻辑,从而能够思想分布式的大数据计算。上层通过在严格的 map reduce 编程模型下编写业务逻辑(计算),下层框架统一解决分布式计算的 dirty work 细节,并行运算、错误容忍、数据分发、负载均衡等。 |
第二次总结:2022/6/8 14:25
MapReduce 的编程模型思想就是 map(函数,e.g. std::apply)+reduce(aggregate, e.g. std::accumulate)。然后,但是这个模型这里有一些细节需要理解的。
首先是模型里面处理的数据模式,我们必须明白到底为什么这样设计。
我们知道,mapreduce 里面数据是以 key-value pair 的形式存在的,编程的时候,有时候用 `unordered_map/map`, 有时候用 `unordered_set/set`, 那么什么时候用 `set` 什么时候用 `map` 呢(当然我们都知道 `map` 就是用 `set` 实现的,不过底层 `node` 类型是一个 `pair
所以这里要理解一下为什么 mapreduce 的编程模型是 kv pair。
为什么 MapReduce 是数据是基于 key - value 的? |
||
普通的 map + reduce (源自 lisp 函数是编程模型)的表达能力有限,可以认为是限定了某个 key 下做 map + reduce,比如对一个数组做平方再求和。大数据下,除了 aggregate 全局数据,一般还需要一个 group by 的语义,这一点是类似于 SQL 的设计的。(当然,下面的 SQL 语句举例中,没有 map 的步骤,这一点了解就好,实际如果我们对 * 进行一些 projection 是不是就能当作是一种 map 呢,笑)。 |
||
即,values(set) 的 mapReduce =
而 key-value 的 mapReduce =
|
然后是编程模型的细节,mapreduce 的编程模型中,用户需要编写的有两个部分,就是 map 和 reduce。用户需要注意到的有三个部分,分别是 map、shuffle、reduce。其中并行多机处理的阶段是 map 和 reduce,而 shuffle 阶段要处理中间结果,数据重新分派等工作。
首先 map。
MapReduce 的 map 是什么意思?他的输入和输出分别是什么? |
Map 就是对源输入进行一些处理产生一些输出。这里的输入 1 可能输出 1 或者输出 n,就是一个处理过程。他的输入是一些 key value PAIRS,输出另一些 key value PAIRS。由于一般来说 map 都是做一个一对多的mapping,因此一般理解为拆解split,所以才有下面的图片里面切面包和切黄瓜,这一种 map 其实更难理解,因为他要把同样的面包片映射到不同的输出商品去。另一种 map 比如收集到n个用户的所有资料,我们要丢掉用户,而拆解为用户对 A 的喜爱,对B的喜爱 etc...。 |
(k1, val1), (k1, val2) , (k2,val3) ---> (k3, val4), (k3, val5), (k3, val6), (k4, val7), (k5, val8)... |
shuffle 是一个框架处理的中间过程,结果送 reduce。
MapReduce 的 shuffle 是什么意思?他的输入和输出是什么? |
shuffle 不是打乱的意思而是把同一个 key 的都分配给同一个 reducer。这里实际做的是中间结果的 group by 操作! |
(k3, val4), (k3, val5), (k3, val6), (k4, val7), (k5, val8) -->(k3: {val4, val5, val6}), (k4, {val7}), (k5, {val8}) |
这里 shuffle 阶段之前,其实还有一个 combine 优化 (论文没有)
Combine 优化 |
在 hadoop 的 mapreduce 里面,这里涉及一个二次分片 的类似 hash partition 的东西,从而方便进行 group by,而且还有局部聚合,这样方便,比如多个 mapper 先内部 partition,local group by 之后,再送 reduce 做,这个过程叫做 combine 优化。combine 优化不需要 shuffle,就提前 reduce,可以是单机多核 mapper 的情况下看作是单机做 reduce。 |
Reduce 就是写一个聚合函数而已。
MapReduce 的 reduce 是什么意思?他的输入和输出分别是什么? |
reduce 就是做 aggregation, 不过 reduce 的 key value 也可以重新 map 的,看你喜欢。 |
(k3: {val4, val5, val6}), (k4, {val7}), (k5, {val8}) ---> (k3: sth3), (k4: sth4), (k5: sth5) |
实际尽管编程模型已经严格了,在技术能手的把玩之下,也是可以绕过一些限制的,这主要还是依赖于问题的同构性质和一些 workaround 之类的技术。
说到这里,下面我之前做的的笔记其实就是垃圾啊!其实我根本不在乎 infra 是怎么做的(因为论文本来也只是 high level 提了一下,只要知道下面的那张图 task 是怎么分配的就差不多了,具体的怎么做 rpc 的,怎么做分布式文件系统的还得看 gfs 论文)。。。。
下一个问题是这次实验作业要做的 spark。前面提到 mapreduce (指 hadoop 的 mapreduce)淘汰了,所以要讲一个继任者。Spark,spark 的编程模型同样是基于 mapreduce,但是不再严格,灵活性更高。这里就不是这个博客的内容了,到此为止。
基于上面的故事,附上一些直观的图片(可能有版权问题):
其实从这幅图里面就大概能理解很多细节上为什么还有很多中间的一些操作,比如 shuffle 过程。下面具体讲的 map reduce 是基于 key value pair 上的操作,所以细节是和上面的简单例子是不一样的。
所以论文主要就是从两方面讲解,一个是这个简单的 Programming model,是一个接口。第二个就是接口的实现。
比如写一个统计文档中各个单词数量的程序,可以这样写 map:
这里的 EmitIntermediate 这样写,其实是类似数据库里面 Query Processing 里面为了提高并行度而采用的火山模型(迭代器模型)那样,才有更高的并行度(底层可以任意实现)。如果只是加入到某个列表中,就还是同步的单机模型。实际 MapReduce 的也是通过迭代器模型来实现。
还是在统计词频程序里面,注意的是 MapReduce library 会把 map 产生的所有相同 key 的中间结果做一个 aggregate 再传给 reduce 函数的。
Docker 的主要原理其实在 6.s081 学虚拟化和 dune 的时候基本都明白了,虚拟机是模拟整个 OS 通过硬件虚拟化运行在 host 上,而沙盒或者 docker 这种容器系统只是虚拟了一个让进程运行的环境出来,减少浪费,主要的 kernel 还是用 host 的(docker 只支持 linux 系统,当然,win 上运行 linux container 的方法就是,先做一个 vm,再基于他跑多个 docker container 就行了)。
public class WordCount {
public static class Map extends MapReduceBase implements Mapper {
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(LongWritable key, Text value, OutputCollector output, Reporter reporter) throws IOException {
String line = value.toString();
StringTokenizer tokenizer = new StringTokenizer(line);
while (tokenizer.hasMoreTokens()) {
word.set(tokenizer.nextToken());
output.collect(word, one);
}
}
}
public static class Reduce extends MapReduceBase implements Reducer {
public void reduce(Text key, Iterator values, OutputCollector output, Reporter reporter) throws IOException {
int sum = 0;
while (values.hasNext()) {
sum += values.next().get();
}
output.collect(key, new IntWritable(sum));
}
}
public static void main(String[] args) throws Exception {
JobConf conf = new JobConf(WordCount.class);
conf.setJobName("wordcount");
conf.setOutputKeyClass(Text.class);
conf.setOutputValueClass(IntWritable.class);
conf.setMapperClass(Map.class);
conf.setCombinerClass(Reduce.class);
conf.setReducerClass(Reduce.class);
conf.setInputFormat(TextInputFormat.class);
conf.setOutputFormat(TextOutputFormat.class);
FileInputFormat.setInputPaths(conf, new Path(args[0]));
FileOutputFormat.setOutputPath(conf, new Path(args[1]));
JobClient.runJob(conf);
}
}
while (tokenizer.hasMoreTokens()) {
word.set(tokenizer.nextToken());
output.collect(word, one);
}
这里,output 是传进来的,所以 collect 的时候实际做的可以是进行某个 RPC/RMI 然后 reducer 前面有一个流程会做 按 key group 操作,这个最直接的思路是之前学的 hash aggregate(具体是什么实现之后 lab1 就是写一个 mapreduce 框架,所以不急),之后再传给 reducer。
到实现这一步的时候,考虑的东西其实比较多。单机多核的分布式对于 NUMA 架构(现在的 CPU 基本都是 NUMA,Non Uniform Memory Access, 即各核心划分独立的内存控制器,和 UMA 比少总线竞争)来说,要尽量减少非本地内存的访问,容易出现性能不稳定。对于分布式系统,共享内存无法使用。论文主要讨论分布式系统的,他们的数据通信需要通过网络实现。
具体讨论前提是:
However, given that there is only a single master, its failure is unlikely; therefore our current implementation aborts the MapReduce computation if the master fails. Clients can check for this condition and retry the MapReduce operation if they desire.
虽然上面好像感觉很多东西了,但是仔细想一下感觉也是很想当然的思路,好像这些思路之前的其他课都讲过了。不过实际要做出来,还是有很多细节。
论文第四节讲的是一些扩展功能。时间关系,我暂时略过这部分。第五部分讲的是 performance。理论上读论文最关心的其实就是 performance,包括运行性能和稳定性,但是学习理论的时候反而没什么好看的(除非有什么奇妙细节分析)。第六部分是一些经验,讲解了用上 mapreduce 的一个搜索引擎应用 Large-Scale Indexing,感觉也是一些 PPT 的东西,这里也略过了。
Refinements
略