开启掘金成长之旅!这是我参与「掘金日新计划 · 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图示
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 | 对当前元素和上一次的结果进行聚合操作 |
| aggregations | sum(),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
union可以连接多个流,最后汇总成一个流,流里面的数据使用相同的计算规则
connect值可以连接2个流,最后汇总成一个流,但是流里面的两份数据相互还是独立的,每一份数据使
用一个计算规则
流切分
如果是只需要切分一次的话使用split或者side output都可以
如果想要切分多次,就不能使用split了,需要使用side output
分区算子
- 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接口
图解几个分区算子
具体的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 | 对当前元素和上一次的结果进行聚合操作 |
| aggregation | sum(),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更改