Bootstrap

Flink随笔 20241129 流数据处理:以生产线烤鸡为例理解 Flink

        流数据(streaming data)就像是一条永不停歇的生产线,源源不断地向前推进,带来新的数据。而 Apache Flink 就是这条生产线的核心,它负责对数据进行处理、分类、聚合和存储。为了更好地理解 Flink 的流处理,我们可以通过一个简单的类比来解释:流数据就像是流水线上的烤鸡,而 Flink 就是这条流水线上的工作部,负责对这些烤鸡进行加工、分拣、统计和存储。

下面,我们将通过这个类比一步步讲解 Flink 的核心概念,并展示如何在 Flink 中实现这些操作。

类比:生产线的烤鸡

        想象一下,你在一个生产线的末端,看到源源不断的烤鸡从流水线上流过。你需要对这些鸡进行以下操作:

  1. 去掉烤糊的鸡肉(过滤):生产线上有些鸡被烤得过焦,不能食用,需要将其剔除。
  2. 分拣出火鸡还是普通鸡肉(映射):在生产线的下一步,你需要把鸡肉分成不同的类别,比如火鸡和普通鸡肉。
  3. 统计有多少只火鸡,多少只普通鸡肉(聚合):你需要统计每种鸡肉的数量,以便后续处理。
  4. 将鸡肉存储(存储):最后,所有的合格鸡肉需要被存储,或者被送到市场。

        这些操作正是 Flink 中流数据处理的核心功能。下面我们将逐一介绍如何在 Flink 中实现这些步骤。

1. 去掉烤糊的鸡肉(过滤)

        在生产线的第一步,你需要对烤鸡进行筛选,去掉那些已经烤糊或者质量不合格的鸡肉。在 Flink 中,这就是 过滤操作(Filter)。Flink 提供了 filter() 函数,可以根据条件筛选出符合要求的数据。

        例如,假设我们有一个数据流,其中包含不同类型的鸡肉,而我们只关心健康的鸡肉。你可以通过过滤操作去除那些不符合条件的数据。

DataStream<String> chickenStream = env.fromElements("Turkey", "Burnt Chicken", "Chicken", "Turkey");

DataStream<String> validChickenStream = chickenStream.filter(chicken -> !chicken.equals("Burnt Chicken"));
validChickenStream.print();
2. 分拣出火鸡还是普通鸡肉(映射)

        在生产线的第二步,你需要把鸡肉分成火鸡和普通鸡肉两类,这就是 映射操作(Map)。在 Flink 中,map() 函数可以将输入数据转换成不同的输出数据。在本例中,我们可以将鸡肉类型映射成不同的标签,例如“火鸡”和“普通鸡肉”。

DataStream<String> sortedChickenStream = chickenStream
    .map(new MapFunction<String, String>() {
        @Override
        public String map(String value) throws Exception {
            return value.equals("Turkey") ? "Turkey" : "Chicken";
        }
    });

sortedChickenStream.print();
3. 统计有多少只火鸡,多少只普通鸡肉(聚合)

        接下来,你需要统计每种类型的鸡肉有多少只,这就是 聚合操作(Aggregation)。在 Flink 中,keyBy()reduce() 函数常常一起使用,按指定的键(比如鸡肉类型)对数据进行分组,并对每个组进行聚合计算。

        例如,你可以按鸡肉类型分组,并统计每种鸡肉的数量:

DataStream<Tuple2<String, Integer>> mappedStream = chickenStream
    .map(chicken -> new Tuple2<>(chicken, 1));  // 每个鸡肉事件映射为类型和数量的元组

DataStream<Tuple2<String, Integer>> aggregatedStream = mappedStream
    .keyBy(0)  // 按鸡肉类型分组
    .reduce(new ReduceFunction<Tuple2<String, Integer>>() {
        @Override
        public Tuple2<String, Integer> reduce(Tuple2<String, Integer> value1, Tuple2<String, Integer> value2) throws Exception {
            return new Tuple2<>(value1.f0, value1.f1 + value2.f1);  // 累加数量
        }
    });

aggregatedStream.print();
4. 将鸡肉存储(存储)

        在生产线的最后一步,合格的鸡肉需要被存储或发送到市场。Flink 提供了多种 Sink 操作,可以将处理结果输出到外部系统,如 Kafka、MySQL、Elasticsearch 或 HDFS。你可以根据需要将聚合结果写入数据库或消息队列,便于后续的处理和存储。

        例如,将处理后的数据输出到 Kafka:

aggregatedStream.addSink(new FlinkKafkaProducer<>(
    "kafka-broker",  // Kafka 服务器地址
    "chicken-topic",  // Kafka 主题
    new SimpleStringSchema()  // 数据序列化方式
));
Flink 流数据处理的完整示例

        通过上述步骤,我们可以编写一个完整的 Flink 程序,来实现流数据的处理,类似于生产线上的烤鸡加工过程。以下是一个简单的例子,展示如何使用 Flink 进行数据流的过滤、映射、聚合和存储:

import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.functions.ReduceFunction;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.api.java.tuple.Tuple2;

public class ChickenProcessing {
    public static void main(String[] args) throws Exception {
        // 1. 设置流处理环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 2. 输入数据流:每个事件包含一个鸡肉的类型
        DataStream<String> chickenStream = env.fromElements("Turkey", "Chicken", "Turkey", "Chicken", "Turkey");

        // 3. 映射:将每个事件映射为一个元组(鸡肉类型, 数量)
        DataStream<Tuple2<String, Integer>> mappedStream = chickenStream
            .map(new MapFunction<String, Tuple2<String, Integer>>() {
                @Override
                public Tuple2<String, Integer> map(String value) throws Exception {
                    return new Tuple2<>(value, 1);  // 每个事件都标记为1个鸡肉
                }
            });

        // 4. 聚合:按类型统计每种鸡肉的数量
        DataStream<Tuple2<String, Integer>> aggregatedStream = mappedStream
            .keyBy(0)  // 按鸡肉类型分组
            .reduce(new ReduceFunction<Tuple2<String, Integer>>() {
                @Override
                public Tuple2<String, Integer> reduce(Tuple2<String, Integer> value1, Tuple2<String, Integer> value2) throws Exception {
                    return new Tuple2<>(value1.f0, value1.f1 + value2.f1);  // 累加数量
                }
            });

        // 5. 输出结果到控制台
        aggregatedStream.print();

        // 6. 启动流处理作业
        env.execute("Chicken Processing");
    }
}

输出示例

(Turkey, 3)
(Chicken, 2)
结论

        通过上面的例子,我们可以看到 Flink 在流数据处理中的应用。它不仅可以对数据进行过滤、分类和聚合,还能够将处理结果存储或发送到其他系统。流数据处理就像生产线上的烤鸡加工一样,Flink 作为流水线的核心,不仅对数据进行有效处理,还确保了高效、实时地传递和存储处理结果。

;