- 浏览: 116380 次
- 性别:
- 来自: 武汉
-
文章分类
- 全部博客 (98)
- java (27)
- jms (2)
- jta (0)
- 性能调优及内存分析 (4)
- 设计模式 (14)
- 框架 (6)
- 其它 (9)
- job (1)
- maven (1)
- 服务器 (2)
- 分布式 (3)
- ibatis (1)
- linux (0)
- mysql (0)
- 并发编程 (0)
- java多线程 (2)
- 前端跨域 (1)
- 线程dump分析 (0)
- velocity (0)
- 数据库 (2)
- 协议 (0)
- 监控 (0)
- 开源软件 (2)
- 算法 (0)
- 网络 (1)
- spring (1)
- 编码 (0)
- 数据结构 (0)
- HTable和HTablePool使用注意事项 (0)
- opencms (0)
- android (16)
- 操作系统 (2)
- top (0)
最新评论
-
hold_on:
@Override public boolea ...
android listview的HeadView左右切换图片(仿新浪,网易,百度等切换图片) -
achersnake:
123
Servlet中listener(监听器)和filter的总结 -
angel243fly:
我用了这个方法,还是报同样的错误,还有什么建议吗?
eclipse提示CreateProcess error=87错误的解决方法
转:http://www.cnblogs.com/duguguiyu/archive/2009/02/28/1400278.html
二. 分布式计算(Map/Reduce)
分布式式计算,同样是一个宽泛的概念,在这里,它狭义的指代,按Google Map/Reduce框架所设计的分布式框架。在Hadoop中,分布式文件系统,很大程度上,是为各种分布式计算需求所服务的。我们说分布式文件系统就是加了分布式的文件系统,类似的定义推广到分布式计算上,我们可以将其视为增加了分布式支持的计算函数。从计算的角度上看,Map/Reduce框架接受各种格式的键值对文件作为输入,读取计算后,最终生成自定义格式的输出文件。而从分布式的角度上看,分布式计算的输入文件往往规模巨大,且分布在多个机器上,单机计算完全不可支撑且效率低下,因此Map/Reduce框架需要提供一套机制,将此计算扩展到无限规模的机器集群上进行。依照这样的定义,我们对整个Map/Reduce的理解,也可以分别沿着这两个流程去看。。。
在Map/Reduce框架中,每一次计算请求,被称为作业。在分布式计算Map/Reduce框架中,为了完成这个作业,它进行两步走的战略,首先是将其拆分成若干个Map任务,分配到不同的机器上去执行,每一个Map任务拿输入文件的一部分作为自己的输入,经过一些计算,生成某种格式的中间文件,这种格式,与最终所需的文件格式完全一致,但是仅仅包含一部分数据。因此,等到所有Map任务完成后,它会进入下一个步骤,用以合并这些中间文件获得最后的输出文件。此时,系统会生成若干个Reduce任务,同样也是分配到不同的机器去执行,它的目标,就是将若干个Map任务生成的中间文件为汇总到最后的输出文件中去。当然,这个汇总不总会像1 + 1 = 2那么直接了当,这也就是Reduce任务的价值所在。经过如上步骤,最终,作业完成,所需的目标文件生成。整个算法的关键,就在于增加了一个中间文件生成的流程,大大提高了灵活性,使其分布式扩展性得到了保证。。。
I. 术语对照
和分布式文件系统一样,Google、Hadoop和....我,各执一种方式表述统一概念,为了保证其统一性,特有下表。。。
文中翻译 Hadoop术语 Google术语 相关解释
作业 Job Job 用户的每一个计算请求,就称为一个作业。
作业服务器 JobTracker Master 用户提交作业的服务器,同时,它还负责各个作业任务的分配,管理所有的任务服务器。
任务服务器 TaskTracker Worker 任劳任怨的工蜂,负责执行具体的任务。
任务 Task Task 每一个作业,都需要拆分开了,交由多个服务器来完成,拆分出来的执行单位,就称为任务。
备份任务 Speculative Task Buckup Task 每一个任务,都有可能执行失败或者缓慢,为了降低为此付出的代价,系统会未雨绸缪的实现在另外的任务服务器上执行同样一个任务,这就是备份任务。
II. 基本架构
与分布式文件系统类似,Map/Reduce的集群,也由三类服务器构成。其中作业服务器,在Hadoop中称为Job Tracker,在Google论文中称为Master。前者告诉我们,作业服务器是负责管理运行在此框架下所有作业的,后者告诉我们,它也是为各个作业分配任务的核心。与HDFS的主控服务器类似,它也是作为单点存在的,简化了负责的同步流程。具体的负责执行用户定义操作的,是任务服务器,每一个作业被拆分成很多的任务,包括Map任务和Reduce任务等,任务是具体执行的基本单元,它们都需要分配到合适任务服务器上去执行,任务服务器一边执行一边向作业服务器汇报各个任务的状态,以此来帮助作业服务器了解作业执行的整体情况,分配新的任务等等。。。
除了作业的管理者执行者,还需要有一个任务的提交者,这就是客户端。与分布式文件系统一样,客户端也不是一个单独的进程,而是一组API,用户需要自定义好自己需要的内容,经由客户端相关的代码,将作业及其相关内容和配置,提交到作业服务器去,并时刻监控执行的状况。。。
同作为Hadoop的实现,与HDFS的通信机制相同,Hadoop Map/Reduce也是用了协议接口来进行服务器间的交流。实现者作为RPC服务器,调用者经由RPC的代理进行调用,如此,完成大部分的通信,具体服务器的架构,和其中运行的各个协议状况,参见下图。从图中可以看到,与HDFS相比,相关的协议少了几个,客户端与任务服务器,任务服务器之间,都不再有直接通信关系。这并不意味着客户端就不需要了解具体任务的执行状况,也不意味着,任务服务器之间不需要了解别家任务执行的情形,只不过,由于整个集群各机器的联系比HDFS复杂的多,直接通信过于的难以维系,所以,都统一由作业服务器整理转发。另外,从这幅图可以看到,任务服务器不是一个人在战斗,它会像孙悟空一样招出一群宝宝帮助其具体执行任务。这样做的好处,个人觉得,应该有安全性方面的考虑,毕竟,任务的代码是用户提交的,数据也是用户指定的,这质量自然良莠不齐,万一碰上个搞破坏的,把整个任务服务器进程搞死了,就因小失大了。因此,放在单独的地盘进行,爱咋咋地,也算是权责明确了。。。
与分布式文件系统相比,Map/Reduce框架的还有一个特点,就是可定制性强。文件系统中很多的算法,都是很固定和直观的,不会由于所存储的内容不同而有太多的变化。而作为通用的计算框架,需要面对的问题则要复杂很多,在各种不同的问题、不同的输入、不同的需求之间,很难有一种包治百病的药能够一招鲜吃遍天。作为Map/Reduce框架而言,一方面要尽可能的抽取出公共的一些需求,实现出来。更重要的,是需要提供良好的可扩展机制,满足用户自定义各种算法的需求。Hadoop是由Java来实现的,因此通过反射来实现自定义的扩展,显得比较小菜一碟了。在JobConf类中,定义了大量的接口,这基本上是Hadoop Map/Reduce框架所有可定制内容的一次集中展示。在JobConf中,有大量set接口接受一个Class<? extends xxx>的参数,通常它都有一个默认实现的类,用户如果不满意,则可自定义实现。。。
III. 计算流程
如果一切都按部就班的进行,那么整个作业的计算流程,应该是作业的提交 -> Map任务的分配和执行 -> Reduce任务的分配和执行 -> 作业的完成。而在每个任务的执行中,又包含输入的准备 -> 算法的执行 -> 输出的生成,三个子步骤。沿着这个流程,我们可以很快的整理清晰整个Map/Reduce框架下作业的执行。。。
1、作业的提交
一个作业,在提交之前,需要把所有应该配置的东西都配置好,因为一旦提交到了作业服务器上,就陷入了完全自动化的流程,用户除了观望,最多也就能起一个监督作用,惩治一些不好好工作的任务。。。
基本上,用户在提交代码阶段,需要做的工作主要是这样的:
首先,书写好所有自定的代码,最起码,需要有Map和Reduce的执行代码。在Hadoop中,Map需要派生自Mapper<K1, V1, K2, V2>接口,Reduce需要派生自Reducer<K2, V2, K3, V3>接口。这里都是用的泛型,用以支持不同的键值类型。这两个接口都仅有一个方法,一个是map,一个是reduce,这两个方法都直接受四个参数,前两个是输入的键和值相关的数据结构,第三个是作为输出相关的数据结构,最后一个,是一个Reporter类的实例,实现的时候可以利用它来统计一些计数。除了这两个接口,还有大量可以派生的接口,比如分割的Partitioner<K2, V2>接口。。。
然后,需要书写好主函数的代码,其中最主要的内容就是实例化一个JobConf类的对象,然后调用其丰富的setXXX接口,设定好所需的内容,包括输入输出的文件路径,Map和Reduce的类,甚至包括读取写入文件所需的格式支持类,等等。。。
最后,调用JobClient的runJob方法,提交此JobConf对象。runJob方法会先行调用到JobSubmissionProtocol接口所定义的submitJob方法,将此作业,提交给作业服务器。接着,runJob开始循环,不停的调用JobSubmissionProtocol的getTaskCompletionEvents方法,获得TaskCompletionEvent类的对象实例,了解此作业各任务的执行状况。。。
2、Map任务的分配
当一个作业提交到了作业服务器上,作业服务器会生成若干个Map任务,每一个Map任务,负责将一部分的输入转换成格式与最终格式相同的中间文件。通常一个作业的输入都是基于分布式文件系统的文件(当然在单机环境下,文件系统单机的也可以...),因为,它可以很天然的和分布式的计算产生联系。而对于一个Map任务而言,它的输入往往是输入文件的一个数据块,或者是数据块的一部分,但通常,不跨数据块。因为,一旦跨了数据块,就可能涉及到多个服务器,带来了不必要的复杂性。。。
当一个作业,从客户端提交到了作业服务器上,作业服务器会生成一个JobInProgress对象,作为与之对应的标识,用于管理。作业被拆分成若干个Map任务后,会预先挂在作业服务器上的任务服务器拓扑树。这是依照分布式文件数据块的位置来划分的,比如一个Map任务需要用某个数据块,这个数据块有三份备份,那么,在这三台服务器上都会挂上此任务,可以视为是一个预分配。。。
关于任务管理和分配的大部分的真实功能和逻辑的实现,JobInProgress则依托JobInProgressListener和TaskScheduler的子类。TaskScheduler,顾名思义是用于任务分配的策略类(为了简化描述,用它代指所有TaskScheduler的子类...)。它会掌握好所有作业的任务信息,其assignTasks函数,接受一个TaskTrackerStatus作为参数,依照此任务服务器的状态和现有的任务状况,为其分配新的任务。而为了掌握所有作业相关任务的状况,TaskScheduler会将若干个JobInProgressListener注册到JobTracker中去,当有新的作业到达、移除或更新的时候,JobTracker会告知给所有的JobInProgressListener,以便它们做出相应的处理。。。
任务分配是一个重要的环节,所谓任务分配,就是将合适作业的合适任务分配到合适的服务器上。不难看出,里面蕴含了两个步骤,先是选择作业,然后是在此作业中选择任务。和所有分配工作一样,任务分配也是一个复杂的活。不良好的任务分配,可能会导致网络流量增加、某些任务服务器负载过重效率下降,等等。不仅如此,任务分配还是一个无一致模式的问题,不同的业务背景,可能需要不同的算法才能满足需求。因此,在Hadoop中,有很多TaskScheduler的子类,像Facebook,Yahoo,都为其贡献出了自家用的算法。在Hadoop中,默认的任务分配器,是JobQueueTaskScheduler类。它选择作业的基本次序是:Map Clean Up Task(Map任务服务器的清理任务,用于清理相关的过期的文件和环境...) -> Map Setup Task(Map任务服务器的安装任务,负责配置好相关的环境...) -> Map Tasks -> Reduce Clean Up Task -> Reduce Setup Task -> Reduce Tasks。在这个前提下,具体到Map任务的分配上来。当一个任务服务器工作的游刃有余,期待获得新的任务的时候,JobQueueTaskScheduler会按照各个作业的优先级,从最高优先级的作业开始分配。每分配一个,还会为其留出余量,已被不时之需。举一个例子:系统目前有优先级3、2、1的三个作业,每个作业都有一个可分配的Map任务,一个任务服务器来申请新的任务,它还有能力承载3个任务的执行,JobQueueTaskScheduler会先从优先级3的作业上取一个任务分配给它,然后再留出一个1任务的余量。此时,系统只能在将优先级2作业的任务分配给此服务器,而不能分配优先级1的任务。这样的策略,基本思路就是一切为高优先级的作业服务,优先分配不说,分配了好保留有余力以备不时之需,如此优待,足以让高优先级的作业喜极而泣,让低优先级的作业感慨既生瑜何生亮,甚至是活活饿死。。。
确定了从哪个作业提取任务后,具体的分配算法,经过一系列的调用,最后实际是由JobInProgress的findNewMapTask函数完成的。它的算法很简单,就是尽全力为此服务器非配且尽可能好的分配任务,也就是说,只要还有可分配的任务,就一定会分给它,而不考虑后来者。作业服务器会从离它最近的服务器开始,看上面是否还挂着未分配的任务(预分配上的),从近到远,如果所有的任务都分配了,那么看有没有开启多次执行,如果开启,考虑把未完成的任务再分配一次(后面有地方详述...)。。。
对于作业服务器来说,把一个任务分配出去了,并不意味着它就彻底解放,可以对此任务可以不管不顾了。因为任务可以在任务服务器上执行失败,可能执行缓慢,这都需要作业服务器帮助它们再来一次。因此在Task中,记录有一个TaskAttemptID,对于任务服务器而言,它们每次跑的,其实都只是一个Attempt而已,Reduce任务只需要采信一个的输出,其他都算白忙乎了。。。
3、Map任务的执行
与HDFS类似,任务服务器是通过心跳消息,向作业服务器汇报此时此刻其上各个任务执行的状况,并向作业服务器申请新的任务的。具体实现,是TaskTracker调用InterTrackerProtocol协议的heartbeat方法来做的。这个方法接受一个TaskTrackerStatus对象作为参数,它描述了此时此任务服务器的状态。当其有余力接受新的任务的时候,它还会传入acceptNewTasks为true的参数,表示希望作业服务器委以重任。JobTracker接收到相关的参数后,经过处理,会返回一个HeartbeatResponse对象。这个对象中,定义了一组TaskTrackerAction,用于指导任务服务器进行下一步的工作。系统中已定义的了一堆其TaskTrackerAction的子类,有的对携带的参数进行了扩充,有的只是标明了下ID,具体不详写了,一看便知。。。
当TaskTracker收到的TaskTrackerAction中,包含了LaunchTaskAction,它会开始执行所分配的新的任务。在TaskTracker中,有一个TaskTracker.TaskLauncher线程(确切的说是两个,一个等Map任务,一个等Reduce任务),它们在痴痴的守候着新任务的来到。一旦等到了,会最终调用到Task的createRunner方法,构造出一个TaskRunner对象,新建一个线程来执行。对于一个Map任务,它对应的Runner是TaskRunner的子类MapTaskRunner,不过,核心部分都在TaskRunner的实现内。TaskRunner会先将所需的文件全部下载并拆包好,并记录到一个全局缓存中,这是一个全局的目录,可以供所有此作业的所有任务使用。它会用一些软链接,将一些文件名链接到这个缓存中来。然后,根据不同的参数,配置出一个JVM执行的环境,这个环境与JvmEnv类的对象对应。
接着,TaskRunner会调用JvmManager的launchJvm方法,提交给JvmManager处理。JvmManager用于管理该TaskTracker上所有运行的Task子进程。在目前的实现中,尝试的是池化的方式。有若干个固定的槽,如果槽没有满,那么就启动新的子进程,否则,就寻找idle的进程,如果是同Job的直接放进去,否则杀死这个进程,用一个新的进程代替。每一个进程都是由JvmRunner来管理的,它也是位于单独线程中的。但是从实现上看,这个机制好像没有部署开,子进程是死循环等待,而不会阻塞在父进程的相关线程上,父线程的变量一直都没有个调整,一旦分配,始终都处在繁忙的状况了。
真实的执行载体,是Child,它包含一个main函数,进程执行,会将相关参数传进来,它会拆解这些参数,并且构造出相关的Task实例,调用其run函数进行执行。每一个子进程,可以执行指定个数量的Task,这就是上面所说的池化的配置。但是,这套机制在我看来,并没有运行起来,每个进程其实都没有机会不死而执行新的任务,只是傻傻的等待进程池满,而被一刀毙命。也许是我老眼昏花,没看出其中实现的端倪。。。
4、Reduce任务的分配与执行
比之Map任务,Reduce的分配及其简单,基本上是所有Map任务完成了,有空闲的任务服务器,来了就给分配一个Job任务。因为Map任务的结果星罗棋布,且变化多端,真要搞一个全局优化的算法,绝对是得不偿失。而Reduce任务的执行进程的构造和分配流程,与Map基本完全的一致,没有啥可说的了。。。
但其实,Reduce任务与Map任务的最大不同,是Map任务的文件都在本地隔着,而Reduce任务需要到处采集。这个流程是作业服务器经由此Reduce任务所处的任务服务器,告诉Reduce任务正在执行的进程,它需要的Map任务执行过的服务器地址,此Reduce任务服务器会于原Map任务服务器联系(当然本地就免了...),通过FTP服务,下载过来。这个隐含的直接数据联系,就是执行Reduce任务与执行Map任务最大的不同了。。。
5、作业的完成
当所有Reduce任务都完成了,所需数据都写到了分布式文件系统上,整个作业才正式完成了。此中,涉及到很多的类,很多的文件,很多的服务器,所以说起来很费劲,话说,一图解千语,说了那么多,我还是画两幅图,彻底表达一下吧。。。
首先,是一个时序图。它模拟了一个由3个Map任务和1个Reduce任务构成的作业执行流程。我们可以看到,在执行的过程中,只要有人太慢,或者失败,就会增加一次尝试,以此换取最快的执行总时间。一旦所有Map任务完成,Reduce开始运作(其实,不一定要这样的...),对于每一个Map任务来说,只有执行到Reduce任务把它上面的数据下载完成,才算成功,否则,都是失败,需要重新进行尝试。。。
而第二副图,不是我画的,就不转载了,参见这里,它描述了整个Map/Reduce的服务器状况图,包括整体流程、所处服务器进程、输入输出等,看清楚这幅图,对Map/Reduce的基本流程应该能完全跑通了。有这几点,可能图中描述的不够清晰需要提及一下,一个是在HDFS中,其实还有日志文件,图中没有标明;另一个是步骤5,其实是由TaskTracker主动去拉取而不是JobTracker推送过来的;还有步骤8和步骤11,创建出来的MapTask和ReduceTask,在Hadoop中都是运行在独立的进程上的。。。
IV. Map任务详请
从上面,可以了解到整个Map和Reduce任务的整体流程,而后面要啰嗦的,是具体执行中的细节。Map任务的输入,是分布式文件系统上的,包含键值对信息的文件。为了给每一个Map任务指定输入,我们需要掌握文件格式把它分切成块,并从每一块中分离出键值信息。在HDFS中,输入的文件格式,是由InputFormat<K, V>类来表示的,在JobConf中,它的默认值是TextInputFormat类(见getInputFormat),此类是特化的FileInputFormat<LongWritable, Text>子类,而FileInputFormat<K, V>正是InputFormat<K, V>的子类。通过这样的关系我们可以很容易的理解,默认的文件格式是文本文件,且键是LongWritable类型(整形数),值是Text类型(字符串)。仅仅知道文件类型是不够的,我们还需要将文件中的每一条数据,分离成键值对,这个工作,是RecordReader<K, V>来做的。在TextInputFormat的getRecordReader方法中我们可以看到,与TextInputFormat默认配套使用的,是LineRecordReader类,是特化的RecordReader<LongWritable, Text>的子类,它将每一行作为一个记录,起始的位置作为键,整行的字符串作为值。有了格式,分出了键值,还需要切开分给每一个Map任务。每一个Map任务的输入用InputSplit接口表示,对于一个文件输入而言,其实现是FileSplit,它包含着文件名、起始位置、长度和存储它的一组服务器地址。。。
当Map任务拿到所属的InputSplit后,就开始一条条读取记录,并调用用于定义的Mapper,进行计算(参见MapRunner<K1, V1, K2, V2>和MapTask的run方法),然后,输出。MapTask会传递给Mapper一个OutputCollector<K, V>对象,作为输出的数据结构。它定义了一个collect的函数,接受一个键值对。在MapTask中,定义了两个OutputCollector的子类,一个是MapTask.DirectMapOutputCollector<K, V>,人如其名,它的实现确实很Direct,直截了当。它会利用一个RecordWriter<K, V>对象,collect一调用,就直接调用RecordWriter<K, V>的write方法,写入本地的文件中去。如果觉着RecordWriter<K, V>出现的很突兀,那么看看上一段提到的RecordReader<K, V>,基本上,数据结构都是对应着的,一个是输入一个是输出。输出很对称也是由RecordWriter<K, V>和OutputFormat<K, V>来协同完成的,其默认实现是LineRecordWriter<K, V>和TextOutputFormat<K, V>,多么的眼熟啊。。。
除了这个非常直接的实现之外,MapTask中还有一个复杂的多的实现,是MapTask.MapOutputBuffer<K extends Object, V extends Object>。有道是简单压倒一切,那为什么有很简单的实现,要琢磨一个复杂的呢。原因在于,看上去很美的往往带着刺,简单的输出实现,每调用一次collect就写一次文件,频繁的硬盘操作很有可能导致此方案的低效。为了解决这个问题,这就有了这个复杂版本,它先开好一段内存做缓存,然后制定一个比例做阈值,开一个线程监控此缓存。collect来的内容,先写到缓存中,当监控线程发现缓存的内容比例超过阈值,挂起所有写入操作,建一个新的文件,把缓存的内容批量刷到此文件中去,清空缓存,重新开放,接受继续collect。。。
为什么说是刷到文件中去呢。因为这不是一个简单的照本宣科简单复制的过程,在写入之前,会先将缓存中的内存,经过排序、合并器(Combiner)统计之后,才会写入。如果你觉得Combiner这个名词听着太陌生,那么考虑一下Reducer,Combiner也就是一个Reducer类,通过JobConf的setCombinerClass进行设置,在常用的配置中,Combiner往往就是用用户为Reduce任务定义的那个Reducer子类。只不过,Combiner只是服务的范围更小一些而已,它在Map任务执行的服务器本地,依照Map处理过的那一小部分数据,先做一次Reduce操作,这样,可以压缩需要传输内容的大小,提高速度。每一次刷缓存,都会开一个新的文件,等此任务所有的输入都处理完成后,就有了若干个有序的、经过合并的输出文件。系统会将这些文件搞在一起,再做一个多路的归并外排,同时使用合并器进行合并,最终,得到了唯一的、有序的、经过合并的中间文件(注:文件数量等同于分类数量,在不考虑分类的时候,简单的视为一个...)。它,就是Reduce任务梦寐以求的输入文件。。。
除了做合并,复杂版本的OutputCollector,还具有分类的功能。分类,是通过Partitioner<K2, V2>来定义的,默认实现是HashPartitioner<K2, V2>,作业提交者可以通过JobConf的setPartitionerClass来自定义。分类的含义是什么呢,简单的说,就是将Map任务的输出,划分到若干个文件中(通常与Reduce任务数目相等),使得每一个Reduce任务,可以处理某一类文件。这样的好处是大大的,举一个例子说明一下。比如有一个作业是进行单词统计的,其Map任务的中间结果应该是以单词为键,以单词数量为值的文件。如果这时候只有一个Reduce任务,那还好说,从全部的Map任务那里收集文件过来,分别统计得到最后的输出文件就好。但是,如果单Reduce任务无法承载此负载或效率太低,就需要多个Reduce任务并行执行。此时,再沿用之前的模式就有了问题。每个Reduce任务从一部分Map任务那里获得输入文件,但最终的输出结果并不正确,因为同一个单词可能在不同的Reduce任务那里都有统计,需要想方法把它们统计在一起才能获得最后结果,这样就没有将Map/Reduce的作用完全发挥出来。这时候,就需要用到分类。如果此时有两个Reduce任务,那么将输出分成两类,一类存放字母表排序较高的单词,一类存放字母表排序低的单词,每一个Reduce任务从所有的Map任务那里获取一类的中间文件,得到自己的输出结果。最终的结果,只需要把各个Reduce任务输出的,拼接在一起就可以了。本质上,这就是将Reduce任务的输入,由垂直分割,变成了水平分割。Partitioner的作用,正是接受一个键值,返回一个分类的序号。它会在从缓存刷到文件之前做这个工作,其实只是多了一个文件名的选择而已,别的逻辑都不需要变化。。。
除了缓存、合并、分类等附加工作之外,复杂版本的OutputCollector还支持错误数据的跳过功能,在后面分布式将排错的时候,还会提及,标记一下,按下不表。。。
V. Reduce任务详情
理论上看,Reduce任务的整个执行流程要比Map任务更为的罗嗦一些,因为,它需要收集输入文件,然后才能进行处理。Reduce任务,主要有这么三个步骤:Copy、Sort、Reduce(参见ReduceTask的run方法)。所谓Copy,就是从执行各个Map任务的服务器那里,收罗到本地来。拷贝的任务,是由ReduceTask.ReduceCopier类来负责,它有一个内嵌类,叫MapOutputCopier,它会在一个单独的线程内,负责某个Map任务服务器上文件的拷贝工作。远程拷贝过来的内容(当然也可以是本地了...),作为MapOutput对象存在,它可以在内存中也可以序列化在磁盘上,这个根据内存使用状况来自动调节。整个拷贝过程是一个动态的过程,也就是说它不是一次给好所有输入信息就不再变化了。它会不停的调用TaskUmbilicalProtocol协议的getMapCompletionEvents方法,向其父TaskTracker询问此作业个Map任务的完成状况(TaskTracker要向JobTracker询问后再转告给它...)。当获取到相关Map任务执行服务器的信息后,都会有一个线程开启,做具体的拷贝工作。同时,还有一个内存Merger线程和一个文件Merger线程在同步工作,它们将新鲜下载过来的文件(可能在内存中,简单的统称为文件...),做着归并排序,以此,节约时间,降低输入文件的数量,为后续的排序工作减负。。。
Sort,排序工作,就相当于上述排序工作的一个延续。它会在所有的文件都拷贝完毕后进行,因为虽然同步有做着归并的工作,但可能留着尾巴,没做彻底。经过这一个流程,该彻底的都彻底了,一个崭新的、合并了所有所需Map任务输出文件的新文件,诞生了。而那些千行万苦从其他各个服务器网罗过来的Map任务输出文件,很快的结束了它们的历史使命,被扫地出门一扫而光,全部删除了。。。
所谓好戏在后头,Reduce任务的最后一个阶段,正是Reduce本身。它也会准备一个OutputCollector收集输出,与MapTask不同,这个OutputCollector更为简单,仅仅是打开一个RecordWriter,collect一次,write一次。最大的不同在于,这次传入RecordWriter的文件系统,基本都是分布式文件系统,或者说是HDFS。而在输入方面,ReduceTask会从JobConf那里调用一堆getMapOutputKeyClass、getMapOutputValueClass、getOutputKeyComparator等等之类的自定义类,构造出Reducer所需的键类型,和值的迭代类型Iterator(一个键到了这里一般是对应一组值)。具体实现颇为拐弯抹角,建议看一下Merger.MergeQueue,RawKeyValueIterator,ReduceTask.ReduceValuesIterator等等之类的实现。有了输入,有了输出,不断循环调用自定义的Reducer,最终,Reduce阶段完成。。。
VI. 分布式支持
1、服务器正确性保证
Hadoop Map/Reduce服务器状况和HDFS很类似,由此可知,救死扶伤的方法也是大同小异。废话不多说了,直接切正题。同作为客户端,Map/Reduce的客户端只是将作业提交,就开始搬个板凳看戏,没有占茅坑的行动。因此,一旦它挂了,也就挂了,不伤大雅。而任务服务器,也需要随时与作业服务器保持心跳联系,一旦有了问题,作业服务器可以将其上运行的任务,移交给它人完成。作业服务器,作为一个单点,非常类似的是利用还原点(等同于HDFS的镜像)和历史记录(等同于HDFS的日志),来进行恢复。其上,需要持久化用于恢复的内容,包含作业状况、任务状况、各个任务尝试的工作状况等。有了这些内容,再加上任务服务器的动态注册,就算挪了个窝,还是很容易恢复的。JobHistory是历史记录相关的一个静态类,本来,它也就是一个干写日志活的,只是在Hadoop的实现中,对日志的写入做了面向对象的封装,同时又大量用到观察者模式做了些嵌入,使得看起来不是那么直观。本质上,它就是打开若干个日志文件,利用各类接口来往里面写内容。只不过,这些日志,会放在分布式文件系统中,就不需要像HDFS那样,来一个SecondXXX随时候命,由此可见,有巨人在脚下踩着,真好。JobTracker.RecoveryManager类是作业服务器中用于进行恢复相关的事情,当作业服务器启动的时候,会调用其recover方法,恢复日志文件中的内容。其中步骤,注释中写的很清楚,请自行查看。。。
2、任务执行的正确和速度
整个作业流程的执行,秉承着木桶原理。执行的最慢的Map任务和Reduce任务,决定了系统整体执行时间(当然,如果执行时间在整个流程中占比例很小的话,也许就微不足道了...)。因此,尽量加快最慢的任务执行速度,成为提高整体速度关键。所使用的策略,简约而不简单,就是一个任务多次执行。当所有未执行的任务都分配出去了,并且先富起来的那部分任务已经完成了,并还有任务服务器孜孜不倦的索取任务的时候,作业服务器会开始炒剩饭,把那些正在吭哧吭哧在某个服务器上慢慢执行的任务,再把此任务分配到一个新的任务服务器上,同时执行。两个服务器各尽其力,成王败寇,先结束者的结果将被采纳。这样的策略,隐含着一个假设,就是我们相信,输入文件的分割算法是公平的,某个任务执行慢,并不是由于这个任务本身负担太重,而是由于服务器不争气负担太重能力有限或者是即将撒手西去,给它换个新环境,人挪死树挪活事半功倍。。。
当然,肯定有哽咽的任务,不论是在哪个服务器上,都无法顺利完成。这就说明,此问题不在于服务器上,而是任务本身天资有缺憾。缺憾在何处?每个作业,功能代码都是一样的,别的任务成功了,就是这个任务不成功,很显然,问题出在输入那里。输入中有非法的输入条目,导致程序无法辨识,只能挥泪惜别。说到这里,解决策略也浮出水面了,三十六计走位上,惹不起,还是躲得起的。在MapTask中的MapTask.SkippingRecordReader<K, V>和ReduceTask里的ReduceTask.SkippingReduceValuesIterator<KEY,VALUE>,都是用于干这个事情的。它们的原理很简单,就是在读一条记录前,把当前的位置信息,封装成SortedRanges.Range对象,经由Task的reportNextRecordRange方法提交到TaskTracker上去。TaskTracker会把这些内容,搁在TaskStatus对象中,随着心跳消息,汇报到JobTracker上面。这样,作业服务器就可以随时随刻了解清楚,每个任务正读取在那个位置,一旦出错,再次执行的时候,就在分配的任务信息里面添加一组SortedRanges信息。MapTask或ReduceTask读取的时候,会看一下这些区域,如果当前区域正好处于上述雷区,跳过不读。如此反复,正可谓,道路曲折,前途光明啊。。。
VII. 总结
对于Map/Reduce而言,真正的困难,在于提高其适应能力,打造一款能够包治百病的执行框架。Hadoop已经做得很好了,但只有真正搞清楚了整个流程,你才能帮助它做的更好。。。
二. 分布式计算(Map/Reduce)
分布式式计算,同样是一个宽泛的概念,在这里,它狭义的指代,按Google Map/Reduce框架所设计的分布式框架。在Hadoop中,分布式文件系统,很大程度上,是为各种分布式计算需求所服务的。我们说分布式文件系统就是加了分布式的文件系统,类似的定义推广到分布式计算上,我们可以将其视为增加了分布式支持的计算函数。从计算的角度上看,Map/Reduce框架接受各种格式的键值对文件作为输入,读取计算后,最终生成自定义格式的输出文件。而从分布式的角度上看,分布式计算的输入文件往往规模巨大,且分布在多个机器上,单机计算完全不可支撑且效率低下,因此Map/Reduce框架需要提供一套机制,将此计算扩展到无限规模的机器集群上进行。依照这样的定义,我们对整个Map/Reduce的理解,也可以分别沿着这两个流程去看。。。
在Map/Reduce框架中,每一次计算请求,被称为作业。在分布式计算Map/Reduce框架中,为了完成这个作业,它进行两步走的战略,首先是将其拆分成若干个Map任务,分配到不同的机器上去执行,每一个Map任务拿输入文件的一部分作为自己的输入,经过一些计算,生成某种格式的中间文件,这种格式,与最终所需的文件格式完全一致,但是仅仅包含一部分数据。因此,等到所有Map任务完成后,它会进入下一个步骤,用以合并这些中间文件获得最后的输出文件。此时,系统会生成若干个Reduce任务,同样也是分配到不同的机器去执行,它的目标,就是将若干个Map任务生成的中间文件为汇总到最后的输出文件中去。当然,这个汇总不总会像1 + 1 = 2那么直接了当,这也就是Reduce任务的价值所在。经过如上步骤,最终,作业完成,所需的目标文件生成。整个算法的关键,就在于增加了一个中间文件生成的流程,大大提高了灵活性,使其分布式扩展性得到了保证。。。
I. 术语对照
和分布式文件系统一样,Google、Hadoop和....我,各执一种方式表述统一概念,为了保证其统一性,特有下表。。。
文中翻译 Hadoop术语 Google术语 相关解释
作业 Job Job 用户的每一个计算请求,就称为一个作业。
作业服务器 JobTracker Master 用户提交作业的服务器,同时,它还负责各个作业任务的分配,管理所有的任务服务器。
任务服务器 TaskTracker Worker 任劳任怨的工蜂,负责执行具体的任务。
任务 Task Task 每一个作业,都需要拆分开了,交由多个服务器来完成,拆分出来的执行单位,就称为任务。
备份任务 Speculative Task Buckup Task 每一个任务,都有可能执行失败或者缓慢,为了降低为此付出的代价,系统会未雨绸缪的实现在另外的任务服务器上执行同样一个任务,这就是备份任务。
II. 基本架构
与分布式文件系统类似,Map/Reduce的集群,也由三类服务器构成。其中作业服务器,在Hadoop中称为Job Tracker,在Google论文中称为Master。前者告诉我们,作业服务器是负责管理运行在此框架下所有作业的,后者告诉我们,它也是为各个作业分配任务的核心。与HDFS的主控服务器类似,它也是作为单点存在的,简化了负责的同步流程。具体的负责执行用户定义操作的,是任务服务器,每一个作业被拆分成很多的任务,包括Map任务和Reduce任务等,任务是具体执行的基本单元,它们都需要分配到合适任务服务器上去执行,任务服务器一边执行一边向作业服务器汇报各个任务的状态,以此来帮助作业服务器了解作业执行的整体情况,分配新的任务等等。。。
除了作业的管理者执行者,还需要有一个任务的提交者,这就是客户端。与分布式文件系统一样,客户端也不是一个单独的进程,而是一组API,用户需要自定义好自己需要的内容,经由客户端相关的代码,将作业及其相关内容和配置,提交到作业服务器去,并时刻监控执行的状况。。。
同作为Hadoop的实现,与HDFS的通信机制相同,Hadoop Map/Reduce也是用了协议接口来进行服务器间的交流。实现者作为RPC服务器,调用者经由RPC的代理进行调用,如此,完成大部分的通信,具体服务器的架构,和其中运行的各个协议状况,参见下图。从图中可以看到,与HDFS相比,相关的协议少了几个,客户端与任务服务器,任务服务器之间,都不再有直接通信关系。这并不意味着客户端就不需要了解具体任务的执行状况,也不意味着,任务服务器之间不需要了解别家任务执行的情形,只不过,由于整个集群各机器的联系比HDFS复杂的多,直接通信过于的难以维系,所以,都统一由作业服务器整理转发。另外,从这幅图可以看到,任务服务器不是一个人在战斗,它会像孙悟空一样招出一群宝宝帮助其具体执行任务。这样做的好处,个人觉得,应该有安全性方面的考虑,毕竟,任务的代码是用户提交的,数据也是用户指定的,这质量自然良莠不齐,万一碰上个搞破坏的,把整个任务服务器进程搞死了,就因小失大了。因此,放在单独的地盘进行,爱咋咋地,也算是权责明确了。。。
与分布式文件系统相比,Map/Reduce框架的还有一个特点,就是可定制性强。文件系统中很多的算法,都是很固定和直观的,不会由于所存储的内容不同而有太多的变化。而作为通用的计算框架,需要面对的问题则要复杂很多,在各种不同的问题、不同的输入、不同的需求之间,很难有一种包治百病的药能够一招鲜吃遍天。作为Map/Reduce框架而言,一方面要尽可能的抽取出公共的一些需求,实现出来。更重要的,是需要提供良好的可扩展机制,满足用户自定义各种算法的需求。Hadoop是由Java来实现的,因此通过反射来实现自定义的扩展,显得比较小菜一碟了。在JobConf类中,定义了大量的接口,这基本上是Hadoop Map/Reduce框架所有可定制内容的一次集中展示。在JobConf中,有大量set接口接受一个Class<? extends xxx>的参数,通常它都有一个默认实现的类,用户如果不满意,则可自定义实现。。。
III. 计算流程
如果一切都按部就班的进行,那么整个作业的计算流程,应该是作业的提交 -> Map任务的分配和执行 -> Reduce任务的分配和执行 -> 作业的完成。而在每个任务的执行中,又包含输入的准备 -> 算法的执行 -> 输出的生成,三个子步骤。沿着这个流程,我们可以很快的整理清晰整个Map/Reduce框架下作业的执行。。。
1、作业的提交
一个作业,在提交之前,需要把所有应该配置的东西都配置好,因为一旦提交到了作业服务器上,就陷入了完全自动化的流程,用户除了观望,最多也就能起一个监督作用,惩治一些不好好工作的任务。。。
基本上,用户在提交代码阶段,需要做的工作主要是这样的:
首先,书写好所有自定的代码,最起码,需要有Map和Reduce的执行代码。在Hadoop中,Map需要派生自Mapper<K1, V1, K2, V2>接口,Reduce需要派生自Reducer<K2, V2, K3, V3>接口。这里都是用的泛型,用以支持不同的键值类型。这两个接口都仅有一个方法,一个是map,一个是reduce,这两个方法都直接受四个参数,前两个是输入的键和值相关的数据结构,第三个是作为输出相关的数据结构,最后一个,是一个Reporter类的实例,实现的时候可以利用它来统计一些计数。除了这两个接口,还有大量可以派生的接口,比如分割的Partitioner<K2, V2>接口。。。
然后,需要书写好主函数的代码,其中最主要的内容就是实例化一个JobConf类的对象,然后调用其丰富的setXXX接口,设定好所需的内容,包括输入输出的文件路径,Map和Reduce的类,甚至包括读取写入文件所需的格式支持类,等等。。。
最后,调用JobClient的runJob方法,提交此JobConf对象。runJob方法会先行调用到JobSubmissionProtocol接口所定义的submitJob方法,将此作业,提交给作业服务器。接着,runJob开始循环,不停的调用JobSubmissionProtocol的getTaskCompletionEvents方法,获得TaskCompletionEvent类的对象实例,了解此作业各任务的执行状况。。。
2、Map任务的分配
当一个作业提交到了作业服务器上,作业服务器会生成若干个Map任务,每一个Map任务,负责将一部分的输入转换成格式与最终格式相同的中间文件。通常一个作业的输入都是基于分布式文件系统的文件(当然在单机环境下,文件系统单机的也可以...),因为,它可以很天然的和分布式的计算产生联系。而对于一个Map任务而言,它的输入往往是输入文件的一个数据块,或者是数据块的一部分,但通常,不跨数据块。因为,一旦跨了数据块,就可能涉及到多个服务器,带来了不必要的复杂性。。。
当一个作业,从客户端提交到了作业服务器上,作业服务器会生成一个JobInProgress对象,作为与之对应的标识,用于管理。作业被拆分成若干个Map任务后,会预先挂在作业服务器上的任务服务器拓扑树。这是依照分布式文件数据块的位置来划分的,比如一个Map任务需要用某个数据块,这个数据块有三份备份,那么,在这三台服务器上都会挂上此任务,可以视为是一个预分配。。。
关于任务管理和分配的大部分的真实功能和逻辑的实现,JobInProgress则依托JobInProgressListener和TaskScheduler的子类。TaskScheduler,顾名思义是用于任务分配的策略类(为了简化描述,用它代指所有TaskScheduler的子类...)。它会掌握好所有作业的任务信息,其assignTasks函数,接受一个TaskTrackerStatus作为参数,依照此任务服务器的状态和现有的任务状况,为其分配新的任务。而为了掌握所有作业相关任务的状况,TaskScheduler会将若干个JobInProgressListener注册到JobTracker中去,当有新的作业到达、移除或更新的时候,JobTracker会告知给所有的JobInProgressListener,以便它们做出相应的处理。。。
任务分配是一个重要的环节,所谓任务分配,就是将合适作业的合适任务分配到合适的服务器上。不难看出,里面蕴含了两个步骤,先是选择作业,然后是在此作业中选择任务。和所有分配工作一样,任务分配也是一个复杂的活。不良好的任务分配,可能会导致网络流量增加、某些任务服务器负载过重效率下降,等等。不仅如此,任务分配还是一个无一致模式的问题,不同的业务背景,可能需要不同的算法才能满足需求。因此,在Hadoop中,有很多TaskScheduler的子类,像Facebook,Yahoo,都为其贡献出了自家用的算法。在Hadoop中,默认的任务分配器,是JobQueueTaskScheduler类。它选择作业的基本次序是:Map Clean Up Task(Map任务服务器的清理任务,用于清理相关的过期的文件和环境...) -> Map Setup Task(Map任务服务器的安装任务,负责配置好相关的环境...) -> Map Tasks -> Reduce Clean Up Task -> Reduce Setup Task -> Reduce Tasks。在这个前提下,具体到Map任务的分配上来。当一个任务服务器工作的游刃有余,期待获得新的任务的时候,JobQueueTaskScheduler会按照各个作业的优先级,从最高优先级的作业开始分配。每分配一个,还会为其留出余量,已被不时之需。举一个例子:系统目前有优先级3、2、1的三个作业,每个作业都有一个可分配的Map任务,一个任务服务器来申请新的任务,它还有能力承载3个任务的执行,JobQueueTaskScheduler会先从优先级3的作业上取一个任务分配给它,然后再留出一个1任务的余量。此时,系统只能在将优先级2作业的任务分配给此服务器,而不能分配优先级1的任务。这样的策略,基本思路就是一切为高优先级的作业服务,优先分配不说,分配了好保留有余力以备不时之需,如此优待,足以让高优先级的作业喜极而泣,让低优先级的作业感慨既生瑜何生亮,甚至是活活饿死。。。
确定了从哪个作业提取任务后,具体的分配算法,经过一系列的调用,最后实际是由JobInProgress的findNewMapTask函数完成的。它的算法很简单,就是尽全力为此服务器非配且尽可能好的分配任务,也就是说,只要还有可分配的任务,就一定会分给它,而不考虑后来者。作业服务器会从离它最近的服务器开始,看上面是否还挂着未分配的任务(预分配上的),从近到远,如果所有的任务都分配了,那么看有没有开启多次执行,如果开启,考虑把未完成的任务再分配一次(后面有地方详述...)。。。
对于作业服务器来说,把一个任务分配出去了,并不意味着它就彻底解放,可以对此任务可以不管不顾了。因为任务可以在任务服务器上执行失败,可能执行缓慢,这都需要作业服务器帮助它们再来一次。因此在Task中,记录有一个TaskAttemptID,对于任务服务器而言,它们每次跑的,其实都只是一个Attempt而已,Reduce任务只需要采信一个的输出,其他都算白忙乎了。。。
3、Map任务的执行
与HDFS类似,任务服务器是通过心跳消息,向作业服务器汇报此时此刻其上各个任务执行的状况,并向作业服务器申请新的任务的。具体实现,是TaskTracker调用InterTrackerProtocol协议的heartbeat方法来做的。这个方法接受一个TaskTrackerStatus对象作为参数,它描述了此时此任务服务器的状态。当其有余力接受新的任务的时候,它还会传入acceptNewTasks为true的参数,表示希望作业服务器委以重任。JobTracker接收到相关的参数后,经过处理,会返回一个HeartbeatResponse对象。这个对象中,定义了一组TaskTrackerAction,用于指导任务服务器进行下一步的工作。系统中已定义的了一堆其TaskTrackerAction的子类,有的对携带的参数进行了扩充,有的只是标明了下ID,具体不详写了,一看便知。。。
当TaskTracker收到的TaskTrackerAction中,包含了LaunchTaskAction,它会开始执行所分配的新的任务。在TaskTracker中,有一个TaskTracker.TaskLauncher线程(确切的说是两个,一个等Map任务,一个等Reduce任务),它们在痴痴的守候着新任务的来到。一旦等到了,会最终调用到Task的createRunner方法,构造出一个TaskRunner对象,新建一个线程来执行。对于一个Map任务,它对应的Runner是TaskRunner的子类MapTaskRunner,不过,核心部分都在TaskRunner的实现内。TaskRunner会先将所需的文件全部下载并拆包好,并记录到一个全局缓存中,这是一个全局的目录,可以供所有此作业的所有任务使用。它会用一些软链接,将一些文件名链接到这个缓存中来。然后,根据不同的参数,配置出一个JVM执行的环境,这个环境与JvmEnv类的对象对应。
接着,TaskRunner会调用JvmManager的launchJvm方法,提交给JvmManager处理。JvmManager用于管理该TaskTracker上所有运行的Task子进程。在目前的实现中,尝试的是池化的方式。有若干个固定的槽,如果槽没有满,那么就启动新的子进程,否则,就寻找idle的进程,如果是同Job的直接放进去,否则杀死这个进程,用一个新的进程代替。每一个进程都是由JvmRunner来管理的,它也是位于单独线程中的。但是从实现上看,这个机制好像没有部署开,子进程是死循环等待,而不会阻塞在父进程的相关线程上,父线程的变量一直都没有个调整,一旦分配,始终都处在繁忙的状况了。
真实的执行载体,是Child,它包含一个main函数,进程执行,会将相关参数传进来,它会拆解这些参数,并且构造出相关的Task实例,调用其run函数进行执行。每一个子进程,可以执行指定个数量的Task,这就是上面所说的池化的配置。但是,这套机制在我看来,并没有运行起来,每个进程其实都没有机会不死而执行新的任务,只是傻傻的等待进程池满,而被一刀毙命。也许是我老眼昏花,没看出其中实现的端倪。。。
4、Reduce任务的分配与执行
比之Map任务,Reduce的分配及其简单,基本上是所有Map任务完成了,有空闲的任务服务器,来了就给分配一个Job任务。因为Map任务的结果星罗棋布,且变化多端,真要搞一个全局优化的算法,绝对是得不偿失。而Reduce任务的执行进程的构造和分配流程,与Map基本完全的一致,没有啥可说的了。。。
但其实,Reduce任务与Map任务的最大不同,是Map任务的文件都在本地隔着,而Reduce任务需要到处采集。这个流程是作业服务器经由此Reduce任务所处的任务服务器,告诉Reduce任务正在执行的进程,它需要的Map任务执行过的服务器地址,此Reduce任务服务器会于原Map任务服务器联系(当然本地就免了...),通过FTP服务,下载过来。这个隐含的直接数据联系,就是执行Reduce任务与执行Map任务最大的不同了。。。
5、作业的完成
当所有Reduce任务都完成了,所需数据都写到了分布式文件系统上,整个作业才正式完成了。此中,涉及到很多的类,很多的文件,很多的服务器,所以说起来很费劲,话说,一图解千语,说了那么多,我还是画两幅图,彻底表达一下吧。。。
首先,是一个时序图。它模拟了一个由3个Map任务和1个Reduce任务构成的作业执行流程。我们可以看到,在执行的过程中,只要有人太慢,或者失败,就会增加一次尝试,以此换取最快的执行总时间。一旦所有Map任务完成,Reduce开始运作(其实,不一定要这样的...),对于每一个Map任务来说,只有执行到Reduce任务把它上面的数据下载完成,才算成功,否则,都是失败,需要重新进行尝试。。。
而第二副图,不是我画的,就不转载了,参见这里,它描述了整个Map/Reduce的服务器状况图,包括整体流程、所处服务器进程、输入输出等,看清楚这幅图,对Map/Reduce的基本流程应该能完全跑通了。有这几点,可能图中描述的不够清晰需要提及一下,一个是在HDFS中,其实还有日志文件,图中没有标明;另一个是步骤5,其实是由TaskTracker主动去拉取而不是JobTracker推送过来的;还有步骤8和步骤11,创建出来的MapTask和ReduceTask,在Hadoop中都是运行在独立的进程上的。。。
IV. Map任务详请
从上面,可以了解到整个Map和Reduce任务的整体流程,而后面要啰嗦的,是具体执行中的细节。Map任务的输入,是分布式文件系统上的,包含键值对信息的文件。为了给每一个Map任务指定输入,我们需要掌握文件格式把它分切成块,并从每一块中分离出键值信息。在HDFS中,输入的文件格式,是由InputFormat<K, V>类来表示的,在JobConf中,它的默认值是TextInputFormat类(见getInputFormat),此类是特化的FileInputFormat<LongWritable, Text>子类,而FileInputFormat<K, V>正是InputFormat<K, V>的子类。通过这样的关系我们可以很容易的理解,默认的文件格式是文本文件,且键是LongWritable类型(整形数),值是Text类型(字符串)。仅仅知道文件类型是不够的,我们还需要将文件中的每一条数据,分离成键值对,这个工作,是RecordReader<K, V>来做的。在TextInputFormat的getRecordReader方法中我们可以看到,与TextInputFormat默认配套使用的,是LineRecordReader类,是特化的RecordReader<LongWritable, Text>的子类,它将每一行作为一个记录,起始的位置作为键,整行的字符串作为值。有了格式,分出了键值,还需要切开分给每一个Map任务。每一个Map任务的输入用InputSplit接口表示,对于一个文件输入而言,其实现是FileSplit,它包含着文件名、起始位置、长度和存储它的一组服务器地址。。。
当Map任务拿到所属的InputSplit后,就开始一条条读取记录,并调用用于定义的Mapper,进行计算(参见MapRunner<K1, V1, K2, V2>和MapTask的run方法),然后,输出。MapTask会传递给Mapper一个OutputCollector<K, V>对象,作为输出的数据结构。它定义了一个collect的函数,接受一个键值对。在MapTask中,定义了两个OutputCollector的子类,一个是MapTask.DirectMapOutputCollector<K, V>,人如其名,它的实现确实很Direct,直截了当。它会利用一个RecordWriter<K, V>对象,collect一调用,就直接调用RecordWriter<K, V>的write方法,写入本地的文件中去。如果觉着RecordWriter<K, V>出现的很突兀,那么看看上一段提到的RecordReader<K, V>,基本上,数据结构都是对应着的,一个是输入一个是输出。输出很对称也是由RecordWriter<K, V>和OutputFormat<K, V>来协同完成的,其默认实现是LineRecordWriter<K, V>和TextOutputFormat<K, V>,多么的眼熟啊。。。
除了这个非常直接的实现之外,MapTask中还有一个复杂的多的实现,是MapTask.MapOutputBuffer<K extends Object, V extends Object>。有道是简单压倒一切,那为什么有很简单的实现,要琢磨一个复杂的呢。原因在于,看上去很美的往往带着刺,简单的输出实现,每调用一次collect就写一次文件,频繁的硬盘操作很有可能导致此方案的低效。为了解决这个问题,这就有了这个复杂版本,它先开好一段内存做缓存,然后制定一个比例做阈值,开一个线程监控此缓存。collect来的内容,先写到缓存中,当监控线程发现缓存的内容比例超过阈值,挂起所有写入操作,建一个新的文件,把缓存的内容批量刷到此文件中去,清空缓存,重新开放,接受继续collect。。。
为什么说是刷到文件中去呢。因为这不是一个简单的照本宣科简单复制的过程,在写入之前,会先将缓存中的内存,经过排序、合并器(Combiner)统计之后,才会写入。如果你觉得Combiner这个名词听着太陌生,那么考虑一下Reducer,Combiner也就是一个Reducer类,通过JobConf的setCombinerClass进行设置,在常用的配置中,Combiner往往就是用用户为Reduce任务定义的那个Reducer子类。只不过,Combiner只是服务的范围更小一些而已,它在Map任务执行的服务器本地,依照Map处理过的那一小部分数据,先做一次Reduce操作,这样,可以压缩需要传输内容的大小,提高速度。每一次刷缓存,都会开一个新的文件,等此任务所有的输入都处理完成后,就有了若干个有序的、经过合并的输出文件。系统会将这些文件搞在一起,再做一个多路的归并外排,同时使用合并器进行合并,最终,得到了唯一的、有序的、经过合并的中间文件(注:文件数量等同于分类数量,在不考虑分类的时候,简单的视为一个...)。它,就是Reduce任务梦寐以求的输入文件。。。
除了做合并,复杂版本的OutputCollector,还具有分类的功能。分类,是通过Partitioner<K2, V2>来定义的,默认实现是HashPartitioner<K2, V2>,作业提交者可以通过JobConf的setPartitionerClass来自定义。分类的含义是什么呢,简单的说,就是将Map任务的输出,划分到若干个文件中(通常与Reduce任务数目相等),使得每一个Reduce任务,可以处理某一类文件。这样的好处是大大的,举一个例子说明一下。比如有一个作业是进行单词统计的,其Map任务的中间结果应该是以单词为键,以单词数量为值的文件。如果这时候只有一个Reduce任务,那还好说,从全部的Map任务那里收集文件过来,分别统计得到最后的输出文件就好。但是,如果单Reduce任务无法承载此负载或效率太低,就需要多个Reduce任务并行执行。此时,再沿用之前的模式就有了问题。每个Reduce任务从一部分Map任务那里获得输入文件,但最终的输出结果并不正确,因为同一个单词可能在不同的Reduce任务那里都有统计,需要想方法把它们统计在一起才能获得最后结果,这样就没有将Map/Reduce的作用完全发挥出来。这时候,就需要用到分类。如果此时有两个Reduce任务,那么将输出分成两类,一类存放字母表排序较高的单词,一类存放字母表排序低的单词,每一个Reduce任务从所有的Map任务那里获取一类的中间文件,得到自己的输出结果。最终的结果,只需要把各个Reduce任务输出的,拼接在一起就可以了。本质上,这就是将Reduce任务的输入,由垂直分割,变成了水平分割。Partitioner的作用,正是接受一个键值,返回一个分类的序号。它会在从缓存刷到文件之前做这个工作,其实只是多了一个文件名的选择而已,别的逻辑都不需要变化。。。
除了缓存、合并、分类等附加工作之外,复杂版本的OutputCollector还支持错误数据的跳过功能,在后面分布式将排错的时候,还会提及,标记一下,按下不表。。。
V. Reduce任务详情
理论上看,Reduce任务的整个执行流程要比Map任务更为的罗嗦一些,因为,它需要收集输入文件,然后才能进行处理。Reduce任务,主要有这么三个步骤:Copy、Sort、Reduce(参见ReduceTask的run方法)。所谓Copy,就是从执行各个Map任务的服务器那里,收罗到本地来。拷贝的任务,是由ReduceTask.ReduceCopier类来负责,它有一个内嵌类,叫MapOutputCopier,它会在一个单独的线程内,负责某个Map任务服务器上文件的拷贝工作。远程拷贝过来的内容(当然也可以是本地了...),作为MapOutput对象存在,它可以在内存中也可以序列化在磁盘上,这个根据内存使用状况来自动调节。整个拷贝过程是一个动态的过程,也就是说它不是一次给好所有输入信息就不再变化了。它会不停的调用TaskUmbilicalProtocol协议的getMapCompletionEvents方法,向其父TaskTracker询问此作业个Map任务的完成状况(TaskTracker要向JobTracker询问后再转告给它...)。当获取到相关Map任务执行服务器的信息后,都会有一个线程开启,做具体的拷贝工作。同时,还有一个内存Merger线程和一个文件Merger线程在同步工作,它们将新鲜下载过来的文件(可能在内存中,简单的统称为文件...),做着归并排序,以此,节约时间,降低输入文件的数量,为后续的排序工作减负。。。
Sort,排序工作,就相当于上述排序工作的一个延续。它会在所有的文件都拷贝完毕后进行,因为虽然同步有做着归并的工作,但可能留着尾巴,没做彻底。经过这一个流程,该彻底的都彻底了,一个崭新的、合并了所有所需Map任务输出文件的新文件,诞生了。而那些千行万苦从其他各个服务器网罗过来的Map任务输出文件,很快的结束了它们的历史使命,被扫地出门一扫而光,全部删除了。。。
所谓好戏在后头,Reduce任务的最后一个阶段,正是Reduce本身。它也会准备一个OutputCollector收集输出,与MapTask不同,这个OutputCollector更为简单,仅仅是打开一个RecordWriter,collect一次,write一次。最大的不同在于,这次传入RecordWriter的文件系统,基本都是分布式文件系统,或者说是HDFS。而在输入方面,ReduceTask会从JobConf那里调用一堆getMapOutputKeyClass、getMapOutputValueClass、getOutputKeyComparator等等之类的自定义类,构造出Reducer所需的键类型,和值的迭代类型Iterator(一个键到了这里一般是对应一组值)。具体实现颇为拐弯抹角,建议看一下Merger.MergeQueue,RawKeyValueIterator,ReduceTask.ReduceValuesIterator等等之类的实现。有了输入,有了输出,不断循环调用自定义的Reducer,最终,Reduce阶段完成。。。
VI. 分布式支持
1、服务器正确性保证
Hadoop Map/Reduce服务器状况和HDFS很类似,由此可知,救死扶伤的方法也是大同小异。废话不多说了,直接切正题。同作为客户端,Map/Reduce的客户端只是将作业提交,就开始搬个板凳看戏,没有占茅坑的行动。因此,一旦它挂了,也就挂了,不伤大雅。而任务服务器,也需要随时与作业服务器保持心跳联系,一旦有了问题,作业服务器可以将其上运行的任务,移交给它人完成。作业服务器,作为一个单点,非常类似的是利用还原点(等同于HDFS的镜像)和历史记录(等同于HDFS的日志),来进行恢复。其上,需要持久化用于恢复的内容,包含作业状况、任务状况、各个任务尝试的工作状况等。有了这些内容,再加上任务服务器的动态注册,就算挪了个窝,还是很容易恢复的。JobHistory是历史记录相关的一个静态类,本来,它也就是一个干写日志活的,只是在Hadoop的实现中,对日志的写入做了面向对象的封装,同时又大量用到观察者模式做了些嵌入,使得看起来不是那么直观。本质上,它就是打开若干个日志文件,利用各类接口来往里面写内容。只不过,这些日志,会放在分布式文件系统中,就不需要像HDFS那样,来一个SecondXXX随时候命,由此可见,有巨人在脚下踩着,真好。JobTracker.RecoveryManager类是作业服务器中用于进行恢复相关的事情,当作业服务器启动的时候,会调用其recover方法,恢复日志文件中的内容。其中步骤,注释中写的很清楚,请自行查看。。。
2、任务执行的正确和速度
整个作业流程的执行,秉承着木桶原理。执行的最慢的Map任务和Reduce任务,决定了系统整体执行时间(当然,如果执行时间在整个流程中占比例很小的话,也许就微不足道了...)。因此,尽量加快最慢的任务执行速度,成为提高整体速度关键。所使用的策略,简约而不简单,就是一个任务多次执行。当所有未执行的任务都分配出去了,并且先富起来的那部分任务已经完成了,并还有任务服务器孜孜不倦的索取任务的时候,作业服务器会开始炒剩饭,把那些正在吭哧吭哧在某个服务器上慢慢执行的任务,再把此任务分配到一个新的任务服务器上,同时执行。两个服务器各尽其力,成王败寇,先结束者的结果将被采纳。这样的策略,隐含着一个假设,就是我们相信,输入文件的分割算法是公平的,某个任务执行慢,并不是由于这个任务本身负担太重,而是由于服务器不争气负担太重能力有限或者是即将撒手西去,给它换个新环境,人挪死树挪活事半功倍。。。
当然,肯定有哽咽的任务,不论是在哪个服务器上,都无法顺利完成。这就说明,此问题不在于服务器上,而是任务本身天资有缺憾。缺憾在何处?每个作业,功能代码都是一样的,别的任务成功了,就是这个任务不成功,很显然,问题出在输入那里。输入中有非法的输入条目,导致程序无法辨识,只能挥泪惜别。说到这里,解决策略也浮出水面了,三十六计走位上,惹不起,还是躲得起的。在MapTask中的MapTask.SkippingRecordReader<K, V>和ReduceTask里的ReduceTask.SkippingReduceValuesIterator<KEY,VALUE>,都是用于干这个事情的。它们的原理很简单,就是在读一条记录前,把当前的位置信息,封装成SortedRanges.Range对象,经由Task的reportNextRecordRange方法提交到TaskTracker上去。TaskTracker会把这些内容,搁在TaskStatus对象中,随着心跳消息,汇报到JobTracker上面。这样,作业服务器就可以随时随刻了解清楚,每个任务正读取在那个位置,一旦出错,再次执行的时候,就在分配的任务信息里面添加一组SortedRanges信息。MapTask或ReduceTask读取的时候,会看一下这些区域,如果当前区域正好处于上述雷区,跳过不读。如此反复,正可谓,道路曲折,前途光明啊。。。
VII. 总结
对于Map/Reduce而言,真正的困难,在于提高其适应能力,打造一款能够包治百病的执行框架。Hadoop已经做得很好了,但只有真正搞清楚了整个流程,你才能帮助它做的更好。。。
发表评论
-
hbase master挂掉-zookeeper连接超时原因
2012-10-26 15:59 0并行运行hbase删表,建表操作,多个表多个region, ... -
HBase性能优化方法总结(四):数据计算
2012-10-26 15:50 0本文主要是从HBase应用程序设计与开发的角度,总结几种常 ... -
HBase性能优化方法总结(三):读表操作
2012-10-26 15:38 0本文主要是从HBase应用程序设计与开发的角度,总结几种常 ... -
HBase性能优化方法总结(二):写表操作
2012-10-26 13:59 0本文主要是从HBase应用程序设计与开发的角度,总结几 ... -
HBase性能优化方法总结(一):表的设计
2012-10-26 13:42 0本文主要是从HBase应用程序设计与开发的角度,总结几种常 ... -
hbase内scan
2012-10-26 13:11 0Bug表现:折扣搜索中,某个宝贝参加了一个毫无相关的折扣活 ... -
HTable和HTablePool使用注意事项
2012-07-27 14:35 800http://www.cnblogs.com/panfeng4 ... -
简化的分布式事务框架设计
2012-07-03 09:54 0一、关于XA及其实现 这 ... -
分布式基础学习
2011-09-20 13:37 704<p>转:http://www.cnblo ...
相关推荐
级联H桥SVG无功补偿系统在不平衡电网中的三层控制策略:电压电流双闭环PI控制、相间与相内电压均衡管理,级联H桥SVG无功补偿系统在不平衡电网中的三层控制策略:电压电流双闭环PI控制、相间与相内电压均衡管理,不平衡电网下的svg无功补偿,级联H桥svg无功补偿statcom,采用三层控制策略。 (1)第一层采用电压电流双闭环pi控制,电压电流正负序分离,电压外环通过产生基波正序有功电流三相所有H桥模块直流侧平均电压恒定,电流内环采用前馈解耦控制; (2)第二层相间电压均衡控制,注入零序电压,控制通过注入零序电压维持相间电压平衡; (3)第三层相内电压均衡控制,使其所有子模块吸收的有功功率与其损耗补,从而保证所有H桥子模块直流侧电压值等于给定值。 有参考资料。 639,核心关键词: 1. 不平衡电网下的SVG无功补偿 2. 级联H桥SVG无功补偿STATCOM 3. 三层控制策略 4. 电压电流双闭环PI控制 5. 电压电流正负序分离 6. 直流侧平均电压恒定 7. 前馈解耦控制 8. 相间电压均衡控制 9. 零序电压注入 10. 相内电压均衡控制 以上十个关键词用分号分隔的格式为:不
GTX 1080 PCB图纸,内含图纸查看软件
内容概要:本文档详细介绍了利用 DeepSeek 进行文本润色和问答交互时提高效果的方法和技巧,涵盖了从明确需求、提供适当上下文到尝试开放式问题以及多轮对话的十个要点。每一部分内容都提供了具体的示范案例,如指定回答格式、分步骤提问等具体实例,旨在指导用户更好地理解和运用 DeepSeek 提升工作效率和交流质量。同时文中还强调了根据不同应用场景调整提示词语气和风格的重要性和方法。 适用人群:适用于希望通过优化提问技巧以获得高质量反馈的企业员工、科研人员以及一般公众。 使用场景及目标:本文针对所有期望提高 DeepSeek 使用效率的人群,帮助他们在日常工作中快速获取精准的答案或信息,特别是在撰写报告、研究材料准备和技术咨询等方面。此外还鼓励用户通过不断尝试不同形式的问题表述来进行有效沟通。 其他说明:该文档不仅关注实际操作指引,同样重视用户思维模式转变——由简单索取答案向引导 AI 辅助创造性解决问题的方向发展。
基于FPGA与W5500实现的TCP网络通信测试平台开发——Zynq扩展口Verilog编程实践,基于FPGA与W5500芯片的TCP网络通信测试及多路Socket实现基于zynq开发平台和Vivado 2019软件的扩展开发,基于FPGA和W5500的TCP网络通信 测试平台 zynq扩展口开发 软件平台 vivado2019.2,纯Verilog可移植 测试环境 压力测试 cmd命令下ping电脑ip,同时采用上位机进行10ms发包回环测试,不丢包(内部数据回环,需要时间处理) 目前实现单socket功能,多路可支持 ,基于FPGA; W5500; TCP网络通信; Zynq扩展口开发; 纯Verilog可移植; 测试平台; 压力测试; 10ms发包回环测试; 单socket功能; 多路支持。,基于FPGA与W5500的Zynq扩展口TCP通信测试:可移植Verilog实现的高效网络通信
Labview液压比例阀伺服阀试验台多功能程序:PLC通讯、液压动画模拟、手动控制与调试、传感器标定、报警及记录、自动实验、数据处理与查询存储,报表生成与打印一体化解决方案。,Labview液压比例阀伺服阀试验台多功能程序:PLC通讯、液压动画模拟、手动控制与调试、传感器标定、报警管理及实验自动化,labview液压比例阀伺服阀试验台程序:功能包括,同PLC通讯程序,液压动画,手动控制及调试,传感器标定,报警设置及报警记录,自动实验,数据处理曲线处理,数据库存储及查询,报表自动生成及打印,扫码枪扫码及信号录入等~ ,核心关键词:PLC通讯; 液压动画; 手动控制及调试; 传感器标定; 报警设置及记录; 自动实验; 数据处理及曲线处理; 数据库存储及查询; 报表生成及打印; 扫码枪扫码。,Labview驱动的智能液压阀测试系统:多功能控制与数据处理
华为、腾讯、万科员工职业发展体系建设与实践.pptx
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
电网不对称故障下VSG峰值电流限制的柔性控制策略:实现电流平衡与功率容量的优化利用,电网不对称故障下VSG峰值电流限制的柔性控制策略:兼顾平衡电流与功率控制切换的动态管理,电网不对称故障下VSG峰值电流限制的柔性不平衡控制(文章完全复现)。 提出一种在不平衡运行条件下具有峰值电流限制的可变不平衡电流控制方法,可灵活地满足不同操作需求,包括电流平衡、有功或无功恒定运行(即电流控制、有功控制或无功控制之间的相互切),注入电流保持在安全值内,以更好的利用VSG功率容量。 关键词:VSG、平衡电流控制、有功功率控制、无功功率控制。 ,VSG; 峰值电流限制; 柔性不平衡控制; 电流平衡控制; 有功功率控制; 无功功率控制。,VSG柔性控制:在电网不对称故障下的峰值电流限制与平衡管理
1、文件内容:libpinyin-tools-0.9.93-4.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/libpinyin-tools-0.9.93-4.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、更多资源/技术支持:公众号禅静编程坊
数据集是一个以经典动漫《龙珠》为主题的多维度数据集,广泛应用于数据分析、机器学习和图像识别等领域。该数据集由多个来源整合而成,涵盖了角色信息、战斗力、剧情片段、台词以及角色图像等多个方面。数据集的核心内容包括: 角色信息:包含《龙珠》系列中的主要角色及其属性,如名称、种族、所属系列(如《龙珠》《龙珠Z》《龙珠超》等)、战斗力等级等。 图像数据:提供角色的图像资源,可用于图像分类和角色识别任务。这些图像来自动画剧集、漫画和相关衍生作品。 剧情与台词:部分数据集还包含角色在不同故事中的台词和剧情片段,可用于文本分析和自然语言处理任务。 战斗数据:记录角色在不同剧情中的战斗力变化和战斗历史,为研究角色成长和剧情发展提供支持。 数据集特点 多样性:数据集整合了角色、图像、文本等多种类型的数据,适用于多种研究场景。 深度:不仅包含角色的基本信息,还涵盖了角色的成长历程、技能描述和与其他角色的互动关系。 实用性:支持多种编程语言(如Python、R)的数据处理和分析,提供了详细的文档和示例代码。
基于protues仿真的多功公交站播报系统设计(仿真图、源代码) 该设计为基于protues仿真的多功公交站播报系统,实现温度显示、时间显示、和系统公交站播报功能; 具体功能如下: 1、系统使用51单片机为核心设计; 2、时钟芯片进行时间和日期显示; 3、温度传感器进行温度读取; 4、LCD12864液晶屏进行相关显示; 5、按键设置调节时间; 6、按键设置报站; 7、仿真图、源代码; 操作说明: 1、下行控制报站:首先按下(下行设置按键),(下行指示灯)亮,然后按下(手动播报)按键控制播报下一站; 2、上行控制报站:首先按上(上行设置按键),(上行指示灯)亮,然后按下(手动播报)按键控制播报下一站; 3、按下关闭播报按键,则关闭播报功能和清除显示
采用Java后台技术和MySQL数据库,在前台界面为提升用户体验,使用Jquery、Ajax、CSS等技术进行布局。 系统包括两类用户:学生、管理员。 学生用户 学生用户只要实现了前台信息的查看,打开首页,查看网站介绍、琴房信息、在线留言、轮播图信息公告等,通过点击首页的菜单跳转到对应的功能页面菜单,包括网站首页、琴房信息、注册登录、个人中心、后台登录。 学生用户通过账户账号登录,登录后具有所有的操作权限,如果没有登录,不能在线预约。学生用户退出系统将注销个人的登录信息。 管理员通过后台的登录页面,选择管理员权限后进行登录,管理员的权限包括轮播公告管理、老师学生信息管理和信息审核管理,管理员管理后点击退出,注销登录信息。 管理员用户具有在线交流的管理,琴房信息管理、琴房预约管理。 在线交流是对前台用户留言内容进行管理,删除留言信息,查看留言信息。
MATLAB可以用于开发人脸识别考勤系统。下面是一个简单的示例流程: 1. 数据采集:首先收集员工的人脸图像作为训练数据集。可以要求员工提供多张照片以获得更好的训练效果。 2. 图像预处理:使用MATLAB的图像处理工具对采集到的人脸图像进行预处理,例如灰度化、裁剪、缩放等操作。 3. 特征提取:利用MATLAB的人脸识别工具包,如Face Recognition Toolbox,对处理后的图像提取人脸特征,常用的方法包括主成分分析(PCA)和线性判别分析(LDA)等。 4. 训练模型:使用已提取的人脸特征数据集训练人脸识别模型,可以选择支持向量机(SVM)、卷积神经网络(CNN)等算法。 5. 考勤系统:在员工打卡时,将摄像头捕获的人脸图像输入到训练好的模型中进行识别,匹配员工信息并记录考勤数据。 6. 结果反馈:根据识别结果,可以自动生成考勤报表或者实时显示员工打卡情况。 以上只是一个简单的步骤,实际开发过程中需根据具体需求和系统规模进行定制和优化。MATLAB提供了丰富的图像处理和机器学习工具,是开发人脸识别考勤系统的一个很好选择。
hjbvbnvhjhjg
HCIP、软考相关学习PPT提供下载
绿豆BOX UI8版:反编译版六个全新UI+最新后台直播管理源码 最新绿豆BOX反编译版六个UI全新绿豆盒子UI8版本 最新后台支持直播管理 作为UI6的升级版,UI8不仅修复了前一版本中存在的一些BUG,还提供了6套不同的UI界面供用户选择,该版本有以下特色功能: 在线管理TVBOX解析 在线自定义TVBOX 首页布局批量添加会员信息 并支持导出批量生成卡密 并支持导出直播列表管理功能
vue3的一些语法以及知识点
西门子大型Fanuc机器人汽车焊装自动生产线程序经典解析:PLC博图编程与MES系统通讯实战指南,西门子PLC博图汽车焊装自动生产线FANUC机器人程序经典结构解析与MES系统通讯,西门子1500 大型程序fanuc 机器人汽车焊装自动生产线程序 MES 系统通讯 大型程序fanuc机器人汽车焊装自动生产线程序程序经典结构清晰,SCL算法堆栈,梯形图和 SCL混编使用博图 V14以上版本打开 包括: 1、 PLC 博图程序 2 触摸屏程序 ,西门子1500; 大型程序; fanuc机器人; 汽车焊装自动生产线; MES系统通讯; SCL算法; 梯形图; SCL混编; 博图V14以上版本。,西门子博图大型程序:汽车焊装自动生产线MES系统通讯与机器人控制
DeepSeek:从入门到精通
计及信息间隙决策与多能转换的综合能源系统优化调度模型:实现碳经济最大化与源荷不确定性考量,基于信息间隙决策与多能转换的综合能源系统优化调度模型:源荷不确定性下的高效碳经济调度策略,计及信息间隙决策及多能转的综合能源系统优化调度 本代码构建了含风电、光伏、光热发电系统、燃气轮机、燃气锅炉、电锅炉、储气、储电、储碳、碳捕集装置的综合能源系统优化调度模型,并考虑P2G装置与碳捕集装置联合运行,从而实现碳经济的最大化,最重要的是本文引入了信息间隙决策理论考虑了源荷的不确定性(本代码的重点)与店铺的47代码形成鲜明的对比,注意擦亮眼睛,认准原创,该代码非常适合修改创新,,提供相关的模型资料 ,计及信息间隙决策; 综合能源系统; 优化调度; 多能转换; 碳经济最大化; 风电; 光伏; 燃气轮机; 储气; 储电; 储碳; 碳捕集装置; P2G装置联合运行; 模型资料,综合能源系统优化调度模型:基于信息间隙决策和多能转换的原创方案