Flink
版本问题,flink使用Scala时,flink1.12兼容scala2.11,flink1.12不兼容scala2.12
一、简介
flink是对有界流和无界数据流进行有状态的计算(所谓状态,指的是之前的数据sparkStreaming中只有updatestateByKey有状态,但是flink中任何的算子都可以有状态,可以自己定义)
1.1sparkStreaming与flink的区别
(1)事件驱动型
sparkStreaming是时间驱动,一个批次中假如没有数据依然会从kafka拉取数据,走计算逻辑,只不过没有结果。
flink是事件驱动的(事件即指数据)来一条计算一条,不会浪费资源且实时性更好。(是和spark批处理的本质区别)
(2)流处理与批处理
spark处理数据都是按照批次处理的,如果每隔批次的时间较长,可认为是批处理,若每个批次相隔的时间很短,是微批处理来模拟的流处理。
flink的流与批是根据数据有无结束限定的。如果flink读取文本文件有开头有结尾是批处理(批处理时也是事件驱动型,来一条数据处理一条),如果从kafka读取数据,有开头没有结尾是流处理(流处理是事件驱动型)
flink使用时间窗口就能实现spark的微批处理。
(3)关于并行度的问题
spark在代码中可以更改并行度(即进行分区数的设置,但是只有一些特定的算子能进行指定,像map,flapmap是不能的)
flink中任何算子都能设置并行度(map.setParallelism(3))注意keyby没有,因为keyby是hash操作将数据送入后面的算子中。
/**
* 并行度的设置
* 1、在代码中算子单独设置 sum:2 flatmap:3
* 2、代码中env 设置:4
* 3、提交任务参数:6
* 4、flink配置文件:1
* 如果总共6个slot,提交的时候设置并行度是9,任务时调动不起来的,且默认的一个任务所需要的slot个数就是任务中的最大并行度
*/
flink与spark任务划分的方式不同,spark根据产生shuffer操作会产生新的stage,而flink是只要算子之间的并行度不同就会产生新的stage
(4)
以上(3)结论的图[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KJpOUwix-1641372478560)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211210220337526.png)]
代码如下:读端口并行度为1,flatmap:3,keyby,sum:2,print:4是env设置的全局并行度
public class Flink03_WordCount_Unbounded {
public static void main(String[] args) throws Exception {
//创建执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(4);
//读端口数据
DataStreamSource<String> sourceDS = env.socketTextStream("linux2", 9999);
//读取的数据扁平化
SingleOutputStreamOperator<Tuple2<String, Integer>> tupleDS = sourceDS.flatMap(new Flink02_WordCount_Bounded.MyFlapMapFunc()).setParallelism(3);
KeyedStream<Tuple2<String, Integer>, String> keyDS = tupleDS.keyBy(new KeySelector<Tuple2<String, Integer>, String>() {
public String getKey(Tuple2<String, Integer> value) throws Exception {
return value.f0;
}
});
SingleOutputStreamOperator<Tuple2<String, Integer>> result = keyDS.sum(1).setParallelism(2);
result.print();
//启动任务
env.execute();
}
}
1.2分层API
2020年12月8日发布的最新版本****1.12.0****, 已经完成实现了真正的****流批一体****
从简到易:
sql:SQL查询可以直接在Table API定义的表上执行。
table api:表可以动态变化,API提供可比较的操作,例如select、project、join、group-by、aggregate等,用户可自定义函数(UDF)进行扩展,Table API程序在执行之前会经过内置优化器进行优化。
datastream/dataSet API:DataStream/DataSet 与表可以无缝切换。
process function:最底层的API,算子无法实现的需要使用最底层process编写程序。
sql也能应用与流处理
1.3flink 流批处理不同
flink不向spark,没有reducrByKey,分组和聚合是分开的,先groupby,再聚合。?
flink流(StreamExecutionEnvironment)处理与批(ExecutionEnvironment)处理所需的执行环境不同。
flink批处理有groupby,流处理只有keyBy。
flink批处理也是来一条执行一条,只不过最后输出一次结果,flink流处理来一条执行一条输出一次结果。
1.4 flink 部署模式
(1)local模式
(2)standaloan模式:提交任务前要先启动flink集群。是flink自己提供计算资源,不依赖于其他框架,可在没有hadoop节点中单独安装flink集群
flink-conf.xml配置文件:
1、可配置taskmanager总共内存,单节点可用的插槽,默认并行度
2、jobmanager的高可用,包含zk的配置
3、状态后端:指的是存储状态的地址,通常有3种,jobmanager(是内存级别的),hdfs,rocksdb(是指一种kv的存储格式,且支持增量的操作,是最常用的)
(3)yarn模式:用yarn做资源的分配,基于hadoop集群之上。
flink基于yarn墨水有3种提交作业的方式
① session-cluster:启动flink集群后向yarn申请得到资源,这个资源是永远不变的,当提交多个任务时,会共用申请到的资源,如果前面的job用尽了申请到的slot且任务不结束,后面的job则申请不到资源无法执行。此种模式适合job比较短的需要频繁提交的批处理任务。
备注:session和per-job的区别:session是一次性申请flink集群资源,所有的任务用这一个资源,job运行完后下一个任务只需要去获得slot即可,而per-job需要针对每隔任务都去yarn申请资源,所以per-job模式提交会比session提交反应慢。(数据库连接池与普通获取数据库连接方式的区别吧)
② application mode:client
备注:application和per-job区别:用户自主程序运行的位置。application相当于spark的client,用户住程序运行在本地的jobmanager
③ per-job-cluster:cluster,用的最多的是per-job-cluster
1.5 flink运行架构
1、jobManager:作用
①. 响应客户端请求
②.向resureManager申请资源
③.将job生成作业图-数据流图-执行图(切分任务)
④.分发执行图到taskManager上,负责任务的协调
2、resourceManager:
①.此处是flink单独的resourceManger,不是yarn的
②.flink的resourceManager和jobManager运行在同一个节点上
3、dispatcher:分发器:
只有能在网页上提交任务的模式才有触发器
4、taskManager:tm上有slot插槽,slot是对tm的内存资源进行隔离,并不会隔离cpu
5、并行度
一个流程序的并行度是代码算子中最大的并行度,而每个算子可以有不同的并行度,每个算子的并行度就叫subtask
stream在算子之间传输数据的形式有两种:
① one-to-one:source--map(flatMap)算子之间没有重分区是one-to-one的对应关系,类似spark的窄依赖。
②Redistributing:flatMap-keyBy(or window)有重分区,每一个算子的子任务依据所选择的transformation发送数据到不同的目标任务。例如,keyBy()基于hashCode重分区、broadcast和rebalance会随机重新分区,这些算子都会引起redistribute过程,类似spark的宽依赖。
6、Operator Chains(任务链)
类似spark中的stage
但是flink的任务链划分和spark中stage划分是不一样的。
想要算子划分到同一个任务链中必须满足以下3个条件:
第一:算子没有产生重分区
第二:算子之间的并行度要相同
第三:算子属于同一个共享组
这样划分的目的是为了减少网络IO的。
7、共享组
//全局并行度为 1
// souorce --flatMap.slotSharingGroup("group1")--map --keyBy--sum.slotSharingGroup("group2")--print
map是在default还是在group1组?
任务链划分如下:
souorce / flatMap及map在group1组 /keyBy及sum及print在default组
思考:
slot的个数通常是job中最大的并行度,这里并行度是2,却使用了5个slot,原因是什么?
是不同算子设置了不同的共享组。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yqSJz6Bj-1641372478561)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211214213600616.png)]
例1:
//全局并行度为 1
// souorce --flatMap.slotSharingGroup("group1")--map.setParallelism(3) --keyBy--sum.slotSharingGroup("group2")--print
会切分4个任务链,使用5个slot(可认为共享组有就近原则?)
所以之前说的slot的数量是算子中的最大并行度是同一个共享组的时候,完善应是:
任务所需的slot数量是每个共享组中最大的并行度之和。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GiAr61RG-1641372478561)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211214223635769.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pjDK6lnH-1641372478562)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211214223957658.png)]
例2:
代码中设置:
//设置全局禁用任务链
env.disableOperatorChaining();
keyBy和sum是一个操作在一起,其余的任务链都分开了。需要3个slot
此外可针对算子单独设置:
flatMap.startNewChain()是和前边的划分开,出来一个新任务链。
flatMap.disableChaining()是前后都划分开,自己单独一个任务链。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a50HRZFF-1641372478562)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211214224757409.png)]
flatmap开启新链条
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hh5NT52K-1641372478563)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211214225535380.png)]
flatmap.disableChaining()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2kVd3ADE-1641372478563)(C:\Users\jing\AppData\Roaming\Typora\typora-user-images\image-20211214225713026.png)]
总结:
任务链及共享组总结:
1、默认情况下
所有算子都在一个共享组内,开启了任务链和并
任务需要的slot数量:最大并行度
任务链的个数:要看有无宽依赖,算子的并行度是否相同
2、 给算子独立设置了共享组
开启了任务链合并(就近)
所需的slot数量:每个共享组中最大并行度之和
任务链的个数:宽依赖,并行度,共享组
3、全局禁用任务链
任务就是在一个共享组内
任务所需的slot数量:最大并行度
任务链的个数:算子的个数
4、给某个算子开启新的任务链
同一个共享组
任务所需的slot数量:最大并行度
任务链的个数:宽依赖,并行度,开启的新任务链的算子
5、给某个算子禁用任务链
同一个共享组
任务所需的slot数量:最大并行度
任务链的个数:宽依赖,并行度,禁用任务链的算子
总之:
slot:只跟共享组和最大并行度有关
任务链的个数:跟宽依赖,并行度,并行度,时否开启和禁用任务链
subtask
默认共享组:
同一个算子的subtask肯定是在不同的slot执行的,因为flink是从任务的空闲时间和资源共享的角度考虑的
例如一个任务两个并行度
slot1 slot2
source flatmap
flapmap map
map keyBy
keyBy
slot1中的算子是从上到下执行的,前边的执行完后空出资源,后边的算子可继续使用,且在同一个节点上
减少了网络传输且资源进行了充分利用,如果两个flatmap放一起,忙的时候都忙闲的时候都闲会争抢资源。
1.6 flink 流处理核心编程
1.6.1 environment
// 批处理环境
ExecutionEnvironment benv = ExecutionEnvironment.getExecutionEnvironment();
// 流式数据处理环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
1.6.2 source
@Test
//从集合中读数据
public void SourceCollection() throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
//准备集合
List<WaterSensor> waterSensors = Arrays.asList(new WaterSensor("ws_001", 1577844001L, 45),
new WaterSensor("ws_002", 1577844015L, 43),
new WaterSensor("ws_003", 1577844020L, 42));
//从集合读数据
DataStreamSource<WaterSensor> waterSensorDataStreamSource = env.fromCollection(waterSensors);
waterSensorDataStreamSource.print();
env.execute();
}
//从文件读数据
@Test
public void SourceFile() throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<String> wordcount = env.readTextFile("F:\\bigdata\\flink\\flink_learn_code\\src\\main\\resources\\input\\wordcount");
wordcount.print();
env.execute();
}
//从端口读数据
@Test
public void SourceScoket() throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<String> wordcount = env.socketTextStream("linux1",9999);
wordcount.print();
env.execute();
}
//从kafka读数据
@Test
public void SourceKafka() throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
Properties properties = new Properties();
properties.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"linux1:9092,linux2:9092,linux3:9092");
properties.setProperty(ConsumerConfig.GROUP_ID_CONFIG,"flinkkafka");
DataStreamSource kafkaSD = env.addSource(new FlinkKafkaConsumer("sink_table", new SimpleStringSchema(), properties));
kafkaSD.print();
env.execute();
}
自定义source
package operateFunction;
import bean.WaterSensor;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.source.SourceFunction;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class MySurce {
public static void main(String[] args) throws Exception {
// 1. 创建执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env
.addSource(new MySource("linux1", 9999))
.print();
env.execute();
}
public static class MySource implements SourceFunction<WaterSensor> {
private String host;
private int port;
private volatile boolean isRunning = true;
private Socket socket;
public MySource(String host, int port) {
this.host = host;
this.port = port;
}
@Override
public void run(SourceContext<WaterSensor> ctx) throws Exception {
// 实现一个从socket读取数据的source
socket = new Socket(host, port);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
String line = reader.readLine();
while (isRunning && line != null) {
String[] split = line.split(" ");
ctx.collect(new WaterSensor(split[0], Long.valueOf(split[1]), Integer.valueOf(split[2])));
line= reader.readLine();
}
}
/**
* 大多数的source在run方法内部都会有一个while循环,
* 当调用这个方法的时候, 应该可以让run方法中的while循环结束
*/
@Override
public void cancel() {
isRunning = false;
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
1.6.3 transforms
①对数据操作的算子
map
flatmap:一进多出,炸裂
所有Flink函数类都有其Rich版本。它与常规函数的不同在于,可以获取运行环境的上下文,并拥有一些生命周期方法,所以可以实现更复杂的功能。也有意味着提供了更多的,更丰富的功能。例如:RichMapFunction
②重分区算子
package operateFunction;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
/**
* 重分区算子
*/
public class TranceformRebalance {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(5);
DataStreamSource<String> socketDS = env.socketTextStream("linux1", 9999);
SingleOutputStreamOperator<String> map = socketDS.map(x -> x).setParallelism(2);
map.print("map").setParallelism(2);
// socketDS.keyBy(data -> data).print("keyby");
//随机分区
// socketDS.shuffle().print("shuffer");
//rebalance map1是先找一个开始分区,比如5,然后 45-12345-12345 map2:345-12345-12345
map.rebalance().print("rebalance");
//rescale 是先将分区平分,然后数据在每个分区内轮询。map1: 45-345-345 map2:12-12-12-12
map.rescale().print("rescale");
//并行度是1
// socketDS.global().print("gloal");
//此处会报错,8个并行度往socket一个并行度发会报错 forward 上下算子的并行度要相同
//socketDS.forward().print("forward");
//broadcast发往下游所有分区,每个一份
// socketDS.broadcast().print("broadcast");
//自定义重分区
env.execute();
}
}
③控制流的算子
split切分后类型不可变,output侧输出流类型是可变的
split(数据类型不可变)和select:可以使用output侧输出流(数据类型可以改变)代替他
将流中数根据某一特征划分,给流打上标记,然后根据select能筛选出被打上指定标记的流(1.12已移除)
connect:
允许连接两个不同类型的流(String,Integer),只能连接两个流,连接后依然保持着各自的数据类型。
union:只能连接相同类型的流,可以连接2个以上甚至多个流。
返回的是ConnectedStreams类型,后面可以调用map,flatmap,process方法。
join:经常配合开窗操作
connect算子
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<Integer> intStream = env.fromElements(1, 2, 3, 4, 5);
DataStreamSource<String> stringStream = env.fromElements("a", "b", "c");
ConnectedStreams<Integer, String> connect = intStream.connect(stringStream);
// TODO ConnectedStreams 的map flatmap需要传CoMapFunction
connect.map(new CoMapFunction<Integer, String, Object>() {
@Override
public Object map1(Integer value) throws Exception {
return value;
}
@Overrides
public Object map2(String value) throws Exception {
return value;
}
});
④聚合算子
集合算子要在keyBy之后
常见的滚动聚合算子
sum, min,max,minBy,maxBy
如果流中存储的是POJO或者scala的样例类, 参数使用字段名
如果流中存储的是元组, 参数就是位置(基于0...)
reduce
//max只改变当前统计的这一个字段,其他字段会保持第一次输出的字段值不变
SingleOutputStreamOperator<WaterSensor> vc = keyedDS.max("vc");
//maxBy除了改变统计字段取最大值,如果两条数据最大值相同,其他值不同,其他字段的值通过后一个参数设置
//first参数:不是是取第一个
如果是false:取本条数据的字段,若是TRUE,取上一个最大值中的字段
SingleOutputStreamOperator<WaterSensor> vc1 = keyedDS.maxBy("vc",first=false);
reduce:
如果WaterSensor 温度和时间都要最大的:可使用reduce更灵活
SingleOutputStreamOperator<WaterSensor> reduceDS = keyedDS.reduce(new ReduceFunction<WaterSensor>() {
@Override
public WaterSensor reduce(WaterSensor value1, WaterSensor value2) throws Exception {
//id:keyby后的id都一样,随便取其中一个,ts:取最大时间 vc:取最大水位
return new WaterSensor(value1.getId(), value2.getTs(), Math.max(value1.getVc(), value2.getVc()));
}
});
⑤富函数
示例:
RichMapFunction:
1、open close生命周期方法:
程序在和其他第三方数据库有交互时使用,全局在每个并行度上只调用一次。
比如要在map中读完kafka数据写到mysql,map每次处理一条数据都创建关闭一次连接,可以使用声明周期方法中的open(创建连接方法) close(关闭连接方法)
2、getRuntimeContext() 获取运行时上下文:
可以做状态编程
⑥ process
最后process算子:
其他算子都搞不定,可以用最底层的process算子
package operateFunction;
import org.apache.flink.api.common.functions.RuntimeContext;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.TimerService;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.ProcessFunction;
import org.apache.flink.util.Collector;
public class TransformProcess {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(5);
DataStreamSource<String> sockDS = env.socketTextStream("linux1", 9999);
//process 实现flapmap功能
SingleOutputStreamOperator<String> flatMapDS = sockDS.process(new MyFlatmapProcess());
//process 实现pmap功能
SingleOutputStreamOperator<Tuple2<String, Integer>> wordToOneDS =