分布式系统设计实践
上QQ阅读APP看书,第一时间看更新

1.4 分布式计算系统

分布式存储系统只解决了大数据的存储问题,并没有解决大数据的计算问题。当计算量远远超过了单机的处理能力后,该怎么办呢?一种方式是各自开发专属的分布式计算框架,但这些计算框架很难做到通用和共享。因此,在不同公司或同一公司的不同团队中,存在着各种各样的分布式计算框架,造成了很大的浪费,而且框架的质量也良莠不齐。

1.4.1 批处理分布式计算系统

谷歌公司于2004年发表的MapReduce论文几近完美地解决了这个问题。MapReduce通过下面两个看似简单却包含了深刻智慧的函数,轻而易举地解决了一大类大数据计算问题。

map (<K1, V1>) → list(<K2, V2>)[10]

reduce (<K2, list(V2)>) → list(V3)[11]

如图1-2所示,使用MapReduce解决问题的步骤如下。

(1)需要将输入表示成一系列的键值对<K1, V1>。

(2)定义一个map函数,其输入是上一步的一个键值对<K1, V1>,其输出则是另一种键值对<K2, V2>的列表。

图1-2 MapReduce工作原理

(3)运行时,MapReduce框架会对每一个输入的键值对<K1, V1>调用map函数(执行map函数的机器称为Mapper),并生成一系列另一种键值对<K2, V2>。然后,MapReduce框架会根据K2进行分区(partition),即根据K2的值,将<K2, V2>对在多个称为Reducer(即执行reduce函数的机器)的机器间进行分发。

(4)还需要定义一个reduce函数,该函数的输入是一系列K2和与其对应的V2值的列表,输出是另一种值V3的列表。

(5)运行时,MapReduce框架会调用reduce函数,由reduce函数来对同一个K2V2的列表进行聚合。

MapReduce本质上是一种“分而治之”的策略,只不过数据规模很大而已。它首先把全部输入分成多个部分,每部分启动一个Mapper;然后,等所有Mapper都执行完后,将Mapper的输出根据K2做分区,对每个分区启动一个Reducer,由Reducer进行聚合。

MapReduce看似简单,却能够解决一大类问题。MapReduce能够解决的问题具有下列特征。

  • 需要一次性处理大批的数据,而且在处理前数据已经就绪,即所谓的批处理系统。
  • 数据集能够被拆分,而且可以独立进行计算,不同的数据集之间没有依赖。例如,谷歌的PageRank算法的迭代实现,每一次迭代时,可以把数据分为不同的分区,不同分区之间没有依赖,因此就可以利用MapReduce实现。但斐波那契数列的计算问题则不然,其后面值的计算必须要等前面的值计算出来后方可开始,因此就不能利用MapReduce实现。
  • 计算对实时性要求不高。这是因为MapReduce计算的过程非常耗时。

1.4.2 流处理分布式计算系统

对于那些不断有新数据进来,而且对实时性要求很高的计算(如实时的日志分析、实时的股票推荐系统等),MapReduce就不适用了。于是,流处理系统应运而生。

根据对新数据的处理方式,流处理系统分为以下两大类。

  • 微批处理(micro-batch processing)系统:当新数据到达时,并不立即进行处理,而是等待一小段时间,然后将这一小段时间内到达的数据成批处理。这类系统的例子有Apache Spark。
  • 真正的流处理(true stream processing)系统:当一条新数据到达后,立刻进行处理。这类系统的例子有Apache Storm、Apache Samza和Kafka Streams(只是一个客户端库)。

1.4.3 混合系统

在分布式计算领域,还有一种混合了批处理和流处理的系统,这类系统的一个例子是电商的智能推荐系统,其既需要批处理的功能(为了确保响应速度,预先将大量的计算通过批处理系统完成),也需要流处理的功能(根据用户的最新行为,对推荐系统进行实时调整)。

对于这类系统,有一种很流行的架构,即Lamda架构(如图1-3所示),其思想是用一个批处理系统(如MapReduce)来进行批处理计算,再用一个实时处理系统(如Apache Spark/Storm)来进行实时计算,最后用一个合并系统将二者的计算结果结合起来并生成最终的结果。

图1-3 Lamda架构

对于混合系统的实现,有篇非常有趣的文章值得一读,“Questioning the Lambda Architecture”一文中提到了Lamda架构的一个很大的缺点,即处理逻辑需要在批处理系统和流处理系统中实现两遍。该文提到了一种新的混合系统实现方式,即利用Kafka可以保存历史消息的特性,根据业务的需要,在Kafka中保存一定时间段内的历史数据,当需要进行批处理时,则访问Kafka中保存的历史数据,当需要实时处理时,则消费Kafka中的最新消息。如此这般,处理逻辑就只需要实现一套了。感兴趣的读者,可以读一读此文。