在Flink的世界观中,任何类型的数据都可以形成一种事件流。例如信用卡交易、传感器测量、服务器日志、网站或移动应用程序上的用户交互记录等,所有这些数据都可以形成一种流,因为数据都是一条一条产生的。
根据数据流是否有时间边界,可将数据流分为有界流和无界流。有界流产生的数据集称为有界数据集,无界流产生的数据集称为无界数据集,如图1-10所示。
图1-10 有界流数据集和无界流数据集
1.有界数据集
定义一个数据流的开始,也定义数据流的结束,就会产生有界数据集。有界数据集的特点是数据是静止不动的,或者说当处理此类数据时不考虑数据的追加操作。例如,读取MySQL数据库、文本文件、HDFS系统等存储介质中的数据进行计算分析。
有界数据集具有时间边界,时间范围可能是一分钟,也可能是一天内的交易数据。可以在读取所有数据后再进行计算,对有界数据集的处理通常称为批处理(Batch Processing)。例如,将数据从RDBMS或文件系统中读取出来,然后使用分布式系统进行处理,最后将处理结果写入指定的存储介质(HDFS等)中的过程就称为批处理。目前业界流行的批处理框架有Apache Hadoop、Apache Spark等。
批处理的数据查询方式如图1-11所示。
图1-11 批处理的数据查询方式
2.无界数据集
定义一个数据流的开始,但没有定义数据流的结束,就会产生无界数据集。无界数据集会无休止地产生新数据,是没有边界的。例如,实时读取Kafka中的消息数据进行计算、实时日志监控等。
对无界数据集必须持续处理,即数据被读取后需要立刻处理,不能等到所有数据都到达再处理,因为数据输入是无限的,在任何时候输入都不会完成。处理无界数据集通常要求以特定顺序读取事件(例如事件发生的顺序),以便能够推断结果的完整性。对无界数据集的处理被称为流处理。
有界数据集与无界数据集其实是一个相对的概念,如果每间隔一分钟、一小时、一天对数据进行一次计算,那么认为这一段时间的数据相对是有界的。有界的流数据又可以一条一条地按照顺序发送给计算引擎进行处理,在这种情况下可以认为数据是相对无界的。因此,有界数据集与无界数据集可以相互转换。Flink正是使用这种方式将有界数据集与无界数据集进行统一处理,从而将批处理和流处理统一在一套流式引擎中,能够同时实现批处理与流处理任务。
目前业界的Apache Storm、Apache Spark、Apache Flink等流处理框架都能不同程度地支持处理流式数据,而能够同时实现批处理与流处理的典型代表为Apache Spark和Apache Flink。其中Apache Spark的流处理是一个微批场景,它会在指定的时间间隔发起一次计算,而不是每条数据都会触发计算,相当于把无界数据集按照批次切分成微批(有界数据集)进行计算。Apache Flink使用流计算模式能够很好地进行流式计算和批量计算,是大数据处理领域冉冉升起的一颗新星。
流处理数据查询方式如图1-12所示。
图1-12 流处理数据查询方式
Flink提供了丰富的数据处理接口,并将接口抽象成4层,由下向上分别为Stateful Stream Processing API、DataStream/DataSet API、Table API以及SQL API,开发者可以根据具体需求选择任意一层接口进行应用开发,如图1-13所示。
图1-13 Flink接口分层
1.Stateful Stream Processing API
Flink中处理有状态流最底层的接口,它通过Process Function(低阶API,Flink提供的最具表达力的底层接口)嵌入DataStream API中,允许用户自由地处理一个或多个流中的事件,并使用一致的容错状态。此外,用户可以注册事件时间和处理时间回调,从而允许程序实现复杂的计算。用户可以通过这个API接口操作状态、时间等底层数据。
使用Stateful Stream Process API接口可以实现非常复杂的流式计算逻辑,开发灵活性非常强,但是用户使用成本也相对较高。
2.DataStream/DataSet API
实际上,大多数应用程序不需要上述低级抽象,而是针对核心API进行编程的,例如DataStream API和DataSet API。DataStream API用于处理无界数据集,即流处理;DataSet API用于处理有界数据集,即批处理。这两种API都提供了用于数据处理的通用操作,例如各种形式的转换、连接、聚合等。
低级Process Function与DataStream API集成在一起,从而使得仅对某些操作进行低级抽象成为可能。DataSet API在有限的数据集上提供了其他原语,例如循环/迭代。
3.Table API
Table API作为批处理和流处理统一的关系型API,即查询在无界实时流或有界批数据集上以相同的语义执行,并产生相同的结果。Flink中的Table API通常用于简化数据分析、数据流水线和ETL应用程序的定义。
Table API构建在DataStream/DataSet API之上,提供了大量编程接口,例如GroupByKey、Join等操作,是批处理和流处理统一的关系型API,使用起来更加简洁。使用Table API允许在表与DataStream/DataSet数据集之间无缝切换,并且可以将Table API与DataStream/DataSet API混合使用。
Table API的原理是将内存中的DataStream/DataSet数据集在原有的基础上增加Schema信息,将数据类型统一抽象成表结构,然后通过Table API提供的接口处理对应的数据集,从而简化数据分析。
此外,Table API程序还会通过优化规则在数据处理过程中对处理逻辑进行大量优化。
4.SQL API
Flink提供的最高级别的抽象是SQL API。这种抽象在语义和表达方式上均类似于Table API,但是将程序表示为SQL查询表达式。SQL抽象与Table API紧密交互,并且可以对Table API中定义的表执行SQL查询。
此外,SQL语言具有比较低的学习成本,能够让数据分析人员和开发人员快速上手。
在Hadoop中,实现一个MapReduce应用程序需要编写Map和Reduce两部分;在Storm中,实现一个Topology需要编写Spout和Bolt两部分;同样,实现一个Flink应用程序也需要同样的逻辑。
一个Flink应用程序由3部分构成,或者说将Flink的操作算子可以分成3部分,分别为Source、Transformation和Sink,如图1-14所示。
图1-14 Flink程序构成
· Source:数据源部分。负责读取指定存储介质中的数据,转为分布式数据流或数据集,例如readTextFile()、socketTextStream()等算子。Flink在流处理和批处理上的Source主要有4种:基于本地集合、基于文件、基于网络套接字Socket和自定义Source。
· Transformation:数据转换部分。负责对一个或多个数据流或数据集进行各种转换操作,并产生一个或多个输出数据流或数据集,例如map()、flatMap()、keyBy()等算子。
· Sink:数据输出部分。负责将转换后的结果数据发送到HDFS、文本文件、MySQL、Elasticsearch等目的地,例如writeAsText()算子。图1-15描述了一个Flink应用程序的3部分。
图1-15 Flink程序的3部分
Flink应用程序可以消费来自消息队列或分布式日志这类流式数据源(例如Apache Kafka或Kinesis)的实时数据,也可以从各种数据源中消费有界的历史数据。同样,Flink应用程序生成的结果流也可以发送到各种数据存储系统中(例如数据库、对象存储等),Flink数据的读取与写入如图1-16所示。
图1-16 Flink数据源的读取与写入