【Flink】Flink算子大全

Flink算子大全:Transformation操作符完全指南

前言

上一篇我们学习了 Flink 的 Source,知道了数据怎么进来。这篇文章来聊聊 Transformation(转换算子)------数据进来之后怎么加工处理。

算子是 Flink 程序的核心,就像工厂里的流水线工位。数据流过每个算子,被加工、过滤、聚合,最终变成我们想要的结果。掌握常用算子,是写好 Flink 程序的基础。

🏠个人主页:你的主页


目录


一、算子分类总览

1.1 算子全景图

复制代码
┌────────────────────────────────────────────────────────────────────────┐
│                      Flink Transformation 算子分类                       │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                    基础转换(一进一出)                            │   │
│  │    map    flatMap    filter    project                          │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                    分区操作(重分布数据)                          │   │
│  │    keyBy    shuffle    rebalance    rescale    broadcast        │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                    聚合操作(需要先keyBy)                         │   │
│  │    reduce    sum    min    max    minBy    maxBy    aggregate   │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                    多流操作(合并或连接)                          │   │
│  │    union    connect    coMap    coFlatMap    join    coGroup    │   │
│  └─────────────────────────────────────────────────────────────────┘   │
│                                                                         │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                    窗口操作(后续专门讲)                          │   │
│  │    window    timeWindow    countWindow    trigger    evictor    │   │
│  └─────────────────────────────────────────────────────────────────┘   │
└────────────────────────────────────────────────────────────────────────┘

1.2 算子的输入输出关系

算子类型 输入 输出 说明
map 1条 1条 一对一转换
flatMap 1条 0~N条 一对多转换
filter 1条 0或1条 过滤
keyBy DataStream KeyedStream 按key分组
reduce KeyedStream DataStream 归约聚合
union 多个DataStream 1个DataStream 合并同类型流
connect 2个DataStream ConnectedStream 连接不同类型流

二、基础转换算子

2.1 map:一对一转换

作用:对每条数据进行转换,输入一条输出一条。

java 复制代码
// 示例1:简单类型转换
DataStream<Integer> numbers = env.fromElements(1, 2, 3, 4, 5);
DataStream<Integer> doubled = numbers.map(n -> n * 2);
// 输入:1, 2, 3, 4, 5
// 输出:2, 4, 6, 8, 10

// 示例2:类型变换
DataStream<String> strings = numbers.map(n -> "Number: " + n);
// 输入:1, 2, 3
// 输出:"Number: 1", "Number: 2", "Number: 3"

// 示例3:对象转换
DataStream<Order> orders = ...;
DataStream<OrderDTO> dtos = orders.map(order -> {
    OrderDTO dto = new OrderDTO();
    dto.setOrderId(order.getId());
    dto.setAmount(order.getPrice() * order.getQuantity());
    return dto;
});
复制代码
map 算子:
输入流:  ──●────●────●────●────●──→
              │      │      │      │      │
              ↓      ↓      ↓      ↓      ↓
          f(●)   f(●)   f(●)   f(●)   f(●)
              │      │      │      │      │
              ↓      ↓      ↓      ↓      ↓
输出流:  ──○────○────○────○────○──→

每个输入对应一个输出

2.2 flatMap:一对多转换

作用:一条输入可以产生0条、1条或多条输出。

java 复制代码
// 示例1:切分单词(经典用法)
DataStream<String> lines = env.fromElements("hello world", "flink is great");
DataStream<String> words = lines.flatMap((String line, Collector<String> out) -> {
    for (String word : line.split(" ")) {
        out.collect(word);
    }
});
// 输入:"hello world", "flink is great"
// 输出:"hello", "world", "flink", "is", "great"

// 示例2:过滤+转换(返回0或1条)
DataStream<Integer> numbers = env.fromElements(1, 2, 3, 4, 5);
DataStream<Integer> evenDoubled = numbers.flatMap((Integer n, Collector<Integer> out) -> {
    if (n % 2 == 0) {
        out.collect(n * 2);
    }
    // 奇数不输出任何数据
});
// 输入:1, 2, 3, 4, 5
// 输出:4, 8

// 示例3:JSON数组展开
DataStream<String> jsonArrays = env.fromElements("[1,2,3]", "[4,5]");
DataStream<Integer> flattened = jsonArrays.flatMap((String json, Collector<Integer> out) -> {
    // 解析JSON数组,每个元素单独输出
    ObjectMapper mapper = new ObjectMapper();
    int[] array = mapper.readValue(json, int[].class);
    for (int value : array) {
        out.collect(value);
    }
});
// 输入:"[1,2,3]", "[4,5]"
// 输出:1, 2, 3, 4, 5
复制代码
flatMap 算子:
输入流:  ──●─────────●──────────●──→
              │            │            │
              ↓            ↓            ↓
          f(●)        f(●)         f(●)
           /|\          |            (无输出)
          / | \         |
         ↓  ↓  ↓        ↓
输出流:  ──○──○──○────○────────────→

一个输入可产生多个输出,也可能无输出

2.3 filter:过滤

作用:根据条件过滤数据,只保留满足条件的。

java 复制代码
// 示例1:简单过滤
DataStream<Integer> numbers = env.fromElements(1, 2, 3, 4, 5);
DataStream<Integer> evenNumbers = numbers.filter(n -> n % 2 == 0);
// 输入:1, 2, 3, 4, 5
// 输出:2, 4

// 示例2:对象过滤
DataStream<Order> orders = ...;
DataStream<Order> bigOrders = orders.filter(order -> order.getAmount() > 1000);
// 只保留金额大于1000的订单

// 示例3:复杂条件
DataStream<User> users = ...;
DataStream<User> activeVipUsers = users.filter(user -> 
    user.isVip() && 
    user.getLastLoginTime() > System.currentTimeMillis() - 86400000
);
// VIP且最近24小时登录过的用户

2.4 map vs flatMap vs filter 对比

复制代码
┌────────────────────────────────────────────────────────────────────┐
│                    三个算子对比                                      │
│                                                                     │
│  map:      输入1条 → 输出1条(必须输出)                             │
│            适合:类型转换、字段提取、计算                             │
│                                                                     │
│  flatMap:  输入1条 → 输出0~N条(灵活)                               │
│            适合:切分、展开、过滤+转换                                │
│                                                                     │
│  filter:   输入1条 → 输出0或1条(原样保留或丢弃)                     │
│            适合:单纯的条件过滤                                       │
│                                                                     │
│  选择建议:                                                          │
│  - 只是转换格式? → map                                             │
│  - 只是过滤?     → filter                                          │
│  - 又要过滤又要转换? → flatMap                                      │
│  - 一条变多条?   → flatMap                                         │
└────────────────────────────────────────────────────────────────────┘

三、分区算子

3.1 keyBy:按Key分组(最重要!)

作用:将数据按指定的 key 进行分组,相同 key 的数据会进入同一个分区。

java 复制代码
// 示例1:按字段分组
DataStream<Order> orders = ...;
KeyedStream<Order, String> keyedOrders = orders.keyBy(order -> order.getUserId());
// 同一用户的订单会到同一个分区

// 示例2:使用lambda
DataStream<Tuple2<String, Integer>> tuples = ...;
KeyedStream<Tuple2<String, Integer>, String> keyed = tuples.keyBy(t -> t.f0);

// 示例3:多字段联合作为key
KeyedStream<Order, Tuple2<String, String>> keyed = orders.keyBy(
    order -> Tuple2.of(order.getUserId(), order.getProductId())
);
// 同一用户买同一商品的订单会到同一分区
复制代码
keyBy 分区原理:
                           hash(key) % numPartitions
输入流:  
用户A订单 ──┐
用户B订单 ──┼─→ keyBy(userId) ─→ ┌─────────────┐ ─→ 分区0:用户A的所有订单
用户A订单 ──┤                    │   Hash分区   │ ─→ 分区1:用户B的所有订单
用户C订单 ──┤                    └─────────────┘ ─→ 分区2:用户C的所有订单
用户B订单 ──┘

相同key一定到同一分区!

注意事项

  • keyBy 后得到的是 KeyedStream,可以使用聚合算子
  • key 必须正确实现 hashCode()equals()
  • 避免数据倾斜:key 分布要均匀

3.2 shuffle:随机分发

java 复制代码
DataStream<Integer> shuffled = numbers.shuffle();
// 数据随机发送到下游的某个并行实例

3.3 rebalance:轮询分发

java 复制代码
DataStream<Integer> rebalanced = numbers.rebalance();
// 轮询方式均匀分发到所有并行实例
// 适合解决数据倾斜问题

3.4 rescale:局部轮询

java 复制代码
DataStream<Integer> rescaled = numbers.rescale();
// 只在相邻的上下游实例间轮询,不走网络
// 适合上下游并行度有倍数关系时

3.5 broadcast:广播

java 复制代码
DataStream<Integer> broadcasted = numbers.broadcast();
// 每条数据发送到所有并行实例
// 适合小数据量的全局配置

3.6 分区策略对比

复制代码
┌────────────────────────────────────────────────────────────────────┐
│                    分区策略可视化                                    │
│                                                                     │
│  Forward(默认,并行度相同时):                                      │
│  上游0 ──→ 下游0                                                   │
│  上游1 ──→ 下游1                                                   │
│                                                                     │
│  Shuffle(随机):                                                   │
│  上游0 ──╲   ╱──→ 下游0                                            │
│          ╳                                                         │
│  上游1 ──╱   ╲──→ 下游1                                            │
│                                                                     │
│  Rebalance(全局轮询):                                             │
│  上游0 ──→ 下游0 → 下游1 → 下游2 → 下游0 ...                        │
│  上游1 ──→ 下游1 → 下游2 → 下游0 → 下游1 ...                        │
│                                                                     │
│  Rescale(局部轮询):                                               │
│  上游0 ──→ 下游0, 下游1(循环)                                      │
│  上游1 ──→ 下游2, 下游3(循环)                                      │
│                                                                     │
│  Broadcast(广播):                                                 │
│  上游0 ──→ 下游0, 下游1, 下游2(所有)                               │
└────────────────────────────────────────────────────────────────────┘

四、聚合算子

聚合算子需要先 keyBy,在 KeyedStream 上使用。

4.1 reduce:归约

作用:将相同 key 的数据两两合并,最终得到一个结果。

java 复制代码
// 示例1:求和
DataStream<Tuple2<String, Integer>> wordCounts = ...;
DataStream<Tuple2<String, Integer>> result = wordCounts
    .keyBy(t -> t.f0)
    .reduce((t1, t2) -> Tuple2.of(t1.f0, t1.f0 + t2.f1));
// ("hello", 1), ("hello", 1), ("world", 1)
// → ("hello", 2), ("world", 1)

// 示例2:找最大值
DataStream<Order> maxOrderByUser = orders
    .keyBy(Order::getUserId)
    .reduce((o1, o2) -> o1.getAmount() > o2.getAmount() ? o1 : o2);
// 每个用户金额最大的订单
复制代码
reduce 工作原理:
key=A: ──1────2────3────4──→
          │     │     │     │
          └──┬──┘     │     │
             │        │     │
            1+2=3     │     │
              └────┬──┘     │
                   │        │
                  3+3=6     │
                    └────┬──┘
                         │
                        6+4=10
输出:    ──1────3────6────10──→

4.2 简单聚合:sum/min/max/minBy/maxBy

java 复制代码
// 基于Tuple的聚合(按字段位置)
DataStream<Tuple3<String, String, Integer>> data = ...;
// (用户ID, 商品ID, 金额)

// sum:对指定字段求和
data.keyBy(t -> t.f0).sum(2);  // 按用户求总金额

// min/max:返回指定字段的最小/最大值,其他字段不保证
data.keyBy(t -> t.f0).min(2);  // 只保证f2是最小的,f1可能不对

// minBy/maxBy:返回指定字段最小/最大的那条完整记录
data.keyBy(t -> t.f0).minBy(2);  // 返回金额最小的完整记录

min vs minBy 的区别

java 复制代码
// 假设输入:
// ("user1", "productA", 100)
// ("user1", "productB", 50)

// min(2) 输出:("user1", "productA", 50)  // f1可能不对!
// minBy(2) 输出:("user1", "productB", 50)  // 完整的最小记录

4.3 aggregate:自定义聚合

作用:最灵活的聚合方式,可以自定义累加逻辑和输出格式。

java 复制代码
/**
 * AggregateFunction<IN, ACC, OUT>
 * IN: 输入类型
 * ACC: 累加器类型
 * OUT: 输出类型
 */
public class AverageAggregator implements AggregateFunction<Order, Tuple2<Long, Long>, Double> {
    
    // 创建累加器
    @Override
    public Tuple2<Long, Long> createAccumulator() {
        return Tuple2.of(0L, 0L);  // (总金额, 订单数)
    }
    
    // 累加
    @Override
    public Tuple2<Long, Long> add(Order order, Tuple2<Long, Long> acc) {
        return Tuple2.of(acc.f0 + order.getAmount(), acc.f1 + 1);
    }
    
    // 获取结果
    @Override
    public Double getResult(Tuple2<Long, Long> acc) {
        return acc.f1 == 0 ? 0.0 : (double) acc.f0 / acc.f1;
    }
    
    // 合并累加器(窗口合并时用)
    @Override
    public Tuple2<Long, Long> merge(Tuple2<Long, Long> a, Tuple2<Long, Long> b) {
        return Tuple2.of(a.f0 + b.f0, a.f1 + b.f1);
    }
}

// 使用
DataStream<Double> avgAmounts = orders
    .keyBy(Order::getUserId)
    .window(TumblingEventTimeWindows.of(Time.minutes(5)))
    .aggregate(new AverageAggregator());

五、多流操作算子

5.1 union:合并同类型流

作用:将多个类型相同的流合并成一个流。

java 复制代码
DataStream<Order> orderStream1 = ...;  // 来自Kafka
DataStream<Order> orderStream2 = ...;  // 来自文件
DataStream<Order> orderStream3 = ...;  // 来自数据库

// 合并多个流
DataStream<Order> allOrders = orderStream1
    .union(orderStream2)
    .union(orderStream3);

// 或者一次性合并
DataStream<Order> allOrders = orderStream1.union(orderStream2, orderStream3);
复制代码
union:
流1: ──●────●────●──→
流2: ──■────■────■──→
流3: ──▲────▲──→
                    ↓
合并: ──●──■──●──▲──■──●──▲──■──→

特点:
- 必须是相同类型
- 合并后顺序不保证
- 可以合并多个流

5.2 connect:连接不同类型流

作用:连接两个类型可以不同的流,之后用 CoMap 或 CoFlatMap 处理。

java 复制代码
// 两个不同类型的流
DataStream<Order> orderStream = ...;
DataStream<Rule> ruleStream = ...;  // 规则流(控制流)

// 连接
ConnectedStreams<Order, Rule> connected = orderStream.connect(ruleStream);

// 使用 CoMap 处理
DataStream<String> result = connected.map(new CoMapFunction<Order, Rule, String>() {
    
    private Rule currentRule;
    
    @Override
    public String map1(Order order) {
        // 处理订单流
        if (currentRule != null && order.getAmount() > currentRule.getThreshold()) {
            return "Large order: " + order.getId();
        }
        return "Normal order: " + order.getId();
    }
    
    @Override
    public String map2(Rule rule) {
        // 处理规则流
        this.currentRule = rule;
        return "Rule updated: " + rule.getName();
    }
});
复制代码
connect:
流1(Order): ──●────●────●──→
流2(Rule):  ──■────■──→
                    ↓ connect
ConnectedStream: 可以分别处理两种类型
                    ↓ coMap/coFlatMap
输出流:     ──○────○────○──→

典型应用场景

  • 动态规则:业务流 + 规则流
  • 维表关联:主流 + 维度流
  • 控制流:数据流 + 控制信号流

5.3 connect + broadcast:广播连接

java 复制代码
// 规则流广播到所有并行实例
BroadcastStream<Rule> broadcastRules = ruleStream
    .broadcast(ruleStateDescriptor);

// 连接主流和广播流
DataStream<Result> result = orderStream
    .connect(broadcastRules)
    .process(new BroadcastProcessFunction<Order, Rule, Result>() {
        
        @Override
        public void processElement(Order order, ReadOnlyContext ctx, Collector<Result> out) {
            // 处理订单,可以读取广播状态
            Rule rule = ctx.getBroadcastState(ruleStateDescriptor).get("current");
            // ... 处理逻辑
        }
        
        @Override
        public void processBroadcastElement(Rule rule, Context ctx, Collector<Result> out) {
            // 更新广播状态
            ctx.getBroadcastState(ruleStateDescriptor).put("current", rule);
        }
    });

5.4 join:窗口连接

java 复制代码
// 两个订单流按用户ID关联
DataStream<Tuple2<Order, Payment>> joined = orders
    .join(payments)
    .where(Order::getOrderId)       // orders的key
    .equalTo(Payment::getOrderId)   // payments的key
    .window(TumblingEventTimeWindows.of(Time.minutes(5)))
    .apply((order, payment) -> Tuple2.of(order, payment));

5.5 coGroup:分组连接

java 复制代码
// coGroup比join更灵活,可以处理左/右流没有匹配的情况
DataStream<String> result = orders
    .coGroup(payments)
    .where(Order::getOrderId)
    .equalTo(Payment::getOrderId)
    .window(TumblingEventTimeWindows.of(Time.minutes(5)))
    .apply(new CoGroupFunction<Order, Payment, String>() {
        @Override
        public void coGroup(Iterable<Order> orders, Iterable<Payment> payments, 
                           Collector<String> out) {
            // orders和payments是同一窗口内、相同key的所有数据
            // 可以处理一对多、多对多等复杂关系
        }
    });

六、富函数与生命周期

6.1 什么是富函数?

普通函数(如 MapFunction)只能处理数据。富函数(RichFunction) 提供更多能力:

  • 生命周期方法:open()close()
  • 获取运行时上下文:并行度、子任务索引等
  • 访问状态

6.2 富函数示例

java 复制代码
public class MyRichMapFunction extends RichMapFunction<String, String> {
    
    private transient Connection dbConnection;
    private transient ValueState<Integer> countState;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        // 初始化资源,只在任务启动时调用一次
        dbConnection = DriverManager.getConnection("jdbc:mysql://...");
        
        // 获取运行时信息
        int subtaskIndex = getRuntimeContext().getIndexOfThisSubtask();
        int parallelism = getRuntimeContext().getNumberOfParallelSubtasks();
        System.out.println("SubTask " + subtaskIndex + "/" + parallelism + " started");
        
        // 初始化状态
        ValueStateDescriptor<Integer> descriptor = 
            new ValueStateDescriptor<>("count", Integer.class);
        countState = getRuntimeContext().getState(descriptor);
    }
    
    @Override
    public String map(String value) throws Exception {
        // 使用数据库连接
        // 使用状态
        Integer count = countState.value();
        if (count == null) count = 0;
        countState.update(count + 1);
        
        return value + "_" + count;
    }
    
    @Override
    public void close() throws Exception {
        // 清理资源,任务结束时调用
        if (dbConnection != null) {
            dbConnection.close();
        }
    }
}

6.3 生命周期图

复制代码
┌────────────────────────────────────────────────────────────────────┐
│                    RichFunction 生命周期                             │
│                                                                     │
│  任务启动                                                            │
│      │                                                              │
│      ↓                                                              │
│  ┌────────┐                                                         │
│  │ open() │  ← 初始化资源、获取状态、一次性准备工作                     │
│  └────────┘                                                         │
│      │                                                              │
│      ↓                                                              │
│  ┌────────────────────────────────────────┐                         │
│  │            处理数据(循环)               │                         │
│  │  map() / flatMap() / filter() / ...    │  ← 每条数据调用一次       │
│  └────────────────────────────────────────┘                         │
│      │                                                              │
│      ↓                                                              │
│  ┌─────────┐                                                        │
│  │ close() │  ← 清理资源、释放连接                                    │
│  └─────────┘                                                        │
│      │                                                              │
│      ↓                                                              │
│  任务结束                                                            │
└────────────────────────────────────────────────────────────────────┘

6.4 常用富函数类

普通函数 富函数版本
MapFunction RichMapFunction
FlatMapFunction RichFlatMapFunction
FilterFunction RichFilterFunction
ReduceFunction RichReduceFunction
ProcessFunction 本身就是富函数

七、侧输出流

7.1 什么是侧输出?

侧输出(Side Output) 允许从一个算子输出多个流,适合:

  • 分流:把数据分成多个类别
  • 处理异常数据
  • 处理迟到数据

7.2 使用ProcessFunction实现侧输出

java 复制代码
// 定义侧输出标签
final OutputTag<Order> bigOrderTag = new OutputTag<Order>("big-order"){};
final OutputTag<Order> smallOrderTag = new OutputTag<Order>("small-order"){};
final OutputTag<String> errorTag = new OutputTag<String>("error"){};

// 使用ProcessFunction分流
SingleOutputStreamOperator<Order> mainStream = orders
    .process(new ProcessFunction<Order, Order>() {
        @Override
        public void processElement(Order order, Context ctx, Collector<Order> out) {
            try {
                if (order.getAmount() > 10000) {
                    // 大订单 -> 侧输出
                    ctx.output(bigOrderTag, order);
                } else if (order.getAmount() < 100) {
                    // 小订单 -> 侧输出
                    ctx.output(smallOrderTag, order);
                } else {
                    // 普通订单 -> 主输出
                    out.collect(order);
                }
            } catch (Exception e) {
                // 异常数据 -> 错误侧输出
                ctx.output(errorTag, "Error processing: " + order.getId());
            }
        }
    });

// 获取各个流
DataStream<Order> normalOrders = mainStream;
DataStream<Order> bigOrders = mainStream.getSideOutput(bigOrderTag);
DataStream<Order> smallOrders = mainStream.getSideOutput(smallOrderTag);
DataStream<String> errors = mainStream.getSideOutput(errorTag);

// 分别处理
bigOrders.addSink(new BigOrderAlertSink());
smallOrders.addSink(new SmallOrderSink());
errors.addSink(new ErrorLogSink());
复制代码
侧输出示意:
                                    ┌─→ bigOrderTag → 大订单流
                                    │
输入流 ──→ ProcessFunction ──→ 主流 ──┼─→ smallOrderTag → 小订单流
      (amount判断)              │
                                    └─→ errorTag → 错误流

7.3 窗口迟到数据侧输出

java 复制代码
OutputTag<Order> lateDataTag = new OutputTag<Order>("late-data"){};

DataStream<OrderSummary> result = orders
    .keyBy(Order::getUserId)
    .window(TumblingEventTimeWindows.of(Time.minutes(5)))
    .allowedLateness(Time.minutes(1))
    .sideOutputLateData(lateDataTag)  // 迟到数据输出到侧输出
    .aggregate(new OrderAggregator());

// 获取迟到数据单独处理
DataStream<Order> lateOrders = result.getSideOutput(lateDataTag);

八、算子链控制

8.1 什么是算子链?

算子链(Operator Chain) 是 Flink 的优化:把多个算子合并到一个 Task 中执行,减少线程切换和网络传输。

复制代码
优化前:
Source ──网络──→ Map ──网络──→ Filter ──网络──→ Sink
  │              │              │              │
线程1          线程2          线程3          线程4

优化后(算子链):
┌───────────────────────────────┐        ┌─────────┐
│ Source → Map → Filter         │──网络──→│  Sink   │
│        (同一线程)             │        │         │
└───────────────────────────────┘        └─────────┘
            线程1                          线程2

8.2 算子链的条件

自动chain的条件:

  1. 上下游并行度相同
  2. 数据传输方式是 Forward
  3. 在同一个 SlotSharingGroup
  4. 没有被禁用

8.3 手动控制算子链

java 复制代码
// 1. 禁用当前算子的chain(不和上游chain)
stream.map(...).disableChaining();

// 2. 从当前算子开始新的chain
stream.map(...).startNewChain();

// 3. 全局禁用算子链
env.disableOperatorChaining();

// 4. 设置SlotSharingGroup(不同组不能chain)
stream.map(...).slotSharingGroup("groupA");
stream.filter(...).slotSharingGroup("groupB");

8.4 什么时候需要手动控制?

场景 操作
某算子特别重,需要单独监控 disableChaining()
排查性能问题,想看各算子耗时 disableChaining()
资源隔离,防止互相影响 不同 SlotSharingGroup

九、总结

这篇文章我们全面学习了 Flink 的 Transformation 算子:

核心算子速查表

算子 作用 输入→输出
map 一对一转换 1→1
flatMap 一对多转换 1→0~N
filter 过滤 1→0或1
keyBy 按key分组 DataStream→KeyedStream
reduce 归约聚合 KeyedStream→DataStream
sum/min/max 简单聚合 KeyedStream→DataStream
union 合并同类型流 N个流→1个流
connect 连接不同类型流 2个流→ConnectedStream

选择建议

复制代码
数据转换?
├─ 一对一 → map
├─ 一对多 → flatMap
└─ 过滤   → filter

数据分组?
└─ keyBy → 然后用聚合算子

多流处理?
├─ 同类型合并 → union
└─ 不同类型连接 → connect

需要生命周期?
└─ 使用 RichXxxFunction

需要分流?
└─ ProcessFunction + 侧输出

下一篇文章,我们将学习 Flink 数据输出 Sink,看看处理完的数据怎么写入 Kafka、MySQL、Redis 等存储系统。


热门专栏推荐

等等等还有许多优秀的合集在主页等着大家的光顾,感谢大家的支持


文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起来评论区一起讨论😊

希望能和诸佬们一起努力,今后我们一起观看感谢您的阅读🙏

如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🌟

相关推荐
ayingmeizi1632 小时前
智慧养老的数字化转型:AI CRM如何重构全链路增长
大数据·人工智能·重构
老马聊技术3 小时前
HBase单节点环境搭建详细教程
大数据·数据库·hbase
xerthwis3 小时前
Flink:从“微批”到“真流”,数据处理的哲学转向与时代抉择
大数据·flink
jqpwxt3 小时前
启点创新智慧景区服务平台,智慧景区数字驾驶舱建设
大数据·人工智能
阿里云大数据AI技术3 小时前
Hologres Dynamic Table:高效增量刷新,构建实时统一数仓的核心利器
大数据·人工智能·阿里云·实时数仓·hologres
Familyism3 小时前
ES基础入门
大数据·elasticsearch·搜索引擎
weixin_395448914 小时前
动态分辨率的ioufsd
eureka·flink·etcd
跨境卫士情报站4 小时前
摆脱砍单魔咒!Temu 自养号系统化采购,低成本高安全
大数据·人工智能·安全·跨境电商·亚马逊·防关联
AI营销实验室4 小时前
AI CRM系统升级,原圈科技赋能销冠复制
大数据·人工智能