Hadoop 1.x版本

首先-先了解一下必知概念

From:MapReduce工作原理图文详解,JobTracker和TaskTracker概述

  • 客户端(Client):编写mapreduce程序,配置作业,提交作业,这就是程序员完成的工作;
  • JobTracker:JobTracker是一个后台服务进程,启动之后,会一直监听并接收来自各个TaskTracker发送的心跳信息,包括资源使用情况和任务运行情况等信息。
    • 作业控制:在hadoop中每个应用程序被表示成一个作业,每个作业又被分成多个任务,JobTracker的作业控制模块则负责作业的分解和状态监控。
    • 状态监控:主要包括TaskTracker状态监控、作业状态监控和任务状态监控。主要作用:容错和为任务调度提供决策依据。
    • JobTracker只有一个,他负责了任务的信息采集整理,你就把它当做包工头把,这个和采用Master/Slave结构中的Master保持一致
    • JobTracker 对应于 NameNode
    • 一般情况应该把JobTracker部署在单独的机器上
  • TaskTracker:TaskTracker是JobTracker和Task之间的桥梁。TaskTracker与JobTracker和Task之间采用了RPC协议进行通信。
    • 从JobTracker接收并执行各种命令:运行任务、提交任务、杀死任务等
    • 将本地节点上各个任务的状态通过心跳周期性汇报给JobTracker,节点健康情况、资源使用情况,任务执行进度、任务运行状态等,比如说map task我做完啦,你什么时候让reduce task过来拉数据啊
    • TaskTracker是运行在多个节点上的slaver服务。TaskTracker主动与JobTracker通信,接收作业,并负责直接执行每一个任务。
    • TaskTracker都需要运行在HDFS的DataNode上
  • HDFS:保存作业的数据、配置信息等等,最后的结果也是保存在hdfs上面
    • NameNode: 管理文件目录结构,接受用户的操作请求,管理数据节点(DataNode)
    • DataNode:是HDFS中真正存储数据的
      • Block:是hdfs读写数据的基本单位,默认64MB大小,就是说如果你有130MB数据,那就要分成三个block,两个存放64MB,最后一个存放2MB数据,虽然最后一个block块是64MB,但实际上占用空间为2MB
    • Sencondary NameNode:它的目的是帮助 NameNode 合并编辑日志,减少 NameNode 启动时间,在文件系统中设置一个检查点来帮助NameNode更好的工作。它不是要取代掉NameNode也不是NameNode的备份。可参考Secondary NameNode:它究竟有什么作用?

其次-走一遍流程

规范是:

  1. 执行的步骤

    • 步骤中涉及的知识点

      • 知识点的补充

按照这样的规范进行讲解

  1. 在客户端启动一个作业。拿个比方说,我提交了一个hive程序

    • 客户端(Client):编写mapreduce程序,配置作业,提交作业,这就是程序员完成的工作;
  2. 向JobTracker请求一个Job ID,就像你排队买车一样,你不得摇个号啊,没有这个号你就不能买车(执行任务)。

    • JobTracker:JobTracker是一个后台服务进程,启动之后,会一直监听并接收来自各个TaskTracker发送的心跳信息,包括资源使用情况和任务运行情况等信息。

      • 作业控制:在hadoop中每个应用程序被表示成一个作业,每个作业又被分成多个任务,JobTracker的作业控制模块则负责作业的分解和状态监控。
      • 状态监控:主要包括TaskTracker状态监控、作业状态监控和任务状态监控。主要作用:容错和为任务调度提供决策依据。
      • JobTracker只有一个,他负责了任务的信息采集整理,你就把它当做包工头把,这个和采用Master/Slave结构中的Master保持一致
      • JobTracker 对应于 NameNode
      • 一般情况应该把JobTracker部署在单独的机器上
  3. 将运行作业所需要的资源文件复制到HDFS上,包括MapReduce程序打包的JAR文件、配置文件和客户端计算所得的输入划分信息。这些文件都存放在JobTracker专门为该作业创建的文件夹中。文件夹名为该作业的Job ID。JAR文件默认会有10个副本(mapred.submit.replication属性控制);输入划分信息(Split)告诉了JobTracker应该为这个作业启动多少个map任务等信息。

    • HDFS:保存作业的数据、配置信息等等,最后的结果也是保存在hdfs上面

      • NameNode: 管理文件目录结构,接受用户的操作请求,管理数据节点(DataNode)
      • DataNode:是HDFS中真正存储数据的
        • Block:是hdfs读写数据的基本单位,默认64MB大小,就是说如果你有130MB数据,那就要分成三个block,两个存放64MB,最后一个存放2MB数据,虽然最后一个block块是64MB,但实际上占用空间为2MB
      • Sencondary NameNode:它的目的是帮助 NameNode 合并编辑日志,减少 NameNode 启动时间,在文件系统中设置一个检查点来帮助NameNode更好的工作。它不是要取代掉NameNode也不是NameNode的备份。可参考[Secondary NameNode:它究竟有什么作用?
  4. JobTracker接收到作业后,将其放在一个作业队列里(一般来说,公司部门都与自己的队列,默认的调度方法是FIFO,也就是first in first out-队列),等待作业调度器对其进行调度,当作业调度器根据自己的调度算法调度到该作业时,会根据输入划分信息(Split)为每个划分创建一个map任务,并将map任务分配给TaskTracker执行。对于map和reduce任务,TaskTracker根据主机核的数量和内存的大小有固定数量的map槽和reduce槽。这里需要强调的是:map任务不是随随便便地分配给某个TaskTracker的,这里有个概念叫:数据本地化(Data-Local)。意思是:将map任务分配给含有该map处理的数据块的TaskTracker上,同时将程序JAR包复制到该TaskTracker上来运行,这叫“运算移动,数据不移动”。而分配reduce任务时并不考虑数据本地化。

    • TaskTracker:TaskTracker是JobTracker和Task之间的桥梁。TaskTracker与JobTracker和Task之间采用了RPC协议进行通信。

      • 从JobTracker接收并执行各种命令:运行任务、提交任务、杀死任务等
      • 将本地节点上各个任务的状态通过心跳周期性汇报给JobTracker,节点健康情况、资源使用情况,任务执行进度、任务运行状态等,比如说map task我做完啦,你什么时候让reduce task过来拉数据啊
      • TaskTracker是运行在多个节点上的slaver服务。TaskTracker主动与JobTracker通信,接收作业,并负责直接执行每一个任务。
      • TaskTracker都需要运行在HDFS的DataNode上
  5. TaskTracker每隔一段时间会给JobTracker发送一个心跳,告诉JobTracker它依然在运行,同时心跳中还携带着很多的信息,比如当前map任务完成的进度等信息。当JobTracker收到作业的最后一个任务完成信息时,便把该作业设置成“成功”。当JobClient查询状态时,它将得知任务已完成,便显示一条消息给用户。

Hadoop 2.x版本

相较于1.x的版本,目前绝大多数公司用的都是基于2.x的版本,很大的区别就在于使用了Yarn作为了资源管理器,可以使不同的计算框架运行与同一个资源调度器下,而且也解决了1.x版本中JobTracker压力过大,无法扩展及NameNode单点故障等问题。

首先阐述几个重要概念

  1. ResouceManager
    包含主要的组件:定时调用器(Scheduler)以及应用管理器(ApplicationManager)。
  • 定时调度器(Scheduler):从本质上来说,定时调度器就是一种策略,或者说一种算法。当 Client 提交一个任务的时候,它会根据所需要的资源以及当前集群的资源状况进行分配。注意,它只负责向应用程序分配资源,并不做监控以及应用程序的状态跟踪。
  • 应用管理器(ApplicationManager):同样,听名字就能大概知道它是干嘛的。应用管理器就是负责管理 Client 用户提交的应用。上面不是说到定时调度器(Scheduler)不对用户提交的程序监控嘛,其实啊,监控应用的工作正是由应用管理器(ApplicationManager)完成的。
  1. ApplicationMaster
  • 每当 Client 提交一个 Application 时候,就会新建一个 ApplicationMaster 。由这个 ApplicationMaster 去与 ResourceManager 申请容器资源,获得资源后会将要运行的程序发送到容器上启动,然后进行分布式计算。
  1. NodeManager
  • NodeManager 是 ResourceManager 在每台机器的上代理,负责容器的管理,并监控他们的资源使用情况(cpu,内存,磁盘及网络等),以及向 ResourceManager/Scheduler 提供这些资源使用报告。

执行过程如图所示

From:https://blog.csdn.net/lb812913059/article/details/79897863

  1. 通过submit或者waitForCompletion提交作业,waitForCompletion()方法通过每秒循环轮转作业进度如果发现与上次报告有改变,则将进度报告发送到控制台

  2. 向ResourceManager申请Application ID,RM检查输入输出说明、计算输入分片

  3. 复制作业的资源文件,将作业信息(jar、配置文件、分片信息)复制到HDFS上用户的应用缓存目录中

  4. 通过submitApplication()方法提交作业到资源管理器

  5. 资源管理器在收到submitApplication()消息后,将请求传递给调度器(Scheduler)
    调度器为其分配一个容器Container,然后RM在NM的管理下在container中启动程序的ApplicationMaster进程

  6. ApplicationMaster对作业进行初始化,创建过个薄记对象以跟踪作业进度
    是一个java应用程序,他的主类是MRAppmaster

  7. ApplicationMaster接受来自HDFS在客户端计算的输入分片
    对每一个分片创建一个map任务,任务对象,由mapreduce.job.reduces属性设置reduce个数

  • uber模式:当任务小的时候就会启动一个JVM运行MapReduce作业,这在MapReduce1中是不允许的;这样的作业在YARN中成为uber作业,通过设置mapreduce.job.ubertask.enable设置为false使用;那什么是小任务呢?当小于10个mapper且只有1个reducer且输入大小小于一个HDFS块的任务
  1. 如果作业不适合uber任务运行,ApplicationMaster就会为所有的map任务和reduce任务向资源管理器申请容器
    请求为任务指定内存需求,map任务和reduce任务的默认都会申请1024MB的内存

  2. 资源管理器为任务分配了容器,ApplicationMaster就通过节点管理器启动容器。
    该任务由主类YarnChild的java应用程序执行。

  3. 运行任务之前,首先将资源本地化,包括作业配置、jar文件和所有来自分布式缓存的文件

  4. 最后执行map任务和reduce任务

用人话描述大致描述一下

首先Client向ResourceManager (RM)提交一个Application,RM找了下下资源比较丰富的NodeManager(NM),要求他开辟一个container来启动ApplicationMaster(AM), AM收集到启动任务需要用到的资源量(如申请的map的个数依赖于Input Split的大小),将所需要的资源量向RM提交,RM通过一个资源列表的方式选择一些资源相对丰富的NM返回,告诉AM哪一些节点NM可用启动任务,AM开始和NM进行通信,告知启动对应的map/reduce任务;NM开辟一些列的container来执行这些任务,和AM保持通信。一旦所有任务执行结束,AM向Client输出结果并向RM注销自己

以提交一个完整的mapreduce任务来演示,这里用hive

<span style="color:#000000"><code class="language-sql">hive<span style="color:#669900">></span> <span style="color:#c678dd">set</span> mapred<span style="color:#999999">.</span>job<span style="color:#999999">.</span>queue<span style="color:#999999">.</span>name<span style="color:#669900">=</span>root<span style="color:#999999">.</span>xxxxxxxxx<span style="color:#999999">;</span><span style="color:#669900">></span> <span style="color:#c678dd">insert</span> overwrite <span style="color:#c678dd">table</span> query_result<span style="color:#669900">></span> <span style="color:#c678dd">select</span> <span style="color:#669900">*</span> <span style="color:#c678dd">from</span> A <span style="color:#c678dd">join</span> B <span style="color:#c678dd">on</span> A<span style="color:#999999">.</span>name<span style="color:#669900">=</span>B<span style="color:#999999">.</span>name<span style="color:#999999">;</span>    <span style="color:#5c6370"># 这里是提交一个job,这里提交hive任务</span>Query ID <span style="color:#669900">=</span> xx_20161216130626_40fda9ef<span style="color:#669900">-</span><span style="color:#98c379">386</span>f<span style="color:#669900">-</span><span style="color:#98c379">4</span>ef3<span style="color:#669900">-</span><span style="color:#98c379">9</span>e13<span style="color:#669900">-</span><span style="color:#98c379">7</span>b08ad69118c  <span style="color:#5c6370"># 申请资源</span>
Total jobs <span style="color:#669900">=</span> <span style="color:#98c379">3</span>
Launching Job <span style="color:#98c379">1</span> <span style="color:#c678dd">out</span> <span style="color:#c678dd">of</span> <span style="color:#98c379">3</span>
Number <span style="color:#c678dd">of</span> reduce tasks <span style="color:#669900">not</span> specified<span style="color:#999999">.</span> Estimated <span style="color:#c678dd">from</span> input <span style="color:#c678dd">data</span> size: <span style="color:#98c379">1</span>   <span style="color:#5c6370"># reduce个数如果没有设置,那就根据split来确定</span>
<span style="color:#669900">In</span> <span style="color:#c678dd">order</span> <span style="color:#c678dd">to</span> change the average <span style="color:#c678dd">load</span> <span style="color:#c678dd">for</span> a reducer <span style="color:#999999">(</span><span style="color:#669900">in</span> bytes<span style="color:#999999">)</span>:<span style="color:#c678dd">set</span> hive<span style="color:#999999">.</span><span style="color:#c678dd">exec</span><span style="color:#999999">.</span>reducers<span style="color:#999999">.</span>bytes<span style="color:#999999">.</span>per<span style="color:#999999">.</span>reducer<span style="color:#669900">=</span><span style="color:#669900"><</span>number<span style="color:#669900">></span>
<span style="color:#669900">In</span> <span style="color:#c678dd">order</span> <span style="color:#c678dd">to</span> <span style="color:#c678dd">limit</span> the maximum number <span style="color:#c678dd">of</span> reducers:<span style="color:#c678dd">set</span> hive<span style="color:#999999">.</span><span style="color:#c678dd">exec</span><span style="color:#999999">.</span>reducers<span style="color:#999999">.</span>max<span style="color:#669900">=</span><span style="color:#669900"><</span>number<span style="color:#669900">></span>
<span style="color:#669900">In</span> <span style="color:#c678dd">order</span> <span style="color:#c678dd">to</span> <span style="color:#c678dd">set</span> a constant number <span style="color:#c678dd">of</span> reducers:<span style="color:#c678dd">set</span> mapreduce<span style="color:#999999">.</span>job<span style="color:#999999">.</span>reduces<span style="color:#669900">=</span><span style="color:#669900"><</span>number<span style="color:#669900">></span>
<span style="color:#c678dd">Starting</span> Job <span style="color:#669900">=</span> job_1481285758114_429080<span style="color:#999999">,</span> Tracking URL <span style="color:#669900">=</span> http:<span style="color:#5c6370">//bigdata-hdp-apache500.xg01:8088/proxy/application_1481285758114_429080/</span>
<span style="color:#c678dd">Kill</span> Command <span style="color:#669900">=</span> <span style="color:#669900">/</span>usr<span style="color:#669900">/</span><span style="color:#c678dd">local</span><span style="color:#669900">/</span>hadoop<span style="color:#669900">-</span><span style="color:#98c379">2.7</span><span style="color:#98c379">.2</span><span style="color:#669900">/</span>bin<span style="color:#669900">/</span>hadoop job  <span style="color:#669900">-</span><span style="color:#c678dd">kill</span> job_1481285758114_429080
Hadoop job information <span style="color:#c678dd">for</span> Stage<span style="color:#669900">-</span><span style="color:#98c379">1</span>: number <span style="color:#c678dd">of</span> mappers: <span style="color:#98c379">2</span><span style="color:#999999">;</span> number <span style="color:#c678dd">of</span> reducers: <span style="color:#98c379">1</span>
<span style="color:#98c379">2016</span><span style="color:#669900">-</span><span style="color:#98c379">12</span><span style="color:#669900">-</span><span style="color:#98c379">16</span> <span style="color:#98c379">13</span>:<span style="color:#98c379">06</span>:<span style="color:#98c379">39</span><span style="color:#999999">,</span><span style="color:#98c379">518</span> Stage<span style="color:#669900">-</span><span style="color:#98c379">1</span> map <span style="color:#669900">=</span> <span style="color:#98c379">0</span><span style="color:#669900">%</span><span style="color:#999999">,</span>  reduce <span style="color:#669900">=</span> <span style="color:#98c379">0</span><span style="color:#669900">%</span>
<span style="color:#98c379">2016</span><span style="color:#669900">-</span><span style="color:#98c379">12</span><span style="color:#669900">-</span><span style="color:#98c379">16</span> <span style="color:#98c379">13</span>:<span style="color:#98c379">06</span>:<span style="color:#98c379">50</span><span style="color:#999999">,</span><span style="color:#98c379">846</span> Stage<span style="color:#669900">-</span><span style="color:#98c379">1</span> map <span style="color:#669900">=</span> <span style="color:#98c379">50</span><span style="color:#669900">%</span><span style="color:#999999">,</span>  reduce <span style="color:#669900">=</span> <span style="color:#98c379">0</span><span style="color:#669900">%</span><span style="color:#999999">,</span> Cumulative CPU <span style="color:#98c379">3.7</span> sec
<span style="color:#98c379">2016</span><span style="color:#669900">-</span><span style="color:#98c379">12</span><span style="color:#669900">-</span><span style="color:#98c379">16</span> <span style="color:#98c379">13</span>:<span style="color:#98c379">06</span>:<span style="color:#98c379">55</span><span style="color:#999999">,</span><span style="color:#98c379">987</span> Stage<span style="color:#669900">-</span><span style="color:#98c379">1</span> map <span style="color:#669900">=</span> <span style="color:#98c379">100</span><span style="color:#669900">%</span><span style="color:#999999">,</span>  reduce <span style="color:#669900">=</span> <span style="color:#98c379">0</span><span style="color:#669900">%</span><span style="color:#999999">,</span> Cumulative CPU <span style="color:#98c379">11.24</span> sec
<span style="color:#98c379">2016</span><span style="color:#669900">-</span><span style="color:#98c379">12</span><span style="color:#669900">-</span><span style="color:#98c379">16</span> <span style="color:#98c379">13</span>:<span style="color:#98c379">07</span>:<span style="color:#98c379">13</span><span style="color:#999999">,</span><span style="color:#98c379">418</span> Stage<span style="color:#669900">-</span><span style="color:#98c379">1</span> map <span style="color:#669900">=</span> <span style="color:#98c379">100</span><span style="color:#669900">%</span><span style="color:#999999">,</span>  reduce <span style="color:#669900">=</span> <span style="color:#98c379">67</span><span style="color:#669900">%</span><span style="color:#999999">,</span> Cumulative CPU <span style="color:#98c379">12.53</span> sec
<span style="color:#98c379">2016</span><span style="color:#669900">-</span><span style="color:#98c379">12</span><span style="color:#669900">-</span><span style="color:#98c379">16</span> <span style="color:#98c379">13</span>:<span style="color:#98c379">07</span>:<span style="color:#98c379">28</span><span style="color:#999999">,</span><span style="color:#98c379">715</span> Stage<span style="color:#669900">-</span><span style="color:#98c379">1</span> map <span style="color:#669900">=</span> <span style="color:#98c379">100</span><span style="color:#669900">%</span><span style="color:#999999">,</span>  reduce <span style="color:#669900">=</span> <span style="color:#98c379">82</span><span style="color:#669900">%</span><span style="color:#999999">,</span> Cumulative CPU <span style="color:#98c379">16.31</span> sec
<span style="color:#98c379">2016</span><span style="color:#669900">-</span><span style="color:#98c379">12</span><span style="color:#669900">-</span><span style="color:#98c379">16</span> <span style="color:#98c379">13</span>:<span style="color:#98c379">07</span>:<span style="color:#98c379">58</span><span style="color:#999999">,</span><span style="color:#98c379">252</span> Stage<span style="color:#669900">-</span><span style="color:#98c379">1</span> map <span style="color:#669900">=</span> <span style="color:#98c379">100</span><span style="color:#669900">%</span><span style="color:#999999">,</span>  reduce <span style="color:#669900">=</span> <span style="color:#98c379">100</span><span style="color:#669900">%</span><span style="color:#999999">,</span> Cumulative CPU <span style="color:#98c379">17.7</span> sec
MapReduce Total cumulative CPU <span style="color:#c678dd">time</span>: <span style="color:#98c379">17</span> seconds <span style="color:#98c379">700</span> msec
Ended Job <span style="color:#669900">=</span> job_1481285758114_429080
Stage<span style="color:#669900">-</span><span style="color:#98c379">4</span> <span style="color:#669900">is</span> selected <span style="color:#c678dd">by</span> condition resolver<span style="color:#999999">.</span>
Stage<span style="color:#669900">-</span><span style="color:#98c379">3</span> <span style="color:#669900">is</span> filtered <span style="color:#c678dd">out</span> <span style="color:#c678dd">by</span> condition resolver<span style="color:#999999">.</span>
Stage<span style="color:#669900">-</span><span style="color:#98c379">5</span> <span style="color:#669900">is</span> filtered <span style="color:#c678dd">out</span> <span style="color:#c678dd">by</span> condition resolver<span style="color:#999999">.</span>
Moving <span style="color:#c678dd">data</span> <span style="color:#c678dd">to</span>: hdfs:<span style="color:#5c6370">//mycluster-tj/tmp/hive-staging/hadoop_hive_2016-12-16_13-06-26_309_6781206579090170211-1/-ext-10000</span>
Loading <span style="color:#c678dd">data</span> <span style="color:#c678dd">to</span> <span style="color:#c678dd">table</span> test<span style="color:#999999">.</span>query_result
<span style="color:#c678dd">Table</span> test<span style="color:#999999">.</span>query_result stats: <span style="color:#999999">[</span>numFiles<span style="color:#669900">=</span><span style="color:#98c379">1</span><span style="color:#999999">,</span> numRows<span style="color:#669900">=</span><span style="color:#98c379">3</span><span style="color:#999999">,</span> totalSize<span style="color:#669900">=</span><span style="color:#98c379">69</span><span style="color:#999999">,</span> rawDataSize<span style="color:#669900">=</span><span style="color:#98c379">66</span><span style="color:#999999">]</span>
MapReduce Jobs Launched:
Stage<span style="color:#669900">-</span>Stage<span style="color:#669900">-</span><span style="color:#98c379">1</span>: Map: <span style="color:#98c379">2</span>  Reduce: <span style="color:#98c379">1</span>   Cumulative CPU: <span style="color:#98c379">17.7</span> sec   HDFS <span style="color:#c678dd">Read</span>: <span style="color:#98c379">13657</span> HDFS <span style="color:#c678dd">Write</span>: <span style="color:#98c379">142</span> SUCCESS
Total MapReduce CPU <span style="color:#c678dd">Time</span> Spent: <span style="color:#98c379">17</span> seconds <span style="color:#98c379">700</span> msec
OK
owntest<span style="color:#999999">.</span>name  owntest<span style="color:#999999">.</span>age   owntest2<span style="color:#999999">.</span>workplace    owntest2<span style="color:#999999">.</span>name
<span style="color:#c678dd">Time</span> taken: <span style="color:#98c379">96.932</span> seconds
</code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

以上是在客户端、JobTracker、TaskTracker的层次来分析MapReduce的工作原理的,下面我们再细致一点,从map任务和reduce任务的层次来分析分析吧。

从Map,Reduce,Shuffle理解

总体步骤

也就分成input,split,map,shuffle,reduce五个步骤,这里我们按照例子来简单说明下,再详细的说明我会放在后面的细分部分,这部分作用是了解下mapreduce的怎么个流程。这里是一个wordcount的过程了

  1. input:也就是数据存储位置,这里当然是类似于hdfs这样的分布式存储啦,

  2. split:因为map task只读split,而split基本上和hdfs的基本存储块block同样大小注意:split是逻辑分片,一个split对应一个map,你可以把它当做map的单位块来理解,投喂进map的时候必须要这样的格式,打个比方,比如只收硬币的地铁站,你只能投放1元硬币的,你投什么五毛,一角的,都是犯法的!对,警察叔叔就是他!

  3. map:这里做的是wordcount,而map程序是由程序原来编写的,如果非要用代码来写,我用python写你别打我

    <span style="color:#000000"><code class="language-python">splitdata <span style="color:#669900">=</span> <span style="color:#669900">'Deer Bear River'</span>
    aftermap <span style="color:#669900">=</span> <span style="color:#669900">map</span><span style="color:#999999">(</span><span style="color:#c678dd">lambda</span> x<span style="color:#999999">:</span><span style="color:#999999">(</span>x<span style="color:#999999">,</span><span style="color:#98c379">1</span><span style="color:#999999">)</span><span style="color:#999999">,</span>splitdata<span style="color:#999999">.</span>split<span style="color:#999999">(</span><span style="color:#669900">" "</span><span style="color:#999999">)</span><span style="color:#999999">)</span>
    <span style="color:#c678dd">print</span> aftermap<span style="color:#5c6370"># [('Deer', 1), ('Bear', 1), ('River', 1)]</span>
    </code></span>
    • 1
    • 2
    • 3
    • 4
    • 5

    三个map对应三组Split,我这里只取了其中一组,就是这么个意思,组成key/value键值对

  4. shuffle:这是一个比较核心的过程,shuffle有洗牌的意思,这里的意思你把她理解成在拉斯维加斯赌场发牌的小姐姐,但是这个小姐姐并不是随机发牌,而是把红桃发给A先生,黑桃都发给B先生,诸如此类。如果非要说有什么套路,那么其中有一个HashPartitioner就帮我们做了hash,你把它想成低配版的聚类,狭义版聚类,因为这里的类特喵的必须是值hash(key)%reduceNum相等的发到同一个reduce里去 啊!

  5. reduce:既然都说了似wordcount了,那我,额,额,做戏做全套,我还是用python来写这个过程

    <span style="color:#000000"><code class="language-python">shuffledata <span style="color:#669900">=</span> <span style="color:#999999">[</span><span style="color:#999999">(</span><span style="color:#669900">'Deer'</span><span style="color:#999999">,</span> <span style="color:#98c379">1</span><span style="color:#999999">)</span><span style="color:#999999">,</span><span style="color:#999999">(</span><span style="color:#669900">'Deer'</span><span style="color:#999999">,</span> <span style="color:#98c379">1</span><span style="color:#999999">)</span><span style="color:#999999">,</span><span style="color:#999999">(</span><span style="color:#669900">'River'</span><span style="color:#999999">,</span> <span style="color:#98c379">1</span><span style="color:#999999">)</span><span style="color:#999999">]</span>
    afterreduce_dict <span style="color:#669900">=</span> <span style="color:#999999">{</span><span style="color:#999999">}</span>
    <span style="color:#c678dd">for</span> i <span style="color:#c678dd">in</span> shuffledata<span style="color:#999999">:</span><span style="color:#c678dd">if</span> i<span style="color:#999999">[</span><span style="color:#98c379">0</span><span style="color:#999999">]</span> <span style="color:#669900">not</span> <span style="color:#c678dd">in</span> afterreduce_dict<span style="color:#999999">:</span>afterreduce_dict<span style="color:#999999">[</span>i<span style="color:#999999">[</span><span style="color:#98c379">0</span><span style="color:#999999">]</span><span style="color:#999999">]</span> <span style="color:#669900">=</span> <span style="color:#98c379">0</span>afterreduce_dict<span style="color:#999999">[</span>i<span style="color:#999999">[</span><span style="color:#98c379">0</span><span style="color:#999999">]</span><span style="color:#999999">]</span> <span style="color:#669900">+=</span><span style="color:#98c379">1</span><span style="color:#c678dd">print</span> afterreduce_dict<span style="color:#5c6370"># {'River': 1, 'Deer': 2}</span>
    </code></span>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这个‘reduce’只是对传统意义上的取键值对统计而已,理解这层意思就行,不要纠结形式

总体的步骤就是这么5步,其实最玄妙的shuffle还没有拆开细讲,接下来,见证shuffle的时刻,百分之九十抄的(捂脸)

再细分

shuffle是什么?

除了map,reduce,中间部分就是shuffle,它横跨了map和reduce两端,可以说是核心过程

这里你只要清楚Shuffle的大致范围就成-怎样把map task的输出结果有效地传送到reduce端。也可以这样理解, Shuffle描述着数据从map task输出到reduce task输入的这段过程。你说我map task干完活了要输出数据了,然后接下来数据给哪个reduce?不能因为我和那个reduce关系好,我把所有输出结果都给它把,总要考虑下别的reduce的感受啊!那怎么分配?就有了shuffle过程!上面给了两张图,我都是盗来的,一张是官方的解释,一张是中文翻译,Tkanks-MapReduce:详解Shuffle过程、MapReduce工作原理图文详解

shuffle解决什么问题?

在Hadoop这样的集群环境中,大部分map task与reduce task的执行是在不同的节点上。当然很多情况下Reduce执行时需要跨节点去拉取其它节点上的map task结果。如果集群正在运行的job有很多,那么task的正常执行对集群内部的网络资源消耗会很严重。这种网络消耗是正常的,我们不能限制,能做的就是最大化地减少不必要的消耗。还有在节点内,相比于内存,磁盘IO对job完成时间的影响也是可观的。从最基本的要求来说,我们对Shuffle过程的期望可以有:

  • 完整地从map task端拉取数据到reduce 端。
  • 在跨节点拉取数据时,尽可能地减少对带宽的不必要消耗。
  • 减少磁盘IO对task执行的影响。

Map端分析

与上图步骤1,2,3,4一一对应来解释

  1. InputSplit:输入数据来源于HDFS的block,当然在MapReduce概念中,map task只读取Split,也就是是我们所说的分片。在进行map计算之前,mapreduce会根据输入文件计算输入分片(input split),每个输入分片(input split)针对一个map任务。

    • Block是HDFS的基本存储单元,上文也有写,Block默认大小是64MB
    • Split是map task只读的单位,存储的并非数据本身,而是一个分片长度和一个记录数据的位置的数组
    • Split与block的对应关系可能是多对一,默认是一对一

Running map task 就是程序员编写好的map函数了,因此map函数效率相对好控制,而且一般map操作都是本地化操作也就是在数据存储节点上进行,这也就是上问所提到的 数据本地化!

  • 数据本地化(Data-Local)。意思是:将map任务分配给含有该map处理的数据块的TaskTracker上,同时将程序JAR包复制到该TaskTracker上来运行,这叫“运算移动,数据不移动”。而分配reduce任务时并不考虑数据本地化。
  1. partition:partition是分割map每个节点的结果,按照key分别映射给不同的reduce,也是可以自定义的。这里其实可以理解归类。我们对于错综复杂的数据归类。比如在动物园里有牛羊鸡鸭鹅,他们都是混在一起的,但是到了晚上他们就各自牛回牛棚,羊回羊圈,鸡回鸡窝。partition的作用就是把这些数据归类。只不过在写程序的时候,mapreduce使用哈希HashPartitioner帮我们归类了。默认是hash(key)%R(Reduce task的数量)哈希函数分散到各个reducer去

    • 简单的hash做分配,就比如说我map task输出键值对(1,1),(2,1),(3,1), 我有三个reducer,假设我写个hash是除3取余的值为分配到的reducer位置(其实是要将key先变hash code再除的,我这里简略),那么(1,1)被分配到第一个reduce,(2,1)被分配到第二个reduce,(3,1)被分配到第三个reduce,同理再来个(4,1)被分配到第一个reduce,HashPartitioner的目的就是合理的分组策略将使得每个Reducer获得的计算负载差距不大,从而整体reduce的性能更加均衡。
    • 没理解HashPartitioner的胖友,可以参考下这个MapReduce 编程 系列十 使用HashPartitioner来调节Reducer的计算负载

**Memory Buffer & 3:**这是一个环形的缓冲区。map task输出结果首先会进入一个缓冲区内,这个缓冲区的大小是100MB,如果map task内容太大,是很容易撑爆内存的,所以会有一个守护进程,每当缓冲区到达上限80%的时候,就会启动一个Spill(溢写)进程,它的作用是把内存里的map task的结果写入到磁盘。这里值得注意的是,溢写程序是单独的一个进程,不会影响map task的继续输出(在写磁盘过程中,另外的20%内存可以继续写入数据,两种操作互不干扰,但如果在此期间缓冲区被填满,那么map就会阻塞写入内存的操作,让写入磁盘操作完成后再执行写入内存。)。当溢写线程启动后,需要对这80MB空间内的key做排序(Sort)。排序是MapReduce模型默认的行为,这里的排序也是对序列化的字节做的排序。shuffle排序可以参考这里

  • 在这个缓冲区内会有二次排序,先对partition分区排序,然后对同一个partition内的数据进行排序。
  • 很多人的误解在Map阶段,如果不使用Combiner便不会排序,这是错误的,不管你用不用Combiner,Map Task均会对产生的数据排序(如果没有Reduce Task,则不会排序, 实际上Map阶段的排序就是为了减轻Reduce端排序负载)
  • 数据按照partition值和key两个关键字升序排序,移动的只是索引数据,排序结果是Kvmeta中数据按照partition为单位聚集在一起,同一partition内的按照key有序。

​ Combiner:如果client设置过Combiner,那么现在就是使用Combiner的时候了。将有相同key的key/value对的value加起来,减少溢写到磁盘的数据量,combiner简单说就是map端的reduce!。Combiner会优化MapReduce的中间结果,所以它在整个模型中会多次使用。那哪些场景才能使用Combiner呢?从这里分析,Combiner的输出是Reducer的输入,Combiner绝不能改变最终的计算结果。所以从我的想法来看,Combiner只应该用于那种Reduce的输入key/value与输出key/value类型完全一致,且不影响最终结果的场景。比如累加,最大值等。但对于均值就会有影响;Combiner的使用一定得慎重,如果用好,它对job执行效率有帮助,反之会影响reduce的最终结果。

  1. Merge: 每次溢写会在磁盘上生成一个溢写文件,如果map的输出结果真的很大,有多次这样的溢写发生,磁盘上相应的就会有多个溢写文件存在。当map task真正完成时,内存缓冲区中的数据也全部溢写到磁盘中形成一个溢写文件。最终磁盘中会至少有一个这样的溢写文件存在(如果map的输出结果很少,当map执行完成时,只会产生一个溢写文件),因为最终的文件只有一个,所以需要将这些溢写文件归并到一起。

引用:然后为merge过程创建一个叫file.out的文件和一个叫file.out.Index的文件用来存储最终的输出和索引,一个partition一个partition的进行合并输出。对于某个partition来说,从索引列表中查询这个partition对应的所有索引信息,每个对应一个段插入到段列表中。也就是这个partition对应一个段列表,记录所有的Spill文件中对应的这个partition那段数据的文件名、起始位置、长度等等。然后对这个partition对应的所有的segment进行合并,目标是合并成一个segment。当这个partition对应很多个segment时,会分批地进行合并:先从segment列表中把第一批取出来,以key为关键字放置成最小堆,然后从最小堆中每次取出最小的输出到一个临时文件中,这样就把这一批段合并成一个临时的段,把它加回到segment列表中;再从segment列表中把第二批取出来合并输出到一个临时segment,把其加入到列表中;这样往复执行,直到剩下的段是一批,输出到最终的文件中。最终的索引数据仍然输出到Index文件中。
 也就是说,上述三个spill.out文件中,同一个颜色的表示计算出的partition的值是一致的,根据每一个spill.out文件都有spill.index文件对应,里面记录了该out文件中某个partition的位置,将同一个partition从三个spill.out文件中都拿出来进行归并操作,搞定后变成了最终文件的头部,然后再采集蓝色部分代表的partition,往复操作,最终每个map都会对应出一个结果文件,等待reduce来拉取自己需要处理的部分,至于怎么拉,拉谁,这个都记录在index中,其实就是partition的值了

  • Merge是怎样的?比如“aaa”从某个map task读取过来时值是5,从另外一个map 读取时值是8,因为它们有相同的key,所以得merge成group。什么是group。对于“aaa”就是像这样的:{“aaa”, [5, 8, 2, …]},数组中的值就是从不同溢写文件中读取出来的,然后再把这些值放在一起。
  • 合并(Combine)和归并(Merge)的区别:
    两个键值对<“a”,1>和<“a”,1>,如果合并,会得到<“a”,2>,如果归并,会得到<“a”,<1,1>>,每个map都会有一个环形缓冲区,merge默认对此溢写文件进行合并,而combine则需要设置,设置后,同个key的value会相加

  • 补充一下,combine这个如果设置了,会在两个地方被触发;
  1. 第一次是在溢写磁盘前,缓冲区对key进行排序后,会触发一次;
  2. 第二次是在merge最终将一堆溢写文件合并成单个文件时,也会触发一次,但在这里触发的时候需在 numSpills>mapreduce.map.combine.minspills(默认3) ,combiner时才执行;如果map输出规模较少,只存在一两个溢写的文件,那么不值得调用combine带来的开销。
  • 补充另一点,在shuffle阶段中map端涉及到的排序

在spill的过程中时,会对partition和key进行排序,确保partition内的key是有序的,这里用的是优化后的快排;然后再溢写到小文件之后,针对这些小文件,最终需要merge到一个文件,这里也会出现一次排序,这里用的基本思想就是归并排序,其实是可以理解的,因为在溢写的文件块内,已经保证局部有序了,相当于只需要将局部有序的列表进行合并,形成一个全局有序的,这个让归并来做实在合适,甚至只需要做归并的后半部分!我用代码阐述下

<span style="color:#000000"><code class="language-python">
<span style="color:#5c6370"># 假设两个溢写文件且是两个partition的情况,里面的key已经有序了,现在需要全局有序,也就是小文件a,b merge到temp的过程</span>a <span style="color:#669900">=</span> <span style="color:#999999">[</span><span style="color:#98c379">1</span><span style="color:#999999">,</span><span style="color:#98c379">2</span><span style="color:#999999">,</span><span style="color:#98c379">4</span><span style="color:#999999">,</span><span style="color:#98c379">7</span><span style="color:#999999">,</span><span style="color:#98c379">20</span><span style="color:#999999">,</span><span style="color:#98c379">23</span><span style="color:#999999">]</span>
b <span style="color:#669900">=</span> <span style="color:#999999">[</span><span style="color:#98c379">3</span><span style="color:#999999">,</span><span style="color:#98c379">5</span><span style="color:#999999">,</span><span style="color:#98c379">9</span><span style="color:#999999">,</span><span style="color:#98c379">11</span><span style="color:#999999">,</span><span style="color:#98c379">13</span><span style="color:#999999">,</span><span style="color:#98c379">17</span><span style="color:#999999">,</span><span style="color:#98c379">19</span><span style="color:#999999">,</span><span style="color:#98c379">20</span><span style="color:#999999">]</span>
a_size <span style="color:#669900">=</span> <span style="color:#669900">len</span><span style="color:#999999">(</span>a<span style="color:#999999">)</span>
b_size <span style="color:#669900">=</span> <span style="color:#669900">len</span><span style="color:#999999">(</span>b<span style="color:#999999">)</span>
temp <span style="color:#669900">=</span> <span style="color:#999999">[</span><span style="color:#999999">]</span>
i<span style="color:#999999">,</span>j <span style="color:#669900">=</span> <span style="color:#98c379">0</span><span style="color:#999999">,</span><span style="color:#98c379">0</span>
<span style="color:#c678dd">while</span> i <span style="color:#669900">!=</span> a_size <span style="color:#669900">and</span> j <span style="color:#669900">!=</span> b_size<span style="color:#999999">:</span><span style="color:#c678dd">if</span> a<span style="color:#999999">[</span>i<span style="color:#999999">]</span> <span style="color:#669900"><</span> b<span style="color:#999999">[</span>j<span style="color:#999999">]</span><span style="color:#999999">:</span>temp<span style="color:#999999">.</span>append<span style="color:#999999">(</span>a<span style="color:#999999">[</span>i<span style="color:#999999">]</span><span style="color:#999999">)</span>i <span style="color:#669900">=</span> i <span style="color:#669900">+</span> <span style="color:#98c379">1</span><span style="color:#c678dd">else</span><span style="color:#999999">:</span>temp<span style="color:#999999">.</span>append<span style="color:#999999">(</span>b<span style="color:#999999">[</span>j<span style="color:#999999">]</span><span style="color:#999999">)</span>j <span style="color:#669900">=</span> j <span style="color:#669900">+</span> <span style="color:#98c379">1</span>temp<span style="color:#999999">.</span>extend<span style="color:#999999">(</span>b<span style="color:#999999">[</span>j<span style="color:#999999">:</span><span style="color:#999999">]</span> <span style="color:#c678dd">if</span> i <span style="color:#669900">==</span> a_size <span style="color:#c678dd">else</span> a<span style="color:#999999">[</span>i<span style="color:#999999">:</span><span style="color:#999999">]</span><span style="color:#999999">)</span><span style="color:#999999">[</span><span style="color:#98c379">1</span><span style="color:#999999">,</span> <span style="color:#98c379">2</span><span style="color:#999999">,</span> <span style="color:#98c379">3</span><span style="color:#999999">,</span> <span style="color:#98c379">4</span><span style="color:#999999">,</span> <span style="color:#98c379">5</span><span style="color:#999999">,</span> <span style="color:#98c379">7</span><span style="color:#999999">,</span> <span style="color:#98c379">9</span><span style="color:#999999">,</span> <span style="color:#98c379">11</span><span style="color:#999999">,</span> <span style="color:#98c379">13</span><span style="color:#999999">,</span> <span style="color:#98c379">17</span><span style="color:#999999">,</span> <span style="color:#98c379">19</span><span style="color:#999999">,</span> <span style="color:#98c379">20</span><span style="color:#999999">,</span> <span style="color:#98c379">20</span><span style="color:#999999">,</span> <span style="color:#98c379">23</span><span style="color:#999999">]</span></code></span>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

代码比较简陋,但是阐述了归并的基本思想,就是使用外部排序的方式,开辟一个缓冲区进行排序,这样就不会局限于内存的限制了,其实在大数据排序中经常碰到,指针交替前进,最后刷完整个列表;

至此,map端的所有工作都已结束,最终生成的这个文件也存放在TaskTracker够得着的某个本地目录内。每个reduce task不断地通过RPC从JobTracker那里获取map task是否完成的信息,如果reduce task得到通知,获知某台TaskTracker上的map task执行完成,Shuffle的后半段过程开始启动。

关于这个过程更详细的请见 MapReduce shuffle过程详解

Reduce端分析

  1. Copy过程: Reduce会接收到不同map任务传来的数据,并且每个map传来的数据都是有序的。Reduce进程启动一些数据copy线程(Fetcher),通过HTTP方式请求map task所在的TaskTracker获取map task的输出文件。因为map task早已结束,这些文件就归TaskTracker管理在本地磁盘中。
  • 默认情况下,当整个MapReduce作业的所有已执行完成的Map Task任务数超过Map Task总数的5%后,JobTracker便会开始调度执行Reduce Task任务。然后Reduce Task任务默认启动mapred.reduce.parallel.copies(默认为5)个MapOutputCopier线程到已完成的Map Task任务节点上分别copy一份属于自己的数据。 这些copy的数据会首先保存的内存缓冲区中,当内冲缓冲区的使用率达到一定阀值后,则写到磁盘上。
  • 有人可能会问:分区中的数据怎么知道它对应的reduce是哪个呢?其实map任务一直和其父TaskTracker保持联系,而TaskTracker又一直和JobTracker保持心跳。所以JobTracker中保存了整个集群中的宏观信息。只要reduce任务向JobTracker获取对应的map输出位置就ok了。还有就是map端已经做完了partition,reduce根据partition标识符来拉自己需要的数据
  1. Merge: 这里的merge如map端的merge动作,只是数组中存放的是不同map端copy来的数值。Copy过来的数据会先放入内存缓冲区中,这里的缓冲区大小要比map端的更为灵活,它基于JVM的heap size设置,因为Shuffle阶段Reducer不运行,所以应该把绝大部分的内存都给Shuffle用。这里需要强调的是,merge有三种形式:1)内存到内存 2)内存到磁盘 3)磁盘到磁盘。默认情况下第一种形式不启用,让人比较困惑,是吧。当内存中的数据量到达一定阈值,就启动内存到磁盘的merge。与map 端类似,这也是溢写的过程,这个过程中如果你设置有Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。第二种merge方式一直在运行,直到没有map端的数据时才结束,然后启动第三种磁盘到磁盘的merge方式生成最终的那个文件。
  2. Reducer的输入文件。不断地merge后,最后会生成一个“最终文件”。为什么加引号?因为这个文件可能存在于磁盘上,也可能存在于内存中。对我们来说,当然希望它存放于内存中,直接作为Reducer的输入,但默认情况下,这个文件是存放于磁盘中的。当Reducer的输入文件已定,整个Shuffle才最终结束。
  3. reduce阶段:和map函数一样也是程序员编写的,最终结果是存储在hdfs上的。

总结:详细讲解MapReduce过程(整理补充)相关推荐

  1. 详细讲解MapReduce二次排序过程

    2019独角兽企业重金招聘Python工程师标准>>> 我在15年处理大数据的时候还都是使用MapReduce, 随着时间的推移, 计算工具的发展, 内存越来越便宜, 计算方式也有了 ...

  2. python爬虫详细步骤-Python爬虫入门,详细讲解爬虫过程

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理 以下文章来源于凌晨安全,作者 MoLing 1. 爬虫就是模拟浏览器抓取东西,爬虫 ...

  3. Python爬虫入门,详细讲解爬虫过程

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理 以下文章来源于凌晨安全,作者 MoLing 1. 爬虫就是模拟浏览器抓取东西,爬虫 ...

  4. 推荐系统[二]:召回算法超详细讲解[召回模型演化过程、召回模型主流常见算法(DeepMF/TDM/Airbnb Embedding/Item2vec等)、召回路径简介、多路召回融合]

    搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排).系统架构.常见问题.算法项目实战总结.技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排 ...

  5. Hadoop之MapReduce介绍整理

    Hadoop之MapReduce介绍整理 什么是批处理 ​ 在了解MapReduce之前,需要了解批处理的概念,批处理模式是一种最早进行大规模数据处理的模式.批处理主要操作大规模静态数据集,并在整体数 ...

  6. C语言答案解析,C语言题库带详细讲解答案解析.doc

    C语言题库带详细讲解答案解析.doc 下载提示(请认真阅读)1.请仔细阅读文档,确保文档完整性,对于不预览.不比对内容而直接下载带来的问题本站不予受理. 2.下载的文档,不会出现我们的网址水印. 3. ...

  7. Redis最详细讲解

    Redis最详细讲解 原文地址 本文脑图 redis基本数据结构 本文脑图 前言 Redis核心对象 String类型 int SDS SDS与c语言字符串对比 String类型应用 Hash类型 字 ...

  8. 【体系结构】有关Oracle SCN知识点的整理--补充内容

    [体系结构]有关Oracle SCN知识点的整理--补充内容 小麦苗自己整理的内容参考:[体系结构]有关Oracle SCN知识点的整理  http://blog.itpub.net/26736162 ...

  9. 视频 TS 内容详细讲解

    视频 TS 内容详细讲解 题记:时间戳将每一秒分成90000份,即将每一毫秒分成90份 MPEG2系统用于视音频同步以及系统时钟恢复的时间标签分别在ES,PES和TS这3个层次中. 在TS 层, TS ...

最新文章

  1. 用Scrapy爬虫框架爬取食品论坛数据并存入数据库
  2. HDU 4279 占坑先。。。
  3. java关闭inputstream_关闭Java InputStreams
  4. const的使用 || 对象增强写法 (对象字面量)
  5. C++实现深度优先搜索DFS(附完整源码)
  6. Java工程师的进阶之路-Kafka篇(一)
  7. 苹果为什么收购英特尔手机基带业务?库克解释了一下
  8. 【BootStrap笔记】BootStrap+css写旅游网页面
  9. 路由型交换机建立VSF及删除VSF
  10. 大数据十年回顾(2):当代理论与Google云
  11. DayDayUp:2019.01.24新东方年会—俞敏洪点赞并奖励吐槽神曲《释放自我》12万元!—附全歌词(牛逼的人在哪里都不会彷徨)
  12. linux 安装apache apu,Apache编译安装
  13. 关于Quartus ii 在计算机从有线网切换到无线状态下出现的问题
  14. U盘如何恢复正常存储U盘
  15. NVlabs/noise2noise代码(一)概览与运行
  16. Windows取证——登录过的用户名、新建的用户名和访问的网址文件(墨者学院)
  17. 【机器学习基础】numpy 简称 np
  18. 性能测试的各种监控工具大全
  19. 道路裂缝坑洼图像开源数据集汇总
  20. Java实现发送邮件带多个附件,可以直接用

热门文章

  1. gtj2018如何生成工程量报表_广联达BIM土建计量平台GTJ2018
  2. 记录一下pandas的分组统计功能,agg
  3. java 分布式 redis缓存技术_JAVA架构师系列课程分布式缓存技术Redis权威指南,资源教程下载...
  4. html列表按时间排序代码_程序员小白必看:腾讯视频版排序列表的实现
  5. html5表单讲解,HTML5 表单新增元素与属性讲解
  6. oracle是堆屎山,Oracle NUMBER 类型细讲
  7. pyspark 通过list 构建rdd
  8. ine.strip().split()
  9. echarts实时更新数据_虎牙为S10拼了8.0年度更新!随时回放实时数据,还能养柴犬...
  10. 图论算法-图论的表示、分类及基本概念(系列1)