MapReduce源码分析之Task中关于对应TaskAttempt存储Map方案的一些思考
我们知道,MapReduce有三层调度模型,即Job——>Task——>TaskAttempt,并且:
1、通常一个Job存在多个Task,这些Task总共有Map Task和Redcue Task两种大的类型(为简化描述,Map-Only作业、JobSetup Task等复杂的情况这里不做考虑);
2、每个Task可以尝试运行1-n此,而且通常很多情况下都是1次,只有当开启了推测执行原理且存在拖后腿Task,或者Task之前执行失败时,Task才执行多次。
而TaskImpl中存在一个成员变量attempts,用来存储Task所包含TaskAttempt中TaskAttemptId与TaskAttempt的映射关系,定义及初始化如下:
- private Map<TaskAttemptId, TaskAttempt> attempts;
- this.attempts = Collections.emptyMap();
也就是说,attempts一开始被初始化为Collections.emptyMap(),我们看下其实现:
- @SuppressWarnings("unchecked")
- public static final <K,V> Map<K,V> emptyMap() {
- return (Map<K,V>) EMPTY_MAP;
- }
- @SuppressWarnings("unchecked")
- public static final Map EMPTY_MAP = new EmptyMap<>();
- /**
- * @serial include
- */
- private static class EmptyMap<K,V>
- extends AbstractMap<K,V>
- implements Serializable
- {
- private static final long serialVersionUID = 6428348081105594320L;
- public int size() {return 0;}
- public boolean isEmpty() {return true;}
- public boolean containsKey(Object key) {return false;}
- public boolean containsValue(Object value) {return false;}
- public V get(Object key) {return null;}
- public Set<K> keySet() {return emptySet();}
- public Collection<V> values() {return emptySet();}
- public Set<Map.Entry<K,V>> entrySet() {return emptySet();}
- public boolean equals(Object o) {
- return (o instanceof Map) && ((Map<?,?>)o).isEmpty();
- }
- public int hashCode() {return 0;}
- // Preserves singleton property
- private Object readResolve() {
- return EMPTY_MAP;
- }
- }
可以看出,EmptyMap就是一个空的Map,大小为0,isEmpty为true,containsKey和containsValue等针对任何key或value均为false。
而在生成TaskAttempt后将其添加至attempts的逻辑如下:
- // 将创建的任务运行尝试TaskAttemptImpl实例attempt与其ID的对应关系添加到TaskImpl的任务运行尝试集合attempts中,
- // attempts先被初始化为Collections.emptyMap()
- // this.attempts = Collections.emptyMap();
- switch (attempts.size()) {
- case 0:
- // 如果attempts大小为0,即为Collections.emptyMap(),则将其更换为Collections.singletonMap(),并加入该TaskAttemptImpl实例attempt
- attempts = Collections.singletonMap(attempt.getID(),
- (TaskAttempt) attempt);
- break;
- case 1:
- // 如果attempts大小为1,即为Collections.singletonMap(),则将其替换为LinkedHashMap,并加入之前和现在的TaskAttemptImpl实例attempt
- Map<TaskAttemptId, TaskAttempt> newAttempts
- = new LinkedHashMap<TaskAttemptId, TaskAttempt>(maxAttempts);
- newAttempts.putAll(attempts);
- attempts = newAttempts;
- attempts.put(attempt.getID(), attempt);
- break;
- default:
- // 如果attempts大小大于1,说明其实一个LinkedHashMap,直接put吧
- attempts.put(attempt.getID(), attempt);
- break;
- }
当Task第一次生成TaskAttempt,并将其加入attempts时,attempts为Collections.emptyMap(),其大小肯定为0,此时将TaskAttempt加入attempts时,会将attempts转换成Collections.singletonMap,即只含有一个Key-Value对的Map。而Collections.singletonMap定义如下:
- public static <K,V> Map<K,V> singletonMap(K key, V value) {
- return new SingletonMap<>(key, value);
- }
- private static class SingletonMap<K,V>
- extends AbstractMap<K,V>
- implements Serializable {
- private static final long serialVersionUID = -6979724477215052911L;
- private final K k;
- private final V v;
- SingletonMap(K key, V value) {
- k = key;
- v = value;
- }
- public int size() {return 1;}
- public boolean isEmpty() {return false;}
- public boolean containsKey(Object key) {return eq(key, k);}
- public boolean containsValue(Object value) {return eq(value, v);}
- public V get(Object key) {return (eq(key, k) ? v : null);}
- private transient Set<K> keySet = null;
- private transient Set<Map.Entry<K,V>> entrySet = null;
- private transient Collection<V> values = null;
- public Set<K> keySet() {
- if (keySet==null)
- keySet = singleton(k);
- return keySet;
- }
- public Set<Map.Entry<K,V>> entrySet() {
- if (entrySet==null)
- entrySet = Collections.<Map.Entry<K,V>>singleton(
- new SimpleImmutableEntry<>(k, v));
- return entrySet;
- }
- public Collection<V> values() {
- if (values==null)
- values = singleton(v);
- return values;
- }
- }
由此可以看出,SingletonMap是只包含一对Key-Value的Map,其size大小固定为1,containsKey和containsValue返回入参key、value是否与SingletonMap内部的k、v相等,get会根据入参是否为k,来确定返回v还是null,等等。
而当attempts大小为1,即为Collections.singletonMap时,再添加TaskAttempt的话,就需要将attempts更换为LinkedHashMap,将之前的和新添加的TaskAttempt加入,此后,如果再有TaskAttempt要加入的话,直接put即可。LinkedHashMap初始化时,其容量已被确定,为maxAttempts,这个maxAttempts取自方法getMaxAttempts(),它在TaskImpl中是一个抽象方法,由其两个子类MapTaskImpl、ReduceTaskImpl分别实现,如下:
TaskImpl.Java
- // No override of this method may require that the subclass be initialized.
- protected abstract int getMaxAttempts();
MapTaskImpl.java
- @Override
- protected int getMaxAttempts() {
- return conf.getInt(MRJobConfig.MAP_MAX_ATTEMPTS, 4);
- }
ReduceTaskImpl.java
- @Override
- protected int getMaxAttempts() {
- return conf.getInt(MRJobConfig.REDUCE_MAX_ATTEMPTS, 4);
- }
可见,Map和Reduce任务的TaskAttempt都有一个限制,分别取自参数mapreduce.map.maxattempts、mapreduce.reduce.maxattempts,参数未配置的话,均默认为4。既然有了TaskAttempt个数的上限,那么我们初始化LinkedHashMap指定容量即可,其构造如下:
- /**
- * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
- * with the specified initial capacity and a default load factor (0.75).
- *
- * @param initialCapacity the initial capacity
- * @throws IllegalArgumentException if the initial capacity is negative
- */
- public LinkedHashMap(int initialCapacity) {
- super(initialCapacity);
- accessOrder = false;
- }
调用父类HashMap的构造函数,如下:
- /**
- * Constructs an empty <tt>HashMap</tt> with the specified initial
- * capacity and the default load factor (0.75).
- *
- * @param initialCapacity the initial capacity.
- * @throws IllegalArgumentException if the initial capacity is negative.
- */
- public HashMap(int initialCapacity) {
- this(initialCapacity, DEFAULT_LOAD_FACTOR);
- }
确定其初始容量为指定的initialCapacity。
思考:
MapReduce为什么要这么设计呢?我想了想,大体有关于业务逻辑和性能等方面的两个原因:
1、Task的调度执行是有顺序的,而Task的抽象类TaskImpl的实现类,无论是MapTaskImpl,还是ReduceTaskImpl的构造,都是必须先进行的,这样就有一个问题,如果attempts上来就被构造为指定大小的LinkedHashMap,势必会造成空间的浪费,还有性能的消耗,况且,作业执行成功与否,还是后话,而如果我们初始化为Collections.emptyMap(),则很容易解决上面两个问题;
2、按照常理来说,理想情况下,每个Task应该有且只有一个TaskAttempt,只有当任务运行失败后重试,或开启推测执行机制后为有效加快拖后腿任务的执行而开启的备份任务等情况时,才会存在多个TaskAttempt,而在第一个TaskAttempt被构造时,将attempts由Collections.emptyMap()升级为Collections.singletonMap(),无论是在空间利用、性能上,还是业务逻辑上,都比较贴合实际情况;
3、再需要重试任务或开启备份任务时,才将attempts由Collections.singletonMap()升级为指定容量的LinkedHashMap,里面有延迟加载的理念;
4、占用资源越少,性能越高,对于其他作业或任务来说,是一种福音,能够整体提高集群的资源利用效率。
上述性能和业务逻辑方面的考虑,您或许不以为然,可能觉得性能提升不大,但是如果在大规模集群中,当作业数量庞大、任务数目数量庞大时,这种优势就愈发明显,而它带来的好处,于已,于别的作业来说,都会是一种福音!这种设计上的细节,值得我们学习、借鉴与反思!
MapReduce源码分析之Task中关于对应TaskAttempt存储Map方案的一些思考的更多相关文章
- MapReduce源码分析之JobSubmitter(一)
JobSubmitter,顾名思义,它是MapReduce中作业提交者,而实际上JobSubmitter除了构造方法外,对外提供的唯一一个非private成员变量或方法就是submitJobInter ...
- MapReduce源码分析之新API作业提交(二):连接集群
MapReduce作业提交时连接集群是通过Job的connect()方法实现的,它实际上是构造集群Cluster实例cluster,代码如下: private synchronized void co ...
- kernel 3.10内核源码分析--hung task机制
kernel 3.10内核源码分析--hung task机制 一.相关知识: 长期以来,处于D状态(TASK_UNINTERRUPTIBLE状态)的进程 都是让人比较烦恼的问题,处于D状态的进程不能接 ...
- angular源码分析:angular中脏活累活的承担者之$interpolate
一.首先抛出两个问题 问题一:在angular中我们绑定数据最基本的方式是用两个大括号将$scope的变量包裹起来,那么如果想将大括号换成其他什么符号,比如换成[{与}],可不可以呢,如果可以在哪里配 ...
- angular源码分析:angular中入境检察官$sce
一.ng-bing-html指令问题 需求:我需要将一个变量$scope.x = '<a href="http://www.cnblogs.com/web2-developer/&qu ...
- angular源码分析:angular中各种常用函数,比较省代码的各种小技巧
angular的工具函数 在angular的API文档中,在最前面就是讲的就是angular的工具函数,下面列出来 angular.bind //用户将函数和对象绑定在一起,返回一个新的函数 angu ...
- angular源码分析:angular中的依赖注入式如何实现的
一.准备 angular的源码一份,我这里使用的是v1.4.7.源码的获取,请参考我另一篇博文:angular源码分析:angular源代码的获取与编译环境安装 二.什么是依赖注入 据我所知,依赖注入 ...
- angular源码分析:angular中$rootscope的实现——scope的一生
在angular中,$scope是一个关键的服务,可以被注入到controller中,注入其他服务却只能是$rootscope.scope是一个概念,是一个类,而$rootscope和被注入到cont ...
- MapReduce源码分析之LocatedFileStatusFetcher
LocatedFileStatusFetcher是MapReduce中一个针对给定输入路径数组,使用配置的线程数目来获取数据块位置的实用类.它的主要作用就是利用多线程技术,每个线程对应一个任务,每个任 ...
随机推荐
- 十. 图形界面(GUI)设计5.布局设计
在界面设计中,一个容器要放置许多组件,为了美观,为组件安排在容器中的位置,这就是布局设计.java.awt中定义了多种布局类,每种布局类对应一种布局的策略.常用的有以下布局类: FlowLayout, ...
- iOS应用程序多语言本地化
多语言在应用程序中一般有两种做法:一.程序中提供给用户自己选择的机会:二.根据当前用户当前移动设备的语言自动将我们的app切换对应语言. 第一种做法比较简单完全靠自己的发挥了,这里主要讲第二种做法,主 ...
- Pressed状态和clickable,duplicateParentState的关系
做Android开发的人都用过Selector,可以方便的实现View在不同状态下的背景.不过,相信大部分开发者遇到过和我一样的问题,本文会从源码角度,解释这些问题. 首先,这里简单描述一下,我遇到的 ...
- javascriptMVC框架面向对象编程
//抽象形状类 $.Class("Shape", {}, { //构造函数 init : function(edge) { this.edge = edge; }, //实例方法 ...
- Hadoop Trash回收站使用指南
转载:https://blog.csdn.net/sunnyyoona/article/details/78869778 我们在删除一个文件时,遇到如下问题,提示我们不能删除文件放回回收站: sudo ...
- 【重点突破】—— Vue2.0 transition 动画Demo实践填坑
前言:vue1.0版本和2.0版本的过渡系统改变是很大的,具体请详看文档介绍.本文转载自郭锦荣的博客,一共列举了四种transition的使用实践,分别是css过渡.css动画.javascript钩 ...
- nyoj237 游戏高手的烦恼(最小点覆盖)
题目237 题目信息 pid=237" style="text-decoration:none; color:rgb(55,119,188)">执行结果 本题排行 ...
- 无password身份验证:安全、简单且部署高速
Passwordless authentication: Secure, simple, and fast to deploy [编者按]本文作者为 Florian Heinemann 与 Rober ...
- spring mvc跨域设置(全局)
//--------------第一步//spring 5版本全局配置方式 @Configuration @EnableWebMvc public class SpringMvcBeans imple ...
- Linux的fuser命令解析
fuser命令是用来显示所有正在使用着指定的file, file system 或者 sockets的进程信息. 例一: #fuser –m –u /mnt/usb1 /mnt/usb1: 1347c ...