Flink核心API

593 阅读10分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天 juejin.cn/post/716729…

四种Api模式

  • 低级API(Stateful Stream Processing):提供了对时间和状态的细粒度控制,简洁性和易用性较差, 主要应用在一些复杂事件处理逻辑上。
  • 核心API(DataStream/DataSet API):主要提供了针对流数据和批数据的处理,是对低级API进行了一 些封装,提供了filter、sum、max、min等高级函数,简单易用。
  • Table API:一般与DataSet或者DataStream紧密关联,可以通过一个DataSet或DataStream创建出 一个Table,然后再使用类似于filter, join,或者 select这种操作。最后还可以将一个Table对象转成 DataSet或DataStream。
  • SQL:Flink的SQL底层是基于Apache Calcite,Apache Calcite实现了标准的SQL,使用起来比其他 API更加灵活,因为可以直接使用SQL语句。Table API和SQL可以很容易地结合在一块使用,因为它 们都返回Table对象。

Api图示

image-20220714091249456

DataStream Api

DataSource

DataSource是程序输入的数据源,Flink本身内置了非常多的数据源,也支持自定义数据源,目前提供的内置数据源,在企业中开发绝对是足够的

  • 基于socket

  • 基于Collection

  • 第三方数据源(不局限于以下三个)

    • Kafka
    • RabbitMQ
    • NiFi

针对source的这些Connector中,工作中最常用的就是kafka

恢复机制

程序错误,机器故障、网络故障时,Flink有容错机制能够恢复并继续运行。

针对Flink提供的接口,如果开启了checkpoint,Flink可以提供容错性保证

  • Socket:at most once
  • Collection:exactly once
  • Kafka0.10以上:exactly once
基于collection的source
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecuti
 //使用collection集合生成DataStream
 DataStreamSource<Integer> text = env.fromCollection(Arrays.asList(1, 2
 text.print().setParallelism(1);
 env.execute("StreamCollectionSourceJava");  

Transformation

transformation是Flink程序的计算算子,负责对数据进行处理,Flink提供了大量的算子,这点上和Spark类似

算子说明
map输入一个元素进行处理,返回一个元素
flatMap输入一个元素进行处理,返回多个元素
filter对数据进行过滤,符合条件留下
keyBy根据key分组,相同key数据放入同一个分区
reduce对当前元素和上一次的结果进行聚合操作
aggregationssum(),min(),max()等
union合并多个流,多个流数据类型必须一致
connect只能连接两个流,两个流的数据类型可以不同
split把一个流分为多个流
shuffle对数据进行随机分区
rebalance对数据进行再平衡,重新分区,消除数据倾斜
rescale重分区
partationCustom自定义分区

分析几个重点的算子:

  • union

    • 多个流合并,但是数据类型必须一致
    • 处理规则也必须一致
  • connect

    • 两个流被connect后,指示放入同一个流,内部依然保持各自的数据和形式不改变,相互独立。
    • connect方法会返回connectedStream,在此流中需要适用CoMap,CoFlatMap,类似正常流的map和flatMap
  • split

    • 把一个流切分成多个流
    • 切分后的流不能再分
    • 场景:将一份数据切分成多份,便于对每一份数据进行不同的逻辑处理
    • 该方法已经过时,官方不推荐使用,官方推进使用side output方法
  • side output

    • 进行切分后的流还可以二次切分
union与connect

image-20220719221653442

union可以连接多个流,最后汇总成一个流,流里面的数据使用相同的计算规则

connect值可以连接2个流,最后汇总成一个流,但是流里面的两份数据相互还是独立的,每一份数据使

用一个计算规则

流切分

如果是只需要切分一次的话使用split或者side output都可以

如果想要切分多次,就不能使用split了,需要使用side output

image-20220719221824177

分区算子
  • Random:随机分区
  • rebalance:对数据集进行再平衡,重新分区,消除数据倾斜
  • rescale:重分区
  • custom partition:自定义分区

random:随机分区,它表示将上游数据随机发送到下游算子实例的每个分区,代码层面是调用shuffle方法,shuffle底层对于的是shufflePartitioner类,这个类有selectChannel函数,这个函数会进行计算数据会发送到哪个分区,里面调用的是random.nextInt方法,所以该算子是随机分区的。

public int selectChannel(SerializationDelegate<StreamRecord<T>> record) {
 return random.nextInt(numberOfChannels);
}

rebalance:重新平衡分区(循环分区),它会对数据进行再平衡,未每个分区创建相同的负载,实际上就是通过循环的方式给下游算子的每个分区分配数据,在代码层面调用的是rebalance方法,查看源码,该方法调用的是RebalancePartitioner这个类,里面有一个setup函数和selectChannel函数,setup会根据分区数初始化一个随机值nextChannelToSentTo,然后selectChannel函数会使用该随机值+1和分区数取模,把计算的值赋给该变量,后面以此类推,实现向多个下游算子实例的多个分区循环发送数据,这样每个分区获取的数据基本一致。

public void setup(int numberOfChannels) {
 super.setup(numberOfChannels);
 nextChannelToSendTo = ThreadLocalRandom.current().nextInt(numberOfChannels)
}
public int selectChannel(SerializationDelegate<StreamRecord<T>> record) {
 nextChannelToSendTo = (nextChannelToSendTo + 1) % numberOfChannels;
 return nextChannelToSendTo;
}

rescale:重分区

查看源码,rescale底层对应的是RescalePartitioner这个类里面有一个selectChannel函数,这里面的numberOfChannels是分区数量,其实也可以认为是我们所说的算子的并行度,因为一个分区是由一个线程负责处理的,它们两个是一一对应的。

public int selectChannel(SerializationDelegate<StreamRecord<T>> record) {
 if (++nextChannelToSendTo >= numberOfChannels) {
 nextChannelToSendTo = 0;
 }
 return nextChannelToSendTo;
}
* The subset of downstream operations to which the upstream operation sends
* elements depends on the degree of parallelism of both the upstream and downs
* For example, if the upstream operation has parallelism 2 and the downstream 
* has parallelism 4, then one upstream operation would distribute elements to 
* downstream operations while the other upstream operation would distribute to
* two downstream operations. If, on the other hand, the downstream operation h
* 2 while the upstream operation has parallelism 4 then two upstream operation
* distribute to one downstream operation while the other two upstream operatio
* distribute to the other downstream operations.

如果上游操作有2个并发,而下游操作有4个并发,那么上游的1个并发结果循环分配给下游的2个并发操 作,上游的另外1个并发结果循环分配给下游的另外2个并发操作。另一种情况,如果上游有4个并发操作而下游有2个并发操作,那么上游的其中2个并发操作的结果会分配给下游的一个并发操作,而上游的另外2个并发操作的结果则分配给下游的另外1个并发操作。

注意:rescale与rebalance的区别是rebalance会产生全量重分区,而rescale不会。

broadcast:广播分区,将上游算子实例中的数据输出到下游算子实例的每个分区中,适合用于大数据集

查看源码,broadcast底层对应的是BroadcastPartitioner这个类.看这个类中selectChannel函数代码的注释,提示广播分区不支持选择Channel,因为会输出数据到下游的每个Channel中,就是发送到下游算子实例的每个分区中

custom partition:自定义分区,可以按照自定义规则实现自定义分区需要实现Partitioner接口

图解几个分区算子

image-20220719223242505

image-20220719223249632

image-20220719223255868

具体的Api参考官网案例

DataSink

DataSink是 输出组件,负责把计算好的数据输出到其它存储介质中。

一般会输出到消息队列或者数据库,print方法一般适用于测试

Flink内置Connectors

  • Kafka
  • Cassandra
  • Elasticsearch
  • Hdfs
  • RabbitMQ
  • NiFi
  • JDBC
  • redis

容错保障

Redis:at least once

Kafka:at least once/exactly once

DataSet Api

DataSet也可分为三块来进行分析

  • DataSource
  • Transformation
  • Sink

DataSource

针对DataSet批处理而言,应用最多的是读取HDFS数据。

  • 基于集合

    • fromCollection
  • 基于文件

    • readTextFile(path),读取hdfs中的数据文件

Transformation

常见算子如下:

算子说明
map输入一个元素进行处理,返回一个元素
mapPartation类似map,一次处理一个分区的数据
flatMap输入一个元素进行处理,可以返回多个元素
filter对数据进行过滤,符合条件的数据会被留下
reduce对当前元素和上一次的结果进行聚合操作
aggregationsum(),min(),max()等

用法与DataStream相关的算子类似

DataSet一些常用的算子:

算子解释
distinct返回数据集中去重之后的元素
join内连接
outerjoin外连接
cross获取两个数据集的笛卡尔积
union返回多个数据集的总和,数据类型需要一致
first-n获取集合中的前N个元素
  • distinct:去重算子

  • join:内连接,连接两份数据,类似sql join

  • outer join:类似SQL左右连接

  • cross:获取笛卡尔积

  • union:返回两个数据集的总和,数据类型需要一致

  • first-n:获取集合中前N个元素

    • 这个比较常用,实际工作中我们经常会计算TopN的产品信息,或者商户信息等

DataSink

Flink针对DataSet提供了一些已经实现好的数据目的地

其中最常见的是向HDFS中写入数据:

  • writeAsText():将元素以字符串形式逐行写入,这些字符串通过调用每个元素的toString()方法来获取

  • writeAsCsv():将元组以逗号分隔写入文件中,行及字段之间的分隔是可配置的,每个字段的值来自对象

    的toString()方法

  • print:打印每个元素的toString()方法的值,测试时用

Table Api & Flink SQL

Table API和SQL的由来:

Flink针对标准的流处理和批处理提供了两种关系型API,Table API和SQL。

  • Table API允许用户以一种很直观的方式进行select 、fifilter和join操作。
  • Flink SQL基于 Apache Calcite实现标准SQL。
  • Flink Table API、SQL和Flink的DataStream API、DataSet API是紧密联系在一起的。
  • Table API和SQL是一种关系型 API,用户可以像操作 Mysql 数据库表一样的操作数据,而不需要写代码,更不需要手工的对代码进行调优

依赖信息:

flink-table-api-java-bridge_2.12
版本:1.11.0
flink-table-planner-blink_2.12
版本:1.11.0

Table API和SQL通过join API集成在一起,这个join API的核心概念是Table,Table可以作为查询的输入和输出。

使用案例

// 获取TableEnvironment
 EnvironmentSettings sSettings = EnvironmentSettings.newInstance().use
 TableEnvironment sTableEnv = TableEnvironment.create(sSettings);
// 创建输入表
 sTableEnv.executeSql("" +
 "create table myTable(\n" +
 "id int,\n" +
 "name string\n" +
 ") with (\n" +
 "'connector.type' = 'filesystem',\n" +
 "'connector.path' = 'D:\data\source',\n" +
 "'format.type' = 'csv'\n" +
 ")");
// 书写sql语句
Table result = sTableEnv.sqlQuery("select id,name from myTable")
// 执行并打印
result.execute().print()

DataStream、DataSet与Table互转

Table API和SQL可以很容易的和DataStream和DataSet程序集成到一块。

通过TableEnvironment ,可以把 DataStream 或 者 DataSet注册为Table。

这样就可以使用Table API 和 SQL查询了。

1.DataStream创建表

  • 创建view视图
  • 创建table对象
 //获取StreamTableEnvironment
 StreamExecutionEnvironment ssEnv = StreamExecutionEnvironment.getExec
 EnvironmentSettings ssSettings = EnvironmentSettings.newInstance().us
 StreamTableEnvironment ssTableEnv = StreamTableEnvironment.create(ssE
 //获取DataStream
 ArrayList<Tuple2<Integer, String>> data = new ArrayList<>();
 data.add(new Tuple2<Integer,String>(1,"jack"));
 data.add(new Tuple2<Integer,String>(2,"tom"));
 data.add(new Tuple2<Integer,String>(3,"mick"));
 DataStreamSource<Tuple2<Integer, String>> stream = ssEnv.fromCollecti
 //第一种:将DataStream转换为view视图
 ssTableEnv.createTemporaryView("myTable",stream,$("id"),$("name"));
 ssTableEnv.sqlQuery("select * from myTable where id > 1").execute().print
 //第二种:将DataStream转换为table对象
 Table table = ssTableEnv.fromDataStream(stream, $("id"), $("name"));
 table.select($("id"), $("name"))
 .filter($("id").isGreater(1))
 .execute()
 .print();

2.DataSet创建表

新的Blink引擎不支持这种操作,老版本支持,已经不常用了。

将 Table 转换为 DataStream 或者 DataSet 时,你需要指定生成的 DataStream 或者 DataSet 的数据类型

通常最方便的选择是转换成 Row

  • Row: 通过角标映射字段,支持任意数量的字段,支持 null 值,无类型安全(type-safe)检查
  • POJO: Java中的实体类,这个实体类中的字段名称需要和Table中的字段名称保持一致,支持任意数量的字段,支持null值,有类型安全检查。
  • Case Class: 通过角标映射字段,不支持null值,有类型安全检查。
  • Tuple: 通过角标映射字段,Scala中限制22个字段,Java中限制25个字段,不支持null值,有类型安全检查。
  • Atomic Type: Table 必须有一个字段,不支持 null 值,有类型安全检查。

3.将表转换成 DataStream

两种模式

  • Append Mode:仅附加,不更新
  • Retract Mode:可以始终使用此模式,它使用一个Boolean标识来编码INSERT和DELETE更改