大数据基础原理
1. 背景
在各行各业的发展中,无论来源、记录方式如何,人们必然会积累各种各样的数据,并且倾向于通过统计数据分析现实情况,以此作为指导行动方向的依据。因此,统计学中一直围绕着数据进行建模与问题分析,给出对数据背后反映问题的判断。
由于计算机的发展,承载数据统计和分析的实体自然而然地变成了各式各样的计算机。在计算机和互联网的发展下,在数据分析领域也催生了一些新的计算形态。而且尽管各种行业的数据模型不同, 但是不同需求之间是有共性的。
如今已经为人熟知的大数据的概念,包括规模的增长,异构多样的数据带来的各种处理需求,其实在计算机领域是可以预见到的。在小数据,单机的情况下可以良好工作的算法,在数据规模激增的情况下不一定行得通,需要以各种新的设计来解决相应的问题。
在硬件上,表现为多核处理器和多机系统的发展;在软件层面,有对应高性能并行计算的MPI,当然也有以Google的MapReduce为滥觞的一系列集群式ETL的数据处理框架。
Hadoop是大数据处理框架在开源社区的先驱,下文仅讨论Hadoop生态相关的计算框架。
发展历程
由于提升单台计算机的成本曲线让人望而生畏, 由相对廉价的计算机集群执行的分布式计算成为了一种现实的选择。
因为互联网业务的特点,处理大量的文档,网络请求日志来生成报告乃至进行机器学习,大规模图学习成为其常见的场景。伴随着这些场景,Google的工程师实践并提出了MapReduce计算模型,同时贡献了GFS,Big Table。之后, 业界逐渐出现并演化了一批用于大数据处理的框架和组件,Hadoop即是其中的早期代表。
Hadoop的组成至少包含以下部分:
yarn是计算集群任务调度的服务中心;map reduce计算引擎;hdfs的文件系统。
MapReduce本身的概念其实并不难理解,但是其面对真实场景需要解决大量的工程问题。
以Hadoop为例,其本身就要处理很多异常情况。典型问题,如在集群中出现节点异常是家常便饭,每一步都可能出错或者被意外中断;mapreduce的数据分片经历了大量的基准测试和优化,在不同场景下要达到高性能的配置也相当复杂。hadoop的实现也没有达到一个完美的地步。hadoop只能处理批量离线数据,而且为了使用它执行一个操作经常需要做很多的MapReduce组合。由于其数据在处理过程中大部分都需要落盘,因此hadoop在性能上并没有优势,直接使用map reduce的方式到今天为止已经完全淡出工业界了。
尽管如此,入门大数据领域的时候了解MapReduce仍然是有其意义的。
2. MapReduce计算模型
我们了解一下MapReduce的计算模型。
MapReduce是一个在多个节点的集群中处理数据的计算框架/过程。一个MapReduce的系统通常由map、shuffle和reduce组成。数据的流向是从master节点发送数据开始, 到reduce节点结束。
一个典型的map reduce包含下图几个阶段:
map,reduce的计算形式:
map (k1, v1) -> list(k2, v2)
reduce (k2, list(v2)) -> list(v2)
map阶段之前,数据会形成分片,由master节点派发给不同的worker节点;每个数据的分片中可能包含了不同的key;
map阶段,每个worker节点执行map计算任务;
map阶段是并行的,在worker节点计算输出后保存(本地),传给下一个阶段;
从map到reduce,数据会经历shuffle,其过程取决于具体的实现,并不是统一的;
reduce阶段也是在worker节点完成。worker接收到master节点分配的reduce任务,拉取map阶段的输出作为输入,计算最终的输出。跟Map阶段不同,Reduce阶段不能并行。
如何容错
master节点记录了每个节点的任务状态,并且通过ping来判断worker节点是否失败; master节点在worker任务失败的时候会重置worker节点的状态;整个任务的执行依赖于worker对任务处理的原子性。
combiner函数
MapReduce同时包含了combiner函数,也是针对map的输出进行处理,跟reducer的区别是,它是处理worker本地数据的,其输出将作为reducer的输入;针对本来要发送给reducer的数据,做一些局部的排序或合并。而有序的数据在处理上可以取出小块切片后reduce而不影响结果,既能减少网络IO,也能减少reduce阶段的计算量。
shuffle阶段
shuffle包含map阶段和reduce阶段的操作,是一个统称;
在map阶段,根据key,val将数据划分到指定的reduce任务,这个叫做partitioner,也是shuffle的一个阶段;
在reduce阶段,shuffle根据map输出的结果,对内存和磁盘内的数据处理,对将由同一个reducer处理的数据进行合并。
需要注意的是,reduce阶段的shuffle排序和合并的算法在使用Spark框架的时候无法定制,仅能通过comparator指定如何排序。
combiner优化
当reduce满足结合律,可以使用reducer直接替换combiner
如果reduce不满足结合律,无法直接用reducer替换combiner,具体问题具体分析
2.1 MapReduce combiner的实际计算样例
计算平均值:
combiner是优化中间计算的一种方式,当使用combiner时,需要注意reducer是否满足结合率;
不妨设两个节点上有待执行map的数据集合,为A1,A2,显然,列数据的平均值不能用各自平均值取平均: AVG(AVG(A1), AVG(A2)) != AVG(A1∪A2)
这种情况需要在每条数据附加一个记录数1,最后在reduce阶段以总数和记录总数求得平均值;
计算中位数
另一个例子是计算数据的中位数: 这个目标任务的combiner函数不能是简单的取中位数。
一种方案是将map 输出的v2存为列表,这种方法会占用较多的内存;
在第一种方案的基础上其实可以进行优化:不保留所有的v2,而是记录v2的取值和对应的计数,最后得到的就是类似<k1, v1, count1>, <k2, v2, count2> ...的输出,其中列表是排好序的,在reduce阶段,遍历key的列表,在count1+count2...超过一半的总数时得到对应的中位数;同样可以优化内存的使用。
2.2. Spark计算框架
Spark是一个最为流行的大数据计算引擎。Spark基于hadoop的mapreduce计算模型做出抽象,并且在原有的资源调度、迭代计算的基础上做了许多优化,同时保留了对hadoop的hdfs,yarn等依赖。尽管它跟hadoop有关联,但是其跟hadoop并不是互为替换的关系。除此之外,伴随着更加方便的API和程序框架诞生的其他计算组件,则构建了Spark大数据处理的生态。
2.3 Hadoop Shuffle和Spark Shuffle
从功能上看,Hadoop和Spark的Shuffle是类似的,没有区别。从实现的细节上,两者才有不同。map端的shuffle一般为shuffle的Write阶段,reduce端的shuffle一般为shuffle的read阶段。
相对于Hadoop的 MapReduce,最开始的Spark在默认的情况下不对 Shuffle 的数据进行排序,即Hash Shuffle,目的是为了减少shuffle时候的性能浪费,对不需要排序的数据忽略这一步。但是,后来Spark的Shuffle经历了Hash、Sort、Tungsten-Sort(堆外内存)三阶段发展历程。
对于Hash Shuffle来说,在 Map Task 过程按照 Hash 的方式重组 Partition 的数据,不进行排序。每个 Map Task 为每个 Reduce Task 生成一个文件,通常会产生大量的文件(即对应为 M*R 个中间文件),伴随大量的随机磁盘 I/O 操作与大量的内存开销。
在Spark 1.2以后的版本中,默认的ShuffleManager改成了SortShuffleManager。SortShuffleManager相较于HashShuffleManager来说,有了一定的改进。主要就在于,每个Task在进行shuffle操作时,虽然也会产生较多的临时磁盘文件,但是最后会将所有的临时文件合并(merge)成一个磁盘文件,因此每个Task就只有一个磁盘文件。在下一个stage的shuffle read task拉取自己的数据时,只要根据索引读取每个磁盘文件中的部分数据即可[4]。而且针对不同的算子,会判断是否启用SortShuffle,从而避免不必要的排序。
2.4 Spark SQL, Spark Streaming, Structured Streaming
在Spark中,会用到RDD,DataFrame,DataSet(类似DataFrame)几种核心数据结构。
RDD为resilient distributed dataset,弹性分布式数据集,其结构上是Java 或Scala对象的集合.。Spark中在其上定义了很多操作,并且其接口类似函数式编程,能够简洁直观地表示一系列操作。其缺点是性能难以优化,而且会带来使用的时候本质上要自己维护DAG的困难。
DataFrame是数据的一个不可变分布式集合,其结构是列式存储,区别于RDD, 可以通过其列名直接访问数据列;相较RDD,DataFrame 是高级的 API,提供类似于SQL 的 query 接口。
Spark Steaming的出现早于Structured Streaming,处理的对象是DStream, 而Structured Streaming是Spark 2.0发布带来的特性,处理的对象是DataFrame。
两者都是以时间划分出微批处理,针对一定时间间隔的小批数据进行处理,以此模拟流式数据处理。
其中,Spark Steaming接近RDD对象数据结构,缺点是效率不高。而Structured Streaming同样是微批处理的形式,但其在DataFrame基础上的流式处理性能提升了。
简单来说,跟Hadoop相比,Spark具有如下的优点:
基于DAG的任务执行调度机制,算子的更强表现力,降低了开发的心智负担,更加贴近实际的开发;更加高效的计算执行模式,让更多的数据计算直接在内存中执行减少IO开销。
3. 设计模式、算子与流式计算
MapReduce设计模式
从MapReduce开始,其实就已经有了很多设计模式,可以参见《MapReduce设计模式》。
一些典型的设计模式如下:
数值概要设计模式 numerical summarization pattern,计算总数,最大值,最小值等都是概要模式
索引模式 indexing pattern 产生一个数据集的索引以达到快速检索的目的,例如文本的倒排索引
过滤模式 filtering pattern 过滤掉一些不需要的数据。
Spark的算子或多或少是这些map reduce设计模式的体现。
算子的分类
(1)Transformation变换/转换算子:这种变换不会执行真正的作业,因为一些变换不需要马上得到结果,而且可以在多个变换后计算复合操作,减少计算量。
(2)Action行动算子:这类算子会触发提交job作业,并将数据输出到Spark系统。
Transformation变换/转换算子
1. map算子
2. flatMap算子
3. mapPartitions算子
4. mapPartitionsWithIndex算子
5. cache算子、persist算子
6. union算子
7. cartesian算子
8. groupBy算子
9. filter算子
10. sample算子
11. combineByKey 算子 reduceByKey算子
12. join算子
Action类型的算子:
1.foreach
2.saveAsTextFile
3.collect
4.count
4. Hadoop生态的组件
Pig 和Hive
Pig和HiveQL是在HDFS上使用的数据查询语言。
HBase, Cassandra, HDFS
HBase是仿照谷歌BitTable的开源实现,是面向列的开源数据库;HDFS是GFS的开源实现,是一种文件系统;Cassandra是个分布式的key-value数据库。
Pregel,GraphX
图数据结构是分布式计算中一个重要的领域。图的数据可以分为网络、树、类RDBMS结构、稀疏矩阵以及其它一些结构(from Spark GraphX in Action)。为了处理图,在OLTP领域有Neo4j, NebulaGraph等组件;而在OLAP领域以Pregel,GraphX等为代表。
Pregel是一个迭代式的分布式图计算算法和系统,在此基础上有Spark的GraphX。到目前为止,GraphX还有一些尚未实现的特性和优化空间。
数据转换Sqoop
sqoop 是一个数据库转换的工具,提供了从hadoop文件格式到关系型数据库之间的数据互转。
Storm,Flink
前面讲到Spark可以进行流式数据的处理,然而Spark到目前为止的流式处理延迟只能达到秒级,而另外两种流式处理框架Storm、Flink可以达到μs到ms级。
这里只讲Flink。为什么Spark跟Flink会有如此大的差异,主要还是数据处理原理的差异。Spark的处理方式是将流式处理看作批处理的一种特殊形式,每收到一个间隔的数据才进行处理,在实时性上难以提升。
而Flink处理的模型是基于算子(operator)的连续数据流。Flink设计了一套高度灵活的窗口机制,从而对数据能够执行真正的流式计算,每有一条数据就能进行期望的算子操作。
上图中是一个Dataflow,数据src,数据sink,以及transformation算子均为节点,它们通过stream相连.数据从一个operator出发,经过stream被其他operator处理。同时,一个 stream 可以包含多个分区,一个算子可以被分成多个算子的子任务,每一个子任务是在不同的线程或者不同的机器节点中独立执行的。
Flink也提供了跟Spark一样的DataSet,DataStream API,下图是Flink的架构。
5. 计算样例
PageRank如何使用MapReduce计算
PageRank是搜索引擎中一个计算网页重要性的算法。可以使用Map Reduce实现简化的PageRank。在一个图中,网页之间通过超链接相连,因此可以用一个有向图/邻接矩阵表示。
PageRank的数据结构:
矩阵F(A,B),项F(x,y)表示从A到B的出链关系,1为有出链
算法:
初始化的时候每个网页是一个相同的均值,每次的网页i的值为刚好其他网页权重按比例分配给它的权重之和。
而对网页i来说,它分配给其他网页的权重是其已有权重的平均值。即W(u) = Σ D(v属于Su)/Out(v)。
Map的时候,针对网页u,计算网页u到其他网页的权重,每条记录<u,W(u)>变成列表<vi,W(u)/count(vi∈Su)> (i = 1,2,...)
Reduce的时候,由vi为key,加和所有的权重,即得到网页v的权重。(v跟u不是同一个网页)
一次计算得到的权重不是最终结果,PageRank要经过多次迭代计算得到最终各个网页的权重[7]。
这是一个马尔科夫随机过程。为了避免该过程不收敛,可以加入虚拟的节点,以及一个概率点击系数,表示从任意节点可能跳转到该节点的概率。
References
[1] Dean J , Ghemawat S . MapReduce: Simplified Data Processing on Large Clusters[C]// Proceedings of the 6th conference on Symposium on Opearting Systems Design & Implementation - Volume 6. USENIX Association, 2004.
[2] MapReduce-MPI Library, https://mapreduce.sandia.gov/
[3] Hadoop Map/Reduce教程, http://hadoop.apache.org/docs/r1.0.4/cn/mapred_tutorial.html
[4] MapReduce Shuffle 和 Spark Shuffle, https://cloud.tencent.com/developer/article/1651735, 2020
[5] Carbone P , Katsifodimos A , † Kth, et al. Apache flink : Stream and batch processing in a single engine. 2015.
[6] Data Streaming Fault Tolerance, https://ci.apache.org/projects/flink/flink-docs-release-1.3/internals/stream_checkpointing.html#checkpointing
[7] PageRank in Map Reduce, https://medium.com/swlh/pagerank-on-mapreduce-55bcb76d1c99