MapReduce论文阅读笔记

MapReduce论文

介绍

  • 实时系统致力于分割输入数据,在大量的机器上调度执行程序,处理机器故障以及管理机器间通信——方便没有任何高并发、分布式系统开发经验的程序员能够利用大型分布式系统的资源

  • 用重新执行作为容错的主要机制(re-execution)

实施

实施环境
  • 他们的环境中(论问提及)
    • 机器通常是有双处理核心的x86架构linux系统,2-4GB内存
    • 使用商用的网络硬件——1-100MB/s
    • 集群中有成百上千的机器,所以通常会出现机器故障
    • 存储由普通的IDE磁盘提供,一个内部开发的分布式系统用于管理这些磁盘上的数据;使用副本在不可靠的机器上提供可靠性和可用性
    • 用户提交的是job,job内包含task,调度器则将job分配到集群中的一系列可用的机器上
实施流程
  • reduce数分区函数(例如哈希值对r取余)来设定reduce数,r由用户指定

  • 当用户调用mapreduce程序,以下片段会被执行

    • 先对要处理的文件分片(通常是64MB一个块——这个值可以通过配置参数设置),然后在集群上启动许多相同的程序(分布式执行)
    • 多个程序中有一个特殊的Master程序,除此之外都是由Master分配的工作结点,有M个map任务和R个reduce任务被分配(在闲置结点中选择)。
    • 相关map结点(worker)读取相关分片内容,并从中解析出键值对,传给map函数(用户自定义,解析键值对是worker内部方法),map函数产生的键值对缓存在内存中
    • 缓存的键值对(分片成R份)将会本写入本地磁盘,这些缓存的键值对的及其地址将会返回给master结点,master结点会将这些信息发送给reduce节点
    • 当一个reduce结点呗master发送这些信息,reduce将会使用RPC读取这些mapWorker磁盘中的数据,每一个reduce结点读取完所有中间数据,将会根据中间键对齐进行排序,这样所有的相同的key都聚集到相同的reduce上;关于为何要排序——通常有很多不同的键对应的值会传输到相同的reduce节点上(reduce节点数由用户决定,所以不完全对应类别数————可以不可以边读边排序?好像不可以,会占用带宽过长时间,容易出现故障);如果数据过多超出内存容量,将会使用外部排序——注意:reduce结点和reduce方法有区别,结点为物理上的结点(被master指派为reduce结点),但是reduce方法为最终的调用方法
    • reduce结点遍历所有中间数据和对应的唯一中间键(每个键对应很多数据),并将每个键和它对应的数据传送给reduce方法,reduce方法的输出将被追加到该reduce对应分片(键)的最终的输出文件中
    • 当所有map任务和reduce任务完成后,master唤醒用户程序,这是,mapReduce程序调用用户程序并返回
  • 最终会产生R个输出文件(每个reduce任务一个),通常用户会将其输送给下一个mapreduce程序去最进一步的分类

master结点数据结构
  • master保有着许多数据结构,对于每个map任务和reduce任务来说,master保存着他们的状态(闲置、运行中、完成),以及他们的身份(非闲置结点)
    • 关于状态解释:
      • 闲置:未分配任务(整个流程不会得到数据——除非有宕机?)
      • 运行中:在任务安排中(可能存在某个时间他并不会执行工作——而是在等待)
      • 完成:任务全部完成
  • master是map结点和reduce结点之间的管道,数据位置信息将会通过master传送。因此,对于每个完成的map任务,master结点会存储R个中间数据的位置和大小信息(由map任务产生)——这些信息会在map任务完成的时候传送给master,information将会推送给正在运行的reduce任务
容错
  • master结点会周期性的ping每个worker,如果一段时间没有接收到回应,则会标记这个worker失效了
  • 每个map任务结点完成了任务之后都会重置回idle状态,此时它会变成供其他任务调度的结点;同样的,失败了也会重置为idle状态
  • 完成的map任务会在机器故障的时候重新执行,因为他们的输出存储在本地磁盘,但是由于机器故障,所以没办法访问;但是如果reduce完成了,机器故障了,不用重新执行,因为他们的输出是存储在公共文件系统中的
  • 如果有一个map任务重启了(原结点故障,需要在另一个节点上重新执行该map任务),每一个reduce结点都会注意到这件事,所有在A上未完成读取的结点会转向读取B的数据
  • Master结点故障
    • 定期保留备份(容易实现),一旦出现故障,就恢复最新备份
    • 如果只有一个Master,且短时间没有办法恢复,mapreduce任务会中止,client能够检测到这个情况,如果他们愿意,可以重新执行整个mapreduce任务
  • 语义可能存在的故障
    • 如果master接受到的消息为一个状态为“已完成”的map结点发来的,它就会忽视这个消息(只会接收“正在运行”结点发来的消息)
    • 原子操作:不可分割的操作
    • 通过原子操作来保证只会每个reduce任务(可能在多个节点上运行)只有一个输出
    • 如果mapReduce程序是不确定性的(比如有随机因素),则处理相同key的reduce的输出就会不一样(而又因为原子性操作,每个key的reduce文件只会保留一份,这样可能会导致结果会有争议)
本地化
  • 网络带宽在环境中是很稀缺的资源,所以我们优先尝试使用存储在本地的资源(动态调整map任务——让map处理的分片刚好在本地),如果尝试失败,则分配一个最近的结点让它负责map任务
  • 在一个集群中,一大部分的worker都使用本地的资源,不消耗带宽
任务粒度
  • 理想情况下,map和reduce的任务数远大于worker结点数是最好的
  • 同一个worker负责多个任务有利于负载均衡,也可以加速故障时恢复(故障后,该worker上的任务会散布到集群其他节点上去)
  • 现实中M和R的数量是有限的,主要因为两个方面,一个是时间,一个是内存
  • R的数量取决于用户,通常R会比M小很多,但是也会比worker数量多(文中举例:M=200000,R=5000)
备份任务
  • 通常延长MapReduce任务执行时间的是“掉队者”——使用太长的事件处理任务
  • 缓解手段:
    • 当一个MapReduce任务接近完成时,master会调度其余正在执行的任务的备份来执行,而一个任务完成的依据(被标记为已完成)是一个正在运行的程序或者备份程序完成(有时候因为硬件原因,先运行的程序可能比备份程序完成的还慢)——但是这种新增的操作不会超过整体资源的几个百分点[他们发现这种方式能显著减少MR任务运行时间]

优化

分区函数:
  • 默认的分区函数是hash(hash(key) mode R),但是他可能会导致极其不平衡的分区
排序:
  • 系统保证reduce输出的分区文件中内容是按照键值对的顺序来排序的,这样方便用户对于结果进行操作
Combiner方法
  • 允许用户指定一个合并方法在数据发送给网络之前合并数据(比如单词数,map阶段存储在本地的数据有很多重复的键,我们可以先一步合并这些键,再发送给reduce),这样可以减少带宽使用,但是不是所有任务都可以进行combiner操作,比如求平均数就不能用这个操作
输入输出内容
  • 用户可以自定义输入类型,通过实现reader接口
副作用
  • 生成中间文件(除了标准输出文件之外的文件)?不太懂
跳过损坏数据(是数据格式不对还是数据损坏?)
  • 每个worker程序都安装了一个信号处理器(会抓取分片异常和总线错误——这些错误会导致MR任务失败),每次遇到错误时候,用户代码会产生信号,发给worker,worker在重启任务时,会根据信号跳过这些片段
本地调试:
  • 在大型分布式系统上调试很困难,所以论文提供了一套小的mapreduce库,可以在单机上运行以供用户调试
状态信息:
  • Master节点提供一个http服务器以呈现所有任务的状态,可以提供给用户很好的监控任务进度
计数器
  • MR库提供一个计数器以记录各种事件发生情况(需要用户自己设定计数器并添加进map和reduce程序)

单词计数代码:

#include "mapreduce/mapreduce.h"
// User’s map function
class WordCounter : public Mapper {
public:
virtual void Map(const MapInput& input) {
const string& text = input.value();
const int n = text.size();
for (int i = 0; i < n; ) {
// Skip past leading whitespace
while ((i < n) && isspace(text[i]))
i++;
// Find word end
int start = i;
while ((i < n) && !isspace(text[i]))
i++;
if (start < i)
Emit(text.substr(start,i-start),"1");
} }
};
REGISTER_MAPPER(WordCounter);
// User’s reduce function
class Adder : public Reducer {
virtual void Reduce(ReduceInput* input) {
// Iterate over all entries with the
// same key and add the values
int64 value = 0;
while (!input->done()) {
value += StringToInt(input->value());
input->NextValue();
}
// Emit sum for input->key()
Emit(IntToString(value));
}
};
REGISTER_REDUCER(Adder);
int main(int argc, char** argv) {
ParseCommandLineFlags(argc, argv);
MapReduceSpecification spec;
// Store list of input files into "spec"
for (int i = 1; i < argc; i++) {
MapReduceInput* input = spec.add_input();
input->set_format("text");
input->set_filepattern(argv[i]);
input->set_mapper_class("WordCounter");
}
// Specify the output files:
// /gfs/test/freq-00000-of-00100
// /gfs/test/freq-00001-of-00100
// ...
MapReduceOutput* out = spec.output();
out->set_filebase("/gfs/test/freq");
out->set_num_tasks(100);
out->set_format("text");
out->set_reducer_class("Adder");
// Optional: do partial sums within map
// tasks to save network bandwidth
out->set_combiner_class("Adder");
// Tuning parameters: use at most 2000
// machines and 100 MB of memory per task
spec.set_machines(2000);
spec.set_map_megabytes(100);
spec.set_reduce_megabytes(100);
// Now run it
MapReduceResult result;
if (!MapReduce(spec, &result)) abort();
// Done: ’result’ structure contains info
// about counters, time taken, number of
// machines used, etc.
return 0;
}
posted @ 2022-05-17 15:12  醉生梦死_0423  阅读(55)  评论(0编辑  收藏  举报