我正在参与掘金创作者训练营第5期, 点击了解活动详情
flink最主要的部分就是flink的各种不同的算子,在flink开发的过程中,如何使用和选择不同的算子是能不能够充分的利用flink的高性能的重中之重。
DataStream算子
DataStream算子的概念是用户可以通过一个或多个 DataStream转换为新的dataStream。算子的的处理维度是在于streaming,而不是enent,也就是对于流进行处理。
Transform转换算子
转换算子有 map,flatMap ,fliter,keyBy,reduce等等。如果有java8的lambda的使用经验的话,对这些算子的用法都能有一个基础的了解。
1.map
dataStream的每一个元素都会转换为另外一个元素
简单写个示例代码
DataStream<SensorReading> avgTemp = sensorData
//近似于lambda里面的map
.map( r -> new SensorReading(r.id, r.timestamp, (r.temperature - 32) * (5.0 / 9.0)))
简单看一下dataStream的api的源码:
/**
* 提供一个 Map 转换 DataStream. 该dataStream的每一个元素都会调用一个MapFunction 。每个MapFunction 返回一个元素。
*/
public <R> SingleOutputStreamOperator<R> map(MapFunction<T, R> mapper) {
TypeInformation<R> outType =
TypeExtractor.getMapReturnTypes(
clean(mapper), getType(), Utils.getCallLocationName(), true);
return map(mapper, outType);
}
2.flatMap
将dataStream中的每一个元素转换为0...n个元素。
示例代码:拆解传入的元素
// 将每个传感器的字符串id拆分为前缀“传感器”和传感器编号
DataStream<String> splitIds = sensorIds
.flatMap((FlatMapFunction<String, String>)
(id, out) -> { for (String s: id.split("_")) { out.collect(s);}})
.returns(Types.STRING);
源码的介绍 :
/**
方法 在DataStream上应用flatMap转换。转换会调用
FlatMapFunction 用于数据流的每个元素。每个FlatMapFunction调用可以返回任意数量的元素,包括0到n个
*/
public <R> SingleOutputStreamOperator<R> flatMap(FlatMapFunction<T, R> flatMapper) {
TypeInformation<R> outType =
TypeExtractor.getFlatMapReturnTypes(
clean(flatMapper), getType(), Utils.getCallLocationName(), true);
return flatMap(flatMapper, outType);
}
3.filter
将dataStream中符合条件的元素过滤出来
示例代码:过滤出25度以下的温度出来
DataStream<SensorReading> filteredReadings = readings
.filter(r -> r.temperature >= 25);
源码的介绍:
/**
*对dataStream应用筛选器转换。转换调用FilterFunction,保留数据流的每个元素
过滤判断函数返回false的元素,返回判断条件true的。
*/
public SingleOutputStreamOperator<T> filter(FilterFunction<T> filter) {
return transform("Filter", getType(), new StreamFilter<>(clean(filter)));
}
4.reduce
聚合计算,最后聚合成一个元素
示例代码:聚合传感器传来的最高温度和当前的时间
DataStream<SensorReading> maxTempPerSensor = keyed
.reduce((r1, r2) -> {
if (r1.temperature > r2.temperature) {
return r1;
} else {
return r2;
}
});
源码的介绍:
/**
对按给定key分组的分组数据流应用reduce转换位置。 ReduceFunction将接收基于key的输入值。只有
具有相同键的输入值将转到相同的聚合reducer。
*/
public SingleOutputStreamOperator<T> reduce(ReduceFunction<T> reducer) {
ReduceTransformation<T, KEY> reduce =
new ReduceTransformation<>(
"Keyed Reduce",
environment.getParallelism(),
transformation,
clean(reducer),
keySelector,
getKeyType());
getExecutionEnvironment().addOperator(reduce);
return new SingleOutputStreamOperator<>(getExecutionEnvironment(), reduce);
}
5.keyby
逻辑上将流分区为不相交的分区。具有相同Keys的所有记录都分配给同一分区,此转换返回KeyedStream。
示例代码:根据传感器id分组
// group sensor readings by sensor id
KeyedStream<SensorReading, String> keyed = readings
.keyBy(r -> r.id);
源码的介绍:
/**
* 创建了一个新的KeyedStream,使用提供的key对其运算符状态进行分区。
*/
public <K> KeyedStream<T, K> keyBy(KeySelector<T, K> key) {
Preconditions.checkNotNull(key);
return new KeyedStream<>(this, clean(key));
}
window算子
window算子就属于flink非常特别的部分:
window(keyedStream->windowStream)下面例子是一个每5秒钟进行滚动的流,不重合的window可以在后面再做聚合计算。
DataStream<SensorReading> avgTemp = sensorData
// group readings in 1 second windows
.window( TumblingEventTimeWindows.of(Time.seconds(5)))
// compute average temperature using a user-defined function
.apply(new TemperatureAverager());
window算子处理完以后可以把keyby流或者data流转换为window流,那么具体计算时候怎么使用window流呢,只要利用apply() 函数就可以直接转换了,比如下文就把传入的kafka数据首先进行一个2秒的滚动窗口聚合,然后再进行CreditTuple1WindowFunction() 的转换,最后利用SingleOutputStreamOperator来进行侧边流的合并广播。
SingleOutputStreamOperator<Tuple1<String>> singleOutputStreamOperator =
streamSource.windowAll(TumblingProcessingTimeWindows.of(Time.seconds(2L)))
.apply(new CreditTuple1WindowFunction());
//事件流和广播的配置流连接,形成BroadcastConnectedStream
BroadcastConnectedStream<Tuple1<String>, String> connectedStream =
singleOutputStreamOperator.connect(broadcastConfigStream);
windos流和keyedstream以及datastream的转换方法如上图所示。
partition算子
flink提供了partition算子让用户在数据转换完成后需要对数据分区进行更细粒度的配置的时候,有了非常好的选择。
@Deprecated
public <K> DataStream<T> partitionCustom(Partitioner<K> partitioner, int field) {
Keys.ExpressionKeys<T> outExpressionKeys =
new Keys.ExpressionKeys<>(new int[] {field}, getType());
return partitionCustom(partitioner, outExpressionKeys);
}