You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

151 lines
14 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 15 | 分布式计算模式之MR一门同流合污的艺术
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
我在[第12篇文章](https://time.geekbang.org/column/article/152164)中与你介绍两层调度时提到Mesos的第二层调度是由Framework完成的。这里的Framework通常就是计算框架比如Hadoop、Spark等。用户基于这些计算框架可以完成不同类型和规模的计算。
那么在接下来的4篇文章我们就要进入“第三站分布式计算技术”了。在这一站我将与你详细介绍分布式领域中的4种计算模式包括MapReduce、Stream、Actor和流水线。而今天这篇文章我们就先从MR模式开始吧。
Hadoop这个框架主要用于解决海量数据的计算问题。那么它是如何做到海量数据计算的呢你可能会想既然是海量数据规模这么大那就分成多个进程每个进程计算一部分然后汇总一下结果就可以提升运算速度了。其实整个计算流程我们可以很形象地用一个词来解释就是“同流合污“。
没错就是这种想法在分布式领域中就叫作MR模式即Map Reduce模式。接下来我们就一起揭开MR模式的神秘面纱吧。
## 什么是分而治之?
分而治之Divide-and-Conquer是计算机处理问题的一个很重要的思想简称为分治法。
顾名思义,分治法就是将一个复杂的、难以直接解决的大问题,分割成一些规模较小的、可以比较简单的或直接求解的子问题,这些子问题之间相互独立且与原问题形式相同,递归地求解这些子问题,然后将子问题的解合并得到原问题的解。
比如,现在要统计全中国的人口数,由于中国的人口规模很大,如果让工作人员依次统计每个省市的人口数,工作量会非常大。在实际统计中,我们通常会按照省分别统计,比如湖南省的工作人员统计湖南省的人口数,湖北省的工作人员统计湖北省的人口数等,然后汇总各个省的人口数,即可得到全国人口数。
这,就是一个非常好的分而治之的例子。
当然这种分治的思想还广泛应用于计算机科学的各个领域中分布式领域中的很多场景和问题也非常适合采用这种思想解决并为此设计出了很多计算框架。比如Hadoop中的MapReduce。
那么,**在分布式领域,具体有哪些问题适合采用分治法呢?**要回答这个问题,我们先看下适合分治法的问题具有哪些特征吧。
* 问题规模比较大或复杂,且问题可以分解为几个规模较小的、简单的同类型问题进行求解;
* 子问题之间相互独立,不包含公共子问题;
* 子问题的解可以合并得到原问题的解。
根据这些特征,我们可以想到,诸如电商统计全国商品数量时,按区域或省市进行统计,然后将统计结果合并得到最终结果等大数据处理场景,均可以采用分治法。
同时,根据这些特征,我们可以推导出,**采用分治法解决问题的核心步骤是**
1. 分解原问题。将原问题分解为若干个规模较小,相互独立,且与原问题形式相同的子问题。
2. 求解子问题。若子问题规模较小且容易被解决则直接求解,否则递归地求解各个子问题。
3. 合并解,就是将各个子问题的解合并为原问题的解。
接下来,我们就一起看看分布式系统中分治法的原理和应用吧。
## 分治法的原理
分布式原本就是为处理大规模应用而生的,所以基于分布式系统,如何分而治之地处理海量数据就是分布式领域中的一个核心问题。
Google提出的MapReduce分布式计算模型Hadoop MapReduce是Google的开源实现作为分治法的典型代表最开始用于搜索领域后来被广泛用于解决各种海量数据的计算问题。下面我将以MapReduce为例带你了解分治法的抽象模型、工作原理和实践应用。
### 抽象模型
如下图所示MapReduce分为Map和Reduce两个核心阶段其中Map对应“分”即把复杂的任务分解为若干个“简单的任务”执行Reduce对应着“合”即对Map阶段的结果进行汇总。
![](https://static001.geekbang.org/resource/image/04/98/04c72243a78728f41e152eba52735198.png)
在第一阶段也就是Map阶段将大数据计算任务拆分为多个子任务拆分后的子任务通常具有如下特征
* 相对于原始任务来说,划分后的子任务与原任务是同质的,比如原任务是统计全国人口数,拆分为统计省的人口数子任务时,都是统计人口数;并且,子任务的数据规模和计算规模会小很多。
* 多个子任务之间没有依赖,可以独立运行、并行计算,比如按照省统计人口数,统计河北省的人口数和统计湖南省的人口数之间没有依赖关系,可以独立、并行地统计。
第二阶段也就是Reduce阶段第一阶段拆分的子任务计算完成后汇总所有子任务的计算结果以得到最终结果。也就是汇总各个省统计的人口数得到全国的总人口数。
### MapReduce工作原理
那么在MapReduce里各个组件是如何分工完成一个复杂任务的呢为了解答这个问题我先带你了解一下MapReduce的组件结构。
![](https://static001.geekbang.org/resource/image/d4/c9/d4676282f7c980953922b6f391cc98c9.png)
如上图所示MapReduce主要包括以下三种组件
* Master也就是MRAppMaster该模块像一个大总管一样独掌大权负责分配任务协调任务的运行并为Mapper分配map()函数操作、为Reducer分配reduce()函数操作。
* Mapper worker负责Map函数功能即负责执行子任务。
* Reducer worker负责Reduce函数功能即负责汇总各个子任务的结果。
基于这三种组件MapReduce的工作流程如下所示
![](https://static001.geekbang.org/resource/image/f3/f1/f3caa7b96cf59cbde8e9f9b0cc84a3f1.png)
程序从User Program开始进入MapReduce操作流程。其中图中的“step1step2step6”表示操作步骤。
step1User Program将任务下发到MRAppMaster中。然后MRAppMaster执行任务拆分步骤把User Program下发的任务划分成M个子任务M是用户自定义的数值。假设MapReduce函数将任务划分成了5个其中Map作业有3个Reduce作业有2个集群内的MRAppMaster以及Worker节点都有任务的副本。
step2MRAppMaster分别为Mapper和Reducer分配相应的Map和Reduce作业。Map作业的数量就是划分后的子任务数量也就是3个Reduce作业是2个。
step3被分配了Map作业的Worker开始读取子任务的输入数据并从输入数据中抽取出<key, value>键值对每一个键值对都作为参数传递给map()函数。
step4map()函数的输出结果存储在环形缓冲区kvBuffer中这些Map结果会被定期写入本地磁盘中被存储在R个不同的磁盘区。这里的R表示Reduce作业的数量也是由用户定义的。在这个案例中R=2。此外每个Map结果的存储位置都会上报给MRAppMaster。
step5MRAppMaster通知Reducer它负责的作业在哪一个分区Reducer远程读取相应的Map结果即中间键值对。当Reducer把它负责的所有中间键值对都读过来后首先根据键值对的key值对中间键值对进行排序将相同key值的键值对聚集在一起从而有利于Reducer对Map结果进行统计。
step6Reducer遍历排序后的中间键值对将具有相同key值的键值对合并并将统计结果作为输出文件存入负责的分区中。
从上述流程可以看出,**整个MapReduce的工作流程主要可以概括为5个阶段**Input输入、Splitting拆分、Mapping映射、Reducing化简以及Final Result输出
所有MapReduce操作执行完毕后MRAppMaster将R个分区的输出文件结果返回给User Program用户可以根据实际需要进行操作。比如通常并不需要合并这R个输出文件而是将其作为输入交给另一个MapReduce程序处理。
### MapReduce实践应用
通过上述的流程描述你大概已经知道MapReduce的工作流程了。接下来我和你分享一个电商统计用户消费记录的例子再帮你巩固一下MapReduce的功能吧。
需要注意的是,为了方便理解,我对下面用的数据做了一定的处理,并不完全是真实场景中的数据。
每隔一段时间,电商都会统计该时期平台的订单记录,从而分析用户的消费倾向。在不考虑国外消费记录的前提下,全国范围内的订单记录已经是一个很大规模的工程了。
在前面的文章中我也提到过,电商往往会在每个省份、多个城市分布式地部署多个服务器,用于管理某一地区的平台数据。因此,针对全国范围内的消费统计,可以拆分成对多个省份的消费统计,并再一次细化到统计每一个城市的消费记录。
为方便描述假设我们现在要统计苏锡常地区第二季度手机订单数量Top3的品牌。我们来看看具体的统计步骤吧。
1. 任务拆分Splitting阶段。根据地理位置分别统计苏州、无锡、常州第二季度手机订单Top3品牌从而将大规模任务划分为3个子任务。
2. 通过循环调用map()函数统计每个品牌手机的订单数量。其中key为手机品牌value为手机购买数量单位万台。如下图Mapping阶段所示为简化描述图中直接列出了统计结果
3. 与前面讲到的计算流程不同的是Mapping阶段和Reducing阶段中间多了一步Shuffling操作。Shuffling阶段主要是读取Mapping阶段的结果并将不同的结果划分到不同的区。在大多数参考文档中Mapping和Reducing阶段的任务分别定义为映射以及归约。但是在映射之后要对映射后的结果进行排序整合然后才能执行归约操作因此往往将这一排序整合的操作单独放出来称之为Shuffling阶段。
4. Reducing阶段归并同一个品牌的购买次数。
5. 得到苏锡常地区第二季度Top3品牌手机的购买记录。
![](https://static001.geekbang.org/resource/image/81/b0/813f5311ab4df0ce94816a3a84946fb0.png)
由上述流程可以看出,**Map/Reduce作业和map()/reduce()函数是有区别的**
* Map 阶段由一定数量的 Map 作业组成,这些 Map 作业是并发任务可以同时运行且操作重复。Map阶段的功能主要由map()函数实现。每个Map作业处理一个子任务比如一个城市的手机消费统计需要调用多次map()函数来处理(因为城市内不同的居民倾向于不同的手机)。
* Reduce阶段执行的是汇总任务结果遍历Map阶段的结果从而返回一个综合结果。与Reduce阶段相关的是reduce()函数它的输入是一个键key和与之对应的一组数据values其功能是将具有相同key值的数据进行合并。Reduce作业处理一个分区的中间键值对期间要对每个不同的key值调用一次reduce()函数。在完成Map作业后每个分区中会存在多个临时文件而执行完Reduce操作后一个分区最终只有一个输出文件。
## 知识扩展Fork-Join计算模式是什么意思呢
MapReduce是一种分而治之的计算模式在分布式领域中除了典型的Hadoop的MapReduce(Google MapReduce的开源实现)还有Fork-Join。你知道Fork-join是什么吗
Fork-Join是Java等语言或库提供的原生多线程并行处理框架采用线程级的分而治之计算模式。它充分利用多核CPU的优势以递归的方式把一个任务拆分成多个“小任务”把多个“小任务”放到多个处理器上并行执行即Fork操作。当多个“小任务”执行完成之后再将这些执行结果合并起来即可得到原始任务的结果即Join操作。
虽然MapReduce是进程级的分而治之计算模式但与Fork-Join的核心思想是一致的。因此Fork-Join又被称为Java版的MapReduce框架。
MapReduce和Fork-Join之间有一个本质的区别
* Fork-Join不能大规模扩展只适用于在单个Java虚拟机上运行多个小任务虽然运行在不同的处理器上但可以相互通信甚至一个线程可以“窃取”其他线程上的子任务。
* MapReduce可以大规模扩展适用于大型计算机集群。通过MapReduce拆分后的任务可以跨多个计算机去执行且各个小任务之间不会相互通信。
## 总结
所谓分而治之,就是将一个复杂的、难以直接解决的大问题,分割成一些规模较小的、可以直接求解的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将子问题的解合并以后就是原问题的解。
分布式计算模型MapReduce就运用了分而治之的思想通过Map操作将大任务分成多个较小的任务去执行得到的多个结果再通过Reduce操作整合成一个完整的结果。所以今天我就以MapReduce为例与你讲述了分布式领域中分治法的模型、原理与应用。
最后,我将今天涉及的核心知识点梳理为了一张思维导图,以方便你理解与记忆。
![](https://static001.geekbang.org/resource/image/cc/ed/cc3a75001eeb7a1ae470831aa7770fed.png)
分而治之的思想,是简单且实用的处理复杂问题的方法。所以无论是计算机领域还是其他研究领域亦或日常生活中,我们都可以用分治法去处理很多复杂庞大的问题,将大问题划分成多个小问题,化繁为简、化整为零。
其实,很多算法并不是凭空创造出来的,都是源于生活并服务于生活的。在日常工作学习中,我们对眼前的问题一筹莫展时,就可以将其化繁为简,从最简单的小问题出发,逐渐增加问题的规模,进而解决这个复杂的问题。同样的道理,我们也可以借鉴生活中的例子去解决专业问题。
## 思考题
MapReduce属于批量处理任务类型吗你能说说其中的原因吗
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!