我们知道,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的映射关系,定义及初始化如下:

  1. private Map<TaskAttemptId, TaskAttempt> attempts;
  1. this.attempts = Collections.emptyMap();

也就是说,attempts一开始被初始化为Collections.emptyMap(),我们看下其实现:

  1. @SuppressWarnings("unchecked")
  2. public static final <K,V> Map<K,V> emptyMap() {
  3. return (Map<K,V>) EMPTY_MAP;
  4. }
  1. @SuppressWarnings("unchecked")
  2. public static final Map EMPTY_MAP = new EmptyMap<>();
  1. /**
  2. * @serial include
  3. */
  4. private static class EmptyMap<K,V>
  5. extends AbstractMap<K,V>
  6. implements Serializable
  7. {
  8. private static final long serialVersionUID = 6428348081105594320L;
  9. public int size()                          {return 0;}
  10. public boolean isEmpty()                   {return true;}
  11. public boolean containsKey(Object key)     {return false;}
  12. public boolean containsValue(Object value) {return false;}
  13. public V get(Object key)                   {return null;}
  14. public Set<K> keySet()                     {return emptySet();}
  15. public Collection<V> values()              {return emptySet();}
  16. public Set<Map.Entry<K,V>> entrySet()      {return emptySet();}
  17. public boolean equals(Object o) {
  18. return (o instanceof Map) && ((Map<?,?>)o).isEmpty();
  19. }
  20. public int hashCode()                      {return 0;}
  21. // Preserves singleton property
  22. private Object readResolve() {
  23. return EMPTY_MAP;
  24. }
  25. }

可以看出,EmptyMap就是一个空的Map,大小为0,isEmpty为true,containsKey和containsValue等针对任何key或value均为false。

而在生成TaskAttempt后将其添加至attempts的逻辑如下:

  1. // 将创建的任务运行尝试TaskAttemptImpl实例attempt与其ID的对应关系添加到TaskImpl的任务运行尝试集合attempts中,
  2. // attempts先被初始化为Collections.emptyMap()
  3. // this.attempts = Collections.emptyMap();
  4. switch (attempts.size()) {
  5. case 0:
  6. // 如果attempts大小为0,即为Collections.emptyMap(),则将其更换为Collections.singletonMap(),并加入该TaskAttemptImpl实例attempt
  7. attempts = Collections.singletonMap(attempt.getID(),
  8. (TaskAttempt) attempt);
  9. break;
  10. case 1:
  11. // 如果attempts大小为1,即为Collections.singletonMap(),则将其替换为LinkedHashMap,并加入之前和现在的TaskAttemptImpl实例attempt
  12. Map<TaskAttemptId, TaskAttempt> newAttempts
  13. = new LinkedHashMap<TaskAttemptId, TaskAttempt>(maxAttempts);
  14. newAttempts.putAll(attempts);
  15. attempts = newAttempts;
  16. attempts.put(attempt.getID(), attempt);
  17. break;
  18. default:
  19. // 如果attempts大小大于1,说明其实一个LinkedHashMap,直接put吧
  20. attempts.put(attempt.getID(), attempt);
  21. break;
  22. }

当Task第一次生成TaskAttempt,并将其加入attempts时,attempts为Collections.emptyMap(),其大小肯定为0,此时将TaskAttempt加入attempts时,会将attempts转换成Collections.singletonMap,即只含有一个Key-Value对的Map。而Collections.singletonMap定义如下:

  1. public static <K,V> Map<K,V> singletonMap(K key, V value) {
  2. return new SingletonMap<>(key, value);
  3. }
  1. private static class SingletonMap<K,V>
  2. extends AbstractMap<K,V>
  3. implements Serializable {
  4. private static final long serialVersionUID = -6979724477215052911L;
  5. private final K k;
  6. private final V v;
  7. SingletonMap(K key, V value) {
  8. k = key;
  9. v = value;
  10. }
  11. public int size()                          {return 1;}
  12. public boolean isEmpty()                   {return false;}
  13. public boolean containsKey(Object key)     {return eq(key, k);}
  14. public boolean containsValue(Object value) {return eq(value, v);}
  15. public V get(Object key)                   {return (eq(key, k) ? v : null);}
  16. private transient Set<K> keySet = null;
  17. private transient Set<Map.Entry<K,V>> entrySet = null;
  18. private transient Collection<V> values = null;
  19. public Set<K> keySet() {
  20. if (keySet==null)
  21. keySet = singleton(k);
  22. return keySet;
  23. }
  24. public Set<Map.Entry<K,V>> entrySet() {
  25. if (entrySet==null)
  26. entrySet = Collections.<Map.Entry<K,V>>singleton(
  27. new SimpleImmutableEntry<>(k, v));
  28. return entrySet;
  29. }
  30. public Collection<V> values() {
  31. if (values==null)
  32. values = singleton(v);
  33. return values;
  34. }
  35. }

由此可以看出,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

  1. // No override of this method may require that the subclass be initialized.
  2. protected abstract int getMaxAttempts();

MapTaskImpl.java

  1. @Override
  2. protected int getMaxAttempts() {
  3. return conf.getInt(MRJobConfig.MAP_MAX_ATTEMPTS, 4);
  4. }

ReduceTaskImpl.java

  1. @Override
  2. protected int getMaxAttempts() {
  3. return conf.getInt(MRJobConfig.REDUCE_MAX_ATTEMPTS, 4);
  4. }

可见,Map和Reduce任务的TaskAttempt都有一个限制,分别取自参数mapreduce.map.maxattempts、mapreduce.reduce.maxattempts,参数未配置的话,均默认为4。既然有了TaskAttempt个数的上限,那么我们初始化LinkedHashMap指定容量即可,其构造如下:

  1. /**
  2. * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance
  3. * with the specified initial capacity and a default load factor (0.75).
  4. *
  5. * @param  initialCapacity the initial capacity
  6. * @throws IllegalArgumentException if the initial capacity is negative
  7. */
  8. public LinkedHashMap(int initialCapacity) {
  9. super(initialCapacity);
  10. accessOrder = false;
  11. }

调用父类HashMap的构造函数,如下:

  1. /**
  2. * Constructs an empty <tt>HashMap</tt> with the specified initial
  3. * capacity and the default load factor (0.75).
  4. *
  5. * @param  initialCapacity the initial capacity.
  6. * @throws IllegalArgumentException if the initial capacity is negative.
  7. */
  8. public HashMap(int initialCapacity) {
  9. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  10. }

确定其初始容量为指定的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方案的一些思考的更多相关文章

  1. MapReduce源码分析之JobSubmitter(一)

    JobSubmitter,顾名思义,它是MapReduce中作业提交者,而实际上JobSubmitter除了构造方法外,对外提供的唯一一个非private成员变量或方法就是submitJobInter ...

  2. MapReduce源码分析之新API作业提交(二):连接集群

    MapReduce作业提交时连接集群是通过Job的connect()方法实现的,它实际上是构造集群Cluster实例cluster,代码如下: private synchronized void co ...

  3. kernel 3.10内核源码分析--hung task机制

    kernel 3.10内核源码分析--hung task机制 一.相关知识: 长期以来,处于D状态(TASK_UNINTERRUPTIBLE状态)的进程 都是让人比较烦恼的问题,处于D状态的进程不能接 ...

  4. angular源码分析:angular中脏活累活的承担者之$interpolate

    一.首先抛出两个问题 问题一:在angular中我们绑定数据最基本的方式是用两个大括号将$scope的变量包裹起来,那么如果想将大括号换成其他什么符号,比如换成[{与}],可不可以呢,如果可以在哪里配 ...

  5. angular源码分析:angular中入境检察官$sce

    一.ng-bing-html指令问题 需求:我需要将一个变量$scope.x = '<a href="http://www.cnblogs.com/web2-developer/&qu ...

  6. angular源码分析:angular中各种常用函数,比较省代码的各种小技巧

    angular的工具函数 在angular的API文档中,在最前面就是讲的就是angular的工具函数,下面列出来 angular.bind //用户将函数和对象绑定在一起,返回一个新的函数 angu ...

  7. angular源码分析:angular中的依赖注入式如何实现的

    一.准备 angular的源码一份,我这里使用的是v1.4.7.源码的获取,请参考我另一篇博文:angular源码分析:angular源代码的获取与编译环境安装 二.什么是依赖注入 据我所知,依赖注入 ...

  8. angular源码分析:angular中$rootscope的实现——scope的一生

    在angular中,$scope是一个关键的服务,可以被注入到controller中,注入其他服务却只能是$rootscope.scope是一个概念,是一个类,而$rootscope和被注入到cont ...

  9. MapReduce源码分析之LocatedFileStatusFetcher

    LocatedFileStatusFetcher是MapReduce中一个针对给定输入路径数组,使用配置的线程数目来获取数据块位置的实用类.它的主要作用就是利用多线程技术,每个线程对应一个任务,每个任 ...

随机推荐

  1. iOS开发——使用Autolayout生成动态高度的TableViewCell单元格

    步骤一.TableViewCell中使用Autolayout 要点:Cell的高度必须在Constraints中指明,但不能定死,需要让内部由内容决定高度的View决定动态高度. 如UILabel设置 ...

  2. Ubuntu 16.04下Shell脚本中使用数组提示:Syntax error: "(" unexpected

    说明:这种现象在CentOS中不会出现. 分析: 可以看出sh指向了dash 解决方式: 1.不要用sh执行,使用./test.sh执行.或者bash执行. 2.根治,直接修改sh的指向,改成bash ...

  3. ios-真机调试出错信息

    更新证书错误Code Sign error: Provisioning profile ‘XXXX'can't be found                   在Xcode中当你在更新了你得证书 ...

  4. libCurl 简单使用

    curl easy  的使用步骤 curl_easy_init() curl_easy_setopt() curl_easy_perform() curl_easy_cleanup() ------- ...

  5. JS 创建长度为100的数组,数值为角标

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  6. httpd 服务的两个节点的HA

    实验目的是:实现两个节点的http和nfs服务的HA集群. 实现条件:准备两个节点.node1,node2作为HA1,HA2提供集群服务.在node1和node2分别按照httpd服务.挂载nfs服务 ...

  7. 在Linux中切换用户时变成-bash-4.3$

    增加普通用户 [root@git-node1 ~]#useradd nulige [root@git-node1 ~]#passwd nulige 输入两次密码 [root@git-node1 ~]# ...

  8. http://blog.csdn.net/pet8766/article/details/8186955

    http://blog.csdn.net/pet8766/article/details/8186955

  9. edittext SearchView 失去焦点问题

    edittext 默认自己主动获取焦点的 并且会出现小键盘非常烦人 <LinearLayout             android:id="@+id/focus"     ...

  10. Docker默认存储路径修改

    Docker默认存储路径: # docker info...... Data loop file: /var/lib/docker/devicemapper/devicemapper/data.... ...