DataStream算子转换过程

DataStream表示为同一种类型的数据流,用来描述业务转换逻辑。通过转换操作,一个DataStream可以被转换成另一个新的DataStream。

DataStream中有2个成员变量:

// 流程序执行的上下文环境
protected final StreamExecutionEnvironment environment;

// 当前DataStream的上一次转换操作(存于StreamExecutionEnvironment的List集合中)
// 即:通过该Transformation,生成当前的DataStream
protected final Transformation<T> transformation;

transformation是当前DataStream的上一次转换操作,得益于transformation,才能转换出当前DataStream。每当调用算子进行转换操作都会产生对应的xxxTransformation,StreamExecutionEnvironment会将这个xxxTransformation添加到List<Transformation<?>>集合中,以便用来构建Pipeline拓扑。(流式作业对应的Pipeline实现类是StreamGraph,批作业对应的Pipeline实现类是Plan)

例如使用DataStream.map()转换时,会在内部创建生成StreamMap算子,同时会把MapFunction对象传入,MapFunction就是我们的数据处理逻辑。StreamMap算子就是AbstractUdfStreamOperator的子类,传入的MapFunction对象会赋值给AbstractUdfStreamOperator中的userFunction变量持有。如此一来,StreamMap算子就持有了MapFunction。

– 底层对应的就是OneInputTransformation转换:

/**
 * 将MapFunction作为参数传入
 */
streamMap.map(new MapFunction<String, JSONObject>() {...});

public <R> SingleOutputStreamOperator<R> map(MapFunction<T, R> mapper) {
    // 获取本次map转换的输出类型
    TypeInformation<R> outType = TypeExtractor.getMapReturnTypes(clean(mapper), getType(),
                                                                 Utils.getCallLocationName(), true);
    return map(mapper, outType);
}

/**
 * 自定义的MapFunction会作为参数,用来构建StreamMap,即StreamOperator的子类
 */
public <R> SingleOutputStreamOperator<R> map(MapFunction<T, R> mapper, TypeInformation<R> outputType) {
    // 参数:算子name,输出类型,StreamOperator的factory
    return transform("Map", outputType, new StreamMap<>(clean(mapper)));
}

有了StreamMap算子和代表数据处理逻辑的MapFunction之后,算子name、本次转换的输出类型、StreamMap算子会作为参数,参与本次的转换:

@PublicEvolving
public <R> SingleOutputStreamOperator<R> transform(
    String operatorName, // outTypeInfo:当前算子的输出类型
    TypeInformation<R> outTypeInfo, // 本次转换的输出类型
    // StreamMap算子继承AbstractUdfStreamOperator,实现OneInputStreamOperator接口
    OneInputStreamOperator<T, R> operator) {

    // SimpleOperatorFactory.of(operator):根据StreamOperator的类型,创建StreamOperatorFactory
    return doTransform(operatorName, outTypeInfo, SimpleOperatorFactory.of(operator));
}

protected <R> SingleOutputStreamOperator<R> doTransform(
    String operatorName,
    TypeInformation<R> outTypeInfo,
    // StreamOperatorFactory:创建StreamOperator的工厂
    StreamOperatorFactory<R> operatorFactory) {

    // 保险起见,确保不会出现InvalidTypesException,保证本次的转换操作不会出问题
    transformation.getOutputType();

    // 创建本次转换所对应的xxxTransformation实例
    OneInputTransformation<T, R> resultTransform = new OneInputTransformation<>(
        this.transformation, // 上一次的Transformation转换操作
        operatorName, // 当前算子的name
        operatorFactory, // MapFunction-->StreamMap -->StreamOperatorFactory
        outTypeInfo, // 当前算子的输出类型
        // 当前算子的并行度,默认为env全局的并行度
        environment.getParallelism());

    // SingleOutputStreamOperator:每次转换操作完毕后,返回给开发者继续操作的数据结构
    @SuppressWarnings({"unchecked", "rawtypes"})
    SingleOutputStreamOperator<R> returnStream = new SingleOutputStreamOperator(environment, resultTransform);

    // 将这个xxxTransformation添加到List<Transformation>列表中,它会被用来生成StreamGraph
    getExecutionEnvironment().addOperator(resultTransform);

    return returnStream;
}

转换时,首先要确保不会出现InvalidTypesException,不然后面的转换就会出问题。StreamOperatorFactory持有StreamMap算子(也就是StreamOperator),StreamMap持有MapFunction。现在要根据StreamOperatorFactory来创建OneInputTransformation,换言之,MapFunction、StreamMap算子都会被封装到这个OneInputTransformation中。当然,也会创建出SingleOutputStreamOperator,作为本次转换结束之后返回给开发者继续操作的数据结构,下游算子可以用它继续进行转换操作。

SingleOutputStreamOperator的本质就是DataStream,在构建它时会调用父类DataStream的构造方法,将这个StreamMap对应的OneInputTransformation赋值给DataStream的成员变量。对于“下一个StreamOperator”而言,它自然就成了所谓的“上一次Transformation”

public DataStream(StreamExecutionEnvironment environment, Transformation<T> transformation) {
		this.environment = Preconditions.checkNotNull(environment, "Execution Environment must not be null.");
		/**
		 * 调用DataStream提供的转换方法来生成对应的StreamOperator后,会根据这个StreamOperator来创建对应的Transformation、返回给开发者继续使用的SingleOutputStreamOperator。
		 * SingleOutputStreamOperator的本质就是DataStream,在构建SingleOutputStreamOperator时就会调用DataStream(父类)的构造,
		 * 将本次转换操作对应的Transformation交给DataStream的成员变量持有,它自然就成了“下一个转换”的上一次Transformation
		 */
		this.transformation = Preconditions.checkNotNull(transformation, "Stream Transformation must not be null.");
	}

map转换生成的OneInputTransformation,会被add到StreamExecutionEnvironment内的 List<Transformation<?>>集合中,后续会根据这个List集合来生成StreamGraph(即用来描述业务处理逻辑的Pipeline)。

在DataStream的转换过程中,不管是哪种类型的转换操作,都是按照以下的程序进行的:

  • 将开发者自定义的xxxFunction封装到(对应)创建好的StreamOperator中
  • 基于StreamOperator构建xxxTransformation
  • 将xxxTransformation添加到StreamExecutionEnvironment的List集合中,用来生成StreamGraph
  • 基于StreamGraph,先后生成JobGraph、ExecutionGraph,申请Slot资源并调度、执行…

Transformation持有StreamOperatorFactory,StreamOperatorFactory持有StreamOperator,StreamOperator持有xxxFunction。正是因为这种持有关系,Transformation才能表达DataStream之间的转换关系,因为xxxFunction中定义的就是数据处理逻辑

### Flink 常用转换算子列表及使用示例 #### 1. **映射 (Map)** `map()` 是一种一对一的操作,用于将输入流中的每个元素通过指定函数进行转换。通常适用于简单的数据结构变换。 ```java DataStream<Integer> input = ...; DataStream<String> mapped = input.map(value -> value.toString()); ``` 此操作会对 `input` 中的每个整数调用 `toString()` 方法,并返回一个新的 `DataStream` 实例[^1]。 --- #### 2. **扁平化映射 (FlatMap)** `flatMap()` 类似于 `map()`,但它允许将单个输入元素拆分为零个、一个或多个输出元素。常用于复杂的数据结构调整场景。 ```java DataStream<String> lines = ...; DataStream<String> words = lines.flatMap((String line, Collector<String> out) -> { for (String word : line.split(" ")) { out.collect(word); } }); ``` 这里每行字符串被分割成单词序列,并逐一收集到输出流中。 --- #### 3. **过滤 (Filter)** `filter()` 用于筛选符合条件的记录,保留满足条件的元素。 ```java DataStream<Integer> numbers = ...; DataStream<Integer> filtered = numbers.filter(value -> value % 2 == 0); ``` 该例子仅保留偶数值[^2]。 --- #### 4. **分组 (KeyBy)** `keyBy()` 将数据按照某个键值进行分区,使得具有相同键的元素会被路由到同一并行任务中处理。 ```java DataStream<Tuple2<String, Integer>> data = ...; KeyedStream<Tuple2<String, Integer>, String> keyed = data.keyBy(value -> value.f0); ``` 此处按元组的第一个字段 (`f0`) 对数据进行了分组[^3]。 --- #### 5. **聚合 (Reduce)** `reduce()` 可以对一组具有相同 key 的元素执行累积运算,最终得到单一的结果。 ```java KeyedStream<Tuple2<String, Integer>, String> keyedData = ...; DataStream<Tuple2<String, Integer>> reduced = keyedData.reduce( (value1, value2) -> Tuple2.of(value1.f0, value1.f1 + value2.f1) ); ``` 这段代码实现了基于键值的累加操作。 --- #### 6. **联合 (Union)** `union()` 合并两个或多条 `DataStream` 流,形成一条新流。注意它不会移除重复项。 ```java DataStream<Integer> stream1 = ...; DataStream<Integer> stream2 = ...; DataStream<Integer> unioned = stream1.union(stream2); ``` 两条独立流在此处合并为一条统一的新流[^1]。 --- #### 7. **连接 (Connect)** 不同于 `union()`,`connect()` 不会简单地混合两股流,而是保持各自的身份不变,以便后续协同处理。 ```java SingleOutputStreamOperator<Integer> streamA = ...; SingleOutputStreamOperator<Long> streamB = ...; ConnectedStreams<Integer, Long> connected = streamA.connect(streamB); ``` 这种设计适合需要跨不同类型数据协作的应用程序[^1]。 --- #### 8. **重新平衡 (Rebalance & Rescale)** - `rebalance()` 和 `rescale()` 都是用来调整数据分布的方式,但两者具体行为有所差异。 - `rebalance()` 平均分配给所有下游任务;而 `rescale()` 则只与部分下游任务建立联系。 ```java // Rebalance Example DataStream<Integer> rebalanced = original.rebalance(); // Rescale Example DataStream<Integer> rescaled = original.rescale(); ``` 这些方法有助于优化负载均衡性能[^5]。 --- #### 9. **广播 (Broadcast)** `broadcast()` 把当前流的所有数据复制一份发往下游每一个平行实例。 ```java DataStream<Integer> broadcasted = original.broadcast(); ``` 这种方式特别适配全局配置更新等场合。 --- #### 10. **自定义分区 (PartitionCustom)** 当内置分区方式无法满足需求时,可借助 `partitionCustom()` 定义个性化规则。 ```java DataStream<Integer> customPartitioned = original.partitionCustom(new Partitioner<Integer>() { @Override public int partition(Integer key, int numPartitions) { return Math.abs(key.hashCode()) % numPartitions; } }, value -> value); ``` 上述片段展示了如何依据特定逻辑重定向流量[^5]。 --- #### 11. **窗口操作 (Window/WindowAll)** 虽然严格意义上不属于基础转换范畴,但作为扩展功能不可或缺: ```java KeyedStream<Tuple2<String, Integer>, String> keyed = ...; WindowedStream<Tuple2<String, Integer>, String, TimeWindow> windowed = keyed.window(TumblingEventTimeWindows.of(Time.seconds(5))); windowed.sum(1).print(); // 或其他窗口计算逻辑 ``` 这一步骤引入时间维度约束,从而支持更复杂的分析模式[^2]。 --- ### 总结说明 以上列举了 Flink 主要的转换算子类别及其典型应用场景。实际开发过程中应根据具体的业务目标灵活选用合适的工具组合来构建高效流水线解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值