Flink
一、Flink简介
Flink是一种高吞吐、低延迟、高性能的分布式大数据处理引擎。相比Spark-Streaming延迟更低。
二、Flink集群搭建
JobManager:即Master
TaskManager:即Worker
安装Flink,Flink有自带的stand-alone模式,
设置参数:master为linux01;worker为linux02和linux03
每个worker可用的槽数为2
启动命令:bin/start-cluster.sh
web端口:8081
任务提交方式:
第一种方式:通过web页面进行提交任务,同时指定参数
第二种方式:使用命令行提交
/opt/apps/flinkflink-1.13.2/bin/flink run
-m linxu01:8081
-p 2
-c com.doit.WorkCount_Java
/opt/flink-java-1.0-SNAPSHOT.jar
linux01 9111 (--hostname node-1.51doit.cn --port 8888)
参数说明:
-m指定主机名后面的端口为JobManager的REST的端口,而不是RPC的端口,RPC通信端口是6123
-p 指定是并行度
-c 指定main方法的全类名
指定jar包位置 args参数
三、编程入门
四、DataStream算子
4.1 Flink Source
在Flink中,Source主要负责数据的读取,有单并行和多并行之分,而算子都是多并行的
4.1.1 基于File的数据源
readTextFile
(1)使用TextInputFormat方式诉求文本文件,并将结果作为String返回。调用readTextFile方法, 只传入读取数据的路径,那么该方法创建的DataStream使用一个有限的数据流数据读取完成, job就退出了。
(2)并行度跟job的并行度保持一致,默认是cpu的逻辑核数。
(3)可以设置并行度
DataStreamSource<String> lines = env.readTextFile("E:\\work2\\mrdata\\data\\date.txt");
// DataStreamSource<String> lines = env.readTextFile("E:\\work2\\mrdata\\data\\date.txt").setParallelism(4);
(4)readTextFile底层调用的是readFile
String path = "/Users/xing/Desktop/a.txt";
TextInputFormat format = new TextInputFormat(new Path(path));
DataStreamSource<String> lines =
env.readFile(format, path, FileProcessingMode.PROCESS_CONTINUOUSLY, 1000);
这里设置了持续读取文件,每间隔1秒就读一次,但有个缺陷,每次都是从头读取,不会记录偏移量
4.1.2 基于集合的数据源
fromCollection fromElements fromParallelCollection generateSequence
* 将集合变成DataStream
*
* 集合类的Source都是有限数据流,即将集合内的数据读完后,就停止了
*
* fromCollection 创建的DataStream并行度为必须为1
* fromElements 创建的DataStream并行度也是必须为1
*fromParallelCollection 创建的DataStream并行度可以是多个
*generateSequence 创建的DataStream并行度可以是多个
List<String> list =
Arrays.asList("spark hadoop", "flink spark", "spark", "hive flume hadopp");
DataStreamSource<String> lines = env.fromCollection(list);
DataStreamSource<String> lines
= env.fromElements("spark", "hadoop", "flink", "hive");
4.1.3 基于Socket的数据源
socketTextStream
从Socket中读取信息,元素可以用分隔符分开。即从一个端口中读取数据。
socket的并行度必须为1,所以只有一个消费者读数据
需要现在linux中使用 nc -lk 9111 开启一个9111的端口
DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);
4.1.4 基于kafka的数据源
addSource
* 使用FlinkKafkaSource从Kafka中读取数据
*
*并行度为 逻辑核数
*
* FlinkKafkaSource创建的DataStream的多并行的,可以有多个消费者从Kafka中读取数据
*前提要开启kafka 和zookeeper 可以创建多个消费者
Properties properties = new Properties();
properties
.setProperty("bootstrap.servers","linux01:9092,linux02:9092,linux03:9092");
properties.setProperty("group.id", "test11");
properties.setProperty("auto.offset.reset","earliest");
DataStream<String> lines =env
.addSource(new FlinkKafkaConsumer<>("test1", new SimpleStringSchema(), properties));
4.1.5 自定义Source
1、实现SourceFunction接口 :单并行
单并行的Source:并行度为1
实现SourceFunction接口,重写run方法 和cancel方法
/**
* run方法是Source对应的Task启动后,会调用该方法,用来产生数据
* 如果是一个【有限】的数据流,run方法中的逻辑执行完后,Source就停止了,整个job也停止了
* 如果是一个【无限】的数据流,run方法中会有while循环,不停的产生数据
*/
例1:自定义一个单并行的source,有限数据流
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> lines = env.addSource(new mySource());
int parallelism = lines.getParallelism();
System.out.println("source的并行度为"+parallelism);
lines.print();
env.execute();
}
//自定义source
private static class mySource implements SourceFunction<String> {
/**
* run方法是Source对应的Task启动后,会调用该方法,用来产生数据
* 如果是一个【有限】的数据流,run方法中的逻辑执行完后,Source就停止了,整个job也停止了
* 如果是一个【无限】的数据流,run方法中会有while循环,不停的产生数据
*/
@Override
public void run(SourceContext<String> ctx) throws Exception {
List<String> words = Arrays.asList("spark", "hadoop", "flink", "hive");
for (String word : words) {
ctx.collect(word);
}
}
@Override
public void cancel() {
}
}
例2:自定义一个单并行的source,无限数据流
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<Integer> lines = env.addSource(new mySource());
int parallelism = lines.getParallelism();
System.out.println("source的并行度为"+parallelism);
lines.print();
env.execute();
}
//自定义source
private static class mySource implements SourceFunction<Integer> {
@Override
public void run(SourceContext<Integer> ctx) throws Exception {
int i =0 ;
while(true){
ctx.collect(i);
i++;
Thread.sleep(1000);
}
}
@Override
public void cancel() {
}
}
2、实现ParallelSourceFunction接口 :多并行
* 实现了ParallelSourceFunction接口的Source,创建的DataStream就是多并行
*
* 自定义一个parallel sources (多并行的Source)
*
* 这个例子是一个【无限】的数据流,run方法中会不停的产生数据(while循环)
*/
public static void main(String[] args) {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
}
//自定义多并行的source
private static class MyParallelSource implements ParallelSourceFunction<Integer> {
private boolean flag = true;
@Override
public void run(SourceContext<Integer> ctx) throws Exception {
int i = 0;
while(flag){
ctx.collect(i);
i++;
Thread.sleep(1000);
}
}
@Override
public void cancel() {
flag = false;
}
}
3、继承RichParallelSourceFunction :多并行,而且有生命周期方法
自定义多并行的Source,继承RichParallelSourceFunction 便能拥有生命周期方法(open run cancel close)
在subtask中方法的执行顺序(生命周期方法):
open(一次) -> run(一次) -> cancel(一次)-> close(一次)
该并行度为8,所以open run cancel close 方法都会调用8次 而在自定义MyParallelSource的构造方法只会被调用一次,因为是在Driver端创建
*/
例子:
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<Integer> lines = env.addSource(new MyParallelSource());
int parallelism2 = lines.getParallelism();
System.out.println("自定义的实现SourceFunction的Source并行度为:" + parallelism2);
lines.print();
env.execute();
}
//多并行的Source,继承了RichParallelSourceFunction
// 即调用完Source后得到的DataStream中对应的数据类型
private static class MyParallelSource extends RichParallelSourceFunction<Integer> {
public MyParallelSource() {
System.out.println("constructor invoke ~~~~~~~~~~~~");
}
@Override
public void open(Configuration parameters) throws Exception {
//getRuntimeContext().getIndexOfThisSubtask() 获取当前subTask的Index
System.out.println("subtask:" + getRuntimeContext().getIndexOfThisSubtask() + " @@@@ open 方法被调用了");
}
@Override
public void close() throws Exception {
System.out.println("subtask:" + getRuntimeContext().getIndexOfThisSubtask() + " &&&& close 方法被调用了");
}
private boolean flag = true;
@Override
public void run(SourceContext<Integer> ctx) throws Exception {
System.out.println("subtask:" + getRuntimeContext().getIndexOfThisSubtask() + " Run方法被调用了~~~~~~~~~~~~~");
int i = 0;
while (flag) {
ctx.collect(i);
i += 1;
Thread.sleep(2000);
}
}
@Override
public void cancel() {
System.out.println("subtask:" + getRuntimeContext().getIndexOfThisSubtask() + " cancel方法被调用了!!!!!!!!!!!!");
flag = false;
}
}
4.1.6 总结
file的文件源,无法做到实时,相当于是批处理,相当于是做离线
基于集合的数据源,一般只是用来做测试
基于socket的数据源,一般也是用来做测试,因为读取数据的并行度必须为1,影响效率
基于kafka的数据源一般用于生产环境中,因为多个并行度,而且可以有多个生产者
有时候需要自定义Source数据源,可以定义单并行,多并行,以及多并行带生命周期方法
4.2 Sink 算子
sink算子 相当于spark中的action算子
4.2.1 Print
* 将数据以标准数据打印,
* PrintSink是多并行的
public static void main(String[] args) throws Exception{
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//整个job的并行度
int parallelism = env.getParallelism();
System.out.println("当前job的执行环境默认的并行度为:" + parallelism);
//DataStreamSource是DataStream的子类
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
lines.print("doit ");
//启动并执行
env.execute();
}
4.2.2 writeAsText
WriteText 将数据写到文件中 该sink方法已经过时,了解即可
public static void main(String[] args) throws Exception{
//创建DataStream,必须调用StreamExecutitionEnvriroment的方法
//StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//整个job的并行度
int parallelism = env.getParallelism();
System.out.println("当前job的执行环境默认的并行度为:" + parallelism);
//DataStreamSource是DataStream的子类
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
lines.writeAsText("/Users/xing/Desktop/out", FileSystem.WriteMode.OVERWRITE);
//启动并执行
env.execute();
}
4.2.3 自定义Sink
继承RichSinkFunction 调用的时候使用addSink
/**
自定义sink方法:实现跟print一样的功能
定义一个类,继承RichSinkFunction 重写invoke方法
invoke是来一条数据就执行一次
open和close生命周期方法是有几个并行度就执行几次,每个task各执行一次
调用该sink方法的时候是使用:addSink
*/
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "linux01:9092,linux02:9092,linux03:9092");
properties.setProperty("group.id", "test11");
properties.setProperty("auto.offset.reset","earliest");
DataStream<String> lines = env.addSource(new FlinkKafkaConsumer<>("test1", new SimpleStringSchema(), properties));
//lines.print() print底层使用的就是addSink
//sink可以指定前缀名,可以指定并行度
lines.addSink(new mySink()).name("我的sink").setParallelism(3);
env.execute("test");
}
//自定义printSink
private static class mySink extends RichSinkFunction<String> {
@Override
public void open(Configuration parameters) throws Exception {
System.out.println("open method invoked ~~~~~~~");
}
@Override
public void close() throws Exception {
System.out.println("close method invoked ########");
}
@Override
public void invoke(String value, Context context) throws Exception {
System.out.println("invoke method invoked $$$$$$$$$$");
int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
System.out.println((indexOfThisSubtask+1) + ">" + value);
}
}
4.3 Transformation 算子
4.3.1 map
/*
map :将数据一条一条的处理,处理一条返回一条
多并行
底层使用的是transform
使用lambda表达式,需要注意两个问题:
*泛型是否会自动推导:
里面的泛型有时候不能自动推导,所以要进行类型指定
当参数中还有类型的时候,比如( line, out) ->,而out是一个collect类型,其中有泛 型,此时不指定类型就会有泛型丢失,
就需要指定数据类型(String line, Collector<Tuple2<String, Integer>> out) ->
*是否需要使用returns
不需要使用returns指定返回结果的数据类型的场景:
---->输入与输出的数据类型一致,输入一行返回一行
需要使用returns指定返回结果的数据类型的场景:
1)如果输入的数据类型和输出的数据类型不一致
2)如果输入一行,返回多行,比如flatMap
*/
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> lines = env.socketTextStream("Linux01", 8888);
//做映射
//调用完map算子后生成的DataStream是单并行的还是多并行的?答案:多并行的
SingleOutputStreamOperator<String> upperDataStream = lines.map(new MapFunction<String, String>() {
@Override
public String map(String s) throws Exception {
return s.toUpperCase();
}
}).setParallelism(2);
//使用lambda表达式的方式
// SingleOutputStreamOperator<String> upperDataStream = lines.map(s -> s.toUpperCase()).setParallelism(2);
/*
使用lambda表达式的方式-->但是输入和输出的数据类型不一致
SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndOne = words
.map(w -> Tuple2.of(w, 1))
.returns(Types.TUPLE(Types.STRING, Types.INT));
*/
System.out.println("map算子对应的DataStream的并行度:" + upperDataStream.getParallelism());
upperDataStream.print();
//启动并执行
env.execute();
}
使用底层方法来实现map的功能:
传入算子名称,返回类型,具体的运算逻辑
transform("MyMap", TypeInformation.of(String.class), new StreamMap<>(func));
// MapFunction泛型是输入和输出的数据类型
func = new MapFunction<String, String>().....
public static void main(String[] args) throws Exception{
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> words = env.socketTextStream("localhost", 8888);
//MapFunction是在JobManager端被new的
MapFunction<String, String> func = new MapFunction<String, String>() {
@Override
public String map(String s) throws Exception {
return s.toUpperCase();
}
};
//调用Transformation,传入算子名称,返回类型,具体的运算逻辑
SingleOutputStreamOperator<String> upperStream = words
.transform("MyMap", TypeInformation.of(String.class), new StreamMap<>(func));
upperStream.print();
//启动并执行
env.execute();
}
4.3.2 map 底层的StreamMap
map filter flatMap 底层都是用的processElement方法,来一条处理一条
map算子 不断的底层实现
1)直接使用
2)使用transForm来实现map
3)使用自定义的StreamMap 来实现
4)将自定义的StreamMap完善,
5)将自定义的StreamMap完善,加入泛型的使用
/*
自定义Streammap:继承一个类,实现一个接口,重写processElement方法
输入的数据类型一定是String,因为读数据的时候就是按照String读过来一行
自定义Streammapu需要实现OneInputStreamOperator接口,但这个接口有太多的抽象方法要实 现,所以再继承AbstractStreamOperator,这样就可以省去重写一些没必要的方法
只重写processElement,该方法中写对数据的处理逻辑,原则上是来一条处理一条
*/
MyStreamMap1 --版本1
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);
SingleOutputStreamOperator<String> myStreamMap1 = lines
.transform("myStreamMap1"
, TypeInformation.of(String.class)
, new MyStreamMapDemo1());
myStreamMap1.print();
env.execute();
}
/*
自定义Streammap:继承一个类,实现一个接口,重写processElement方法
只重写processElement,该方法中写对数据的处理逻辑,原则上是来一条处理一条
*/
private static class MyStreamMapDemo1 extends AbstractStreamOperator<String> implements OneInputStreamOperator<String,String> {
@Override
public void processElement(StreamRecord<String> element) throws Exception {
String in = element.getValue();
String res = in + "小强";
output.collect(element.replace(res));
}
}
MyStreamMap2 --版本2
自定义StreamMap
在MyStreamMap1基础之上,将数据的处理逻辑作为一个函数 传到StreamMap中
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);
MapFunction<String, String> func = new MapFunction<String, String>() {
@Override
public String map(String value) throws Exception {
return value + "小强";
}
};
SingleOutputStreamOperator<String> myStreamMap1 = lines
.transform("myStreamMap1"
, TypeInformation.of(String.class)
, new MyStreamMapDemo2(func));
myStreamMap1.print();
env.execute();
}
/*
自定义Streammap
将处理逻辑作为函数的参数 传进来
*/
private static class MyStreamMapDemo2 extends AbstractStreamOperator<String> implements OneInputStreamOperator<String,String> {
private MapFunction<String, String> mapFunction;
public MyStreamMapDemo2(MapFunction<String, String> mapFunction) {
this.mapFunction = mapFunction;
}
@Override
public void processElement(StreamRecord<String> element) throws Exception {
output.collect(element.replace(
mapFunction.map(element.getValue())
));
}
}
MyStreamMap3 --版本3
/*
自定义StreamMap
在MyStreamMap1基础之上,将数据的处理逻辑作为一个函数 传到StreamMap中
在MyStreamMap2基础之上,全都使用泛型,实现跟源码一样的功能
*/
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);
MapFunction<String, String> func = new MapFunction<String, String>() {
@Override
public String map(String value) throws Exception {
return value + "小强";
}
};
SingleOutputStreamOperator<String> myStreamMap1 = lines
.transform("myStreamMap1"
, TypeInformation.of(String.class)
, new MyStreamMapDemo3<>(func));
myStreamMap1.print();
env.execute();
}
/*
自定义Streammap
加入泛型的使用,使程序更加灵活
需要在类名后定义泛型MyStreamMapDemo3<I,O> I代表输入,O代表输出
继承的是AbstractUdfStreamOperator 要传一个函数MapFunction 使用哪个算子就传哪个Function
*/
private static class MyStreamMapDemo3<I,O> extends AbstractUdfStreamOperator<O,MapFunction<I,O>> implements OneInputStreamOperator<I,O> {
public MyStreamMapDemo3(MapFunction<I, O> userFunction) {
super(userFunction);
}
@Override
public void processElement(StreamRecord<I> element) throws Exception {
output.collect(element.replace(userFunction.map(element.getValue())));
}
}
4.3.3 reduce
/*
reduce:
聚合函数,当字段第一次出现的时候,会直接进行输出,当字段再来的时候,会进行判断该字段是否已经存在,假如存在,则将该字段的聚合后的值取出来,然后作为第一个值,将新传过来的作为第二个值,进行聚合,然后将这个字段的聚合结果进行更新然后输出。
tp1.f1 = tp1.f1 + tp2.f1;
return tp1;
*/
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> words = env.socketTextStream("linux01", 9111);
SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndOne = words.map(new MapFunction<String, Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> map(String s) throws Exception {
return Tuple2.of(s, 1);
}
});
KeyedStream<Tuple2<String, Integer>, Tuple> keyed = wordAndOne.keyBy("f0");
SingleOutputStreamOperator<Tuple2<String, Integer>> reduced = keyed.reduce(new ReduceFunction<Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> reduce(Tuple2<String, Integer> tp1, Tuple2<String, Integer> tp2) throws Exception {
tp1.f1 = tp1.f1 + tp2.f1;
return tp1;
}
});
reduced.print();
env.execute();
}
4.3.4 flatMap
//flatMap的底层实现:
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);
SingleOutputStreamOperator<Tuple2<String, Integer>> res = lines
.transform("myFlatMap"
, TypeInformation.of(new TypeHint<Tuple2<String, Integer>>() {}) //返回的数据类型,有嵌套所以用TypeHint
, new My_FlatMap()); //传StreamFlatMap 这里自定义了StreamMap
res.print();
env.execute();
}
//自定义flatMap方法 实质上就是来一条处理一条,然后返回一条
//继承一个类,实现一个类,重写processElement方法
private static class My_FlatMap extends AbstractStreamOperator<Tuple2<String,Integer>> implements OneInputStreamOperator<String,Tuple2<String,Integer>> {
@Override
public void processElement(StreamRecord<String> element) throws Exception {
//使用getValue拿到数据
String line = element.getValue();
//运用处理逻辑进行处理
String[] words = line.split(" ");
for (String word : words) {
Tuple2<String, Integer> wordAndOne = Tuple2.of(word, 1);
//将处理的结果进行输出,使用output.collect,这里面需要传的是StreamRecord类型,所以不new StreamRecord了
//而是使用element.replace(wordAndOne) 直接将结果输出,避免了多次创建StreamRecord实例,replace会将element中的数据清空
output.collect(element.replace(wordAndOne));
}
}
}
4.3.5 keyBy
1)底层根据哈希值和分区个数来进行分区,不是上游将数据发送到下游,而是将数据写到一个channel缓 存中,当下游需要数据的时候,就会到上游拉取数据
a)可以使用lambda方式,最方便
b)可以使用keySelect的方式,最全,
c)数据不是k-v类型也可以使用
d)当使用下标的方式,需要有条件,数据需要·是tuple类型
2)对于自定义Bean类:
最好用keySelect -->return wordCount.getWord
使用lambda表达式也是很方便
这个Bean不能是private修饰
需要有空参构造器
使用sum的时候,要写Bean中的字段名,比如:sum("count")
3)对于多级的数据:省 市 区 商品的多级分类
使用keySelect return "f0" + "f1" 将两个字段合到一起
或者return Tuple 直接按照一个Tuple进行分区,将分区的字段放进元组中
会将这两个字段的哈希值加到一起
或者直接用lambda表达式:keyBy(tp -> tp.f0 + tp.f1)
或者:
keyBy(tp -> Tuple2.of(tp.f0,tp.f1)
, TypeInfomation.of(new TypeHint<Typle2<String,String>>(){}))
keyBy不能使用returns,这样的化必须要指定数据类型
若将数据封装到Bean中,则可以写一个方法,将需要分区的字段添加到一个方法中,
但是在该类中必须将参加分区的字段重写hash方法,不能把Array作为哦keyBy的字段
/**
keyBy
分区的字段采用下标的时候,必须是元组类型
*/
keyBy("f0");
keyBy(0);
KeyedStream<Tuple2<String, Integer>, Tuple> keyed = wordAndOne.keyBy("f0");
KeyedStream<Tuple2<String, Integer>, Tuple> keyed = wordAndOne.keyBy(0);
//keyBy 采用匿名内部类的方式 new keySelect
KeyedStream<Tuple2<String, Integer>, String> keyed = wordAndOne.keyBy(new KeySelector<Tuple2<String, Integer>, String>() {
@Override
public String getKey(Tuple2<String, Integer> value) throws Exception {
return value.f0;
}
});
/**
keyBy
分区的字段是Bean类中的字段
*/
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> words = env.socketTextStream("linux01", 9111);
SingleOutputStreamOperator<WordCount> wordAndOne = words.map(t -> new WordCount(t, 1));
//采用lambda表达式
KeyedStream<WordCount, String> keyed1 = wordAndOne.keyBy(t -> t.word);
//采用Bean中的字段方式
KeyedStream<WordCount, Tuple> keyed2 = wordAndOne.keyBy("word");
//采用匿名内部类的方式
KeyedStream<WordCount, String> keyed3 = wordAndOne.keyBy(new KeySelector<WordCount, String>() {
@Override
public String getKey(WordCount value) throws Exception {
return value.word;
}
});
//使用sum 要写Bean中的字段名,要一致
SingleOutputStreamOperator<WordCount> sumed = keyed1.sum("count");
sumed.print();
env.execute();
}
//用来封装数据的Bean,不能是private修饰
public static class WordCount {
private String word;
private Integer count;
// 用来封装数据的POJO,如果添加了有参的构造方法,必须添加无惨的构造方法
public WordCount() {}
public WordCount(String word, Integer count) {
this.word = word;
this.count = count;
}
public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
@Override
public String toString() {
return "WordCount{" +
"word='" + word + '\'' +
", count=" + count +
'}';
}
}
/** keyBy
分区的字段有多个,而这多个字段都在元组中
-->可以用下标
-->可以用匿名内部类
-->可以用lambda表达式
*/
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);
//辽宁省,沈阳市,1000
//辽宁省,大连市,1000
//山东省,济南市,1000
//山东省,烟台市,1000
SingleOutputStreamOperator<Tuple3<String, String, Double>> tpDataStream = lines.map(new MapFunction<String, Tuple3<String, String, Double>>() {
@Override
public Tuple3<String, String, Double> map(String line) throws Exception {
String[] fields = line.split(",");
return Tuple3.of(fields[0], fields[1], Double.parseDouble(fields[2]));
}
});
//对于元组,使用下标的方式
KeyedStream<Tuple3<String, String, Double>, Tuple> keyed1 = tpDataStream.keyBy("f0", "f1");
//使用下标的方式
KeyedStream<Tuple3<String, String, Double>, Tuple> keyed2 = tpDataStream.keyBy(0, 1);
//使用lambda的方式
KeyedStream<Tuple3<String, String, Double>, String> keyed3 = tpDataStream.keyBy(tp3 -> tp3.f0 + tp3.f1);
//使用匿名内部类的方式
KeyedStream<Tuple3<String, String, Double>, String> keyed4 = tpDataStream.keyBy(new KeySelector<Tuple3<String, String, Double>, String>() {
@Override
public String getKey(Tuple3<String, String, Double> value) throws Exception {
return value.f0 + value.f1;
}
});
//使用匿名内部类的方式
KeyedStream<Tuple3<String, String, Double>, Tuple2<String, String>> keyed5 = tpDataStream.keyBy(new KeySelector<Tuple3<String, String, Double>, Tuple2<String, String>>() {
@Override
public Tuple2<String, String> getKey(Tuple3<String, String, Double> value) throws Exception {
return Tuple2.of(value.f0, value.f1);
}
});
}
/*
Author: Tao.W
D a te: 2021/8/21
Description:
keyBy
分区的字段有多个,
将分区的字段在POJO中 --POJO即普通的自定义类
需要将参与分区的字段重写equals和hash方法
keyBy的时候,直接些字段的名称
*/
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);
//辽宁省,沈阳市,1000
//辽宁省,大连市,1000
//山东省,济南市,1000
//山东省,烟台市,1000
SingleOutputStreamOperator<ProvinceAndCity> maped = lines.map(new MapFunction<String, ProvinceAndCity>() {
@Override
public ProvinceAndCity map(String value) throws Exception {
String[] arr = value.split(",");
return new ProvinceAndCity(arr[0], arr[1]);
}
});
//keyBy的时候,直接些字段的名称
KeyedStream<ProvinceAndCity, Tuple> keyed = maped.keyBy("pro");
// int parallelism = keyed.getParallelism();
// System.out.println("并行度"+parallelism);
keyed.print();
env.execute();
}
//需要将参与分区的字段重写equals和hash方法
public static class ProvinceAndCity{
private String pro;
private String city;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProvinceAndCity that = (ProvinceAndCity) o;
if (pro != null ? !pro.equals(that.pro) : that.pro != null) return false;
return city != null ? city.equals(that.city) : that.city == null;
}
@Override
public int hashCode() {
int result = pro != null ? pro.hashCode() : 0;
result = 31 * result + (city != null ? city.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "ProvinceAndCity{" +
"pro='" + pro + '\'' +
", city='" + city + '\'' +
'}';
}
public String getPro() {
return pro;
}
public void setPro(String pro) {
this.pro = pro;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public ProvinceAndCity() {
}
public ProvinceAndCity(String pro, String city) {
this.pro = pro;
this.city = city;
}
}
/*
Author: Tao.W
D a te: 2021/8/21
Description:
keyBy
分区的字段有多个,
将分区的字段封装到POJO中 --POJO即普通的自定义类,不是Bean类,Bean类指有各种方法和私有成员变量的自定义类
将省 市 封装到类中
需要将参与分区的字段重写equals和hash方法
*/
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);
//辽宁省,沈阳市,1000
//辽宁省,大连市,1000
//山东省,济南市,1000
//山东省,烟台市,1000
SingleOutputStreamOperator<Tuple3<String, String, Double>> tpDataStream = lines.map(new MapFunction<String, Tuple3<String, String, Double>>() {
@Override
public Tuple3<String, String, Double> map(String line) throws Exception {
String[] fields = line.split(",");
return Tuple3.of(fields[0], fields[1], Double.parseDouble(fields[2]));
}
});
KeyedStream<Tuple3<String, String, Double>, ProvinceAndCity> keyed = tpDataStream.keyBy(tp3 -> ProvinceAndCity.of(tp3.f0, tp3.f1));
SingleOutputStreamOperator<Tuple3<String, String, Double>> sumed = keyed.sum("f2");
sumed.print();
env.execute();
}
//需要将参与分区的字段重写equals和hash方法
public static class ProvinceAndCity{
private String pro;
private String city;
//定义一个of方法
public static ProvinceAndCity of (String pro,String city){
return new ProvinceAndCity(pro,city);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProvinceAndCity that = (ProvinceAndCity) o;
if (pro != null ? !pro.equals(that.pro) : that.pro != null) return false;
return city != null ? city.equals(that.city) : that.city == null;
}
@Override
public int hashCode() {
int result = pro != null ? pro.hashCode() : 0;
result = 31 * result + (city != null ? city.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "ProvinceAndCity{" +
"pro='" + pro + '\'' +
", city='" + city + '\'' +
'}';
}
public String getPro() {
return pro;
}
public void setPro(String pro) {
this.pro = pro;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public ProvinceAndCity() {
}
public ProvinceAndCity(String pro, String city) {
this.pro = pro;
this.city = city;
}
}
4.3.6 Aggregate(sum max maxBy min minBy )
Aggragation:聚合(max,min,sum,maxBy,minBy,sumBy)
1)聚合函数底层使用的都是reduce,要想更加灵活的进行聚合操作,还是自己写reduce方法更方便
2)keyBy之后按照某个字段进行聚合操作,将同一个分区的数据进行聚合操作,
3)滚动聚合:比如每个分区都按照某个字段求一个最大值,来一条比较一条,并将结果更新
4)sum 只能对数字类型的数据进行sum操作
5)比较的时候会调用comparator方法,所以进行比较的字段必须要实现cpmparable方法,如果要对自定 义类进行聚合,则这个类需要实现comparanble接口
6)max与maxBy之间的区别:
对于Tuple2的数据,两者没有区别
对于有多个字段的数据,比如Tuple3(省、市、金额),按照省进行分区,然后求金额最大
:对于max---->只会返回keyBy的字段和最⼤值,如果还有其他字段,返回的是第⼀次出现的值
:对于maxBy-->不但返回keyBy的字段,还会返回最⼩值、最⼤值,如果有多个字段,还会返回最
⼩值、最⼤值所在数据的全部字段
对于min和minBy是一样的
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//辽宁省,沈阳市,1000
//辽宁省,大连市,1000
//辽宁省,本溪市,2000
//山东省,济南市,2000
//山东省,烟台市,1000
DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);
SingleOutputStreamOperator<Tuple3<String, String, Double>> tpDataStream = lines.map(new MapFunction<String, Tuple3<String, String, Double>>() {
@Override
public Tuple3<String, String, Double> map(String line) throws Exception {
String[] fields = line.split(",");
return Tuple3.of(fields[0], fields[1], Double.parseDouble(fields[2]));
}
});
//按照省份进行KeyBy
KeyedStream<Tuple3<String, String, Double>, String> keyed = tpDataStream.keyBy(t -> t.f0);
//max
//SingleOutputStreamOperator<Tuple3<String, String, Double>> res = keyed.max(2);
//传false,则当两条数据相等的时候,返回最新的,否则会返回原来的
SingleOutputStreamOperator<Tuple3<String, String, Double>> res = keyed.maxBy(2, false);
res.print();
env.execute();
}
4.3.7 Union
--将两个数据流合并到一起:合并到一起后,方便统一进行逻辑处理
--可以union多个
**特殊情况:
1)不同的数据类型,不可union到一起
2)一个流的并行度是3,另一个流的并行度是4 ,那么合并之后的并行度是job的逻辑核数
3)自己union自己,则数据会得到双份
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> lines1 = env.socketTextStream("localhost", 8888);
DataStreamSource<String> lines2 = env.socketTextStream("localhost", 9999);
SingleOutputStreamOperator<Integer> nums = lines2.map(Integer::parseInt);
//要union的多个DataStream对应的数据离线必须一致才可以
//DataStream<String> union = lines1.union(nums);
DataStream<String> union = lines1.union(lines2);
//后续可以对union进行操作
union.print();
env.execute();
}
4.3.8 Connect
Connect
--可以将两个类型不一样的流connect到一起;连接到一起后生成一个新的DataStream,包装着原来的 两个流
--只能两个流connect,不可多个
--输入的两个流的类型可以不一致,但是每个流返回的数据类型是一致的
--可以让两个数据流共享状态
--貌合神离:操作的时候,假如map,则要写使用coMapFunction,重写两个map方法
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> lines1 = env.socketTextStream("Linux01", 8888);
DataStreamSource<String> lines2 = env.socketTextStream("linux01", 9999);
SingleOutputStreamOperator<Integer> nums2 = lines2.map(Integer::parseInt).setParallelism(3);
//String类型的流connect Integer类型的流
ConnectedStreams<String, Integer> connected = lines1.connect(nums2);
//进行map操作的时候,使用的是CoMapFunction 而不是MapFunction
SingleOutputStreamOperator<String> res = connected.map(new CoMapFunction<String, Integer, String>() {
//private FlinkState state;
//对第一流进行操作的方法
@Override
public String map1(String value) throws Exception {
//map1可以使用state
return value.toUpperCase();
}
//是对第二个流继续操作的方法
@Override
public String map2(Integer value) throws Exception {
//map2可以使用state
return value + " doit";
}
});
res.print();
env.execute();
}
4.3.9 iterator
iterator
--流式迭代计算,相当于增强的分布式for循环
--需要写三个:迭代计算的逻辑,继续迭代的条件,输出的条件
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//10
DataStreamSource<String> strs = env.socketTextStream("linux01", 8888);
DataStream<Long> numbers = strs.map(Long::parseLong);
//调用iterate方法 DataStream -> IterativeStream
//对Nums进行迭代(不停的输入int的数字)
IterativeStream<Long> iteration = numbers.iterate();
//IterativeStream -> DataStream
//对迭代出来的数据进行运算
// 对输入的数据应用更新模型,即输入数据的处理逻辑
DataStream<Long> iterationBody = iteration.map(new MapFunction<Long, Long>() {
@Override
public Long map(Long value) throws Exception {
System.out.println("iterate input =>" + value);
return value -= 3;
}
});
//只要满足value > 0的条件,就会形成一个回路,重新的迭代,即将前面的输出作为输入,在进行一次应用更新模型,即输入数据的处理逻辑
DataStream<Long> feedback = iterationBody.filter(new FilterFunction<Long>() {
@Override
public boolean filter(Long value) throws Exception {
return value > 0;
}
});
//传入迭代的条件
iteration.closeWith(feedback);
//不满足迭代条件的最后要输出 比如:输入8 则依次输出为:8 5 2 -1
DataStream<Long> output = iterationBody.filter(new FilterFunction<Long>() {
@Override
public boolean filter(Long value) throws Exception {
return value <= 0;
}
});
//数据结果
output.print("output value:");
env.execute();
}
4.3.9 Project
project
--投影,类似map
--只能针对 Tuple 类型的数据
--max.project(2,0) 直接用下标,这个使用更方便,类似map,不需要使用returns来指定数据类型
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//辽宁省,沈阳市,1000
//辽宁省,大连市,1000
//辽宁省,本溪市,2000
//山东省,济南市,1000
//山东省,烟台市,1000
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<Tuple3<String, String, Double>> tpDataStream = lines.map(new MapFunction<String, Tuple3<String, String, Double>>() {
@Override
public Tuple3<String, String, Double> map(String line) throws Exception {
String[] fields = line.split(",");
return Tuple3.of(fields[0], fields[1], Double.parseDouble(fields[2]));
}
});
//按照省份进行KeyBy
KeyedStream<Tuple3<String, String, Double>, String> keyed = tpDataStream.keyBy(t -> t.f0);
SingleOutputStreamOperator<Tuple3<String, String, Double>> max = keyed.max(2);
//做隐射 用map的话,当使用lambda表达式的时候,需要使用returns
//SingleOutputStreamOperator<Tuple2<String, Double>> res = max.map(t -> Tuple2.of(t.f0, t.f2)).returns(Types.TUPLE(Types.STRING, Types.INT));
//用project的话,当使用lambda表达式的时候,不需要使用returns,直接使用下标
SingleOutputStreamOperator<Tuple> res = max.project(2, 0);
res.print();
env.execute();
}
五、物理分区
5.1 Hash分区
*使⽤的是KeyGroupStreamPartitioner
*常用于keyBy
*跟key和下游的分区个数有关
5.2shuffle:随机分区
在flink中shuffle是一个方法,是将数据随机进行分区,shuffle是随机计算一个分区编号
--每来一条,都给他打上一个随机的分区编号,然后对他进行重新分区,随机生成分区编号,这个跟数 据没有关系
--使用了selectChannel方法,这个方法会每来一条就执行一条,这个方法用于分区
--⽣成随机数的⽅式:random.nextInt(numberOfChannels)
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//上游设置并行度为1,并将字段加上分区编号进行输出
DataStreamSource<String> words = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<String> mapped = words.map(new RichMapFunction<String, String>() {
@Override
public String map(String w) throws Exception {
int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
return w + " -> " + indexOfThisSubtask;
}
}).setParallelism(1);
//对数据进行shuffle(将上游的数据随机打散,本质上是生成一个随机的分区号)
DataStream<String> shuffled = mapped.shuffle();
//将数据打上下游的分区编号进行输出
shuffled.addSink(new RichSinkFunction<String>() {
@Override
public void invoke(String value, Context context) throws Exception {
int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
System.out.println(value + " -> " + indexOfThisSubtask);
}
});
env.execute();
}
5.3 rebalance:轮询分区
--将数据轮回着进行分区,来一条分配一条
--如将数据依次分配到0区->1区->2区->0区->1区->2区...
--直接输出,默认的就是采用rebalance(当输出的上游与下游的分区数(并行度)不同的时候,就会划分task类似划分stage)
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//并行度为1
DataStreamSource<String> words = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<String> mapped = words.map(new RichMapFunction<String, String>() {
@Override
public String map(String w) throws Exception {
int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
return w + " -> " + indexOfThisSubtask;
}
}).setParallelism(1);
//轮询:1 -> 2 -> 3 -> 0 -> 1 -> 2
DataStream<String> rebalanced = mapped.rebalance();
rebalanced.addSink(new RichSinkFunction<String>() {
@Override
public void invoke(String value, Context context) throws Exception {
int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
System.out.println(value + " -> " + indexOfThisSubtask);
}
});
env.execute();
}
5.4 rescaling :轮询分区
与rebalance的·区别在于在同一个taskManager中进行轮询,减少网络传输
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//并行度为1
DataStreamSource<String> words = env.socketTextStream("linux01", 8888);
SingleOutputStreamOperator<String> mapped = words.map(new RichMapFunction<String, String>() {
@Override
public String map(String w) throws Exception {
int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
return w + " -> " + indexOfThisSubtask;
}
}).setParallelism(1);
//轮询:1 -> 2 -> 3 -> 0 -> 1 -> 2
DataStream<String> rebalanced = mapped.rescale();
rebalanced.addSink(new RichSinkFunction<String>() {
@Override
public void invoke(String value, Context context) throws Exception {
int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
System.out.println(value + " -> " + indexOfThisSubtask);
}
});
env.execute();
}
5.5 broadCast:广播分区
将数据放到每一个channel,这样一来,下游拉取数据的时候,就实现了每个分区都能取到数据
/*
会将数据广播到每个分区
44 -> 0 -> 4
44 -> 0 -> 1
44 -> 0 -> 0
44 -> 0 -> 6
44 -> 0 -> 7
44 -> 0 -> 3
44 -> 0 -> 5
44 -> 0 -> 2
*/
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//并行度为1
DataStreamSource<String> words = env.socketTextStream("linux01", 8888);
SingleOutputStreamOperator<String> mapped = words.map(new RichMapFunction<String, String>() {
@Override
public String map(String w) throws Exception {
int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
return w + " -> " + indexOfThisSubtask;
}
}).setParallelism(1);
/*
会将数据广播到每个分区
44 -> 0 -> 4
44 -> 0 -> 1
44 -> 0 -> 0
44 -> 0 -> 6
44 -> 0 -> 7
44 -> 0 -> 3
44 -> 0 -> 5
44 -> 0 -> 2
*/
DataStream<String> broadcast = mapped.broadcast();
broadcast.addSink(new RichSinkFunction<String>() {
@Override
public void invoke(String value, Context context) throws Exception {
int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
System.out.println(value + " -> " + indexOfThisSubtask);
}
});
env.execute();
}
5.6 custom:自定义分区器
--调用的时候调用partitionCustom方法,写入两个参数
--第1个参数:new Paritioner的实现,重写partition方法
--第2个参数:KeySelector
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<Tuple2<String, String>> stream1 = lines.map(new RichMapFunction<String, Tuple2<String, String>>() {
@Override
public Tuple2<String, String> map(String value) throws Exception {
int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
return Tuple2.of(value, indexOfThisSubtask + "");
}
}).setParallelism(2);
//使用自定义的分区器:
DataStream<Tuple2<String, String>> stream2 = stream1.partitionCustom(
new Partitioner<String>() {
/**
*
* @param key 通过KeySelector获取到分区的key
* @param numPartitions 下游分区的数量
* @return
*/
@Override
public int partition(String key, int numPartitions) {
System.out.println("下游分区的数量为:" + numPartitions);
int index = 0;
if (key.startsWith("h")) {
index = 1;
} else if (key.startsWith("f")) {
index = 2;
} else if (key.startsWith("s")) {
index = 3;
}
return index;
}
},
//第二个参数,是选择哪个字段作为key
t -> t.f0 //将tuple中的f0当成key
);
stream2.addSink(new RichSinkFunction<Tuple2<String, String>>() {
@Override
public void invoke(Tuple2<String, String> value, Context context) throws Exception {
//bbb : -> 0 -> 2
System.out.println(value.f0 + " : " + value.f1 + " -> " + getRuntimeContext().getIndexOfThisSubtask());
}
});
env.execute();
}
5.7 分区数量于并行度之间的关系
简单来讲:默认是 分区数量 = 下游的并行度
调用物理分区方法,会系那个数据进行重新分区,而分区的数量取决于下游的并行度;
因为当下游有几个并行度,则意味着有几个类似DAG的task在同时执行,那么为了执行的更快,自然是要将数据尽可能的平均发到下游的各个分区,
所以就体现为下游有几个并行,重新分区的时候就有几个分区。
假设下游8个并行,而重新分区的时候只有两个分区,则势必其他的并行计算的task要跨task拉取数据,所以会导致效率不高
六、 Window(窗口)
6.1 Flink 时间
* EventTime 数据产⽣的的时间,以后可将数据的产生时间提取出来
* IngestionTime 数据从消息中间件读取,进⼊到Flink的时间
* ProcessingTime 即数据被Operator处理时的时间
6.2 CountWindow
按照条数进行划分窗口,可以对这个窗口内的数据进行逻辑处理
nonKeyed Window :
即没有kayBy就划分窗口,那么这个窗口是一个单并行的算子,会成为性能瓶颈,所以一般不用
--countWindowAll方法
keyed Window :
先keyBy再划分窗口,多并行,一个分区会有多个组,当组内的数据达到指定条数的时候,这个组才会单独触发,之后会清零重新计数
--countWindow方法
nonKeyed Window
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<Integer> nums = lines.map(Integer::parseInt);
//按照条数划分窗口
//不KeyBY就划分窗口(NonKeyWindow)
AllWindowedStream<Integer, GlobalWindow> windowAll = nums.countWindowAll(5);
//对窗口中的数据进行运算
SingleOutputStreamOperator<Integer> res = windowAll.sum(0);
res.print();
env.execute();
}
keyed Window
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//spark,5
//spark,7
//hive,3
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndCount = lines.map(new MapFunction<String, Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> map(String line) throws Exception {
String[] fields = line.split(",");
return Tuple2.of(fields[0], Integer.parseInt(fields[1]));
}
});
//先keyBy
KeyedStream<Tuple2<String, Integer>, String> keyed = wordAndCount.keyBy(t -> t.f0);
//在划分窗口
WindowedStream<Tuple2<String, Integer>, String, GlobalWindow> window = keyed.countWindow(5);
//对窗口中的数据进行运算(调用WindowFunction)
SingleOutputStreamOperator<Tuple2<String, Integer>> sum = window.sum(1);
sum.print();
env.execute();
}
6.3 Time Window
6.3.0 总结
timeWindow
--按照时间来划分窗口,
--窗口触发,只会计算本窗口内的数据,不同窗口内的数据不会进行累加
1)滚动窗口:
基于processintTime的滚动窗口,
*non keyBy Window单并行,一般不用
*keyBy Window 多并行,keyed后会分区,区内有多个组,只要时间一到,每个分区内的所有 组的数据都会输出,
2)滑动窗口:
基于processintTime的滑动窗口
*传两个参数,一个是窗口的大小,一个是窗口滑动的时间间隔。假如一个10秒的窗口大小,每隔 20秒滑动一次
则数据是不断产生的,没间隔20秒,就会将一个10秒的窗口划过来,然后统计这个窗口内的数据 输出,之后等待20秒再次进行滑动
*non keyBy Window单并行,一般不用
*keyBy Window 多并行,区内有多个组,只要时间一到,每个分区内的所有组的数据都会输出,
3)会话窗口:
基于processintTime的会话窗口
*当数据一直产生的时候不会触发窗口,直到数据产生截至,经过指定的间隔时间,便会触发窗 口,将之前产生的数据统计计算
*nonkeyBy 单并行
*keyBy 每个组单独触发::假如spark一直产生,而flink输入10条停止,则间隔时间后,只有 flink会输出,而spark不会输出
6.3.1 滚动窗口
基于processintTime的滚动窗口,
*non keyBy Window单并行,一般不用
*keyBy Window 多并行,keyed后会分区,区内有多个组,只要时间一到,所有分区内的所有组的数据 都会输出,
non keyBy
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//1
//2
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<Integer> nums = lines.map(Integer::parseInt);
AllWindowedStream<Integer, TimeWindow> window = nums.windowAll(ProcessingTimeSessionWindows.withGap(Time.seconds(5)));
SingleOutputStreamOperator<Integer> sum = window.sum(0);
sum.print();
env.execute();
}
keyed
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//spark,5
//spark,7
//hive,3
DataStreamSource<String> lines = env.socketTextStream("linux01", 8888);
SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndCount = lines.map(new MapFunction<String, Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> map(String line) throws Exception {
String[] fields = line.split(",");
return Tuple2.of(fields[0], Integer.parseInt(fields[1]));
}
});
//先keyBy
KeyedStream<Tuple2<String, Integer>, String> keyed = wordAndCount.keyBy(t -> t.f0);
//在划分窗口
WindowedStream<Tuple2<String, Integer>, String, TimeWindow> window = keyed.window(ProcessingTimeSessionWindows.withGap(Time.seconds(5)));
window.sum(1).print();
env.execute();
}
6.3.2 滑动窗口
基于processintTime的滑动窗口
*传两个参数,一个是窗口的大小,一个是窗口滑动的时间间隔。假如一个10秒的窗口大小,每隔 20秒滑动一次
则数据是不断产生的,没间隔20秒,就会将一个10秒的窗口划过来,然后统计这个窗口内的数据 输出,之后等待20秒再次进行滑动
*non keyBy Window单并行,一般不用
*keyBy Window 多并行,区内有多个组,只要时间一到,每个分区内的所有组的数据都会输出,
non keyBy Window
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//1
//2
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<Integer> nums = lines.map(Integer::parseInt);
//AllWindowedStream<Integer, TimeWindow> window = nums.timeWindowAll(Time.seconds(10), Time.seconds(5));
AllWindowedStream<Integer, TimeWindow> window = nums.windowAll(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5)));
SingleOutputStreamOperator<Integer> sum = window.sum(0);
sum.print();
env.execute();
}
keyBy Window
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//spark,5
//spark,7
//hive,3
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndCount = lines.map(new MapFunction<String, Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> map(String line) throws Exception {
String[] fields = line.split(",");
return Tuple2.of(fields[0], Integer.parseInt(fields[1]));
}
});
//先keyBy
KeyedStream<Tuple2<String, Integer>, String> keyed = wordAndCount.keyBy(t -> t.f0);
//在划分窗口
WindowedStream<Tuple2<String, Integer>, String, TimeWindow> window = keyed.window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5)));
window.sum(1).print();
env.execute();
}
6.3.2 会话窗口
基于processintTime的会话窗口
*当数据一直产生的时候不会触发窗口,直到数据产生截至,经过指定的间隔时间,便会触发窗 口,将之前产生的数据统计计算
*nonkeyBy 单并行
*keyBy 每个组单独触发::假如spark一直产生,而flink输入10条停止,则间隔时间后,只有 flink会输出,而spark不会输出
无 keyBy
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//1
//2
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<Integer> nums = lines.map(Integer::parseInt);
//AllWindowedStream<Integer, TimeWindow> window = nums.timeWindowAll(Time.seconds(10));
AllWindowedStream<Integer, TimeWindow> window = nums.windowAll(TumblingProcessingTimeWindows.of(Time.seconds(10)));
SingleOutputStreamOperator<Integer> sum = window.sum(0);
sum.print();
env.execute();
}
有 keyBy
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//spark,5
//spark,7
//hive,3
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndCount = lines.map(new MapFunction<String, Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> map(String line) throws Exception {
String[] fields = line.split(",");
return Tuple2.of(fields[0], Integer.parseInt(fields[1]));
}
});
//先keyBy
KeyedStream<Tuple2<String, Integer>, String> keyed = wordAndCount.keyBy(t -> t.f0);
//在划分窗口
WindowedStream<Tuple2<String, Integer>, String, TimeWindow> window = keyed.window(TumblingProcessingTimeWindows.of(Time.seconds(10)));
window.sum(1).print();
env.execute();
}
6.4 基于eventTime的时间窗口
数据中携带着数据的产生时间 (需要设置时间标准为enentTime)
要根据数据所携带的时间来算,而不再是系统的时间
**waterMark:水位线,flink中窗口触发的机制
6.4.1 滚动窗口
--当设置时间间隔为5000的时候,则窗口为5000的整数倍0-5000、5000-10000、10000-15000
--新老版本API差别:只是触发时间相差了1毫秒
假如时间间隔是5000
则老版本到4999就会触发,计算的时候只计算到4999
而新版本到4999不会触发,大于4999才会触发,计算的时候同样只计算到4999
延迟时间:
*当设置延迟时间为0的时候:
若数据输入的比较理想,按照时间顺序进到flink,则自然没问题
若数据输入的时候乱序,假设时间间隔为5000,第一个数据进来的时间就是6000,则窗口直接触 发,至于4000、1000这些数据则会丢失,不会再进到下一个窗口
*当设置延迟时间为2000的时候,
若数据输入的时候乱序,假设时间间隔为5000,第一个数据进来的时间就是6000,则不会触发窗口,等到7000才会触发窗口,那么在这2毫秒内可以给3000的数据一个机会等他进来
所以此时到7000才会触发,但计算的还是0-5000的数据,仅是延迟触发
老的API
不keyBy 单并行
输入数据:1000,1
2000,1
4999,1 触发
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> lines = env.socketTextStream("linux01", 8888);
//老版本的API,首先要设置时间标准(时间特征)
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
//提取数据中的EventTime
//提取完EventTime生成Watermark后,对应的DataStream的数据类型和数据格式没有改变
SingleOutputStreamOperator<String> streamAndWatermarks = lines.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.seconds(0)) {
@Override
public long extractTimestamp(String element) {
return Long.parseLong(element.split(",")[0]);
}
});
//对数据进行map去除时间字段
SingleOutputStreamOperator<Integer> word = streamAndWatermarks.map(new MapFunction<String, Integer>() {
@Override
public Integer map(String value) throws Exception {
return Integer.parseInt(value.split(",")[1]);
}
});
//进行开窗 时隔5秒窗口触发
AllWindowedStream<Integer, TimeWindow> windowed = word.windowAll(TumblingEventTimeWindows.of(Time.seconds(5)));
//开窗后,窗口内进行聚合
SingleOutputStreamOperator<Integer> sumed = windowed.sum(0);
sumed.print();
env.execute();
}
有keyBy 多并行
//需要每个分区的数据都达到触发时间的时候才会触发窗口
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
//设置并行度为4
// env.setParallelism(4);
DataStreamSource<String> lines = env.socketTextStream("linux01", 8888);
SingleOutputStreamOperator<Tuple3<Long, String, Integer>> tp3 = lines.map(new MapFunction<String, Tuple3<Long, String, Integer>>() {
@Override
public Tuple3<Long, String, Integer> map(String value) throws Exception {
String[] arr = value.split(",");
return Tuple3.of(Long.parseLong(arr[0]), arr[1], Integer.parseInt(arr[2]));
}
}).setParallelism(2);
//提取时间字段,创建waterMark 这里设置了延迟时间为2秒
SingleOutputStreamOperator<Tuple3<Long, String, Integer>> waterMark = tp3.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<Tuple3<Long, String, Integer>>(Time.seconds(0)) {
@Override
public long extractTimestamp(Tuple3<Long, String, Integer> element) {
return element.f0;
}
});
//先分区。再开窗
KeyedStream<Tuple3<Long, String, Integer>, Tuple> keyed = waterMark.keyBy(1);
WindowedStream<Tuple3<Long, String, Integer>, Tuple, TimeWindow> windowed = keyed.window(TumblingEventTimeWindows.of(Time.seconds(5)));
windowed.sum(2).print();
env.execute();
}
新的API–keyed
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//窗口的长度为5秒
//[0, 4999], [5000, 9999], [10000, 14999]
//1000,spark,5
//2000,spark,7
//3000,hive,3
DataStreamSource<String> lines = env.socketTextStream("linux01", 8888);
//tp3DataStream并行度为2,这个流中有水位线吗?(没有)
SingleOutputStreamOperator<Tuple3<Long, String, Integer>> tp3DataStream = lines.map(new MapFunction<String, Tuple3<Long, String, Integer>>() {
@Override
public Tuple3<Long, String, Integer> map(String line) throws Exception {
String[] fields = line.split(",");
return Tuple3.of(Long.parseLong(fields[0]), fields[1], Integer.parseInt(fields[2]));
}
}).setParallelism(2);
//对多个并行的DataStream提取EventTime生成WaterMark
//有WaterMark的DataStream并行度是多少? 2 ,因为tp3DataStream并行度为2
SingleOutputStreamOperator<Tuple3<Long, String, Integer>> streamWithWaterMark = tp3DataStream
.assignTimestampsAndWatermarks(WatermarkStrategy.<Tuple3<Long, String, Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(2)).withTimestampAssigner(new SerializableTimestampAssigner<Tuple3<Long, String, Integer>>() {
@Override
public long extractTimestamp(Tuple3<Long, String, Integer> tp, long l) {
//Tuple3(1000,spark,5)
return tp.f0;
}
}));
KeyedStream<Tuple3<Long, String, Integer>, String> keyedStream = streamWithWaterMark.keyBy(t -> t.f1);
//在划分窗口
WindowedStream<Tuple3<Long, String, Integer>, String, TimeWindow> window = keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)));
window.sum(2).print();
env.execute();
}
6.4.2 滑动窗口
设窗口长度为10毫秒,每5毫秒滑动一次(滑动的步长亦为5毫秒)
--每隔5毫秒,便会将长度为10毫秒的窗口进行滑动5毫秒
--窗口的起始时间一定是步长的整数倍
--假设窗口长度为10秒,2秒滑动一次
则当第一个输入的为3的时候,第一个窗口为[-6,4)、[-4,6]、[-2,8)...
当第一个输入的为6的时候,第一个窗口为[-4,6)、[6,16]、[16,26)...
当第一个输入的为20的时候,第一个窗口为[10,20)、[20,30)...
当第一个输入的为13的时候,第一个窗口为[4,14),[14,24)
...
以新API—keyed为例
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//窗口的长度为5秒
//[0, 4999], [5000, 9999], [10000, 14999]
//1000,spark,5
//2000,spark,7
//3000,hive,3
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
//tp3DataStream并行度为2,这个流中有水位线吗?(没有)
SingleOutputStreamOperator<Tuple3<Long, String, Integer>> tp3DataStream = lines.map(new MapFunction<String, Tuple3<Long, String, Integer>>() {
@Override
public Tuple3<Long, String, Integer> map(String line) throws Exception {
String[] fields = line.split(",");
return Tuple3.of(Long.parseLong(fields[0]), fields[1], Integer.parseInt(fields[2]));
}
}).setParallelism(1);
//对多个并行的DataStream提取EventTime生成WaterMark
//有WaterMark的DataStream并行度是多少? 2 ,因为tp3DataStream并行度为2
SingleOutputStreamOperator<Tuple3<Long, String, Integer>> streamWithWaterMark = tp3DataStream.assignTimestampsAndWatermarks(WatermarkStrategy.<Tuple3<Long, String, Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(0)).withTimestampAssigner(new SerializableTimestampAssigner<Tuple3<Long, String, Integer>>() {
@Override
public long extractTimestamp(Tuple3<Long, String, Integer> tp, long l) {
//Tuple3(1000,spark,5)
return tp.f0;
}
}));
KeyedStream<Tuple3<Long, String, Integer>, String> keyedStream = streamWithWaterMark.keyBy(t -> t.f1);
//在划分窗口
WindowedStream<Tuple3<Long, String, Integer>, String, TimeWindow> window = keyedStream.window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5)));
window.sum(2).print();
env.execute();
}
6.4.3 会话窗口
当数据的时间间隔超过设定的时间间隔的时候,便会触发窗口
比如:若设定时间间隔为5秒,则
1秒,
3秒,
5秒,
8秒,
13秒 此时便会触发
假如materMark是多并行的,同样需要每个分区都达到触发窗口的时候才会触发窗口
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//窗口的长度为5秒
//[0, 4999], [5000, 9999], [10000, 14999]
//1000,spark,5
//2000,spark,7
//3000,hive,3
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
//tp3DataStream并行度为2,这个流中有水位线吗?(没有)
SingleOutputStreamOperator<Tuple3<Long, String, Integer>> tp3DataStream = lines.map(new MapFunction<String, Tuple3<Long, String, Integer>>() {
@Override
public Tuple3<Long, String, Integer> map(String line) throws Exception {
String[] fields = line.split(",");
return Tuple3.of(Long.parseLong(fields[0]), fields[1], Integer.parseInt(fields[2]));
}
}).setParallelism(1);
SingleOutputStreamOperator<Tuple3<Long, String, Integer>> streamWithWaterMark = tp3DataStream.assignTimestampsAndWatermarks(WatermarkStrategy.<Tuple3<Long, String, Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(0)).withTimestampAssigner(new SerializableTimestampAssigner<Tuple3<Long, String, Integer>>() {
@Override
public long extractTimestamp(Tuple3<Long, String, Integer> tp, long l) {
//Tuple3(1000,spark,5)
return tp.f0;
}
}));
KeyedStream<Tuple3<Long, String, Integer>, String> keyedStream = streamWithWaterMark.keyBy(t -> t.f1);
//在划分窗口
WindowedStream<Tuple3<Long, String, Integer>, String, TimeWindow> window = keyedStream.window(EventTimeSessionWindows.withGap(Time.seconds(5)));
window.sum(2).print();
env.execute();
}
6.5 开窗后的函数
6.5.1 使用聚合函数
*在窗口内使用聚合函数
--会在窗口内进行聚合,输出结果。。max min maxBy minBy sum
若在窗口内进行了keyBy再聚合,则会在同一个窗口中,同一个分区同一个组的数据进行增量聚合,待到窗口触发的时候,便会将结果输出
--在窗口内进行其他的操作
对窗口调用apply方法,就是将窗口内的数据攒起来,然后窗口触发再执行运算逻辑
可以对数据进行聚合操作,也可以将数据保存到hdfs中等操作,但此时是全量聚合,先将窗口内的数据攒起来,当窗口触发的时候,会将这个窗口内的数据进行计算
聚合函数以Reduce为例
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//窗口的长度为5秒
//[0, 4999], [5000, 9999], [10000, 14999]
//1000,spark,5
//2000,spark,7
//3000,hive,3
DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
//tp3DataStream并行度为2,这个流中有水位线吗?(没有)
SingleOutputStreamOperator<Tuple3<Long, String, Integer>> tp3DataStream = lines.map(new MapFunction<String, Tuple3<Long, String, Integer>>() {
@Override
public Tuple3<Long, String, Integer> map(String line) throws Exception {
String[] fields = line.split(",");
return Tuple3.of(Long.parseLong(fields[0]), fields[1], Integer.parseInt(fields[2]));
}
}).setParallelism(1);
//对多个并行的DataStream提取EventTime生成WaterMark
//有WaterMark的DataStream并行度是多少? 2 ,因为tp3DataStream并行度为2
SingleOutputStreamOperator<Tuple3<Long, String, Integer>> streamWithWaterMark = tp3DataStream
.assignTimestampsAndWatermarks(
WatermarkStrategy.<Tuple3<Long, String, Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(0))
.withTimestampAssigner((tp, l) -> tp.f0)
);
KeyedStream<Tuple3<Long, String, Integer>, String> keyedStream = streamWithWaterMark.keyBy(t -> t.f1);
//在划分窗口
WindowedStream<Tuple3<Long, String, Integer>, String, TimeWindow> window = keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)));
SingleOutputStreamOperator<Tuple3<Long, String, Integer>> reduced = window.reduce(new ReduceFunction<Tuple3<Long, String, Integer>>() {
@Override
public Tuple3<Long, String, Integer> reduce(Tuple3<Long, String, Integer> tp1, Tuple3<Long, String, Integer> tp2) throws Exception {
tp1.f2 = tp1.f2 + tp2.f2;
return tp1;
}
});
reduced.print();
env.execute();
}
6.5.2 开窗后进行其他操作
/*
//对窗口调用apply方法,就是将窗口内的数据攒起来,然后窗口触发后再进行运算
* @param key KeyBy的条件
* @param window Window对象,可以获取窗口的一些信息
* @param input 缓存在窗口中的数据(WindowState中,是集合)
* @param out 使用collector输出数据
可以再apply方法中将数据攒起来,进行其他操作
*/
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
DataStreamSource<String> lines = env.socketTextStream("linux01", 8888);
SingleOutputStreamOperator<Tuple3<Long, String, Integer>> tp3DataStream = lines.map(new MapFunction<String, Tuple3<Long, String, Integer>>() {
@Override
public Tuple3<Long, String, Integer> map(String line) throws Exception {
String[] fields = line.split(",");
return Tuple3.of(Long.parseLong(fields[0]), fields[1], Integer.parseInt(fields[2]));
}
}).setParallelism(1);
SingleOutputStreamOperator<Tuple3<Long, String, Integer>> streamWithWaterMark = tp3DataStream
.assignTimestampsAndWatermarks(
WatermarkStrategy.<Tuple3<Long, String, Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(0))
.withTimestampAssigner((tp, l) -> tp.f0)
);
KeyedStream<Tuple3<Long, String, Integer>, String> keyedStream = streamWithWaterMark.keyBy(t -> t.f1);
//在划分窗口
WindowedStream<Tuple3<Long, String, Integer>, String, TimeWindow> window = keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)));
//对窗口调用apply方法,就是将窗口内的数据攒起来,然后窗口触发后再进行运算
window.apply(new WindowFunction<Tuple3<Long, String, Integer>, Tuple2<String, Integer>, String, TimeWindow>() {
/**
*
* @param key KeyBy的条件
* @param window Window对象,可以获取窗口的一些信息
* @param input 缓存在窗口中的数据(WindowState中,是集合)
* @param out 使用collector输出数据
* @throws Exception
*/
@Override
public void apply(String key, TimeWindow window, Iterable<Tuple3<Long, String, Integer>> input, Collector<Tuple2<String, Integer>> out) throws Exception {
// System.out.println("appy方法被调用了!!!!");
// for (Tuple3<Long, String, Integer> tp : input) {
// out.collect(Tuple2.of(tp.f1, tp.f2));
// }
int count = 0;
for (Tuple3<Long, String, Integer> tp : input) {
count += tp.f2;
}
out.collect(Tuple2.of(key, count));
}
}).print();
env.execute();
}
七、算子链
forward:在同一个机器的同一个分区,将数据传到后一个task
1)使用算子连:可以减少线程切换、减少缓冲、降低延迟、提高效率。
2)算子连可以禁用,但不会执行这个操作,明显禁用算子连很愚蠢
-->disableOperaterChain
3)还可以开启调用算子,开启一条新的算子链:
比如flatMap、filter、map本来是一条链,假如在flatMap后调用startNewChain,便会将flatMap和filter之间的算子连断开
-->startNewChaining
4)还可以在某个算子后调用disAbleChaining,这样会将这个算子的前后链都断开
-->disableChaining
/*
Author: Tao.W
D a te: 2021/8/24
Description:
在StreamExecutionEnvironment禁用算子链
禁用算子链非常愚蠢,几乎不使用
当关闭算子链的时候,所有算子之间的链都会断开 会造成很多的task和subTask
整体:socket-->flatMap-->filter-->map-->keyBY-->sum-->print
并行度:1 8 8 8 8 8 8
socket和flatMap 之间会进行rebalance
flatMap--filter--map 之间不用重新分区,之间是forward
map 和 keyBy 之间会进行hash重新分区
keyBy --sum -- print 之间是forward
*/
public class DisableOperatorChaining {
public static void main(String[] args) throws Exception{
StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
//在StreamExecutionEnvironment禁用算子链 整个job都禁用算子链
//禁用算子链非常愚蠢,几乎不使用
//env.disableOperatorChaining();
DataStreamSource<String> lines = env.socketTextStream("linux01", 8888);
SingleOutputStreamOperator<String> words = lines.flatMap(new FlatMapFunction<String, String>() {
@Override
public void flatMap(String line, Collector<String> collector) throws Exception {
for (String word : line.split(" ")) {
collector.collect(word);
}
}
});
SingleOutputStreamOperator<String> filtered = words.filter(new FilterFunction<String>() {
@Override
public boolean filter(String word) throws Exception {
return word.startsWith("h");
}
}).startNewChaining() //开启新链
.disableChaining() //断开算子链
;
SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndOne = filtered.map(new MapFunction<String, Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> map(String word) throws Exception {
return Tuple2.of(word, 1);
}
});
wordAndOne.keyBy(t -> t.f0).sum(1).print();
env.execute();
}
}
八、taskSlot
(0)
在flink中由三个概念: task 、subtask 、 taskslot
task相当于spark中的taskSet,里面都是相同的task,而task的个数即为分区的个数
subTask即相当于spark中的task,是最小的执行单元
taskSlot的个数即分区的个数,来自不同task中的subtask在slot中执行。
例如:
Task-1 Task-2 TaskSlot-1 TaskSlot-2
subtask-a-1 subtask-b-1 subtask-a-1 subtask-a-2
subtask-a-2 subtask-b-2 subtask-b-1 subtask-b-2
并行度为2:TaskSlot-1和TaskSlot-2
在spark中:
TaskSet-1 -shuffle- TaskSet-2 executor-分区1 executor-分区2
task-a-1 task-b-1 task-a-1 task-a-2
task-a-2 task-b-2 task-b-1 task-b-2
同一个task中的subtask,逻辑是相同的,只是处理的数据不同,task-a-1中的1代表读取第一个分区的数据
(1)
每个Worker都会有一个TaskManager
每个TaskManager会有一致多个 task slot (task槽),worker会将内存资源进行等分给slot,而不会分配cpu
每个taskSlot 会运行多个subTask
task槽的个数在集群中可以进行设置,如果实在本地运行,则默认是cpu的逻辑核数
(2)
来自同一个job的不同task的不同subTask可以共享资源槽,而同一个task的subTask不能共享资源槽
--据此:假如某个subTask计算的数据是密集型的,需要占大量的内存,那么就可以将他单独的放到一 个槽中,不影响整体的执行效率
--slotSharingGroup,将对应的subTask打上槽的标签(名字);默认情况下,资源槽的名称为 default,若调用slotSharingGroup修改名称,则槽的名称会改变
:假设某个算子调用slotSharingGroup,并将槽名称改为“doit”,则该算子会进到其他的槽,并将 那个槽名改为doit
:与此同时,该算子下面的算子链所对应的槽也会改名为doit
:但是只是想将该算子单独放到一个槽,所以需要将他之后的算子的槽的名称再改为default
(3)
我的集群中的逻辑核数是8,每个worker是4,所以可达到的job并行度为8,所以taskSolt有8个,即有8个task槽
有了“来自同一个job的不同task的不同subTask可以共享资源槽”后,就可以实现有几个槽就可以有几个并行度
(4)
假设集群资源一共4核,所以4个taskSlot,每个worker2个
现在开启一个job,设置并行度为3,其中的filter算子单独拿出来放到一个槽中,
则此时程序无法运行,因为资源不够,并行度为3,又将filter单独放大一个槽,则现在需要6个槽才可以,但最多只有4个槽
将job的并行度改为2,则此时需要4个槽,便可以运行了
/**
* 设置共享资源槽
* 当·程序中某个算子需要占大量内存的时候,
* 比如filter或者在内存中进行数据的排序,此时可以将这个算子单独放到一个槽中让其运行
*/
public static void main(String[] args) throws Exception{
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStreamSource<String> lines = env.socketTextStream(args[0], 8888);
SingleOutputStreamOperator<String> words = lines.flatMap(new FlatMapFunction<String, String>() {
@Override
public void flatMap(String line, Collector<String> collector) throws Exception {
for (String word : line.split(" ")) {
collector.collect(word);
}
}
});
SingleOutputStreamOperator<String> filtered = words.filter(new FilterFunction<String>() {
@Override
public boolean filter(String word) throws Exception {
return word.startsWith("h");
}
}).slotSharingGroup("doit"); //将DataStream对应的subtask打上标签
//默认情况,资源槽的名称为default,如果调用slotSharingGroup设置了名从,对应的subtask的共享资源槽的名称就改变了
//不同资源槽名称的subtask不能进入到同一个槽位中
//slotSharingGroup该方法调用后,后面的算子的共享资源槽名称都跟随着改变(就近跟随原则)
SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndOne = filtered.map(new MapFunction<String, Tuple2<String, Integer>>() {
@Override
public Tuple2<String, Integer> map(String word) throws Exception {
return Tuple2.of(word, 1);
}
}).slotSharingGroup("default");
wordAndOne.keyBy(t -> t.f0).sum(1).print();
env.execute();
}