在MRAppMaster中,当MapReduce作业初始化时,它会通过作业状态机JobImpl中InitTransition的transition()方法,进行MapReduce作业初始化相关操作,而这其中就包括:

1、调用createSplits()方法,创建分片,并获取任务分片元数据信息TaskSplitMetaInfo数组taskSplitMetaInfo;

2、确定Map Task数目numMapTasks:分片元数据信息数组的长度,即有多少分片就有多少numMapTasks;

3、确定Reduce Task数目numReduceTasks,取作业参数mapreduce.job.reduces,参数未配置默认为0;

4、根据分片元数据信息计算输入长度inputLength,也就是作业大小;

5、根据作业大小inputLength,调用作业的makeUberDecision()方法,决定作业运行模式是Uber模式还是Non-Uber模式。

相关关键代码如下:

  1. // 调用createSplits()方法,创建分片,并获取任务分片元数据信息TaskSplitMetaInfo数组taskSplitMetaInfo
  2. TaskSplitMetaInfo[] taskSplitMetaInfo = createSplits(job, job.jobId);
  3. // 确定Map Task数目numMapTasks:分片元数据信息数组的长度,即有多少分片就有多少numMapTasks
  4. job.numMapTasks = taskSplitMetaInfo.length;
  5. // 确定Reduce Task数目numReduceTasks,取作业参数mapreduce.job.reduces,参数未配置默认为0
  6. job.numReduceTasks = job.conf.getInt(MRJobConfig.NUM_REDUCES, 0);
  7. // 省略部分代码
  8. // 根据分片元数据信息计算输入长度inputLength,也就是作业大小
  9. long inputLength = 0;
  10. for (int i = 0; i < job.numMapTasks; ++i) {
  11. inputLength += taskSplitMetaInfo[i].getInputDataLength();
  12. }
  13. // 根据作业大小inputLength,调用作业的makeUberDecision()方法,决定作业运行模式是Uber模式还是Non-Uber模式
  14. job.makeUberDecision(inputLength);

由此,我们可以看出,作业运行方式Uber or Non-Uber是通过Job的makeUberDecision()方法,传入作业大小inputLength来确定的,本文,我们将研究这一话题,即如何确定作业运行方式Uber or Non-Uber?

《Yarn源码分析之MRAppMaster:作业运行方式Local、Uber、Non-Uber》一文中我们了解了Uber和Non-Uber两种作业运行方式的含义,如下:

1、Uber模式:为降低小作业延迟而设计的一种模式,所有任务,不管是Map Task,还是Reduce Task,均在同一个Container中顺序执行,这个Container其实也是MRAppMaster所在Container;

2、Non-Uber模式:对于运行时间较长的大作业,先为Map Task申请资源,当Map Task运行完成数目达到一定比例后再为Reduce Task申请资源。

在确定了解上述内容后,我们再来看下Job的makeUberDecision()方法,这个Job的实现为JobImpl类,其makeUberDecision()方法代码如下:

  1. /**
  2. * Decide whether job can be run in uber mode based on various criteria.
  3. * @param dataInputLength Total length for all splits
  4. */
  5. private void makeUberDecision(long dataInputLength) {
  6. //FIXME:  need new memory criterion for uber-decision (oops, too late here;
  7. // until AM-resizing supported,
  8. // must depend on job client to pass fat-slot needs)
  9. // these are no longer "system" settings, necessarily; user may override
  10. // 获取系统Uber模式下允许的最大Map任务数sysMaxMaps,
  11. // 取参数mapreduce.job.ubertask.maxmaps,参数未配置默认为9
  12. int sysMaxMaps = conf.getInt(MRJobConfig.JOB_UBERTASK_MAXMAPS, 9);
  13. // 获取系统Uber模式下允许的最大Reduce任务数sysMaxReduces,
  14. // 取参数mapreduce.job.ubertask.maxreduces,参数未配置默认为1
  15. int sysMaxReduces = conf.getInt(MRJobConfig.JOB_UBERTASK_MAXREDUCES, 1);
  16. // 获取系统Uber模式下允许的任务包含数据量最大字节数sysMaxBytes,
  17. // mapreduce.job.ubertask.maxbytes,参数未配置默认为远程作业提交路径remoteJobSubmitDir所在文件系统的默认数据块大小
  18. long sysMaxBytes = conf.getLong(MRJobConfig.JOB_UBERTASK_MAXBYTES,
  19. fs.getDefaultBlockSize(this.remoteJobSubmitDir)); // FIXME: this is wrong; get FS from
  20. // [File?]InputFormat and default block size
  21. // from that
  22. // 获取系统为Uber模式设置的内存资源单元槽Slot大小sysMemSizeForUberSlot,
  23. // 取参数yarn.app.mapreduce.am.resource.mb,参数未配置默认为1536M
  24. long sysMemSizeForUberSlot =
  25. conf.getInt(MRJobConfig.MR_AM_VMEM_MB,
  26. MRJobConfig.DEFAULT_MR_AM_VMEM_MB);
  27. // 获取系统为Uber模式设置的CPU资源单元槽Slot大小sysCPUSizeForUberSlot,
  28. // 取参数yarn.app.mapreduce.am.resource.cpu-vcores,参数未配置默认为1
  29. long sysCPUSizeForUberSlot =
  30. conf.getInt(MRJobConfig.MR_AM_CPU_VCORES,
  31. MRJobConfig.DEFAULT_MR_AM_CPU_VCORES);
  32. // 获取系统是否允许Uber模式标志位uberEnabled,
  33. // 取参数mapreduce.job.ubertask.enable,参数未配置默认为false,不启用
  34. boolean uberEnabled =
  35. conf.getBoolean(MRJobConfig.JOB_UBERTASK_ENABLE, false);
  36. // 判断Map任务数是否满足系统为Uber模式设定的限制条件,结果赋值给smallNumMapTasks
  37. boolean smallNumMapTasks = (numMapTasks <= sysMaxMaps);
  38. // 判断Reduce任务数是否满足系统为Uber模式设定的限制条件,结果赋值给smallNumReduceTasks
  39. boolean smallNumReduceTasks = (numReduceTasks <= sysMaxReduces);
  40. // 判断任务包含数据量大小是否满足系统为Uber模式设定的限制条件,结果赋值给smallInput
  41. boolean smallInput = (dataInputLength <= sysMaxBytes);
  42. // ignoring overhead due to UberAM and statics as negligible here:
  43. // 获取系统配置的Map任务要求的内存大小requiredMapMB,
  44. // 取参数mapreduce.map.memory.mb,参数未配置默认为0
  45. long requiredMapMB = conf.getLong(MRJobConfig.MAP_MEMORY_MB, 0);
  46. // 获取系统配置的Map任务要求的内存大小requiredReduceMB,
  47. // 取参数mapreduce.reduce.memory.mb,参数未配置默认为0
  48. long requiredReduceMB = conf.getLong(MRJobConfig.REDUCE_MEMORY_MB, 0);
  49. // 计算要求的任务内存大小requiredMB,
  50. // 取Map任务要求的内存大小requiredMapMB与Reduce任务要求的内存大小requiredReduceMB中的较大者
  51. long requiredMB = Math.max(requiredMapMB, requiredReduceMB);
  52. // 获取系统uber模式下Map任务要求的CPU核数requiredMapCores,
  53. // 取参数mapreduce.map.cpu.vcores,参数未配置默认为1
  54. int requiredMapCores = conf.getInt(
  55. MRJobConfig.MAP_CPU_VCORES,
  56. MRJobConfig.DEFAULT_MAP_CPU_VCORES);
  57. // 获取系统uber模式下Reduce任务要求的CPU核数requiredReduceCores,
  58. // 取参数mapreduce.reduce.cpu.vcores,参数未配置默认为1
  59. int requiredReduceCores = conf.getInt(
  60. MRJobConfig.REDUCE_CPU_VCORES,
  61. MRJobConfig.DEFAULT_REDUCE_CPU_VCORES);
  62. // 计算要求的任务需要CPU核数requiredCores,
  63. // 取Map任务要求的CPU核数requiredMapCores与Reduce任务要求的CPU核数requiredReduceCores中的较大者
  64. int requiredCores = Math.max(requiredMapCores, requiredReduceCores);
  65. // 特殊处理:如果Reduce任务数目为0,即当为Map-Only任务时,
  66. // 要求的内存大小、CPU核数,以Map任务要求的为准
  67. if (numReduceTasks == 0) {
  68. requiredMB = requiredMapMB;
  69. requiredCores = requiredMapCores;
  70. }
  71. // 当MR作业中任务要求的内存大小requiredMB小于等于系统为Uber模式设置的内存资源单元槽Slot大小sysMemSizeForUberSlot时,
  72. // 或者sysMemSizeForUberSlot被设定为不受限制时,
  73. // 确定为小内存要求,即标志位smallMemory为true
  74. boolean smallMemory =
  75. (requiredMB <= sysMemSizeForUberSlot)
  76. || (sysMemSizeForUberSlot == JobConf.DISABLED_MEMORY_LIMIT);
  77. // 当MR作业中任务要求的CPU核数requiredCores小于等于系统为Uber模式设置的CPU资源单元槽Slot大小sysCPUSizeForUberSlot时,
  78. // 确定为小CPU要求,即标志位smallCpu为true
  79. boolean smallCpu = requiredCores <= sysCPUSizeForUberSlot;
  80. // 确定作业是否为链式作业,并赋值给标志位notChainJob,ture表示非链式作业,false表示为链式作业
  81. boolean notChainJob = !isChainJob(conf);
  82. // User has overall veto power over uberization, or user can modify
  83. // limits (overriding system settings and potentially shooting
  84. // themselves in the head).  Note that ChainMapper/Reducer are
  85. // fundamentally incompatible with MR-1220; they employ a blocking
  86. // queue between the maps/reduces and thus require parallel execution,
  87. // while "uber-AM" (MR AM + LocalContainerLauncher) loops over tasks
  88. // and thus requires sequential execution.
  89. // 判断是否为Uber模式,赋值给isUber,
  90. // 判断的依据为,以下七个条件必须全部满足:
  91. // 1、参数mapreduce.job.ubertask.enable配置为true,即系统允许Uber模式;
  92. // 2、Map任务数满足系统为Uber模式设定的限制条件,即小于等于参数mapreduce.job.ubertask.maxmaps配置的值,如果参数未配置,则应该小于等于9;
  93. // 3、Reduce任务数满足系统为Uber模式设定的限制条件,即小于等于参数mapreduce.job.ubertask.maxreduces配置的值,如果参数未配置,则应该小于等于1;
  94. // 4、任务包含数据量大小满足系统为Uber模式设定的限制条件,即任务数据量小于等于参数mapreduce.job.ubertask.maxbytes配置的值,如果参数未配置,则应小于等于远程作业提交路径remoteJobSubmitDir所在文件系统的默认数据块大小;
  95. // 5、MR作业中任务要求的内存大小requiredMB小于等于系统为Uber模式设置的内存资源单元槽Slot大小sysMemSizeForUberSlot时,或者sysMemSizeForUberSlot被设定为不受限制;
  96. // 6、MR作业中任务要求的CPU核数requiredCores小于等于系统为Uber模式设置的CPU资源单元槽Slot大小sysCPUSizeForUberSlot;
  97. // 7、作业为非链式作业;
  98. isUber = uberEnabled && smallNumMapTasks && smallNumReduceTasks
  99. && smallInput && smallMemory && smallCpu
  100. && notChainJob;
  101. if (isUber) {// 当作业为Uber模式运行时,设置一些必要的参数
  102. LOG.info("Uberizing job " + jobId + ": " + numMapTasks + "m+"
  103. + numReduceTasks + "r tasks (" + dataInputLength
  104. + " input bytes) will run sequentially on single node.");
  105. // make sure reduces are scheduled only after all map are completed
  106. // mapreduce.job.reduce.slowstart.completedmaps参数设置为1,
  107. // 即全部Map任务完成后才会为Reduce任务分配资源
  108. conf.setFloat(MRJobConfig.COMPLETED_MAPS_FOR_REDUCE_SLOWSTART,
  109. 1.0f);
  110. // uber-subtask attempts all get launched on same node; if one fails,
  111. // probably should retry elsewhere, i.e., move entire uber-AM:  ergo,
  112. // limit attempts to 1 (or at most 2?  probably not...)
  113. // Map、Reduce任务的最大尝试次数均为1
  114. conf.setInt(MRJobConfig.MAP_MAX_ATTEMPTS, 1);
  115. conf.setInt(MRJobConfig.REDUCE_MAX_ATTEMPTS, 1);
  116. // disable speculation
  117. // 禁用Map、Reduce任务的推测执行机制
  118. conf.setBoolean(MRJobConfig.MAP_SPECULATIVE, false);
  119. conf.setBoolean(MRJobConfig.REDUCE_SPECULATIVE, false);
  120. } else {// 当作业为Non-Uber模式时,通过info级别日志,输出作业不能被判定为Uber模式的原因,根据上述7个标志位判断即可
  121. StringBuilder msg = new StringBuilder();
  122. msg.append("Not uberizing ").append(jobId).append(" because:");
  123. if (!uberEnabled)
  124. // Uber模式开关未打开,这种模式被禁用了
  125. msg.append(" not enabled;");
  126. if (!smallNumMapTasks)
  127. // 有太多的Map任务
  128. msg.append(" too many maps;");
  129. if (!smallNumReduceTasks)
  130. // 有太多的Reduce任务
  131. msg.append(" too many reduces;");
  132. if (!smallInput)
  133. // 有太大的输入
  134. msg.append(" too much input;");
  135. if (!smallCpu)
  136. // 需要占用过多的CPU
  137. msg.append(" too much CPU;");
  138. if (!smallMemory)
  139. // 需要占用过多的内存
  140. msg.append(" too much RAM;");
  141. if (!notChainJob)
  142. // 是一个链式作业,无法使用Uber模式
  143. msg.append(" chainjob;");
  144. // 记录无法使用Uber模式的日志信息
  145. LOG.info(msg.toString());
  146. }
  147. }

makeUberDecision()方法的逻辑十分清晰,但是涉及到的判断条件、参数比较多,总的来说,一个MapReduce是使用Uber模式还是Non-Uber模式运行,要综合考虑以下7个因素,这些条件缺一不可:

1、 参数mapreduce.job.ubertask.enable配置为true,即系统允许Uber模式,这是一个Uber模式的开关;

2、Map任务数满足系统为Uber模式设定的限制条件,即小于等于参数mapreduce.job.ubertask.maxmaps配置的值,如果参数未配置,则应该小于等于9;

3、Reduce任务数满足系统为Uber模式设定的限制条件,即小于等于参数mapreduce.job.ubertask.maxreduces配置的值,如果参数未配置,则应该小于等于1;

4、任务包含数据量大小满足系统为Uber模式设定的限制条件,即任务数据量小于等于参数mapreduce.job.ubertask.maxbytes配置的值,如果参数未配置,则应小于等于远程作业提交路径remoteJobSubmitDir所在文件系统的默认数据块大小;

5、MR作业中任务要求的内存大小requiredMB小于等于系统为Uber模式设置的内存资源单元槽Slot大小sysMemSizeForUberSlot时,或者sysMemSizeForUberSlot被设定为不受限制;

6、MR作业中任务要求的CPU核数requiredCores小于等于系统为Uber模式设置的CPU资源单元槽Slot大小sysCPUSizeForUberSlot;

7、作业为非链式作业。

前面6个条件在上面的描述和makeUberDecision()方法代码及其注释中都描述的很清晰,读者可自行查阅。

下面,我们重点看看第7个条件:作业为非链式作业,这个条件是如何判断的呢?它是通过isChainJob()方法来判断的,代码如下:

  1. /**
  2. * ChainMapper and ChainReducer must execute in parallel, so they're not
  3. * compatible with uberization/LocalContainerLauncher (100% sequential).
  4. */
  5. private boolean isChainJob(Configuration conf) {
  6. boolean isChainJob = false;
  7. try {
  8. // 获取取Map类名mapClassName,取参数mapreduce.job.map.class
  9. String mapClassName = conf.get(MRJobConfig.MAP_CLASS_ATTR);
  10. if (mapClassName != null) {
  11. // 通过Map类名mapClassName获取Map类Class实例mapClass
  12. Class<?> mapClass = Class.forName(mapClassName);
  13. // 通过Class的isAssignableFrom()方法,看看mapClass是否为ChainMapper的子类,或者就是ChainMapper,
  14. // 是的话,我们认为它就是一个链式作业
  15. if (ChainMapper.class.isAssignableFrom(mapClass))
  16. isChainJob = true;
  17. }
  18. } catch (ClassNotFoundException cnfe) {
  19. // don't care; assume it's not derived from ChainMapper
  20. } catch (NoClassDefFoundError ignored) {
  21. }
  22. try {
  23. // 获取取Reduce类名reduceClassName,取参数mapreduce.job.reduce.class
  24. String reduceClassName = conf.get(MRJobConfig.REDUCE_CLASS_ATTR);
  25. if (reduceClassName != null) {
  26. // 通过Reduce类名reduceClassName获取Reduce类Class实例reduceClass
  27. Class<?> reduceClass = Class.forName(reduceClassName);
  28. // 通过Class的isAssignableFrom()方法,看看reduceClass是否为ChainReducer的子类,或者就是ChainReducer,
  29. // 是的话,我们认为它就是一个链式作业
  30. if (ChainReducer.class.isAssignableFrom(reduceClass))
  31. isChainJob = true;
  32. }
  33. } catch (ClassNotFoundException cnfe) {
  34. // don't care; assume it's not derived from ChainReducer
  35. } catch (NoClassDefFoundError ignored) {
  36. }
  37. return isChainJob;
  38. }

它实际上就是看Map或Reduce是否是ChainMapper或ChainReducer的直接或间接子类,或者就是二者,通过参数mapreduce.job.map.class、mapreduce.job.reduce.class取类名并利用Class.forName构造Class实例,然后通过Class的isAssignableFrom()方法判断Map或Reduce是否是ChainMapper或ChainReducer的直接或间接子类,或者就是二者,就是这么简单。

那么问题又来了,什么是链式作业?为什么继承了ChainMapper或ChainReducer就不能在Uber模式下运行?下面我们一一解答。

首先,链式作业是什么呢?有些时候,你会发现,一个单独的MapReduce Job无法实现你的业务需求,你需要更多的MapReduce Job来处理你的数据,而此时,将多个MapReduce Job串成一条链就形成一个更大的MapReduce Job,这就是链式作业。而链式作业实现的一个根本条件就是其Mapper或Reducer分别继承自ChainMapper和ChainReducer。

那么,为什么继承了ChainMapper或ChainReducer就不能在Uber模式下运行?连同什么是ChainMapper、ChainReducer这个问题,我们一起来做一个最直接最简单的解答,更多详细内容请查看关于专门介绍ChainMapper或ChainReducer的文章。

首先看下ChainMapper的实现,在其内部,有一个Chain类型的成员变量chain,定义并在setup()方法中初始化如下:

  1. private Chain chain;
  2. protected void setup(Context context) {
  3. chain = new Chain(true);
  4. chain.setup(context.getConfiguration());
  5. }

而Chain中有两个最关键的变量,Mapper列表mappers和Thread列表threads如下:

  1. private List<Mapper> mappers = new ArrayList<Mapper>();
  1. private List<Thread> threads = new ArrayList<Thread>();

在ChainMapper的run()方法内,会将Chain的mappers中每个Mapper通过chain的addMapper()方法添加至chain中,而chain的addMapper()方法本质上就是基于每个Mapper生成一个MapRunner线程,然后添加到threads列表内,然后再由Mapper启动chain中所有线程threads,关键代码如下:

ChainMapper的run()方法

  1. public void run(Context context) throws IOException, InterruptedException {
  2. setup(context);
  3. int numMappers = chain.getAllMappers().size();
  4. if (numMappers == 0) {
  5. return;
  6. }
  7. ChainBlockingQueue<Chain.KeyValuePair<?, ?>> inputqueue;
  8. ChainBlockingQueue<Chain.KeyValuePair<?, ?>> outputqueue;
  9. if (numMappers == 1) {
  10. chain.runMapper(context, 0);
  11. } else {
  12. // add all the mappers with proper context
  13. // add first mapper
  14. outputqueue = chain.createBlockingQueue();
  15. chain.addMapper(context, outputqueue, 0);
  16. // add other mappers
  17. for (int i = 1; i < numMappers - 1; i++) {
  18. inputqueue = outputqueue;
  19. outputqueue = chain.createBlockingQueue();
  20. chain.addMapper(inputqueue, outputqueue, context, i);
  21. }
  22. // add last mapper
  23. chain.addMapper(outputqueue, context, numMappers - 1);
  24. }
  25. // start all threads
  26. chain.startAllThreads();
  27. // wait for all threads
  28. chain.joinAllThreads();
  29. }

Chain的其中一个addMapper()方法

  1. /**
  2. * Add mapper that reads and writes from/to the queue
  3. */
  4. @SuppressWarnings("unchecked")
  5. void addMapper(ChainBlockingQueue<KeyValuePair<?, ?>> input,
  6. ChainBlockingQueue<KeyValuePair<?, ?>> output,
  7. TaskInputOutputContext context, int index) throws IOException,
  8. InterruptedException {
  9. Configuration conf = getConf(index);
  10. Class<?> keyClass = conf.getClass(MAPPER_INPUT_KEY_CLASS, Object.class);
  11. Class<?> valueClass = conf.getClass(MAPPER_INPUT_VALUE_CLASS, Object.class);
  12. Class<?> keyOutClass = conf.getClass(MAPPER_OUTPUT_KEY_CLASS, Object.class);
  13. Class<?> valueOutClass = conf.getClass(MAPPER_OUTPUT_VALUE_CLASS,
  14. Object.class);
  15. RecordReader rr = new ChainRecordReader(keyClass, valueClass, input, conf);
  16. RecordWriter rw = new ChainRecordWriter(keyOutClass, valueOutClass, output,
  17. conf);
  18. MapRunner runner = new MapRunner(mappers.get(index), createMapContext(rr,
  19. rw, context, getConf(index)), rr, rw);
  20. threads.add(runner);
  21. }

可以看出,ChainMapper实际上实现了一种多重Mapper,即multiple Mapper,它不再依托一个单独的Map Task,执行一种Map任务,而是依托多个Map Task,执行多种Map任务,所以,它肯定不适合Uber模式,因为Uber模式只限于Map、Reduce等各个任务的单线程串行执行。
        ChainReducer也是如此,不再做特别的说明。

Yarn源码分析之如何确定作业运行方式Uber or Non-Uber?的更多相关文章

  1. Yarn源码分析之MRAppMaster:作业运行方式Local、Uber、Non-Uber

    基于作业大小因素,MRAppMaster提供了三种作业运行方式:本地Local模式.Uber模式.Non-Uber模式.其中, 1.本地Local模式:通常用于调试: 2.Uber模式:为降低小作业延 ...

  2. Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(一)

    我们知道,如果想要在Yarn上运行MapReduce作业,仅需实现一个ApplicationMaster组件即可,而MRAppMaster正是MapReduce在Yarn上ApplicationMas ...

  3. Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(二)

    本文继<Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(一)>,接着讲述MapReduce作业在MRAppMaster上处理总流程,继上篇讲到作业初始化之后的作 ...

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

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

  5. YARN源码分析(一)-----ApplicationMaster

    转自:http://blog.csdn.net/androidlushangderen/article/details/48128955 YARN学习系列:http://blog.csdn.net/A ...

  6. Yarn源码分析之MapReduce作业中任务Task调度整体流程(一)

    v2版本的MapReduce作业中,作业JOB_SETUP_COMPLETED事件的发生,即作业SETUP阶段完成事件,会触发作业由SETUP状态转换到RUNNING状态,而作业状态转换中涉及作业信息 ...

  7. Yarn源码分析之事件异步分发器AsyncDispatcher

    AsyncDispatcher是Yarn中事件异步分发器,它是ResourceManager中的一个基于阻塞队列的分发或者调度事件的组件,其在一个特定的单线程中分派事件,交给AsyncDispatch ...

  8. Yarn源码分析1(Hadoop2.7.2)

    在Hadoop中,调度框架YARN(Yet Another Resource Negotiater)是基于事件的,调度的是MapReduce的Application.Application有一系列的状 ...

  9. Yarn源码分析之参数mapreduce.job.reduce.slowstart.completedmaps介绍

    mapreduce.job.reduce.slowstart.completedmaps是MapReduce编程模型中的一个参数,这个参数的含义是,当Map Task完成的比例达到该值后才会为Redu ...

随机推荐

  1. Problem D: 零起点学算法24——判断奇偶数

    #include<stdio.h> int main() { int a; while(scanf("%d",&a)!=EOF) ==) printf(&quo ...

  2. Scala实战高手****第4课:零基础彻底实战Scala控制结构及Spark源码解析

    1.环境搭建 基础环境配置 jdk+idea+maven+scala2.11.以上工具安装配置此处不再赘述. 2.源码导入 官网下载spark源码后解压到合适的项目目录下,打开idea,File-&g ...

  3. GAILS里面的SAVE方法

    用途 保存一个domain类的实例到数据库,需要的话会级联保存所有的子实例. 举例 def b = new Book(title:"The Shining") b.save() 描 ...

  4. POJ 3020 Antenna Placement 最大匹配

    Antenna Placement Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 6445   Accepted: 3182 ...

  5. Yii2 关于时间格式的用法

    先添加配置文件: 'language' => 'zh-CN', 'timeZone' => 'Asia/Shanghai', 'components' => [ 'formatter ...

  6. FTP服务器需要开几个端口

    原文: https://blog.csdn.net/houbin0912/article/details/72578688 -------------------------------------- ...

  7. docker实战——在测试中使用Docker

    在之前几章中介绍的都是Docker的基础知识,了解什么是镜像,docker基本的启动流程,以及如何去运作一个容器等等. 接下来的几个章节将介绍如何在实际开发和测试过程中使用docker. 将Docke ...

  8. xunsearch使用namespace后bug修复

    xunsearch在使用了namespace后会出现不能正常使用 错误例如以下: Fatal error: Uncaught [vendors\xunsearch\lib\XSException] . ...

  9. Objective-C学习笔记(二十一)——函数的返回值与參数类型

    我们在之前的博客中涉及到的函数都没有參数,同一时候返回值也为void,即不须要返回值. 可是在以后的开发中.函数返回值和參数是必须涉及到的. 所以如今我们来讨论这个问题.我们还是以People类为例. ...

  10. python——unpack问题 ocr_recognize timeout , exception:unpack requires a string argument of length 46

    rObjBuff = "".join(unpack('=%ds' % ObjLen, recv_buf[6+i*ObjLen:6+(i+1)*ObjLen]))score, bbo ...