我们知道,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. redis命令行清缓存

    任务管理器-服务-redis 右击打开详细信息--右击打开文件位置 在这个位置cmd 输入命令redis-cli 在输入命令:flushall 出现ok即清除缓存成功

  2. Spring学习——DI(依赖注入)

    IOC容器,处理对象依赖关系 IOC与DI: IOC :是一个容器,创建对象的容器 DI :在容器创建对象后,处理对象的依赖关系,也叫依赖注入! 方式1:通过set方法注入值 可以给普通属性.集合属性 ...

  3. getopt使用

    参考: http://www.gnu.org/software/libc/manual/html_node/Example-of-Getopt.html http://en.wikipedia.org ...

  4. Integrating Google Sign-In into Your Android App

    To integrate Google Sign-In into your Android app, configure Google Sign-In and add a button to your ...

  5. [Linux] ubuntu 格式化u盘

    $sudo fdisks -l 基本功,格式化命令,以格式化 /dev/sdb4 分区为例:$ sudo umount /dev/sdb4    # 必须先卸载该分区 # 格式化为 FAT 分区$ s ...

  6. Mac的SourceTree安装时如何注册?

    目前安装SourceTree的过程中是必须注册的,但是注册又是国外的,因此需要FQ.但是未注册成功,是没有办法设置SourceTree的代理的,虽然不知道SourceTree有没有设置代理的功能,解决 ...

  7. Python中lambda使用简易教程

    例如: f = lambda x: x+x #lambda后的x为参数 print f(3) >>6 等价于 def func(x): return x+x print func(3) 与 ...

  8. netty参考

    前言 问题 现如今我们使用通用的应用程序或者类库来实现系统之间地互相访问,比如我们经常使用一个HTTP客户端来从web服务器上获取信息,或者通过web service来执行一个远程的调用. 然而,有时 ...

  9. S7:享元模式 Flyweight

    运用共享技术有效的支持大量细粒度的对象. 应用场景: A.减少对相同对象的重复创建 UML: 示例代码:如果在工厂中,有用户,我们就直接调用,没有用户,我们就获取.减少对同一uid的user对象的重复 ...

  10. sipp中的action使用方法

    最近在做sip服务器的压力测试 场景:当主叫收到200 Ok之后要将Contact 头域中的字段放在ACK消息中的请求行和to头域中发给被叫 查看sipp官网文档就使用action, 在主叫脚本中增加 ...