上一篇聊到flink的历史,请看上篇 flink两三事 ----(1)历史。
可以说基本上是起了个大早,赶了个晚集,但是flink能做今天这种热度,没有被spark干死也是不容易。原来大家都在想办法突破MapReduce太慢的问题时候,除了spark,比如还有Tez等框架基本上销声匿迹了。14年flink在apache孵化能活下来并成为顶级项目的关键还是flink的有些自己的创新技术。
Spark的核心概念是RDD,抽象概念是弹性分布式数据集(RDD),它是一个元素集合,划分到集群的各个节点上,可以被并行操作。(不是指最新版本的structure streaming,讲的是历史。)
图1 RDD概念示意
spark相比M/R计算模型差不多,关键是把数据放到内存中迭代。天生的batch模型,对于流处理,最早思路是通过减少batch粒度,也就是mini batch来支持,但同时也限制spark streaming能支撑的时延只能到到秒级,而flink通过增量迭代的能力,支持一次一条数据的处理,处理时延迟可以做到毫秒。最新的structure streaming号称也可以支持毫秒级处理了。下面三个图,对比下几种计算模型的区别:
图2 M/R计算模型
图3 Spark计算模型
图4 Flink计算模型
除了steaming模型有很大区别之外,flink当时比较能拿得出手的就是内存管理了。下面简单讲讲flink的内存管理,基本思路就是放弃了jvm的内存管理,自己单独干。
flink/spark都用JVM。基于 JVM 的数据分析引擎都需要面对将大量数据存到内存中,这就不得不面对 JVM 存在的几个问题:
Flink 并不是将大量对象存在堆上,而是将对象都序列化到一个预分配的内存块上,这个内存块叫做 MemorySegment,它代表了一段固定长度的内存(默认大小为 32KB),也是 Flink 中最小的内存分配单元,并且提供了非常高效的读写方法。你可以把 MemorySegment 想象成是为 Flink 定制的java.nio.ByteBuffer。它的底层可以是一个普通的 Java 字节数组(byte[]),也可以是一个申请在堆外的 ByteBuffer。每条记录都会以序列化的形式存储在一个或多个MemorySegment中。
Flink 中的 Worker 名叫 TaskManager,是用来运行用户代码的 JVM 进程。TaskManager 的堆内存主要被分成了三个部分:
图5 flink内存分配
Flink 采用类似 DBMS 的 sort 和 join 算法,直接操作二进制数据,从而使序列化/反序列化带来的开销达到最小。所以 Flink 的内部实现更像 C/C++ 而非 Java。如果需要处理的数据超出了内存限制,则会将部分数据存储到硬盘上。如果要操作多块MemorySegment就像操作一块大的连续内存一样,Flink会使用逻辑视图(AbstractPagedInputView)来方便操作。
从上面我们能够得出 Flink 积极的内存管理以及直接操作二进制数据有以下几点好处:
flink的这些技术,当年给spark造成了一定的压力,好在spark反应快,立刻放了一个Tungsten的大招来优化性能。
Tungsten项目将是Spark自诞生以来内核级别的最大改动,以大幅度提升Spark应用程序的内存和CPU利用率为目标,旨在最大程度上压榨新时代硬件性能。Project Tungsten包括了3个方面的努力:
第一条内存管理明显是有针对性的意味,不过tungsten的code generation也很有意思,根据databricks自己的总结,对于TPC-DS的查询,带来接近一个量级的性能的提升。
https://databricks.com/blog/2015/04/28/project-tungsten-bringing-spark-closer-to-bare-metal.html
关于flink内存管理就简单聊到这,同学会问,怎么还没有到flink watermark?不急,慢慢来。:)