本文走心了,真的走心了--为了理解Flink,把Flink接口从上到下看了两遍,把介绍Flink的书也看了两遍,手敲了这篇文章和架构图,学习笔记奉献给大家,希望有帮助
随着互联网的不断发展,行业内对于数据的处理能力和计算的实时性要求都在不断增加,随之而来的是计算框架的升级。经过了十余年开源社区的不断演进,现在计算框架已经从第一代的雅虎开源的Hadoop体系进化到目前主流的Spark框架,这两套框架的计算主要是从强依赖硬盘存储能力的计算发展到了内存计算,大大增强了计算力。下一代计算引擎,也就是第三代计算引擎,将会从计算实时性的角度突破,也就是今天要讲到的Flink框架,本文将从简入深的介绍Flink框架的特点。
调度层面Flink支持本地运行以及分布式运行两种,分布式运行可以跑在目前主流的基于Yarn体系中,也可以跑在目前业内最主流的K8S中,Flink本质上还是专注于做计算框架的部分,应用于目前行业内主流的调度引擎上而确保一个分布式的计算能力。
在核心层主要做的是一件事是如何对流式任务进行编排,流式任务不同于离线任务的最大一个特点在于任务编排上面,比如我们有三个事件分别叫1、2、3。在离线任务中,事件运行是有先后顺序的,比如要先运行1再运行2接着运行3,在流式任务场景下这种事件只有逻辑上的先后关系,实际上是同时触发并执行的。
API层面稍后单独用一个模块讲解,在Library方面内置了图计算算法包Gelly和机器学习包FlinkML以及CEP。其中Gelly和FlinkML是基于DataSet API进行开发的,而DataSet API是一个离线批计算的接口,所以本质上Gelly和FlinkML并没有发挥Flink这个天然流式框架的优势。个人觉得,Flink作为Apache相对比较年轻的开源计算框架,对于一些组件化的支持还不完善,相比于Spark生态还有比较大的差距。后续如果能基于Flink Stream API开发出流式的上层应用,会成为Flink的一大亮点。
分布式架构层面Flink和Spark或者Hadoop区别不大,整体框架是:
由Flink的Client向Job Master提交任务,Job Master作为整个集群的管理节点。Task Manager是Slave的角色,负责底层的运算工作。Job Master控制整个计算任务的Checkpoint的进度。Task Manager间可以通过数据流的方式交换数据,同时Task Manager在任务的并行化计算方面比MapReduce做的更好,Flink Task Manager是采用多线程的机制进行并行化数据计算,而传统的MapReduce方法采用的是JVM进程的形式。
作为一个算法发烧友,我还是愿意花更多的篇幅来介绍如何基于Flink框架进行开发。首先看下开放编程接口的API上下游站位关系,
最上一层是Flink SQL,这一层其实是一个上级封装,了解机器学习或者其它更复杂编程模型的同学应该比较清楚,并不是所有的逻辑都能通过SQL来实现。不过目前绝大部分的数据实时处理逻辑通过SQL这一层就可以解决。
TableAPI这一级可以看出Flink要实现流批一体化的野心,Flink希望可以在流式DataStream API和离线DataSet API之上做一层封装,对用户暴露更多复杂计算的函数,同时基于这一层API实现的功能可以完成流批一体。这个设想是很好的,不过目前还有很多功能没办法在这一级实现,也导致了TableAPI的尴尬。
DataStream和DataSet仍是目前的主流编程接口,如果希望在Flink中实现诸如机器学习算法这样负责逻辑的函数,还是要依赖于这一层。至于Runtime内核开发,这个明显是留给非常资深的高级用户来使用,大部分的开发应该不会触碰。
重点讲下DataStream这个开发接口,首先Flink把流式编程模型切分为DataSource模块、Transformation模块和DataSink模块。DataSource和DataSink分别对应着数据的流入和流出。
在DataSource和Sink模块,Flink都原生支持了Apache体系内的很多产品的I/O,比如Kafka Connector和Elastic Search Connector等等。同时在数据接入导出方面还内置了很多数据源,
readFile接口可以实现读写一些CSV、TXT本地文件
Socket数据源可以接收来自其它服务的数据,这个接口非常好,可以让Flink和许多主流的Restful服务适配
在Transformation模块,DataStream提供了诸如map、FlatMap、Filter、Reduce、KeyBy这些函数,我个人感觉Flink的这些接口使用起来还是比较方便的,比如要把下面这个二元组类型的数据都+1,
(a,1),(b,2)->(a,2),(b,3)
只需要使用map函数作如下处理:
val dataStream=env.fromElements((a,1),(b,2))
val mapStream:DataStream[(String,Int)]= dataStream.map(t=>(t._1,t._2+1))
流式开发和离线开发的最大区别在于对于数据时间的理解上,离线开发针对的都是有边界数据,有边界的意思是在开发过程中会用到的数据是有限的。而流式应用,因为数据是实时流入的,所以对应的数据是无边界的。以机器学习算法为例,经常需要缓冲一部分数据求区间内的最大值和最小值,那么在无边界数据条件下如何处理呢?这里就应用到了watermark功能。
首先介绍下几个时间的概念:
Event Time是数据在业务方的真实发生时间,比如某个手机在2019-03-05下午2点被购买,这个时间就是Event Time
Ingestion Time指的是数据进入Flink系统的时间,理论上会比Event Time晚一点
Processing Time:数据在当前系统被处理的时间,也就是Flink worker机器的时间,这个是Flink系统的默认时间
所以从概念层面理解,用Flink去处理业务最合适的时间是Event Time,而系统默认使用Processing Time是一种简化方法,因为流式数据在录入Flink系统的过程中会出现时间乱序。
接着介绍下Window和Watermark的概念,当系统按照Processing Time去流式处理数据的时候,假设某个流式算法需要缓冲5分钟的数据算一次Loss,这个5分钟就是一个Window窗口。但是当5分钟已经结束了,还是没有数据流入计算引擎,这个时候怎么办?接着等还是执行下一个操作。这就用到了Watermark,当等待时间超过Watermark的设定时间,系统就会自动触发计算,无论数据是否满足Window的要求。