我们知道,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. 十. 图形界面(GUI)设计7.文本框和文本区的输入输出

    在GUI中,常用文本框和文本区实现数据的输入和输出.如果采用文本区输入,通常另设一个数据输入完成按钮.当数据输入结束时,点击这个按钮.事件处理程序利用getText()方法从文本区中读取字符串信息.对 ...

  2. 会话对应的线程id

    http://blog.csdn.net/sqlserverdiscovery/article/details/7968117

  3. 谁说 JavaScript 很简单了?

    转载请注明出处,保留原文链接以及作者信息 本文介绍了 JavaScript 初学者应该知道的一些技巧和陷阱.如果你是老司机,就当做回顾了,哪里有写的不好的地方欢迎指出. 1. 你是否尝试过对一个数字数 ...

  4. 【spring data jpa】使用jpa的@Query,自己写的语句,报错:org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'status' cannot be found on null

    报错: org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'status' ...

  5. asp.net 二级域名(路由方式实现)

    自从微软发布 ASP.NET MVC 和routing engine (System.Web.Routing)以来,就设法让我们明白你完全能控制URL和routing,只要与你的application ...

  6. WebLogic Cluster Sevlet的配置

    虽然生产环境中不建议使用,但因为客户需要考试可能用到,所以又做了一遍 1. 配置受管Server,ProxyServer,过程略 2.构建Proxy Application 建立一个ProxyApp的 ...

  7. 异常值监测的方法 Tukey test

    参考: https://www.zhihu.com/question/38066650

  8. 【日志处理、监控ELK、Kafka、Flume等相关资料】

    服务介绍 随着实时分析技术的发展及成本的降低,用户已经不仅仅满足于离线分析.目前我们服务的用户包括微博,微盘,云存储,弹性计算平台等十多个部门的多个产品的日志搜索分析业务,每天处理约32亿条(2TB) ...

  9. idea 配置Spring MVC

    一.idea 生成的Spring MVC 项目将<url-pattern>.form<url-pattern>改成<url-pattern>.do<url-p ...

  10. 基于zookeeper+leveldb搭建activemq集群--转载

    原地址:http://www.open-open.com/lib/view/open1410569018211.html 自从activemq5.9.0开始,activemq的集群实现方式取消了传统的 ...