1. MapReduce
1. MapReduce概述
Hadoop MapReduce 是一个分布式计算框架,用于编写批处理应用程序。编写好的程序可以提交到 Hadoop 集群上用于并行处理大规模的数据。
概念
- 面向批处理的分布式计算框架
- 一种编程模型:MapReduce程序被分为Map(映射)阶段和Reduce(化简)阶段
核心思想
- 分而治之,并行计算
- 移动计算,而非移动数据
特点
- 计算跟着数据走
- 良好的扩展性:计算能力随着节点数增加,近似线性递增
- 高容错
- 状态监控
- 适合海量数据的离线批处理
- 降低了分布式编程的门槛
适用场景
- 数据统计,如:网站的PV、UV统计
- 搜索引擎构建索引
- 海量数据查询
- 复杂数据分析算法实现
不适用场景
- OLAP
- 要求毫秒或秒级返回结果
- 流计算
- 流计算的输入数据集是动态的,而MapReduce是静态的
- DAG计算
- 多个任务之间存在依赖关系,后一个的输入是前一个的输出,构成有向无环图DAG
- 每个MapReduce作业的输出结果都会落盘,造成大量磁盘IO,导致性能非常低下
2. 编程模型
MapReduce 作业通过将输入的数据集拆分为独立的块,这些块由 map 以并行的方式处理,框架对 map 的输出进行排序,然后输入到 reduce 中。MapReduce 框架专门用于 <key,value> 键值对处理,它将作业的输入视为一组 <key,value> 对,并生成一组 <key,value> 对作为输出。输入和输出的 key 和 value 都必须实现Writable 接口。
(input) <k1, v1> -> map -> <k2, v2> -> combine -> <k2, v2> -> reduce -> <k3, v3> (output)
Job & Task(作业与任务)
- 作业是客户端请求执行的一个工作单元
- 包括输入数据、MapReduce程序、配置信息
- 任务是将作业分解后得到的细分工作单元
- 分为Map任务和Reduce任务
input : 读取文本文件
Split:MapReduce会根据输入文件计算输入分片(input split),每个输入分片(input split)针对一个map任务
- 输入数据被划分成等长的小数据块,称为输入切片(Input Split),简称切片
- Split是逻辑概念,仅包含元数据信息,如:数据的起始位置、长度、所在节点等
- 每个Split交给一个Map任务处理,Split的数量决定Map任务的数量
- Split的大小默认等于Block大小
- Split的划分方式由程序设定,Split与HDFS Block没有严格的对应关系
- Split越小,负载越均衡,但集群开销越大;Split越大,Map任务数少,集群的计算并发度降低
Map阶段:就是程序员编写好的map函数了,因此map函数效率相对好控制,而且一般map操作都是本地化操作也就是在数据存储节点上进行
- 由若干Map任务组成,任务数量由Split数量决定
- 输入:Split切片(key-value),输出:中间计算结果(key-value)
Shuffle阶段:由于 Mapping 操作可能是在不同的机器上并行处理的,所以需要通过 shuffling 将相同 key 值的数据分发到同一个节点上去合并,这样才能统计出最终的结果
- Map、Reduce阶段的中间环节,是虚拟阶段
- 负责执行Partition(分区)、Sort(排序)、Spill(溢写)、Merge(合并)、 Fetch(抓取)等工作
- Partition决定了Map任务输出的每条数据放入哪个分区,交给哪个Reduce任务处理
- Reduce任务的数量决定了Partition数量
- Partition编号 = Reduce任务编号 =“key hashcode % reduce task number”(%为取模/取余数)
- 哈希取模的作用:将一个数据集随机均匀分成若干个子集
- 避免和减少Shuffle是MapReduce程序调优的重点
Reduce阶段:开始执行reduce任务,最后结果保留在hdfs上
- 由若干Reduce任务组成,任务数量由程序指定
- 输入:Map阶段输出的中间结果(key-value),输出:最终结果(key-value)
Shuffle
Map端
- Partition/Sort:Map任务将中间结果写入专用内存缓冲区Buffer(默认100M),同时进行Partition和Sort(先按“key hashcode % reduce task number”对数据进行分区,分区内再按key排序)
- Spill:当Buffer的数据量达到阈值(默认80%)时,将数据溢写(Spill)到磁盘的一个临时文件中,文件内数据先分区后排序
- Merge:Map任务结束前,将多个临时文件合并(Merge)为一个Map输出文件,文件内数据先分区后排序
Reduce端
- Fetch:Reduce任务从多个Map输出文件中主动抓取(Fetch)属于自己的分区数据,先写入Buffer,数据量达到阈值后,溢写到磁盘的一个临时文件中
- Merge:数据抓取完成后,将多个临时文件合并为一个Reduce输入文件,文件内数据按key排序
Combiner
combiner 是 map 运算后的可选操作,它实际上是一个本地化的 reduce 操作,它主要是在 map 计算出中间文件后做一个简单的合并重复 key 值的操作。这里以词频统计为例:
map 在遇到一个 hadoop 的单词时就会记录为 1,但是这篇文章里 hadoop 可能会出现 n 多次,那么 map 输出文件冗余就会很多,因此在 reduce 计算前对相同的 key 做一个合并操作,那么需要传输的数据量就会减少,传输效率就可以得到提升。
但并非所有场景都适合使用 combiner,使用它的原则是 combiner 的输出不会影响到 reduce 计算的最终输入,例如:求总数,最大值,最小值时都可以使用 combiner,但是做平均值计算则不能使用 combiner。
Partitioner
partitioner 可以理解成分类器,将 map 的输出按照 key 值的不同分别分给对应的 reducer,支持自定义实现。
3. 案例
public class WordCountApp {
// 这里为了直观显示参数 使用了硬编码,实际开发中可以通过外部传参
private static final String HDFS_URL = "hdfs://192.168.0.107:8020";
private static final String HADOOP_USER_NAME = "root";
public static void main(String[] args) throws Exception {
// 文件输入路径和输出路径由外部传参指定
if (args.length < 2) {
System.out.println("Input and output paths are necessary!");
return;
}
// 需要指明 hadoop 用户名,否则在 HDFS 上创建目录时可能会抛出权限不足的异常
System.setProperty("HADOOP_USER_NAME", HADOOP_USER_NAME);
Configuration configuration = new Configuration();
// 指明 HDFS 的地址
configuration.set("fs.defaultFS", HDFS_URL);
// 创建一个 Job
Job job = Job.getInstance(configuration);
// 设置运行的主类
job.setJarByClass(WordCountApp.class);
// 设置 Mapper 和 Reducer
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 设置 Mapper 输出 key 和 value 的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 设置 Reducer 输出 key 和 value 的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 如果输出目录已经存在,则必须先删除,否则重复运行程序时会抛出异常
FileSystem fileSystem = FileSystem.get(new URI(HDFS_URL), configuration, HADOOP_USER_NAME);
Path outputPath = new Path(args[1]);
if (fileSystem.exists(outputPath)) {
fileSystem.delete(outputPath, true);
}
// 设置作业输入文件和输出文件的路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, outputPath);
// 将作业提交到群集并等待它完成,参数设置为 true 代表打印显示对应的进度
boolean result = job.waitForCompletion(true);
// 关闭之前创建的 fileSystem
fileSystem.close();
// 根据作业结果,终止当前运行的 Java 虚拟机,退出程序
System.exit(result ? 0 : -1);
}
}
2. YARN
1. YARN简介
MapReduce存在先天缺陷(Hadoop 1.X)
- 身兼两职:计算框架 + 资源管理系统
- JobTracker
- 既做资源管理,又做任务调度
- 任务太重,开销过大
- 存在单点故障
- 资源描述模型过于简单,资源利用率较低
- 仅把Task数量看作资源,没有考虑CPU和内存
- 强制把资源分成Map Task Slot和Reduce Task Slot
- 扩展性较差,集群规模上限4K
- 源码难于理解,升级维护困难
Apache YARN (Yet Another Resource Negotiator) 是 hadoop 2.0 引入的集群资源管理系统。用户可以将各种服务框架部署在 YARN 上,由 YARN 进行统一地管理和资源分配。
- YARN,Yet Another Resource Negotiator,另一种资源管理器
- 分布式通用资源管理系统
- 设计目标:聚焦资源管理、通用(适用各种计算框架)、高可用、高扩展
2. YARN原理
- Master/Slave架构
- 将JobTracker的资源管理、任务调度功能分离
- 三种角色:ResourceManager(Master)、NodeManager(Slave)、ApplicationMaster
ResourceManager
ResourceManager 通常在独立的机器上以后台进程的形式运行,它是整个集群资源的主要协调者和管理者。ResourceManager 负责给用户提交的所有应用程序分配资源,它根据应用程序优先级、队列容量、ACLs、数据位置等信息,做出决策,然后以共享的、安全的、多租户的方式制定分配策略,调度集群资源。
主要功能
- 统一管理集群的所有资源
- 将资源按照一定策略分配给各个应用(ApplicationMaster)
- 接收NodeManager的资源上报信息
核心组件
- 用户交互服务(User Service)
- NodeManager管理
- ApplicationMaster管理
- Application管理
- 安全管理
- 资源管理
NodeManager
NodeManager 是 YARN 集群中的每个具体节点的管理者。主要负责该节点内所有容器的生命周期的管理,监视资源和跟踪节点健康。具体如下:
- 启动时向 ResourceManager 注册并定时发送心跳消息,等待 ResourceManager 的指令;
- 维护 Container 的生命周期,监控 Container 的资源使用情况;
- 管理任务运行时的相关依赖,根据 ApplicationMaster 的需要,在启动 Container 之前将需要的程序及其依赖拷贝到本地。
主要功能
- 管理单个节点的资源
- 向ResourceManager汇报节点资源使用情况
- 管理Container的生命周期
核心组件
- NodeStatusUpdater
- ContainerManager
- ContainerExecutor
- NodeHealthCheckerService
- Security
- WebServer
ApplicationMaster
在用户提交一个应用程序时,YARN 会启动一个轻量级的进程 ApplicationMaster。ApplicationMaster 负责协调来自 ResourceManager 的资源,并通过 NodeManager 监视容器内资源的使用情况,同时还负责任务的监控与容错。具体如下:
- 根据应用的运行状态来决定动态计算资源需求;
- 向 ResourceManager 申请资源,监控申请的资源的使用情况;
- 跟踪任务状态和进度,报告资源的使用情况和应用的进度信息;
- 负责任务的容错。
主要功能
- 管理应用程序实例
- 向ResourceManager申请任务执行所需的资源
- 任务调度和监管
实现方式
- 需要为每个应用开发一个AM组件
- YARN提供MapReduce的ApplicationMaster实现
- 采用基于事件驱动的异步编程模型,由中央事件调度器统一管理所有事件
- 每种组件都是一种事件处理器,在中央事件调度器中注册
Contain
Container 是 YARN 中的资源抽象,它封装了某个节点上的多维度资源,如内存、CPU、磁盘、网络等。当 AM 向 RM 申请资源时,RM 为 AM 返回的资源是用 Container 表示的。YARN 会为每个任务分配一个 Container,该任务只能使用该 Container 中描述的资源。 ApplicationMaster 可在 Container 内运行任何类型的任务。例如,MapReduce ApplicationMaster 请求一个容器来启动 map 或 reduce 任务,而 Giraph ApplicationMaster 请求一个容器来运行 Giraph 任务。
概念:Container封装了节点上进程的相关资源,是YARN中资源的抽象
分类:运行ApplicationMaster的Container 、运行应用任务的Container
3. YARN工作机制
- Client 提交作业到 YARN 上
- Resource Manager 选择一个 Node Manager,启动一个 Container 并运行 Application Master 实例
- Application Master 根据实际需要向 Resource Manager 请求更多的 Container 资源(如果作业很小, 应用管理器会选择在其自己的 JVM 中运行任务)
- Application Master 通过获取到的 Container 资源执行分布式计算
原理详述
-
作业提交
client 调用 job.waitForCompletion 方法,向整个集群提交 MapReduce 作业 (第 1 步) 。新的作业 ID(应用 ID) 由资源管理器分配 (第 2 步)。作业的 client 核实作业的输出, 计算输入的 split, 将作业的资源 (包括 Jar 包,配置文件, split 信息) 拷贝给 HDFS(第 3 步)。 最后, 通过调用资源管理器的 submitApplication() 来提交作业 (第 4 步)。 -
作业初始化
当资源管理器收到 submitApplciation() 的请求时, 就将该请求发给调度器 (scheduler), 调度器分配 container, 然后资源管理器在该 container 内启动应用管理器进程, 由节点管理器监控 (第 5 步)。
MapReduce 作业的应用管理器是一个主类为 MRAppMaster 的 Java 应用,其通过创造一些 bookkeeping 对象来监控作业的进度, 得到任务的进度和完成报告 (第 6 步)。然后其通过分布式文件系统得到由客户端计算好的输入 split(第 7 步),然后为每个输入 split 创建一个 map 任务, 根据 mapreduce.job.reduces 创建 reduce 任务对象。 -
任务分配
如果作业很小, 应用管理器会选择在其自己的 JVM 中运行任务。
如果不是小作业, 那么应用管理器向资源管理器请求 container 来运行所有的 map 和 reduce 任务 (第 8 步)。这些请求是通过心跳来传输的, 包括每个 map 任务的数据位置,比如存放输入 split 的主机名和机架 (rack),调度器利用这些信息来调度任务,尽量将任务分配给存储数据的节点, 或者分配给和存放输入 split 的节点相同机架的节点。 -
任务运行
当一个任务由资源管理器的调度器分配给一个 container 后,应用管理器通过联系节点管理器来启动 container(第 9 步)。任务由一个主类为 YarnChild 的 Java 应用执行, 在运行任务之前首先本地化任务需要的资源,比如作业配置,JAR 文件, 以及分布式缓存的所有文件 (第 10 步。 最后, 运行 map 或 reduce 任务 (第 11 步)。
YarnChild 运行在一个专用的 JVM 中, 但是 YARN 不支持 JVM 重用。 -
进度和状态更新
YARN 中的任务将其进度和状态 (包括 counter) 返回给应用管理器, 客户端每秒 (通 mapreduce.client.progressmonitor.pollinterval 设置) 向应用管理器请求进度更新, 展示给用户。 -
作业完成
除了向应用管理器请求作业进度外, 客户端每 5 分钟都会通过调用 waitForCompletion() 来检查作业是否完成,时间间隔可以通过 mapreduce.client.completion.pollinterval 来设置。作业完成之后, 应用管理器和 container 会清理工作状态, OutputCommiter 的作业清理方法也会被调用。作业的信息会被作业历史服务器存储以备之后用户核查。
高可用
ResourceManager高可用
- 1个Active RM、多个Standby RM
- 宕机后自动实现主备切换
- ZooKeeper的核心作用
- Active节点选举
- 恢复Active RM的原有状态信息
- 重启AM,杀死所有运行中的Container
- 切换方式:手动、自动
最后
大家可以关注我的微信公众号一起学习进步。