mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-16 22:23:45 +08:00
mod
This commit is contained in:
150
极客时间专栏/分布式技术原理与算法解析/第三站:分布式计算技术/15 | 分布式计算模式之MR:一门同流合污的艺术.md
Normal file
150
极客时间专栏/分布式技术原理与算法解析/第三站:分布式计算技术/15 | 分布式计算模式之MR:一门同流合污的艺术.md
Normal file
@@ -0,0 +1,150 @@
|
||||
<audio id="audio" title="15 | 分布式计算模式之MR:一门同流合污的艺术" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/e6/97/e602154bdaaef5a6f8adf99ac7a16e97.mp3"></audio>
|
||||
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
我在[第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. 分解原问题。将原问题分解为若干个规模较小,相互独立,且与原问题形式相同的子问题。
|
||||
1. 求解子问题。若子问题规模较小且容易被解决则直接求解,否则递归地求解各个子问题。
|
||||
1. 合并解,就是将各个子问题的解合并为原问题的解。
|
||||
|
||||
接下来,我们就一起看看分布式系统中分治法的原理和应用吧。
|
||||
|
||||
## 分治法的原理
|
||||
|
||||
分布式原本就是为处理大规模应用而生的,所以基于分布式系统,如何分而治之地处理海量数据就是分布式领域中的一个核心问题。
|
||||
|
||||
Google提出的MapReduce分布式计算模型(Hadoop MapReduce是Google的开源实现),作为分治法的典型代表,最开始用于搜索领域,后来被广泛用于解决各种海量数据的计算问题。下面,我将以MapReduce为例,带你了解分治法的抽象模型、工作原理和实践应用。
|
||||
|
||||
### 抽象模型
|
||||
|
||||
如下图所示,MapReduce分为Map和Reduce两个核心阶段,其中Map对应“分”,即把复杂的任务分解为若干个“简单的任务”执行;Reduce对应着“合”,即对Map阶段的结果进行汇总。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/04/98/04c72243a78728f41e152eba52735198.png" alt="">
|
||||
|
||||
在第一阶段,也就是Map阶段,将大数据计算任务拆分为多个子任务,拆分后的子任务通常具有如下特征:
|
||||
|
||||
- 相对于原始任务来说,划分后的子任务与原任务是同质的,比如原任务是统计全国人口数,拆分为统计省的人口数子任务时,都是统计人口数;并且,子任务的数据规模和计算规模会小很多。
|
||||
- 多个子任务之间没有依赖,可以独立运行、并行计算,比如按照省统计人口数,统计河北省的人口数和统计湖南省的人口数之间没有依赖关系,可以独立、并行地统计。
|
||||
|
||||
第二阶段,也就是Reduce阶段,第一阶段拆分的子任务计算完成后,汇总所有子任务的计算结果,以得到最终结果。也就是,汇总各个省统计的人口数,得到全国的总人口数。
|
||||
|
||||
### MapReduce工作原理
|
||||
|
||||
那么,在MapReduce里,各个组件是如何分工完成一个复杂任务的呢?为了解答这个问题,我先带你了解一下MapReduce的组件结构。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d4/c9/d4676282f7c980953922b6f391cc98c9.png" alt="">
|
||||
|
||||
如上图所示,MapReduce主要包括以下三种组件:
|
||||
|
||||
- Master,也就是MRAppMaster,该模块像一个大总管一样,独掌大权,负责分配任务,协调任务的运行,并为Mapper分配map()函数操作、为Reducer分配reduce()函数操作。
|
||||
- Mapper worker,负责Map函数功能,即负责执行子任务。
|
||||
- Reducer worker,负责Reduce函数功能,即负责汇总各个子任务的结果。
|
||||
|
||||
基于这三种组件,MapReduce的工作流程如下所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f3/f1/f3caa7b96cf59cbde8e9f9b0cc84a3f1.png" alt="">
|
||||
|
||||
程序从User Program开始进入MapReduce操作流程。其中图中的“step1,step2,…,step6”表示操作步骤。
|
||||
|
||||
step1:User Program将任务下发到MRAppMaster中。然后,MRAppMaster执行任务拆分步骤,把User Program下发的任务划分成M个子任务(M是用户自定义的数值)。假设,MapReduce函数将任务划分成了5个,其中Map作业有3个,Reduce作业有2个;集群内的MRAppMaster以及Worker节点都有任务的副本。
|
||||
|
||||
step2:MRAppMaster分别为Mapper和Reducer分配相应的Map和Reduce作业。Map作业的数量就是划分后的子任务数量,也就是3个;Reduce作业是2个。
|
||||
|
||||
step3:被分配了Map作业的Worker,开始读取子任务的输入数据,并从输入数据中抽取出<key, value>键值对,每一个键值对都作为参数传递给map()函数。
|
||||
|
||||
step4:map()函数的输出结果存储在环形缓冲区kvBuffer中,这些Map结果会被定期写入本地磁盘中,被存储在R个不同的磁盘区。这里的R表示Reduce作业的数量,也是由用户定义的。在这个案例中,R=2。此外,每个Map结果的存储位置都会上报给MRAppMaster。
|
||||
|
||||
step5:MRAppMaster通知Reducer它负责的作业在哪一个分区,Reducer远程读取相应的Map结果,即中间键值对。当Reducer把它负责的所有中间键值对都读过来后,首先根据键值对的key值对中间键值对进行排序,将相同key值的键值对聚集在一起,从而有利于Reducer对Map结果进行统计。
|
||||
|
||||
step6:Reducer遍历排序后的中间键值对,将具有相同key值的键值对合并,并将统计结果作为输出文件存入负责的分区中。
|
||||
|
||||
从上述流程可以看出,**整个MapReduce的工作流程主要可以概括为5个阶段**,即:Input(输入)、Splitting(拆分)、Mapping(映射)、Reducing(化简)以及Final Result(输出)。
|
||||
|
||||
所有MapReduce操作执行完毕后,MRAppMaster将R个分区的输出文件结果返回给User Program,用户可以根据实际需要进行操作。比如,通常并不需要合并这R个输出文件,而是将其作为输入交给另一个MapReduce程序处理。
|
||||
|
||||
### MapReduce实践应用
|
||||
|
||||
通过上述的流程描述,你大概已经知道MapReduce的工作流程了。接下来,我和你分享一个电商统计用户消费记录的例子,再帮你巩固一下MapReduce的功能吧。
|
||||
|
||||
需要注意的是,为了方便理解,我对下面用的数据做了一定的处理,并不完全是真实场景中的数据。
|
||||
|
||||
每隔一段时间,电商都会统计该时期平台的订单记录,从而分析用户的消费倾向。在不考虑国外消费记录的前提下,全国范围内的订单记录已经是一个很大规模的工程了。
|
||||
|
||||
在前面的文章中我也提到过,电商往往会在每个省份、多个城市分布式地部署多个服务器,用于管理某一地区的平台数据。因此,针对全国范围内的消费统计,可以拆分成对多个省份的消费统计,并再一次细化到统计每一个城市的消费记录。
|
||||
|
||||
为方便描述,假设我们现在要统计苏锡常地区第二季度手机订单数量Top3的品牌。我们来看看具体的统计步骤吧。
|
||||
|
||||
1. 任务拆分(Splitting阶段)。根据地理位置,分别统计苏州、无锡、常州第二季度手机订单Top3品牌,从而将大规模任务划分为3个子任务。
|
||||
1. 通过循环调用map()函数,统计每个品牌手机的订单数量。其中,key为手机品牌,value为手机购买数量(单位:万台)。如下图Mapping阶段所示(为简化描述,图中直接列出了统计结果)。
|
||||
1. 与前面讲到的计算流程不同的是,Mapping阶段和Reducing阶段中间多了一步Shuffling操作。Shuffling阶段主要是读取Mapping阶段的结果,并将不同的结果划分到不同的区。在大多数参考文档中,Mapping和Reducing阶段的任务分别定义为映射以及归约。但是,在映射之后,要对映射后的结果进行排序整合,然后才能执行归约操作,因此往往将这一排序整合的操作单独放出来,称之为Shuffling阶段。
|
||||
1. Reducing阶段,归并同一个品牌的购买次数。
|
||||
1. 得到苏锡常地区第二季度Top3品牌手机的购买记录。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/81/b0/813f5311ab4df0ce94816a3a84946fb0.png" alt="">
|
||||
|
||||
由上述流程可以看出,**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为例,与你讲述了分布式领域中分治法的模型、原理与应用。
|
||||
|
||||
最后,我将今天涉及的核心知识点梳理为了一张思维导图,以方便你理解与记忆。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/cc/ed/cc3a75001eeb7a1ae470831aa7770fed.png" alt="">
|
||||
|
||||
分而治之的思想,是简单且实用的处理复杂问题的方法。所以无论是计算机领域还是其他研究领域亦或日常生活中,我们都可以用分治法去处理很多复杂庞大的问题,将大问题划分成多个小问题,化繁为简、化整为零。
|
||||
|
||||
其实,很多算法并不是凭空创造出来的,都是源于生活并服务于生活的。在日常工作学习中,我们对眼前的问题一筹莫展时,就可以将其化繁为简,从最简单的小问题出发,逐渐增加问题的规模,进而解决这个复杂的问题。同样的道理,我们也可以借鉴生活中的例子去解决专业问题。
|
||||
|
||||
## 思考题
|
||||
|
||||
MapReduce属于批量处理任务类型吗?你能说说其中的原因吗?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
133
极客时间专栏/分布式技术原理与算法解析/第三站:分布式计算技术/16 | 分布式计算模式之Stream:一门背锅的艺术.md
Normal file
133
极客时间专栏/分布式技术原理与算法解析/第三站:分布式计算技术/16 | 分布式计算模式之Stream:一门背锅的艺术.md
Normal file
@@ -0,0 +1,133 @@
|
||||
<audio id="audio" title="16 | 分布式计算模式之Stream:一门背锅的艺术" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/0e/41/0ef7cyye3cd31825336cd07aee0a8941.mp3"></audio>
|
||||
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
在上一篇文章中,我与你介绍了分布式计算模式中的MapReduce模式。这种模式的核心思想是,将大任务拆分成多个小任务,针对这些小任务分别计算后,再合并各小任务的结果以得到大任务的计算结果。
|
||||
|
||||
这种模式下任务运行完成之后,整个任务进程就结束了,属于短任务模式。但,任务进程的启动和停止是一件很耗时的事儿,因此MapReduce对处理实时性的任务就不太合适了。
|
||||
|
||||
实时性任务主要是针对流数据的处理,对处理时延要求很高,通常需要有常驻服务进程,等待数据的随时到来随时处理,以保证低时延。处理流数据任务的计算模式,在分布式领域中叫作Stream。
|
||||
|
||||
今天,我将针对流数据的处理展开分享,和你一起打卡Stream这种计算模式。
|
||||
|
||||
## 什么是Stream?
|
||||
|
||||
近年来,由于网络监控、传感监测、AR/VR等实时性应用的兴起,一类需要处理流数据的业务发展了起来。比如各种直播平台中,我们需要处理直播产生的音视频数据流等。这种如流水般持续涌现,且需要实时处理的数据,我们称之为**流数据**。
|
||||
|
||||
总结来讲,流数据的特征主要包括以下4点:
|
||||
|
||||
- 数据如流水般持续、快速地到达;
|
||||
- 海量数据规模,数据量可达到TB级甚至PB级;
|
||||
- 对实时性要求高,随着时间流逝,数据的价值会大幅降低;
|
||||
- 数据顺序无法保证,也就是说系统无法控制将要处理的数据元素的顺序。
|
||||
|
||||
在分布式领域中,处理流数据的计算模式,就是**流计算,也叫作Stream**。这个名字是不是非常形象呢?
|
||||
|
||||
流计算的职责是实时获取来自不同数据源的海量数据,进行实时分析处理,获得有价值的信息。
|
||||
|
||||
它是一个对实时性要求非常高的计算形式,如果数据处理不及时,很容易导致过时、没用的结果,这时就需要对造成的后果进行“背锅”。从这个角度来说,Stream可谓“一门背锅的艺术”。
|
||||
|
||||
类比于水流的持续不断且变幻莫测,流数据也是以大量、快速、时变的流形式持续在应用中产生,因此**流计算一般用于处理数据密集型应用**。
|
||||
|
||||
比如,百度、淘宝等大型网站中,每天都会产生大量的流数据,这些数据包括用户的搜索内容、用户的浏览记录等。实时采集用户数据,并通过流计算进行实时数据分析,可以了解每个时刻数据流的变化情况,甚至可以分析用户的实时浏览轨迹,从而进行个性化内容实时推荐,提高用户体验。
|
||||
|
||||
此外,我们常用的爱奇艺、腾讯等音视频平台,对电影、电视剧等数据的处理,也是采用了流计算模式。
|
||||
|
||||
那么,这种实时的流计算到底是如何运行的呢?接下来,我们就一起看看流计算的工作原理吧。
|
||||
|
||||
## Stream工作原理
|
||||
|
||||
我在上一篇文章中与你介绍的MapReduce,是一种批量计算的形式。这种模式下,会先收集数据并将其缓存起来,等到缓存写满时才开始处理数据。因此,批量计算的一个缺点就是,从数据采集到得到计算结果之间经历的时间很长。
|
||||
|
||||
而流计算强调的是实时性,数据一旦产生就会被立即处理,当一条数据被处理完成后,会序列化存储到缓存中,然后立刻通过网络传输到下一个节点,由下一个节点继续处理,而不是像MapReduce那样,等到缓存写满才开始处理、传输。为了保证数据的实时性,在流计算中,不会存储任何数据,就像水流一样滚滚向前。
|
||||
|
||||
所以说,流计算属于持续性、低时延、事件驱动型的计算作业。
|
||||
|
||||
从这些分析中可以看出,**使用流计算进行数据处理,一般包括3个步骤**,如下图所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/62/20/62eda28c3df9a4ab2fbe393b469bea20.png" alt="">
|
||||
|
||||
**第一步,提交流式计算作业。**流式计算作业是一种常驻计算服务,比如实时交通监测服务、实时天气预报服务等。对于流式计算作业,首先必须预先定义计算逻辑,并提交到流计算系统中,使得流计算系统知道自己该如何处理数据。
|
||||
|
||||
系统在整个运行期间,由于收集的是同一类型的数据、执行的是同一种服务,因此流式计算作业的处理逻辑不可更改。如果用户停止当前作业运行后再次提交作业,由于流计算不提供数据存储服务,因此之前已经计算完成的数据无法重新再次计算。
|
||||
|
||||
**第二步,加载流式数据进行流计算。**流式计算作业一旦启动将一直处于等待事件触发的状态,一旦有小批量数据进入流式数据存储,系统会立刻执行计算逻辑并迅速得到结果。
|
||||
|
||||
从上图中我们可以看出,在流计算系统中,有多个流处理节点,流处理节点会对数据进行预定义的处理操作,并在处理完后按照某种规则转发给后续节点继续处理。此外,流计算系统中还存在管理节点,主要负责管理处理节点以及数据的流动规则。其中,处理节点的个数以及数据转发的规则,都在第一步作业提交时定义。
|
||||
|
||||
**第三步,持续输出计算结果。**流式计算作业在得到小批量数据的计算结果后,可以立刻将结果数据写入在线/批量系统,无需等待整体数据的计算结果,以进一步做到实时计算结果的实时展现。
|
||||
|
||||
**到这里,我们小结一下吧**。流计算不提供流式数据的存储服务,数据是持续流动的,在计算完成后就会立刻丢弃。流计算适用于需要处理持续到达的流数据、对数据处理有较高实时性要求的场景。为了及时处理流数据,流计算框架必须是低延迟、可扩展、高可靠的。
|
||||
|
||||
流计算的应用场景有很多,比如它是网络监控、传感监测、AR/VR、音视频流等实时应用的发展的基础。所以,目前流计算相关的框架和平台也有很多了,主流的划分方式是将其分为如下3类:
|
||||
|
||||
- 商业级的流计算平台,比如IBM的InfoSphere Streams和TIBCO的StreamBase。InfoSphere Streams支持同时分析多种数据类型并实时执行复杂计算。StreamBase是一个用于实时分析的软件,可以快速构建分析系统,即时做出决策。StreamBase可以为投资银行、对冲基金、政府机构等提供实时数据分析服务。
|
||||
- 开源流计算框架,典型代表是Apache Storm(由Twitter开源)和S4(由Yahoo开源)。Storm是一个分布式的、容错的实时计算系统,可以持续进行实时数据流处理,也可以用于分布式RPC。S4是一个通用的、分区容错的、可扩展的、可插拔的分布式流式系统。这些开源的分布式流计算系统由于具备开源代码,因此比较适合开发人员将其搭建在自身业务系统中。
|
||||
- 各大公司根据自身业务特点而开发的流计算框架,比如Facebook的Puma、百度的Dstream(旨在处理有向无环的数据流)、淘宝的银河流数据处理平台(一个通用的、低延迟、高吞吐、可复用的流数据实时计算系统)。
|
||||
|
||||
除了这些框架外,我们还会经常听到Spark、Flink等。Spark和Flink与Storm框架的不同之处在于,Spark和Flink除了支持流计算,还支持批量计算,因此我没有直接将它们列入上述的流计算框架中。如果你的业务中需要用到或者需要参考某种计算框架或者平台的话,可以再参考其官方文档或者相关的技术文章。
|
||||
|
||||
接下来,我就以Storm这个开源的流计算框架为例,通过介绍Storm的工作原理,以加深你对流计算模式的进一步理解,进而帮助你将其运用到实际业务中。
|
||||
|
||||
## Storm的工作原理
|
||||
|
||||
说到Storm的工作原理,我们先来对比下Storm与MapReduce的区别吧。Hadoop上运行的是“MapReduce作业”,而Storm上运行的是“计算拓扑(Topologies)”。 “作业”和“拓扑”的一个关键区别是:MapReduce 的一个作业在得到结果之后总会结束;而拓扑描述的是计算逻辑,该计算逻辑会永远在集群中运行(除非你杀死该进程)。
|
||||
|
||||
如下图所示,Storm集群上有两种节点,即主节点(Master Node)和工作节点(Worker Nodes)。
|
||||
|
||||
- Nimbus是整个Storm集群的主守护进程,以唯一实例的方式运行在主节点上。它负责把任务分配和分发给集群的工作节点,并监控这些任务的执行情况。当某个节点故障时,它会重新分配该故障节点上的任务到其它节点。
|
||||
- Supervisor是Storm集群里工作守护进程,每个工作节点都存在一个这样的实例。它通过Zookeeper与Nimbus守护进程进行通信。在接受到Nimbus分配的任务后,它会为每个任务启动单独的工作进程。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/dc/e9/dc3647474c58d12510582404087ceee9.png" alt="">
|
||||
|
||||
前面我介绍了Nimbus是负责分发任务或代码的,Supervisor是负责接收任务,并启动和停止工作进程以执行任务的。那么Nimbus和Supervisors之间,具体是怎么协同的呢?下面我们一起看一下。
|
||||
|
||||
如果所有数据和信息均存储在Master Node上,Master Node故障后,会导致整个集群信息丢失,因此引入了ZooKeeper集群来加强可靠性。为此Master Node与Worker Node之间的交互通过ZooKeeper完成,由于Nimbus 和 Supervisors是Master Node和Worker Node之间负责交互的进程,因此Nimbus和Supervisors之间的所有协调都是通过ZooKeeper集群完成的,比如Nimbus会将任务的分配情况或信息发送给ZooKeeper集群,然后Supervisors向ZooKeeper集群获取任务,并启动工作进程以执行任务。
|
||||
|
||||
当Supervisor接收到分配的任务后,会启动工作节点的工作进程(Worker)去执行任务。我们知道,一个计算任务可以分成任务数据的读取以及任务执行两部分。Worker提供了两个组件Spout和Bolt,分别进行数据读取和任务执行。
|
||||
|
||||
在详细介绍Worker组件之前,我首先介绍一下Storm的核心抽象:数据流。数据流是一个无界序列,是在分布式环境中并行创建、处理的一组元组(tuple)。数据流可以由一种能够表述数据流中元组的域(fields)的模式来定义。
|
||||
|
||||
Storm为进行数据流转换提供了基本组件Spout和Bolt。 Spout和Bolt有用户自定义的接口,用于运行特定应用程序的逻辑。如下图所示,Storm上运行的计算拓扑其实是由一系列Spout 和 Bolt 组成的有向无环图,这个有向无环图代表了计算逻辑。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7a/c8/7ae9455e1d125488d9b94283eecb2ac8.png" alt="">
|
||||
|
||||
备注:
|
||||
|
||||
1. 图中箭头,表示数据元组的传递方向。
|
||||
1. 此图引自“[Twitter Analysis with Apache Storm](http://www.treselle.com/blog/twitter-analysis-with-apache-storm/)”。
|
||||
|
||||
接下来,我们看看**Spout和Bolt的含义**吧。
|
||||
|
||||
- Spout用于接收源数据。通常情况下,Spout 会从一个外部的数据源读取数据元组,然后将它们发送到拓扑中。例如,Spout从Twitter API读取推文并将其发布到拓扑中。
|
||||
- Bolt负责处理输入的数据流,比如数据过滤(filtering)、函数处理(functions)、聚合(aggregations)、联结(joins)、数据库交互等。数据处理后可能输出新的流作为下一个Bolt的输入。每个Bolt往往只具备单一的计算逻辑。当我们执行简单的数据流转换时,比如仅进行数据过滤,则通常一个 Bolt 可以实现;而复杂的数据流转换通常需要使用多个 Bolt 并通过多个步骤完成,比如在神经网络中,对原始数据进行特征转换,需要经过数据过滤、清洗、聚类、正则化等操作。
|
||||
|
||||
## 知识扩展:流计算和批量计算的区别是什么?
|
||||
|
||||
MapReduce可以说是一种批量计算,与我们今天介绍的用于实时数据处理的流计算,是什么关系呢?
|
||||
|
||||
虽然流计算和批量计算属于两种不同的计算模式,但并不是非此即彼的关系,只是适用于不同的计算场景。
|
||||
|
||||
在流计算中,数据具有时效性,因此在5G以及人工智能应用的驱动下,专注于实时处理的流计算越来越得到广泛的关注。流计算的低延时、易扩展等性能非常适用于对时延要求高的终端应用(比如直播中音视频的处理等),从而极大提高用户的服务体验。而批量计算适用于对时延要求低的任务。
|
||||
|
||||
在实际运用中,可以根据计算要求,选择不同的计算模式。我将这两种计算模式的特点,总结为了一张表格,以帮助你理解、记忆,以及选择适合自己业务场景的计算模式。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/53/2c/53f87f8de7f10562db5ce74b748bce2c.jpg" alt="">
|
||||
|
||||
## 总结
|
||||
|
||||
今天,我与你介绍了分布式计算模式中的流计算。流数据的价值会随时间的流逝而降低,“时间就是金钱”在流计算中体现得淋漓尽致。这就要求流计算框架必须是低延迟、可扩展、高可靠的。
|
||||
|
||||
在介绍流计算的工作原理时,我首先通过一个流程图,与你介绍了它的3个步骤,即提交流式计算作业、加载流式数据进行流计算和持续输出计算结果。
|
||||
|
||||
然后,我以流计算开源框架中的Storm为例,与你讲述了Storm的核心组件以及通过Spout和Bolt构建有向无环图代表流计算逻辑,以实现流计算,以加深你对流计算原理的理解。
|
||||
|
||||
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fb/5e/fbb7a3acd35fd34bdb7727ce3a0caf5e.png" alt="">
|
||||
|
||||
## 思考题
|
||||
|
||||
离线计算和批量计算,实时计算和流式计算是等价的吗?你能和我说说你做出判断的原因吗?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
128
极客时间专栏/分布式技术原理与算法解析/第三站:分布式计算技术/17 | 分布式计算模式之Actor:一门甩锅的艺术.md
Normal file
128
极客时间专栏/分布式技术原理与算法解析/第三站:分布式计算技术/17 | 分布式计算模式之Actor:一门甩锅的艺术.md
Normal file
@@ -0,0 +1,128 @@
|
||||
<audio id="audio" title="17 | 分布式计算模式之Actor:一门甩锅的艺术" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/3b/e2/3b3e6b76e660d963d4387980b2e0f3e2.mp3"></audio>
|
||||
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
我在前两篇文章中,带你一起学习了MapReduce和Stream计算模式,相信你对批处理和流计算也有了一定的了解。虽然这两种计算模式对数据的处理方式不同,但都是以特定数据类型(分别对应静态数据和动态数据)作为计算维度。
|
||||
|
||||
在接下来两篇文章中,我将从计算过程或处理过程的维度,与你介绍另外两种分布式计算模式,即Actor和流水线。分布式计算的本质就是在分布式环境下,多个进程协同完成一件复杂的事情,但每个进程各司其职,完成自己的工作后,再交给其他进程去完成其他工作。当然,对于没有依赖的工作,进程间是可以并行执行的。
|
||||
|
||||
你是不是想说,分布式进程那么多,如果需要开发者自己去维护每个进程之间的数据、状态等信息,这个开发量可不是一般得大,而且特别容易出错。那么,有没有什么办法可以让开发者只关注自己的逻辑呢?
|
||||
|
||||
答案是肯定的,Actor计算模式就能满足你的需求。也就是说,你可以把数据、状态等都扔给Actor。这是不是“一门甩锅的艺术”呢?
|
||||
|
||||
接下来,我们就一起打卡分布式计算模式中的Actor模式。
|
||||
|
||||
## 什么是Actor?
|
||||
|
||||
在第10篇文章“[分布式体系结构之非集中式结构:众生平等](https://time.geekbang.org/column/article/149653)”中,我曾提到Akka框架基于Actor模型,提供了一个用于构建可扩展的、弹性的、快速响应的应用程序的平台。
|
||||
|
||||
其中,Actor类似于一个“黑盒”对象,封装了自己的状态和行为,使得其他Actor无法直接观察到它的状态,调用它的行为。多个Actor之间通过消息进行通信,这种消息类似于电子邮箱中的邮件。Actor接收到消息之后,才会根据消息去执行计算操作。
|
||||
|
||||
那么,**Actor模型又是什么呢?**Actor模型,代表一种分布式并行计算模型。这种模型有自己的一套规则,规定了Actor的内部计算逻辑,以及多个Actor之间的通信规则。在Actor模型里,每个Actor相当于系统中的一个组件,都是基本的计算单元。
|
||||
|
||||
**Actor模型的计算方式与传统面向对象编程模型(Object-Oriented Programming,OOP)类似**,一个对象接收到一个方法的调用请求(类似于一个消息),从而去执行该方法。
|
||||
|
||||
但是,OOP因为数据封装在一个对象中,不能被外部访问,当多个外部对象通过方法调用方式,即同步方式进行访问时,会存在死锁、竞争等问题,无法满足分布式系统的高并发性需求。而Actor模型通过消息通信,采用的是异步方式,克服了OOP的局限性,适用于高并发的分布式系统。
|
||||
|
||||
举一个最简单的例子,假如你现在定义了三个对象A、B和C,对象C中有一个函数Function,现在对象A和对象B同时调用对象C中的Function,此时对象C中的Function就成为了我们在第3篇文章“[分布式互斥:有你没我,有我没你](https://time.geekbang.org/column/article/141772)”中提到的共享资源,有可能会存在竞争、死锁等问题。
|
||||
|
||||
而对于Actor模式,对象A、B和C对应着Actor A、Actor B和Actor C,当Actor A和Actor B需要执行Actor C中的Function逻辑时,Actor A和 Actor B会将消息发送给Actor C, Actor C的消息队列存储着Actor A和 Actor B的消息,然后根据消息的先后顺序,执行Function即可。
|
||||
|
||||
也就是说,Actor模式采用了异步模式,并且每个Actor封装了自己的数据、方法等,解决了OOP存在的死锁、竞争等问题。
|
||||
|
||||
## Actor计算模式
|
||||
|
||||
接下来,我们再一起看看Actor计算模式吧。如下图所示,描述了具有3个Actor的Actor模型。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/3d/11/3d64427315a82ef08f4dd0fc88d9a311.png" alt="">
|
||||
|
||||
可以看到,**Actor模型的三要素是状态、行为和消息**,有一个很流行的等式:Actor模型=(状态+行为)+ 消息。
|
||||
|
||||
接下来,我们一起看看这三要素的具体含义吧。
|
||||
|
||||
- 状态(State)。Actor的状态指的是,Actor组件本身的信息,相当于OOP对象中的属性。Actor的状态会受Actor自身行为的影响,且只能被自己修改。
|
||||
- 行为(Behavior)。Actor的行为指的是,Actor的计算处理操作,相当于OOP对象中的成员函数。Actor之间不能直接调用其他Actor的计算逻辑。Actor只有收到消息才会触发自身的计算行为。
|
||||
- 消息(Mail)。Actor的消息以邮件形式在多个Actor之间通信传递,每个Actor会有一个自己的邮箱(MailBox),用于接收来自其他Actor的消息,因此Actor模型中的消息也称为邮件。一般情况下,对于邮箱里面的消息,Actor是按照消息达到的先后顺序(FIFO)进行读取和处理的。
|
||||
|
||||
了解了Actor的三要素后,我们再一起看下Actor的工作原理吧。
|
||||
|
||||
### Actor工作原理
|
||||
|
||||
为了方便你理解Actor的工作原理,我会通过讲述3个Actor之间基于消息和消息队列的工作流程进行说明。这3个Actor的工作流程,如下所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ad/7f/addab9026cecd00194755f594b3ab87f.png" alt="">
|
||||
|
||||
1. Actor1和Actor3先后向Actor2发送消息,消息被依次放入Actor2的MailBox队列的队尾;
|
||||
1. Actor2从MailBox队列的队首依次取出消息执行相应的操作,由于Actor1先把消息发送给Actor2,因此Actor2先处理Actor1的消息;
|
||||
1. Actor2处理完Actor1的消息后,更新内部状态,并且向其他Actor发送消息,然后处理Actor3发送的消息。
|
||||
|
||||
了解了Actor之间的消息交互和处理流程,我再以一个具体案例和你详细解读一下Actor之间的消息传递过程吧。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4d/d3/4d1e879439672d2052efd8dca0026dd3.png" alt="">
|
||||
|
||||
我们已经知道,在系统中,不同的组件/模块可以视为不同的Actor。现在有一个执行神经网络的应用,其中有两个组件A和B,分别表示数据处理模块和模型训练模块。假设,我们可以将组件A和B看作两个Actor,训练过程中的数据可以通过消息进行传递。如上图所示,完整的消息传输过程为:
|
||||
|
||||
1. 组件A创建一个Actor System,用来创建并管理多个Actor。
|
||||
1. 组件A产生QuoteRequest消息(即mail消息,比如数据处理后的数据),并将其发送给ActorRef。ActorRef是Actor System创建的组件B对应Actor的一个代理。
|
||||
1. ActorRef 将消息(经过数据处理后的数据)传输给Message Dispatcher模块。Message Dispatcher类似于快递的中转站,负责接收和转发消息。
|
||||
1. Message Dispatcher将消息(数据处理后的数据)加入组件B的MailBox队列的队尾。
|
||||
1. Message Dispatcher将MailBox加入线程。需要注意的是,只有当MailBox是线程时,才能处理MailBox中的消息。
|
||||
1. 组件B的MailBox将队首消息(数据)取出并删除,队首消息交给组件B处理,进行模型训练。
|
||||
|
||||
### Actor关键特征
|
||||
|
||||
通过上面的描述,可以看出Actor的通信机制与日常的邮件通信非常类似。因此,我们可以进一步总结出Actor模型的一些特点:
|
||||
|
||||
- **实现了更高级的抽象。**我在前面提到过,Actor与OOP对象类似,封装了状态和行为。但是,Actor之间是异步通信的,多个Actor可以独立运行且不会被干扰,解决了OOP存在的竞争问题。
|
||||
- **非阻塞性。**在Actor模型中,Actor之间是异步通信的,所以当一个Actor发送信息给另外一个Actor之后,无需等待响应,发送完信息之后可以在本地继续运行其他任务。也就是说,Actor模型通过引入消息传递机制,从而避免了阻塞。
|
||||
- **无需使用锁**。Actor从MailBox中一次只能读取一个消息,也就是说,Actor内部只能同时处理一个消息,是一个天然的互斥锁,所以无需额外对代码加锁。
|
||||
- **并发度高。**每个Actor只需处理本地MailBox的消息,因此多个Actor可以并行地工作,从而提高整个分布式系统的并行处理能力。
|
||||
- **易扩展。**每个Actor都可以创建多个Actor,从而减轻单个Actor的工作负载。当本地Actor处理不过来的时候,可以在远程节点上启动Actor然后转发消息过去。
|
||||
|
||||
虽然Actor模型有上述的诸多优点,但它并不适用于分布式领域中所有的应用平台或计算框架。因为,Actor模型还存在如下一些不足之处:
|
||||
|
||||
- Actor提供了模块和封装,但缺少继承和分层,这使得即使多个Actor之间有公共逻辑或代码部分,都必须在每个Actor中重写这部分代码,也就是说重用性小,业务逻辑的改变会导致整体代码的重写。
|
||||
- Actor可以动态创建多个Actor,使得整个Actor模型的行为不断变化,因此在工程中不易实现Actor模型。此外,增加Actor的同时,也会增加系统开销。
|
||||
- Actor模型不适用于对消息处理顺序有严格要求的系统。因为在Actor模型中,消息均为异步消息,无法确定每个消息的执行顺序。虽然可以通过阻塞Actor去解决顺序问题,但显然,会严重影响Actor模型的任务处理效率。
|
||||
|
||||
尽管Actor模型在需要同步处理的应用等场景具有局限性,但它在异步场景中应用还是比较广泛的。接下来,我们就一起看看Actor目前都应用在哪些地方吧。
|
||||
|
||||
## Actor模型的应用
|
||||
|
||||
Actor模型在1973年被提出,已广泛应用在多种框架和语言中。可以说,很多框架或语言支持Actor编程模型,是为了给开发者提供一个通用的编程框架,让用户可以聚焦到自己的业务逻辑上,而不用像面向对象等编程模型那样需要关心死锁、竞争等问题。
|
||||
|
||||
那么,到底有哪些框架或语言支持Actor编程模型呢?接下来,我就和你列举几个典型的框架或语言吧,以方便你参考。
|
||||
|
||||
- Erlang/OTP。Erlang是一种通用的、面向并发的编程语言,使用Erlang编写分布式应用比较简单,而OTP就是Erlang技术栈中的标准库。Actor模型在Erlang语言中得到广泛支持和应用,其他语言的Actor逻辑实现在一定程度上都是参照了Erlang的模式。实现了Actor模型逻辑的Erlang/OTP,可以用于构建一个开发和运行时环境,从而实现分布式、实时的、高可用性的系统。
|
||||
- Akka。Akka是一个为Java和Scala构建高度并发、分布式和弹性的消息驱动应用程序的工具包。Akka框架基于Actor模型,提供了一个用于构建可扩展的、弹性的、快速响应的应用程序的平台。通过使用Actors和Streams技术, Akka为用户提供了多个服务器,使用户更有效地使用服务器资源并构建可扩展的系统。
|
||||
- Quasar (Java) 。Quasar是一个开源的JVM库,极大地简化了高度并发软件的创建。Quasar在线程实现时,参考了Actor模型,采用异步编程逻辑,从而为JVM提供了高性能、轻量级的线程,可以用在Java和Kotlin编程语言中。
|
||||
|
||||
## 知识扩展:Akka中Actor之间的通信可靠性是通过Akka集群来保证的,那么Akka集群是如何检测节点故障的呢?
|
||||
|
||||
在第10篇文章“[分布式体系结构之非集中式结构:众生平等](https://time.geekbang.org/column/article/149653)”中,我与你介绍了Akka 集群是一个去中心化的架构,比如现在集群中有n个节点,这n个节点之间的关系是对等的。节点之间采用心跳的方式判断该节点是否故障,但未采用集中式架构中的心跳检测方法。
|
||||
|
||||
Akka 集群中的故障检测方法是,集群中每个节点被k个节点通过心跳进行监控,比如k = 3,节点1被节点2、节点3和节点4通过心跳监控,当节点2发现节点1心跳不可达时,就会标记节点1为不可达(unreachable),并且将节点1为不可达的信息通过Gossip传递给集群中的其他节点,这样集群中所有节点均可知道节点1不可达。
|
||||
|
||||
其中,k个节点的选择方式是,将集群中每个节点计算一个哈希值,然后基于哈希值,将所有节点组成一个哈希环(比如,从小到大的顺序),最后根据哈希环,针对每个节点逆时针或顺时针选择k个临近节点作为监控节点。
|
||||
|
||||
## 总结
|
||||
|
||||
接下来,我们小结一下吧。今天,我与你介绍了分布式计算中,一门甩锅的计算模型,即Actor模型。
|
||||
|
||||
首先,我介绍了什么是Actor模型以及Actor模型的三要素,包括状态、行为和消息。
|
||||
|
||||
其次,我介绍了Actor的工作原理,并通过实例介绍了Actor之间通过消息及消息队列进行异步通信的流程,以便于你进一步理解Actor的工作原理。
|
||||
|
||||
最后,我为你介绍了几个当前支持Actor编程模型的框架和语言,以便于你在需要采用Actor模型编程时做一个参考。
|
||||
|
||||
最后,我再通过一张思维导图来归纳一下今天的核心知识点吧。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d6/89/d605ba04f0eb88c6cd0910eef6af4489.png" alt="">
|
||||
|
||||
著名的Erlang并发编程语言,以及Akka这一分布式计算框架都实现了Actor模型的计算逻辑。因此,即使你在之前未曾接触过Actor模型,学习了这篇文章后,你也可以根据开源的Erlang或Akka项目,去更深刻地理解Actor模型了,加油!
|
||||
|
||||
## 思考题
|
||||
|
||||
Actor是否可以采用阻塞方式去运行呢,原因是什么呢?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
151
极客时间专栏/分布式技术原理与算法解析/第三站:分布式计算技术/18 | 分布式计算模式之流水线:你方唱罢我登场.md
Normal file
151
极客时间专栏/分布式技术原理与算法解析/第三站:分布式计算技术/18 | 分布式计算模式之流水线:你方唱罢我登场.md
Normal file
@@ -0,0 +1,151 @@
|
||||
<audio id="audio" title="18 | 分布式计算模式之流水线:你方唱罢我登场" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d8/cd/d8c0e1bbdfddc6915b0bdfb1158e13cd.mp3"></audio>
|
||||
|
||||
你好,我是聂鹏程。今天,我来继续带你打卡分布式核心技术。
|
||||
|
||||
通过前面几篇文章,我们一起学习了分布式计算模式中的MapReduce、Stream和Actor,它们各显神通解决了很多实际问题。
|
||||
|
||||
但是,在现实生活中,经常还会出现这样的情况,前一个任务的结果是另外一个任务的输入。比如工厂生产一瓶饮料,首先需要往瓶子里装上饮料,待饮料装满后,再封口。如果装饮料和封口分别为子任务,那么前一个任务(装饮料)结束后才可以开始第二个任务(封口)。类似这样的作业,就是我们常说的流水线作业。
|
||||
|
||||
在分布式领域中解决类似具有依赖关系的流水线作业的计算模式,叫作流水线计算模式。其实,流水线计算模式是我们在[第1篇文章](https://time.geekbang.org/column/article/140004)中提到的数据并行计算的一种形式,就是将一个任务拆分为多个步骤(子任务),然后多个这样的任务通过对步骤(子任务)的重叠执行,以实现数据并行处理的场景。
|
||||
|
||||
这种流水线模式在计算机领域中最先用于CPU指令设计,后来推广到机器学习领域进行数据处理、模型训练等。在流水线计算模式中,由于前一个子任务执行后,会扔给下一个子任务,由下一个子任务去展现自己的能力,因此可以形象地比喻为“你方唱罢我登场”。
|
||||
|
||||
接下来,我们就一起打卡分布式计算模式中的流水线模式吧。
|
||||
|
||||
## 什么是流水线模式?
|
||||
|
||||
其实,分布式领域的流水线计算模式,就是参考了工业生产中的流水作业模式,将一个任务分为多个步骤执行,使得不同任务可以并行执行。此外,你肯定还会想到计算机技术中的流水线计算吧。
|
||||
|
||||
计算机中的**流水线(Pipeline)**技术是一种将每条指令拆分为多个步骤,多条指令的不同步骤重叠操作,从而实现几条指令并行处理的技术。现代CPU指令采用了流水线设计,将一条CPU指令分为取指(IF)、译码(ID)、执行(EX)、访存(MEM)、回写(WB)五级流水线来执行。
|
||||
|
||||
如下图所示,在第一条指令执行译码操作时,第二条指令就可以执行取指操作了,从而实现了多条指令的并行操作。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/75/08/75c9e4c34848d1d6e9bfc77705d7d108.png" alt="">
|
||||
|
||||
在分布式领域中,**流水线计算模式**也类似,它是将一个大任务拆分为多个步骤执行,不同的步骤可以采用不同的进程执行。这,使得不同任务可以并行执行,从而提高了系统效率。
|
||||
|
||||
以机器学习中的数据预处理为例,假设现在有5个样本数据,每个样本数据进行数据预处理的流程,包括数据去重、数据缺失值处理、数据归一化3个步骤,且需要按照顺序执行。也就是说,数据预处理这个任务可拆分为数据去重—>数据缺失值处理—>数据归一化3个子任务。
|
||||
|
||||
如果现在有3个节点,节点1执行数据去重,节点2执行数据缺失值处理,节点3执行数据归一化。那么,节点1处理完样本1的数据,将处理后的数据发送节点2后,则节点1可以继续处理样本2的数据,同时节点2处理样本1的数据,以此类推,就实现了多任务的并行执行。
|
||||
|
||||
接下来,我们再具体看看分布式领域中的流水线计算模式吧。
|
||||
|
||||
## 流水线计算模式
|
||||
|
||||
流水线计算模式的应用非常广泛,在AI技术中也非常常见。对流水线计算模式的学习,将有助于你学习AI技术,因此我接下来会以机器学习为例,为你介绍流水线计算模式。
|
||||
|
||||
当然,流水线计算模式的原理是通用的,也可以应用到其他领域,比如通信领域中使用HTTP流水线传输、计算机图形学中的图流水线等。
|
||||
|
||||
随着神经网络、深度学习在全世界掀起了All in AI的热潮,用于加速的GPU和TPU也被越来越多的人使用。虽然诸如GPU、TPU之类的加速器可以从根本上减少执行单个训练步骤所需的时间,但**为了达到最佳性能,我们仍然需要高效的输入流水线机制。**
|
||||
|
||||
比如,在流水线模式中数据预处理与GPU/TPU进行模型训练可以重叠进行;再比如,第N个样本进行模型训练时,第N+1个样本可以进行数据预处理,也就是说在第N+1个样本进行预处理前,已经将第N个样本处理后的数据提供给了模型训练,进一步减少了整体的数据处理和模型训练时间。
|
||||
|
||||
Tensorflow是Google开源的一个分布式机器学习框架,已被各大公司采用,比如网易、eBay、Intel等公司。接下来,我就以TensorFlow的输入流水线模式为例,与你介绍流水线技术模式的原理,并带你了解如何构建机器学习的流水线。
|
||||
|
||||
### 流水线计算模式的原理
|
||||
|
||||
TensorFlow运用了流水线模式对输入数据进行预处理,因此称为输入流水线(TensorFlow Training Input Pipelines)。其数据输入流水线主要包含3个步骤:
|
||||
|
||||
- **提取(Extract)**。通过多种途径读取数据,比如内存、本地的HDD或SSD、远程的HDFS、GCS等。数据的种类也有很多,比如图像数据、文本数据、视频数据等。
|
||||
- **转换(Transform)**。使用 CPU处理器对输入的数据进行解析以及预处理操作,包括混合重排(shuffling)、批处理(batching), 以及一些特定的转换。比如图像解压缩和扩充、文本矢量化、视频时序采样等。
|
||||
- **加载(Load**)。将转换后的数据加载到执行机器学习模型的加速器设备上,比如GPU 或 TPU。
|
||||
|
||||
由于输入流水线包含了提取、转换、加载3个步骤,因此**TensorFlow的数据输入流水线也称为ETL流水线**。TensorFlow提供了一个官方API也就是tf.data,利用简单、可重用的数据片段构建复杂的输入流水线。
|
||||
|
||||
没错,在加速模型训练方面,输入流水线是非常重要的一个模块。由上述流程可知,要执行训练步骤,首先需要提取并使用CPU转换数据,然后将其提供给在加速器上运行的模型。
|
||||
|
||||
如果不引入流水线模型的话,当 CPU 正在预处理数据时,加速器处于空闲状态。同样,当GPU/TPU正在训练模型时,CPU 处于空闲状态。因此,训练的用时是 CPU 预处理时间和加速器训练时间的总和。
|
||||
|
||||
为了帮助你理解,**我们一起看下**[**TensorFlow官网**](https://www.tensorflow.org/guide/data_performance)**给出的一个示例吧**。这个例子展示了一个不使用流水线技术和使用流水线技术时,CPU、GPU/TPU的训练过程对比。
|
||||
|
||||
我们先看看不使用流水线技术的训练过程。如下图所示,Prepare 1表示CPU正在对第1个样本数据进行预处理操作,Train 1表示GPU/TPU正在训练第1个样本数据。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5a/f2/5ad2604aac06d7ed8448e0b0b7dfd0f2.png" alt="">
|
||||
|
||||
备注:图片来源为[www.tensorflow.org/guide](http://www.tensorflow.org/guide)。
|
||||
|
||||
图中的“idle”指的是空闲时间。可以看出,如果不使用流水线,CPU 和 GPU/TPU 运作的时间没有重叠,因此在大部分时间都可能处于空闲状态。
|
||||
|
||||
接下来,我们再看看使用流水线技术的训练过程。流水线模型可以将训练步骤的数据预处理和数据训练过程重叠到一起。比如,当GPU/TPU正在训练第 N 个样本数据时,CPU 可以预处理第 N+1 个样本数据。这样做不仅可以最大限度地缩短训练的单步用时,还可以缩短提取和转换数据所需的时间,如下图所示:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/bf/b9/bf19b31ba9dd27cb7b1a6e63019476b9.png" alt="">
|
||||
|
||||
图片来源:[www.tensorflow.org/guide](http://www.tensorflow.org/guide)
|
||||
|
||||
很明显,采用流水线的设计可以充分利用CPU和GPU/TPU,从而避免资源闲置,加速训练过程。
|
||||
|
||||
**到这里,我们来小结一下吧。**
|
||||
|
||||
TensorFlow的输入流水线模式将对数据的操作拆分为提取、转换、加载3个不重叠的部分。当CPU对第N个样本的数据完成预处理之后,会将预处理后的数据发送给GPU/TPU,然后CPU继续对第N+1个样本的数据进行预处理,同时GPU/TPU对第N个样本数据进行模型训练。也就是说,这种计算模式实现了多样本数据处理和模型训练的并行执行。
|
||||
|
||||
可以看出,在模型训练中引入流水线模式,可以提高 CPU、GPU/TPU的利用率,还可以加速训练过程。
|
||||
|
||||
### 实践: 构建机器学习流水线
|
||||
|
||||
前面提到在TensorFlow中,流水线模式主要运用在数据读取阶段。那么,对于一个复杂的机器学习任务,是否也可以构建一套流水线作业呢?
|
||||
|
||||
答案是肯定的。接下来,我们就一起看看,如何构建机器学习流水线。
|
||||
|
||||
一个典型的机器学习训练模型按照流水线计算模式拆分,可以包括如下所示的5个步骤:
|
||||
|
||||
1. **数据输入**,指的是从不同的数据源中导入数据。
|
||||
1. **数据转换**,主要是要把输入的无结构数据转换成合适的格式,以便特征提取。
|
||||
1. **特征提取**,指的是从数据集中提取特征数据。
|
||||
1. **模型训练**,包括提供一个算法,并提供一些训练数据让模型可以学习。学习算法会从训练数据中发现模型,并生成输出模型。
|
||||
1. **模型验证**,指的是通过训练得到的结果,对模型进行错误率验证。比如,图像分类中分类结果的验证,预测中的准确度验证,从而提高模型的准确性。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/9d/21/9de5f259c32b8f3f3cae6edaf5527821.png" alt="">
|
||||
|
||||
值得注意的是,在数据输入和数据转换之间,有时需要进行数据清洗。数据清洗主要是剔除错误数据和不重要的数据,从而降低模型训练的错误率。
|
||||
|
||||
接下来,**我以图像分类为例,带你了解机器学习流水线的流程。**关于图像分类的详细知识点,你可以自行查阅相关资料。
|
||||
|
||||
如下图所示,假如现在有10000张小狗照片,需要训练出一个关于小狗的预测模型。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4a/d3/4a0022453c24ec07309cf49475919ed3.png" alt="">
|
||||
|
||||
假设这10000张照片中,8000张作为训练集,2000张作为测试集,采用CNN进行模型训练。CNN包括输入层、卷积层、池化层、全连接层,其中输入层为数据输入,卷积层和池化层为特征提取,全连接层是连接所有特征,输出数据到分类器中,以得到训练结果。
|
||||
|
||||
如上图所示,生成小狗预测模型的流水线可以分为数据输入、数据转换、特征提取、模型训练、模型验证5部分。具体流程如下:
|
||||
|
||||
1. 输入数据,也就是输入图像数据,即8000张图片,其中图像以像素表示。比如,图片的大小是480**480,那么图像输入数据格式可以是480**480*3的数组。3代表的是,RGB的维度。
|
||||
1. 数据转换,也就是对输入的图像数据进行解析、正则化处理,消除一些噪声数据,得到格式化的数据。
|
||||
1. 特征提取,指的是得到格式化的数据之后,就可以对输入图像进行特征提取,通过卷积操作提取小狗的一些轮廓特征,比如耳朵、尾巴、身体等,然后通过池化层识别出主要特征,比如小狗的耳朵、眼睛、舌头等,对特征进行精简。
|
||||
1. 模型训练。在CNN中模型训练其实和特征提取是相辅相成的,也就是特征提取后,实现特征提取的那些参数就是模型参数,而训练过程,会根据梯度下降法等对参数进行调整,以使得在模型验证阶段预测结果逼近真实结果。也就是说,特征提取和模型训练这两步,在CNN中是放到一起的,这里我为了方便你理解,才显式地把这两步划分了出来。
|
||||
1. 模型验证。将带有标签的测试数据集的图像(2000张)输入到小狗预测模型,将预测结果与实际结果进行对比,如果误差比较大,则对模型参数进行优化并进入下一次迭代训练;如果误差较小,那么得到的结果就是最终的小狗预测模型。
|
||||
|
||||
## 知识扩展:流水线模式和MapReduce模式中,都有将大任务拆分为多个子任务,两者的区别是什么?
|
||||
|
||||
如题目所述,流水线计算模式与分而治之的MapReduce计算模式(你可以再回顾下[第15篇文章](https://time.geekbang.org/column/article/155575)中的相关知识点)有相似之处,都是将一个完整的、大的任务进行划分,但它们**划分的模式**不一样:
|
||||
|
||||
- MapReduce以任务为粒度,将大的任务划分成多个小任务,每个任务都需要执行完整的、相同的步骤,同一任务能被并行执行,可以说是任务并行的一种计算模式;
|
||||
- 而流水线计算模式以步骤为粒度,一个任务拆分为多个步骤,每个步骤执行的是不同的逻辑,多个同类型任务通过步骤重叠以实现不同任务的并行计算,可说是数据并行的一种模式。
|
||||
|
||||
此外,它们的子任务(步骤)间的关系不同:
|
||||
|
||||
- 在MapReduce中,各个子任务可以独立执行,互不干扰,多个子任务执行完后,进行结果合并得到整个任务的结果,因此要求子任务之间是没有依赖关系的;
|
||||
- 而在流水线模式中,多个子任务之间是具有依赖关系的,前一个子任务的输出是后一个子任务的输入。
|
||||
|
||||
所以,综合来讲,MapReduce计算模式适合任务并行的场景,而流水线计算模式适合同类型任务数据并行处理的场景。
|
||||
|
||||
## 总结
|
||||
|
||||
首先,我与你介绍了什么是分布式计算模式中的流水线模式。它参考了工业生产中的流水作业模式,将一个任务分为多个步骤执行,不同任务之间的步骤可以重叠执行,这使得多个不同任务可以并行执行。
|
||||
|
||||
然后,我以典型的机器学习流程为例,介绍了机器学习流水线处理流程,以加深你对分布式流水线计算模型的理解。
|
||||
|
||||
最后,我以CNN进行小狗分类模型训练为例,通过讲述数据输入、数据处理、特征提取(卷积、池化等操作)、模型训练、模型验证等过程,带你进一步理解了流水线计算模式在实际应用中的原理。
|
||||
|
||||
现在,我再通过一张思维导图来归纳一下今天的核心知识点吧。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/43/5e/4329251192d6d77935e0de98ff8ebf5e.png" alt="">
|
||||
|
||||
流水线计算模式适合同类型任务,且每个任务可以拆分为多个步骤(子任务)进行执行的场景,通过重叠执行多个不同任务间的不同步骤实现数据并行。在实际应用场景中,有很多例子,最常见的就是机器学习。相信你在理解了本文的计算原理之后,一定可以将这种研究方法运用在你的工作中,加油!
|
||||
|
||||
## 思考题
|
||||
|
||||
流水线计算模式和流计算的区别是什么?
|
||||
|
||||
我是聂鹏程,感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再会!
|
||||
|
||||
|
||||
Reference in New Issue
Block a user