两层调度时提到,Mesos 的第二层调度是由 Framework 完成的。这里的 Framework 通常就是计算框架,比如 Hadoop、Spark 等。用户基于这些计算框架,可以完成不同类型和规模的计算。
Hadoop 这个框架主要用于解决海量数据的计算问题。海量数据分成多个进程,每个进程计算一部分,然后汇总一下结果,就可以提升运算速度了。在分布式领域中就叫作 MR 模式,即 Map Reduce 模式。
分而治之(Divide-and-Conquer),是计算机处理问题的一个很重要的思想,简称为分治法。
分治法是将一个复杂的、难以直接解决的大问题,分割成一些规模较小的、可以比较简单的或直接求解的子问题,这些子问题之间相互独立且与原问题形式相同,递归地求解这些子问题,然后将子问题的解合并得到原问题的解。
比如,现在要统计全中国的人口数,由于中国的人口规模很大,如果让工作人员依次统计每个省市的人口数,工作量会非常大。在实际统计中,通常会按照省分别统计,比如湖南省的工作人员统计湖南省的人口数,湖北省的工作人员统计湖北省的人口数等,然后汇总各个省的人口数,即可得到全国人口数。
这种分治的思想还广泛应用于计算机科学的各个领域中,分布式领域中的很多场景和问题也非常适合采用这种思想解决,并为此设计出了很多计算框架。比如,Hadoop 中的 MapReduce。
适合分治法的问题的特征:
根据这些特征可以想到,诸如电商统计全国商品数量时,按区域或省市进行统计,然 后将统计结果合并得到最终结果等大数据处理场景,均可以采用分治法。
根据这些特征可以推导出,采用分治法解决问题的核心步骤是:
分布式原本就是为处理大规模应用而生的,所以基于分布式系统,如何分而治之地处理海量数据就是分布式领域中的一个核心问题。
Google 提出的 MapReduce 分布式计算模型(Hadoop MapReduce 是 Google 的开源实现),作为分治法的典型代表,最开始用于搜索领域,后来被广泛用于解决各种海量数据的计算问题。
如下图所示,MapReduce 分为 Map 和 Reduce 两个核心阶段,其中 Map 对应“分”, 即把复杂的任务分解为若干个“简单的任务”执行;Reduce 对应着“合”,即对 Map 阶段的结果进行汇总。
Map 阶段,将大数据计算任务拆分为多个子任务,拆分后的子任务通常具有如下特征:
Reduce 阶段,拆分的子任务计算完成后,汇总所有子任务的计算结果,以得到最终结果。也就是汇总各个省统计的人口数,得到全国的总人口数。
MapReduce 的组件结构:
MapReduce 主要包括以下三种组件:
基于这三种组件,MapReduce 的工作流程如下所示:
程序从 User Program 开始进入 MapReduce 操作流程。其中图中的“step1,step2, …,step6”表示操作步骤。
从上述流程可以看出,整个 MapReduce 的工作流程主要可以概括为 5 个阶段: Input(输入)、Splitting(拆分)、Mapping(映射)、Reducing(化简)以及 Final Result(输出)。
所有 MapReduce 操作执行完毕后,MRAppMaster 将 R 个分区的输出文件结果返回给 User Program,用户可以根据实际需要进行操作。比如,通常并不需要合并这 R 个输出文件,而是将其作为输入交给另一个 MapReduce 程序处理。
一个电商统计用户消费记录的例子,巩固 MapReduce 的功能。每隔一段时间,电商都会统计该时期平台的订单记录,从而分析用户的消费倾向。在不考虑国外消费记录的前提下,全国范围内的订单记录已经是一个很大规模的工程了。
电商往往会在每个省份、多个城市分布式地部署多个服务器, 用于管理某一地区的平台数据。因此针对全国范围内的消费统计,可以拆分成对多个省份的消费统计,并再一次细化到统计每一个城市的消费记录。
假设要统计苏锡常地区第二季度手机订单数量 Top3 的品牌。具体的统计步骤:
由上述流程可以看出,Map/Reduce 作业和 map()/reduce() 函数是有区别的:
Map 阶段由一定数量的 Map 作业组成,这些 Map 作业是并发任务,可以同时运行, 且操作重复。Map 阶段的功能主要由 map() 函数实现。每个 Map 作业处理一个子任务 (比如一个城市的手机消费统计),需要调用多次 map() 函数来处理(因为城市内不同的居民倾向于不同的手机)。
Reduce 阶段执行的是汇总任务结果,遍历 Map 阶段的结果从而返回一个综合结果。与 Reduce 阶段相关的是 reduce() 函数,它的输入是一个键(key)和与之对应的一组数据(values),其功能是将具有相同 key 值的数据进行合并。Reduce 作业处理一个分区的中间键值对,期间要对每个不同的 key 值调用一次 reduce() 函数。在完成 Map 作业后,每个分区中会存在多个临时文件;而执行完 Reduce 操作后,一个分区最终只有 一个输出文件。
MapReduce 是一种分而治之的计算模式,在分布式领域中,除了典型的 Hadoop 的 MapReduce(Google MapReduce 的开源实现),还有 Fork-Join。
Fork-Join 是 Java 等语言或库提供的原生多线程并行处理框架,采用线程级的分而治之计算模式。它充分利用多核 CPU 的优势,以递归的方式把一个任务拆分成多个“小任务”, 把多个“小任务”放到多个处理器上并行执行,即 Fork 操作。当多个“小任务”执行完成之后,再将这些执行结果合并起来即可得到原始任务的结果,即 Join 操作。
虽然 MapReduce 是进程级的分而治之计算模式,但与 Fork-Join 的核心思想是一致的。 因此 Fork-Join 又被称为 Java 版的 MapReduce 框架。
但 MapReduce 和 Fork-Join 之间有一个本质的区别:
分而治之是将一个复杂的、难以直接解决的大问题,分割成一些规模较小的、可以直接求解的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将子问题的解合并以后就是原问题的解。
分布式计算模型 MapReduce 就运用了分而治之的思想,通过 Map 操作将大任务分成多个较小的任务去执行,得到的多个结果再通过 Reduce 操作整合成一个完整的结果。
分而治之的思想是简单且实用的处理复杂问题的方法。所以无论是计算机领域还是其他研究领域亦或日常生活中,都可以用分治法去处理很多复杂庞大的问题,将大问题划分成多个小问题,化繁为简、化整为零。
其实很多算法并不是凭空创造出来的,都是源于生活并服务于生活的。在日常工作学习中,对眼前的问题一筹莫展时,就可以将其化繁为简,从最简单的小问题出发,逐渐增加问题的规模,进而解决这个复杂的问题。同样也可以借鉴生活中的例子去解决专业问题。