全部笔记All notes

Flink DataStream API 算子完全指南

阅读 11m 22s11m 22s read

概述

Flink DataStream API 提供了丰富的算子(Operators)来处理流数据。算子是 Flink 流处理的核心组件,通过组合不同的算子,可以构建复杂的流处理拓扑。本文将系统地介绍 Flink 中的各类算子,包括其功能、使用场景和最佳实践。

算子分类体系

Flink 算子可以按照不同维度进行分类:

按功能分类

  1. 转换算子:map、flatMap、filter、reduce 等
  2. 分区算子:keyBy、rebalance、broadcast 等
  3. 窗口算子:window、windowAll、timeWindow 等
  4. 连接算子:connect、union、join、coGroup 等
  5. 状态算子:process、aggregate、fold 等
  6. 输出算子:print、addSink、writeAsText 等

按并行度特征分类

  1. 一对一算子:map、filter 等(保持并行度)
  2. 重分区算子:keyBy、rebalance 等(改变数据分布)
  3. 全局算子:windowAll、countWindowAll 等(并行度为 1)

数据转换算子

基础转换算子

map 算子

一对一转换,每个输入元素映射为一个输出元素。

// 基础用法
DataStream<Integer> numbers = env.fromElements(1, 2, 3, 4, 5);
DataStream<Integer> squared = numbers.map(x -> x * x);
squared.print(); // 输出: 1, 4, 9, 16, 25

// 复杂对象转换
DataStream<String> jsonStream = env.fromElements(
    "{\"name\":\"Alice\",\"age\":25}",
    "{\"name\":\"Bob\",\"age\":30}"
);

DataStream<User> userStream = jsonStream.map(new MapFunction<String, User>() {
    private final ObjectMapper mapper = new ObjectMapper();
    
    @Override
    public User map(String value) throws Exception {
        return mapper.readValue(value, User.class);
    }
});

// 使用 RichMapFunction 访问运行时上下文
public class EnrichmentMapper extends RichMapFunction<Order, EnrichedOrder> {
    private transient Map<String, UserInfo> userCache;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        // 初始化资源
        userCache = loadUserCache();
        
        // 访问运行时上下文
        int subtaskIndex = getRuntimeContext().getIndexOfThisSubtask();
        int parallelism = getRuntimeContext().getNumberOfParallelSubtasks();
        System.out.printf("Mapper started: subtask %d of %d%n", subtaskIndex, parallelism);
    }
    
    @Override
    public EnrichedOrder map(Order order) throws Exception {
        UserInfo userInfo = userCache.get(order.getUserId());
        return new EnrichedOrder(order, userInfo);
    }
    
    @Override
    public void close() throws Exception {
        // 清理资源
        if (userCache != null) {
            userCache.clear();
        }
    }
}

flatMap 算子

一对多转换,每个输入元素可以映射为零个、一个或多个输出元素。

// 基础用法:分词
DataStream<String> sentences = env.fromElements(
    "Hello World",
    "Flink Streaming",
    "Real Time Processing"
);

DataStream<String> words = sentences.flatMap(new FlatMapFunction<String, String>() {
    @Override
    public void flatMap(String sentence, Collector<String> out) {
        for (String word : sentence.split(" ")) {
            out.collect(word.toLowerCase());
        }
    }
});

// 高级用法:条件性输出
DataStream<Event> events = env.addSource(new EventSource());

DataStream<ProcessedEvent> processed = events.flatMap(
    new RichFlatMapFunction<Event, ProcessedEvent>() {
        private transient Counter validEvents;
        private transient Counter invalidEvents;
        
        @Override
        public void open(Configuration parameters) throws Exception {
            validEvents = getRuntimeContext()
                .getMetricGroup()
                .counter("valid_events");
            invalidEvents = getRuntimeContext()
                .getMetricGroup()
                .counter("invalid_events");
        }
        
        @Override
        public void flatMap(Event event, Collector<ProcessedEvent> out) {
            if (isValid(event)) {
                validEvents.inc();
                // 可能产生多个输出
                for (ProcessedEvent pe : processEvent(event)) {
                    out.collect(pe);
                }
            } else {
                invalidEvents.inc();
                // 不产生输出
            }
        }
    }
);

// 使用 Lambda 表达式(需要类型声明)
DataStream<String> expanded = words
    .flatMap((String word, Collector<String> out) -> {
        out.collect(word);
        out.collect(word.toUpperCase());
    })
    .returns(Types.STRING);

filter 算子

过滤数据,仅保留满足条件的元素。

// 基础过滤
DataStream<Integer> numbers = env.fromElements(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
DataStream<Integer> evenNumbers = numbers.filter(n -> n % 2 == 0);

// 复杂条件过滤
DataStream<Transaction> transactions = env.addSource(new TransactionSource());

DataStream<Transaction> suspiciousTransactions = transactions
    .filter(new FilterFunction<Transaction>() {
        @Override
        public boolean filter(Transaction tx) {
            return tx.getAmount() > 10000 
                && tx.getLocation().equals("UNKNOWN")
                && isNightTime(tx.getTimestamp());
        }
        
        private boolean isNightTime(long timestamp) {
            LocalDateTime time = Instant.ofEpochMilli(timestamp)
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime();
            int hour = time.getHour();
            return hour < 6 || hour > 22;
        }
    });

// 链式过滤优化
DataStream<Order> orders = env.addSource(new OrderSource());

// 不推荐:多次过滤
DataStream<Order> filtered1 = orders.filter(order -> order.getAmount() > 100);
DataStream<Order> filtered2 = filtered1.filter(order -> order.getStatus().equals("PENDING"));

// 推荐:合并过滤条件
DataStream<Order> filteredOptimized = orders
    .filter(order -> order.getAmount() > 100 && order.getStatus().equals("PENDING"));

聚合算子

reduce 算子

对分组后的数据流进行滚动聚合。

// 基础聚合
DataStream<Tuple2<String, Integer>> wordCounts = words
    .map(word -> Tuple2.of(word, 1))
    .keyBy(tuple -> tuple.f0)
    .reduce((t1, t2) -> Tuple2.of(t1.f0, t1.f1 + t2.f1));

// 自定义 ReduceFunction
public class TransactionSumReducer implements ReduceFunction<Transaction> {
    @Override
    public Transaction reduce(Transaction t1, Transaction t2) {
        return new Transaction(
            t1.getUserId(),
            t1.getAmount() + t2.getAmount(),
            Math.max(t1.getTimestamp(), t2.getTimestamp()),
            t1.getType()
        );
    }
}

// 复杂对象聚合
DataStream<UserActivity> activities = env.addSource(new ActivitySource());

DataStream<UserStats> userStats = activities
    .keyBy(UserActivity::getUserId)
    .reduce(new ReduceFunction<UserActivity>() {
        @Override
        public UserActivity reduce(UserActivity a1, UserActivity a2) {
            UserActivity merged = new UserActivity();
            merged.setUserId(a1.getUserId());
            merged.setClickCount(a1.getClickCount() + a2.getClickCount());
            merged.setViewCount(a1.getViewCount() + a2.getViewCount());
            merged.setTotalDuration(a1.getTotalDuration() + a2.getTotalDuration());
            merged.setLastActivityTime(Math.max(a1.getLastActivityTime(), a2.getLastActivityTime()));
            return merged;
        }
    })
    .map(activity -> new UserStats(activity));

aggregate 算子

更灵活的聚合操作,支持中间聚合状态与最终结果类型不同。

// 计算平均值
public class AverageAggregate 
        implements AggregateFunction<Integer, Tuple2<Integer, Integer>, Double> {
    
    @Override
    public Tuple2<Integer, Integer> createAccumulator() {
        return Tuple2.of(0, 0); // (sum, count)
    }
    
    @Override
    public Tuple2<Integer, Integer> add(Integer value, Tuple2<Integer, Integer> acc) {
        return Tuple2.of(acc.f0 + value, acc.f1 + 1);
    }
    
    @Override
    public Double getResult(Tuple2<Integer, Integer> acc) {
        return acc.f1 == 0 ? 0.0 : (double) acc.f0 / acc.f1;
    }
    
    @Override
    public Tuple2<Integer, Integer> merge(Tuple2<Integer, Integer> acc1, 
                                         Tuple2<Integer, Integer> acc2) {
        return Tuple2.of(acc1.f0 + acc2.f0, acc1.f1 + acc2.f1);
    }
}

// 使用示例
DataStream<SensorReading> readings = env.addSource(new SensorSource());

DataStream<Double> avgTemperature = readings
    .keyBy(SensorReading::getSensorId)
    .window(TumblingEventTimeWindows.of(Time.minutes(5)))
    .aggregate(new AverageAggregate());

// 复杂聚合:Top-K
public class TopKAggregate 
        implements AggregateFunction<Event, PriorityQueue<Event>, List<Event>> {
    private final int k;
    
    public TopKAggregate(int k) {
        this.k = k;
    }
    
    @Override
    public PriorityQueue<Event> createAccumulator() {
        return new PriorityQueue<>(k, Comparator.comparingDouble(Event::getScore));
    }
    
    @Override
    public PriorityQueue<Event> add(Event event, PriorityQueue<Event> acc) {
        acc.offer(event);
        if (acc.size() > k) {
            acc.poll(); // 移除最小元素
        }
        return acc;
    }
    
    @Override
    public List<Event> getResult(PriorityQueue<Event> acc) {
        List<Event> result = new ArrayList<>(acc);
        result.sort(Comparator.comparingDouble(Event::getScore).reversed());
        return result;
    }
    
    @Override
    public PriorityQueue<Event> merge(PriorityQueue<Event> acc1, 
                                     PriorityQueue<Event> acc2) {
        PriorityQueue<Event> merged = new PriorityQueue<>(createAccumulator());
        merged.addAll(acc1);
        merged.addAll(acc2);
        while (merged.size() > k) {
            merged.poll();
        }
        return merged;
    }
}

分组算子

keyBy 算子

按照指定的 key 对数据流进行分组,是有状态操作的基础。

// 基础分组
DataStream<Tuple2<String, Integer>> data = env.fromElements(
    Tuple2.of("A", 1),
    Tuple2.of("B", 2),
    Tuple2.of("A", 3),
    Tuple2.of("B", 4)
);

// 使用字段位置
KeyedStream<Tuple2<String, Integer>, Tuple> keyed1 = data.keyBy(0);

// 使用字段名称
KeyedStream<Tuple2<String, Integer>, String> keyed2 = data.keyBy(tuple -> tuple.f0);

// 复合键
DataStream<Order> orders = env.addSource(new OrderSource());

KeyedStream<Order, Tuple2<String, String>> keyedOrders = orders
    .keyBy(order -> Tuple2.of(order.getUserId(), order.getProductId()));

// 自定义 KeySelector
public class OrderKeySelector implements KeySelector<Order, String> {
    @Override
    public String getKey(Order order) throws Exception {
        // 复杂的 key 生成逻辑
        return String.format("%s_%s_%d", 
            order.getUserId(), 
            order.getRegion(),
            order.getTimestamp() / (1000 * 60 * 60) // 按小时分组
        );
    }
}

// 使用 POJO 字段
KeyedStream<Order, String> keyedByUser = orders.keyBy(Order::getUserId);

// 注意事项:Key 必须正确实现 hashCode 和 equals
public class CompositeKey {
    private String userId;
    private String productId;
    
    // 必须重写 hashCode 和 equals
    @Override
    public int hashCode() {
        return Objects.hash(userId, productId);
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        CompositeKey that = (CompositeKey) obj;
        return Objects.equals(userId, that.userId) && 
               Objects.equals(productId, that.productId);
    }
}

分区算子

物理分区

rebalance 算子

轮询方式重新分配数据,实现负载均衡。

// 解决数据倾斜
DataStream<Event> skewedStream = env.addSource(new SkewedSource());

// 重新平衡数据
DataStream<Event> balancedStream = skewedStream
    .rebalance()
    .map(new ExpensiveOperation());

// 实际应用场景
DataStream<String> kafkaStream = env.addSource(kafkaConsumer);

DataStream<ProcessedRecord> processed = kafkaStream
    .rebalance()  // 打破 Kafka 分区的数据倾斜
    .flatMap(new JsonParser())
    .filter(record -> record.isValid())
    .keyBy(ProcessedRecord::getKey)
    .process(new BusinessLogic());

rescale 算子

本地轮询,只在本地 TaskManager 的 slots 之间重分配。

// rescale vs rebalance
DataStream<Integer> stream = env.generateSequence(1, 100).setParallelism(2);

// rebalance:全局轮询
DataStream<Integer> rebalanced = stream
    .rebalance()
    .map(x -> x * 2).setParallelism(4);

// rescale:本地轮询(性能更好)
DataStream<Integer> rescaled = stream
    .rescale()
    .map(x -> x * 2).setParallelism(4);

// 性能对比示例
public class PartitioningBenchmark {
    public static void testPartitioning(StreamExecutionEnvironment env) {
        DataStream<Event> source = env
            .addSource(new HighThroughputSource())
            .setParallelism(4);
        
        // 测试不同分区策略
        source.rebalance().addSink(new DiscardingSink<>()).name("rebalance");
        source.rescale().addSink(new DiscardingSink<>()).name("rescale");
        source.shuffle().addSink(new DiscardingSink<>()).name("shuffle");
        source.broadcast().addSink(new DiscardingSink<>()).name("broadcast");
    }
}

shuffle 算子

随机分配数据到下游分区。

// 随机采样
DataStream<Record> allRecords = env.addSource(new RecordSource());

DataStream<Record> sampledRecords = allRecords
    .shuffle()
    .filter(new RandomSampler(0.1)); // 10% 采样率

// 随机采样器实现
public class RandomSampler implements FilterFunction<Record> {
    private final double samplingRate;
    private final Random random = new Random();
    
    public RandomSampler(double samplingRate) {
        this.samplingRate = samplingRate;
    }
    
    @Override
    public boolean filter(Record record) {
        return random.nextDouble() < samplingRate;
    }
}

broadcast 算子

将数据广播到所有下游任务。

// 规则广播
DataStream<Rule> rules = env
    .addSource(new RuleSource())
    .broadcast();  // 广播到所有任务

DataStream<Event> events = env.addSource(new EventSource());

// 使用广播状态
MapStateDescriptor<String, Rule> ruleStateDescriptor = 
    new MapStateDescriptor<>("rules", String.class, Rule.class);

BroadcastStream<Rule> broadcastRules = rules.broadcast(ruleStateDescriptor);

DataStream<Alert> alerts = events
    .connect(broadcastRules)
    .process(new RuleEvaluator());

// 小表 JOIN 大表
DataStream<Product> products = env.addSource(new ProductSource()); // 小表
DataStream<Order> orders = env.addSource(new OrderSource());       // 大表

DataStream<EnrichedOrder> enrichedOrders = orders
    .connect(products.broadcast())
    .process(new CoProcessFunction<Order, Product, EnrichedOrder>() {
        private final Map<String, Product> productMap = new HashMap<>();
        
        @Override
        public void processElement1(Order order, Context ctx, 
                                   Collector<EnrichedOrder> out) {
            Product product = productMap.get(order.getProductId());
            if (product != null) {
                out.collect(new EnrichedOrder(order, product));
            }
        }
        
        @Override
        public void processElement2(Product product, Context ctx, 
                                   Collector<EnrichedOrder> out) {
            productMap.put(product.getId(), product);
        }
    });

逻辑分区

partitionCustom 算子

自定义分区策略。

// 自定义分区器
public class RegionPartitioner implements Partitioner<String> {
    private final Map<String, Integer> regionToPartition = new HashMap<>();
    
    public RegionPartitioner() {
        regionToPartition.put("NORTH", 0);
        regionToPartition.put("SOUTH", 1);
        regionToPartition.put("EAST", 2);
        regionToPartition.put("WEST", 3);
    }
    
    @Override
    public int partition(String region, int numPartitions) {
        Integer partition = regionToPartition.get(region);
        if (partition != null && partition < numPartitions) {
            return partition;
        }
        // 默认哈希分区
        return Math.abs(region.hashCode() % numPartitions);
    }
}

// 使用自定义分区
DataStream<Order> orders = env.addSource(new OrderSource());

DataStream<Order> partitionedOrders = orders
    .partitionCustom(new RegionPartitioner(), Order::getRegion);

// 范围分区示例
public class RangePartitioner implements Partitioner<Long> {
    private final long[] boundaries;
    
    public RangePartitioner(long[] boundaries) {
        this.boundaries = Arrays.copyOf(boundaries, boundaries.length);
        Arrays.sort(this.boundaries);
    }
    
    @Override
    public int partition(Long key, int numPartitions) {
        int index = Arrays.binarySearch(boundaries, key);
        if (index < 0) {
            index = -index - 1;
        }
        return Math.min(index, numPartitions - 1);
    }
}

窗口算子

时间窗口

滚动窗口(Tumbling Windows)

固定大小、不重叠的窗口。

// 基础滚动窗口
DataStream<Event> events = env.addSource(new EventSource());

DataStream<WindowResult> windowedStream = events
    .keyBy(Event::getKey)
    .window(TumblingEventTimeWindows.of(Time.minutes(5)))
    .aggregate(new EventAggregator());

// 带偏移的滚动窗口(用于时区对齐)
DataStream<Transaction> transactions = env.addSource(new TransactionSource());

DataStream<DailyStats> dailyStats = transactions
    .keyBy(Transaction::getUserId)
    .window(TumblingEventTimeWindows.of(Time.days(1), Time.hours(-8))) // UTC-8
    .process(new DailyStatsCalculator());

// 处理时间滚动窗口
DataStream<Click> clicks = env.addSource(new ClickSource());

DataStream<ClickCount> clickCounts = clicks
    .keyBy(Click::getUserId)
    .window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
    .reduce(new ClickCounter());

// 自定义窗口触发器
public class EarlyFireTrigger extends Trigger<Event, TimeWindow> {
    private final long interval;
    
    @Override
    public TriggerResult onElement(Event element, long timestamp, 
                                  TimeWindow window, TriggerContext ctx) {
        if (window.maxTimestamp() <= ctx.getCurrentWatermark()) {
            return TriggerResult.FIRE;
        } else {
            ctx.registerEventTimeTimer(window.maxTimestamp());
            // 注册早期触发
            long timer = timestamp + interval;
            if (timer < window.maxTimestamp()) {
                ctx.registerEventTimeTimer(timer);
            }
            return TriggerResult.CONTINUE;
        }
    }
    
    @Override
    public TriggerResult onEventTime(long time, TimeWindow window, 
                                    TriggerContext ctx) {
        return time == window.maxTimestamp() ? 
            TriggerResult.FIRE_AND_PURGE : TriggerResult.FIRE;
    }
}

滑动窗口(Sliding Windows)

固定大小、可重叠的窗口。

// 基础滑动窗口
DataStream<SensorReading> readings = env.addSource(new SensorSource());

// 每5秒计算过去1分钟的平均温度
DataStream<AvgTemperature> avgTemps = readings
    .keyBy(SensorReading::getSensorId)
    .window(SlidingEventTimeWindows.of(Time.minutes(1), Time.seconds(5)))
    .apply(new TemperatureAverager());

// 高级用法:自定义窗口函数
public class TrendDetector 
        implements WindowFunction<Price, PriceTrend, String, TimeWindow> {
    
    @Override
    public void apply(String key, TimeWindow window, 
                     Iterable<Price> prices, 
                     Collector<PriceTrend> out) {
        List<Price> priceList = new ArrayList<>();
        prices.forEach(priceList::add);
        
        if (priceList.size() < 2) {
            return;
        }
        
        // 计算趋势
        priceList.sort(Comparator.comparingLong(Price::getTimestamp));
        double firstPrice = priceList.get(0).getValue();
        double lastPrice = priceList.get(priceList.size() - 1).getValue();
        double change = (lastPrice - firstPrice) / firstPrice * 100;
        
        PriceTrend trend = new PriceTrend(
            key,
            window.getStart(),
            window.getEnd(),
            change,
            getTrendType(change)
        );
        
        out.collect(trend);
    }
    
    private TrendType getTrendType(double change) {
        if (change > 5) return TrendType.STRONG_UP;
        if (change > 1) return TrendType.UP;
        if (change < -5) return TrendType.STRONG_DOWN;
        if (change < -1) return TrendType.DOWN;
        return TrendType.STABLE;
    }
}

// 使用增量聚合函数提高性能
DataStream<Trade> trades = env.addSource(new TradeSource());

DataStream<TradeVolume> volumes = trades
    .keyBy(Trade::getSymbol)
    .window(SlidingEventTimeWindows.of(Time.hours(1), Time.minutes(5)))
    .aggregate(
        new AggregateFunction<Trade, VolumeAccumulator, TradeVolume>() {
            @Override
            public VolumeAccumulator createAccumulator() {
                return new VolumeAccumulator();
            }
            
            @Override
            public VolumeAccumulator add(Trade trade, VolumeAccumulator acc) {
                acc.addTrade(trade);
                return acc;
            }
            
            @Override
            public TradeVolume getResult(VolumeAccumulator acc) {
                return acc.toTradeVolume();
            }
            
            @Override
            public VolumeAccumulator merge(VolumeAccumulator a, VolumeAccumulator b) {
                return a.merge(b);
            }
        }
    );

会话窗口

会话窗口根据活动间隔动态创建窗口。

// 基础会话窗口
DataStream<UserAction> actions = env.addSource(new UserActionSource());

// 用户会话分析(30分钟无活动则结束会话)
DataStream<UserSession> sessions = actions
    .keyBy(UserAction::getUserId)
    .window(EventTimeSessionWindows.withGap(Time.minutes(30)))
    .process(new SessionAnalyzer());

// 动态会话间隔
public class DynamicSessionGapExtractor 
        implements SessionWindowTimeGapExtractor<UserAction> {
    
    @Override
    public long extract(UserAction element) {
        // 根据用户类型设置不同的会话超时
        switch (element.getUserType()) {
            case PREMIUM:
                return 60 * 60 * 1000; // 1小时
            case NORMAL:
                return 30 * 60 * 1000; // 30分钟
            case GUEST:
                return 10 * 60 * 1000; // 10分钟
            default:
                return 30 * 60 * 1000;
        }
    }
}

DataStream<UserSession> dynamicSessions = actions
    .keyBy(UserAction::getUserId)
    .window(EventTimeSessionWindows.withDynamicGap(new DynamicSessionGapExtractor()))
    .process(new SessionProcessor());

// 会话窗口处理函数
public class SessionProcessor 
        extends ProcessWindowFunction<UserAction, UserSession, String, TimeWindow> {
    
    @Override
    public void process(String userId, Context context, 
                       Iterable<UserAction> actions, 
                       Collector<UserSession> out) {
        
        List<UserAction> sessionActions = new ArrayList<>();
        actions.forEach(sessionActions::add);
        
        // 按时间排序
        sessionActions.sort(Comparator.comparingLong(UserAction::getTimestamp));
        
        UserSession session = new UserSession();
        session.setUserId(userId);
        session.setStartTime(context.window().getStart());
        session.setEndTime(context.window().getEnd());
        session.setActionCount(sessionActions.size());
        session.setDuration(context.window().getEnd() - context.window().getStart());
        
        // 分析用户行为路径
        List<String> path = sessionActions.stream()
            .map(UserAction::getActionType)
            .collect(Collectors.toList());
        session.setActionPath(path);
        
        // 计算会话价值
        double sessionValue = calculateSessionValue(sessionActions);
        session.setValue(sessionValue);
        
        out.collect(session);
    }
    
    private double calculateSessionValue(List<UserAction> actions) {
        return actions.stream()
            .mapToDouble(this::getActionValue)
            .sum();
    }
    
    private double getActionValue(UserAction action) {
        switch (action.getActionType()) {
            case "purchase": return 100.0;
            case "add_to_cart": return 10.0;
            case "view": return 1.0;
            default: return 0.0;
        }
    }
}

计数窗口

基于元素数量而非时间的窗口。

// 滚动计数窗口
DataStream<Transaction> transactions = env.addSource(new TransactionSource());

// 每100笔交易计算一次
DataStream<TransactionSummary> summaries = transactions
    .keyBy(Transaction::getUserId)
    .countWindow(100)
    .aggregate(new TransactionSummaryAggregator());

// 滑动计数窗口
DataStream<Alert> alerts = transactions
    .keyBy(Transaction::getUserId)
    .countWindow(100, 10) // 窗口大小100,滑动步长10
    .process(new FraudDetector());

// 结合触发器的计数窗口
public class CountAndTimeTrigger extends Trigger<Event, GlobalWindow> {
    private final long maxCount;
    private final long maxTime;
    private final ReducingStateDescriptor<Long> countDesc = 
        new ReducingStateDescriptor<>("count", new Sum(), LongSerializer.INSTANCE);
    private final ValueStateDescriptor<Long> lastTimeDesc = 
        new ValueStateDescriptor<>("lastTime", Long.class);
    
    @Override
    public TriggerResult onElement(Event element, long timestamp, 
                                  GlobalWindow window, TriggerContext ctx) {
        ReducingState<Long> count = ctx.getPartitionedState(countDesc);
        ValueState<Long> lastTime = ctx.getPartitionedState(lastTimeDesc);
        
        count.add(1L);
        
        if (lastTime.value() == null) {
            long timer = ctx.getCurrentProcessingTime() + maxTime;
            ctx.registerProcessingTimeTimer(timer);
            lastTime.update(timer);
        }
        
        if (count.get() >= maxCount) {
            count.clear();
            lastTime.clear();
            return TriggerResult.FIRE_AND_PURGE;
        }
        
        return TriggerResult.CONTINUE;
    }
    
    @Override
    public TriggerResult onProcessingTime(long time, GlobalWindow window, 
                                         TriggerContext ctx) {
        ReducingState<Long> count = ctx.getPartitionedState(countDesc);
        ValueState<Long> lastTime = ctx.getPartitionedState(lastTimeDesc);
        
        if (time >= lastTime.value()) {
            count.clear();
            lastTime.clear();
            return TriggerResult.FIRE_AND_PURGE;
        }
        
        return TriggerResult.CONTINUE;
    }
}

自定义窗口

实现自定义的窗口分配器和窗口函数。

// 自定义窗口分配器
public class CustomWindowAssigner extends WindowAssigner<Event, TimeWindow> {
    private final long windowSize;
    
    @Override
    public Collection<TimeWindow> assignWindows(Event element, long timestamp, 
                                               WindowAssignerContext context) {
        // 基于业务逻辑分配窗口
        long startTime = getWindowStart(element, timestamp);
        long endTime = startTime + windowSize;
        return Collections.singletonList(new TimeWindow(startTime, endTime));
    }
    
    private long getWindowStart(Event element, long timestamp) {
        // 自定义窗口起始时间计算逻辑
        // 例如:基于事件类型的不同窗口对齐
        if (element.getType() == EventType.CRITICAL) {
            // 关键事件立即开启新窗口
            return timestamp;
        } else {
            // 普通事件按小时对齐
            return timestamp - (timestamp % (60 * 60 * 1000));
        }
    }
    
    @Override
    public Trigger<Event, TimeWindow> getDefaultTrigger(StreamExecutionEnvironment env) {
        return EventTimeTrigger.create();
    }
    
    @Override
    public TypeSerializer<TimeWindow> getWindowSerializer(ExecutionConfig executionConfig) {
        return new TimeWindow.Serializer();
    }
    
    @Override
    public boolean isEventTime() {
        return true;
    }
}

// 自定义窗口函数
public class CustomWindowFunction 
        extends ProcessWindowFunction<Event, Result, String, TimeWindow> {
    
    private transient ValueState<Pattern> patternState;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        ValueStateDescriptor<Pattern> descriptor = new ValueStateDescriptor<>(
            "pattern",
            Pattern.class
        );
        patternState = getRuntimeContext().getState(descriptor);
    }
    
    @Override
    public void process(String key, Context context, 
                       Iterable<Event> events, 
                       Collector<Result> out) throws Exception {
        
        Pattern previousPattern = patternState.value();
        Pattern currentPattern = detectPattern(events);
        
        if (previousPattern != null && isSignificantChange(previousPattern, currentPattern)) {
            out.collect(new Result(
                key,
                context.window(),
                previousPattern,
                currentPattern,
                "Pattern changed significantly"
            ));
        }
        
        patternState.update(currentPattern);
    }
    
    private Pattern detectPattern(Iterable<Event> events) {
        // 复杂的模式检测逻辑
        return new Pattern();
    }
    
    private boolean isSignificantChange(Pattern p1, Pattern p2) {
        // 判断模式是否发生显著变化
        return true;
    }
}

连接算子

流连接

union 算子

合并多个相同类型的数据流。

// 基础 union
DataStream<Event> stream1 = env.addSource(new EventSource1());
DataStream<Event> stream2 = env.addSource(new EventSource2());
DataStream<Event> stream3 = env.addSource(new EventSource3());

DataStream<Event> unionStream = stream1.union(stream2, stream3);

// 实际应用:多数据源聚合
public class MultiSourceAggregation {
    public static DataStream<Order> aggregateOrders(StreamExecutionEnvironment env) {
        // 来自不同地区的订单流
        DataStream<Order> northOrders = env.addSource(new RegionOrderSource("NORTH"));
        DataStream<Order> southOrders = env.addSource(new RegionOrderSource("SOUTH"));
        DataStream<Order> eastOrders = env.addSource(new RegionOrderSource("EAST"));
        DataStream<Order> westOrders = env.addSource(new RegionOrderSource("WEST"));
        
        // 合并所有订单流
        return northOrders
            .union(southOrders, eastOrders, westOrders)
            .keyBy(Order::getOrderId)
            .process(new OrderDeduplication()); // 去重处理
    }
}

// 多格式数据统一处理
DataStream<String> jsonStream = env.addSource(new JsonSource());
DataStream<String> xmlStream = env.addSource(new XmlSource());
DataStream<String> csvStream = env.addSource(new CsvSource());

DataStream<Record> unifiedStream = jsonStream
    .map(new JsonParser())
    .union(
        xmlStream.map(new XmlParser()),
        csvStream.map(new CsvParser())
    )
    .filter(Record::isValid)
    .keyBy(Record::getId)
    .reduce(new RecordMerger());

connect 算子

连接两个可能不同类型的数据流。

// 基础 connect
DataStream<Order> orders = env.addSource(new OrderSource());
DataStream<Payment> payments = env.addSource(new PaymentSource());

ConnectedStreams<Order, Payment> connected = orders.connect(payments);

// 使用 CoMapFunction
DataStream<String> processedStream = connected
    .map(new CoMapFunction<Order, Payment, String>() {
        @Override
        public String map1(Order order) {
            return "Order: " + order.toString();
        }
        
        @Override
        public String map2(Payment payment) {
            return "Payment: " + payment.toString();
        }
    });

// 使用 CoFlatMapFunction 实现更复杂的逻辑
public class OrderPaymentMatcher 
        extends RichCoFlatMapFunction<Order, Payment, MatchedTransaction> {
    
    private transient ValueState<Order> orderState;
    private transient ValueState<Payment> paymentState;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        orderState = getRuntimeContext().getState(
            new ValueStateDescriptor<>("order", Order.class));
        paymentState = getRuntimeContext().getState(
            new ValueStateDescriptor<>("payment", Payment.class));
    }
    
    @Override
    public void flatMap1(Order order, Collector<MatchedTransaction> out) throws Exception {
        Payment payment = paymentState.value();
        if (payment != null && payment.getOrderId().equals(order.getOrderId())) {
            out.collect(new MatchedTransaction(order, payment));
            paymentState.clear();
        } else {
            orderState.update(order);
        }
    }
    
    @Override
    public void flatMap2(Payment payment, Collector<MatchedTransaction> out) throws Exception {
        Order order = orderState.value();
        if (order != null && order.getOrderId().equals(payment.getOrderId())) {
            out.collect(new MatchedTransaction(order, payment));
            orderState.clear();
        } else {
            paymentState.update(payment);
        }
    }
}

// 广播连接模式
DataStream<Transaction> transactions = env.addSource(new TransactionSource());
DataStream<Rule> rules = env.addSource(new RuleSource());

BroadcastStream<Rule> broadcastRules = rules.broadcast(ruleStateDescriptor);

DataStream<Alert> alerts = transactions
    .keyBy(Transaction::getUserId)
    .connect(broadcastRules)
    .process(new DynamicAlertFunction());

窗口连接

join 算子

基于窗口的内连接操作。

// 基础窗口 join
DataStream<Order> orders = env.addSource(new OrderSource());
DataStream<Shipment> shipments = env.addSource(new ShipmentSource());

DataStream<FulfilledOrder> fulfilledOrders = orders
    .join(shipments)
    .where(Order::getOrderId)
    .equalTo(Shipment::getOrderId)
    .window(TumblingEventTimeWindows.of(Time.hours(1)))
    .apply(new JoinFunction<Order, Shipment, FulfilledOrder>() {
        @Override
        public FulfilledOrder join(Order order, Shipment shipment) {
            return new FulfilledOrder(order, shipment);
        }
    });

// 自定义 join 函数
public class OrderShipmentJoiner 
        implements JoinFunction<Order, Shipment, FulfilledOrder> {
    
    @Override
    public FulfilledOrder join(Order order, Shipment shipment) {
        FulfilledOrder fulfilled = new FulfilledOrder();
        fulfilled.setOrderId(order.getOrderId());
        fulfilled.setCustomerId(order.getCustomerId());
        fulfilled.setOrderTime(order.getTimestamp());
        fulfilled.setShipmentTime(shipment.getTimestamp());
        fulfilled.setDeliveryTime(shipment.getEstimatedDelivery());
        fulfilled.setCarrier(shipment.getCarrier());
        fulfilled.setTrackingNumber(shipment.getTrackingNumber());
        
        // 计算履行时间
        long fulfillmentTime = shipment.getTimestamp() - order.getTimestamp();
        fulfilled.setFulfillmentTime(fulfillmentTime);
        
        return fulfilled;
    }
}

// 多流 join
DataStream<Click> clicks = env.addSource(new ClickSource());
DataStream<View> views = env.addSource(new ViewSource());
DataStream<Purchase> purchases = env.addSource(new PurchaseSource());

// 先 join clicks 和 views
DataStream<ClickView> clickViews = clicks
    .join(views)
    .where(Click::getSessionId)
    .equalTo(View::getSessionId)
    .window(SlidingEventTimeWindows.of(Time.minutes(30), Time.minutes(5)))
    .apply(new ClickViewJoiner());

// 再 join 购买数据
DataStream<UserJourney> journeys = clickViews
    .join(purchases)
    .where(ClickView::getUserId)
    .equalTo(Purchase::getUserId)
    .window(TumblingEventTimeWindows.of(Time.hours(1)))
    .apply(new UserJourneyBuilder());

coGroup 算子

类似于 join,但提供更灵活的连接逻辑。

// 基础 coGroup
DataStream<Click> clicks = env.addSource(new ClickSource());
DataStream<Impression> impressions = env.addSource(new ImpressionSource());

DataStream<CampaignStats> stats = clicks
    .coGroup(impressions)
    .where(Click::getCampaignId)
    .equalTo(Impression::getCampaignId)
    .window(TumblingEventTimeWindows.of(Time.hours(1)))
    .apply(new CoGroupFunction<Click, Impression, CampaignStats>() {
        @Override
        public void coGroup(Iterable<Click> clicks, 
                           Iterable<Impression> impressions, 
                           Collector<CampaignStats> out) {
            
            int clickCount = 0;
            int impressionCount = 0;
            String campaignId = null;
            
            for (Click click : clicks) {
                clickCount++;
                if (campaignId == null) {
                    campaignId = click.getCampaignId();
                }
            }
            
            for (Impression impression : impressions) {
                impressionCount++;
                if (campaignId == null) {
                    campaignId = impression.getCampaignId();
                }
            }
            
            if (campaignId != null) {
                double ctr = impressionCount > 0 ? 
                    (double) clickCount / impressionCount : 0.0;
                    
                out.collect(new CampaignStats(
                    campaignId, 
                    impressionCount, 
                    clickCount, 
                    ctr
                ));
            }
        }
    });

// 高级 coGroup:左外连接实现
public class LeftOuterJoinFunction 
        implements CoGroupFunction<Order, Payment, OrderStatus> {
    
    @Override
    public void coGroup(Iterable<Order> orders, 
                       Iterable<Payment> payments, 
                       Collector<OrderStatus> out) {
        
        List<Payment> paymentList = new ArrayList<>();
        payments.forEach(paymentList::add);
        
        for (Order order : orders) {
            if (paymentList.isEmpty()) {
                // 没有匹配的支付记录
                out.collect(new OrderStatus(order, null, "UNPAID"));
            } else {
                // 有匹配的支付记录
                for (Payment payment : paymentList) {
                    out.collect(new OrderStatus(order, payment, "PAID"));
                }
            }
        }
    }
}

间隔连接

基于时间间隔的流连接。

// Interval Join
DataStream<Order> orders = env.addSource(new OrderSource());
DataStream<Payment> payments = env.addSource(new PaymentSource());

DataStream<MatchedTransaction> matched = orders
    .keyBy(Order::getOrderId)
    .intervalJoin(payments.keyBy(Payment::getOrderId))
    .between(Time.minutes(-5), Time.minutes(30)) // 订单后5分钟到30分钟内的支付
    .process(new ProcessJoinFunction<Order, Payment, MatchedTransaction>() {
        @Override
        public void processElement(Order order, Payment payment, 
                                  Context ctx, 
                                  Collector<MatchedTransaction> out) {
            
            // 验证支付金额
            if (Math.abs(order.getAmount() - payment.getAmount()) < 0.01) {
                out.collect(new MatchedTransaction(
                    order,
                    payment,
                    payment.getTimestamp() - order.getTimestamp(),
                    "MATCHED"
                ));
            } else {
                out.collect(new MatchedTransaction(
                    order,
                    payment,
                    payment.getTimestamp() - order.getTimestamp(),
                    "AMOUNT_MISMATCH"
                ));
            }
        }
    });

// 复杂的间隔连接:包含上下界的动态设置
public class DynamicIntervalJoin {
    public static DataStream<Result> performJoin(
            DataStream<EventA> streamA,
            DataStream<EventB> streamB) {
        
        return streamA
            .keyBy(EventA::getKey)
            .intervalJoin(streamB.keyBy(EventB::getKey))
            .between(Time.seconds(-10), Time.seconds(20))
            .lowerBoundExclusive() // 不包含下界
            .upperBoundExclusive() // 不包含上界
            .process(new ProcessJoinFunction<EventA, EventB, Result>() {
                @Override
                public void processElement(EventA a, EventB b, 
                                          Context ctx, 
                                          Collector<Result> out) {
                    
                    long timeDiff = b.getTimestamp() - a.getTimestamp();
                    
                    // 基于时间差的不同处理逻辑
                    if (timeDiff < 5000) {
                        out.collect(new Result(a, b, "IMMEDIATE"));
                    } else if (timeDiff < 15000) {
                        out.collect(new Result(a, b, "DELAYED"));
                    } else {
                        out.collect(new Result(a, b, "LATE"));
                    }
                }
            });
    }
}

处理函数

ProcessFunction 系列

最底层、最灵活的处理函数。

// 基础 ProcessFunction
public class EventProcessFunction extends ProcessFunction<Event, ProcessedEvent> {
    private transient ValueState<Long> countState;
    private transient ListState<Event> bufferState;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        ValueStateDescriptor<Long> countDescriptor = 
            new ValueStateDescriptor<>("count", Long.class, 0L);
        countState = getRuntimeContext().getState(countDescriptor);
        
        ListStateDescriptor<Event> bufferDescriptor = 
            new ListStateDescriptor<>("buffer", Event.class);
        bufferState = getRuntimeContext().getListState(bufferDescriptor);
    }
    
    @Override
    public void processElement(Event event, Context ctx, 
                              Collector<ProcessedEvent> out) throws Exception {
        
        // 更新计数
        Long count = countState.value();
        count++;
        countState.update(count);
        
        // 缓存事件
        bufferState.add(event);
        
        // 注册定时器
        long timer = ctx.timestamp() + 60000; // 1分钟后
        ctx.timerService().registerEventTimeTimer(timer);
        
        // 访问时间戳和水位线
        Long timestamp = ctx.timestamp();
        Long watermark = ctx.timerService().currentWatermark();
        
        // 输出处理后的事件
        out.collect(new ProcessedEvent(event, count, timestamp, watermark));
    }
    
    @Override
    public void onTimer(long timestamp, OnTimerContext ctx, 
                       Collector<ProcessedEvent> out) throws Exception {
        
        // 定时器触发时的处理逻辑
        List<Event> events = new ArrayList<>();
        bufferState.get().forEach(events::add);
        
        // 批量处理缓存的事件
        ProcessedEvent batchResult = processBatch(events);
        out.collect(batchResult);
        
        // 清理状态
        bufferState.clear();
    }
}

// KeyedProcessFunction 示例
public class SessionWindowFunction 
        extends KeyedProcessFunction<String, UserAction, UserSession> {
    
    private transient ValueState<UserSession> sessionState;
    private transient ValueState<Long> lastActionTime;
    private static final long SESSION_TIMEOUT = 30 * 60 * 1000; // 30分钟
    
    @Override
    public void open(Configuration parameters) throws Exception {
        sessionState = getRuntimeContext().getState(
            new ValueStateDescriptor<>("session", UserSession.class));
        lastActionTime = getRuntimeContext().getState(
            new ValueStateDescriptor<>("lastAction", Long.class));
    }
    
    @Override
    public void processElement(UserAction action, Context ctx, 
                              Collector<UserSession> out) throws Exception {
        
        UserSession session = sessionState.value();
        if (session == null) {
            session = new UserSession(action.getUserId());
        }
        
        // 更新会话
        session.addAction(action);
        session.updateLastActionTime(action.getTimestamp());
        
        // 设置会话超时定时器
        Long lastTime = lastActionTime.value();
        if (lastTime != null) {
            ctx.timerService().deleteEventTimeTimer(lastTime + SESSION_TIMEOUT);
        }
        
        long newTimer = action.getTimestamp() + SESSION_TIMEOUT;
        ctx.timerService().registerEventTimeTimer(newTimer);
        
        // 更新状态
        sessionState.update(session);
        lastActionTime.update(action.getTimestamp());
    }
    
    @Override
    public void onTimer(long timestamp, OnTimerContext ctx, 
                       Collector<UserSession> out) throws Exception {
        
        // 会话超时,输出会话并清理状态
        UserSession session = sessionState.value();
        if (session != null) {
            session.setEndTime(timestamp - SESSION_TIMEOUT);
            out.collect(session);
            
            sessionState.clear();
            lastActionTime.clear();
        }
    }
}

// CoProcessFunction 示例
public class OrderPaymentProcessor 
        extends CoProcessFunction<Order, Payment, Transaction> {
    
    private transient MapState<String, Order> pendingOrders;
    private transient MapState<String, Payment> pendingPayments;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        pendingOrders = getRuntimeContext().getMapState(
            new MapStateDescriptor<>("orders", String.class, Order.class));
        pendingPayments = getRuntimeContext().getMapState(
            new MapStateDescriptor<>("payments", String.class, Payment.class));
    }
    
    @Override
    public void processElement1(Order order, Context ctx, 
                               Collector<Transaction> out) throws Exception {
        
        Payment payment = pendingPayments.get(order.getOrderId());
        if (payment != null) {
            // 找到匹配的支付
            out.collect(createTransaction(order, payment));
            pendingPayments.remove(order.getOrderId());
        } else {
            // 缓存订单,等待支付
            pendingOrders.put(order.getOrderId(), order);
            
            // 设置超时定时器(30分钟)
            ctx.timerService().registerProcessingTimeTimer(
                ctx.timerService().currentProcessingTime() + 30 * 60 * 1000
            );
        }
    }
    
    @Override
    public void processElement2(Payment payment, Context ctx, 
                               Collector<Transaction> out) throws Exception {
        
        Order order = pendingOrders.get(payment.getOrderId());
        if (order != null) {
            // 找到匹配的订单
            out.collect(createTransaction(order, payment));
            pendingOrders.remove(payment.getOrderId());
        } else {
            // 缓存支付,等待订单
            pendingPayments.put(payment.getOrderId(), payment);
        }
    }
    
    @Override
    public void onTimer(long timestamp, OnTimerContext ctx, 
                       Collector<Transaction> out) throws Exception {
        
        // 清理超时的待处理数据
        List<String> timeoutOrders = new ArrayList<>();
        
        for (Map.Entry<String, Order> entry : pendingOrders.entries()) {
            if (entry.getValue().getTimestamp() + 30 * 60 * 1000 < timestamp) {
                timeoutOrders.add(entry.getKey());
                
                // 输出超时交易
                out.collect(createTimeoutTransaction(entry.getValue()));
            }
        }
        
        timeoutOrders.forEach(orderId -> {
            try {
                pendingOrders.remove(orderId);
            } catch (Exception e) {
                // 处理异常
            }
        });
    }
}

定时器服务

ProcessFunction 中的定时器功能。

// 定时器服务示例
public class TimerServiceExample extends KeyedProcessFunction<String, Event, Result> {
    
    @Override
    public void processElement(Event event, Context ctx, 
                              Collector<Result> out) throws Exception {
        
        // 获取定时器服务
        TimerService timerService = ctx.timerService();
        
        // 当前时间
        long currentProcessingTime = timerService.currentProcessingTime();
        long currentWatermark = timerService.currentWatermark();
        
        // 注册处理时间定时器
        timerService.registerProcessingTimeTimer(currentProcessingTime + 5000);
        
        // 注册事件时间定时器
        if (ctx.timestamp() != null) {
            timerService.registerEventTimeTimer(ctx.timestamp() + 10000);
        }
        
        // 删除定时器
        timerService.deleteProcessingTimeTimer(currentProcessingTime + 5000);
        timerService.deleteEventTimeTimer(ctx.timestamp() + 10000);
    }
    
    @Override
    public void onTimer(long timestamp, OnTimerContext ctx, 
                       Collector<Result> out) throws Exception {
        
        // 判断是哪种定时器
        boolean isEventTime = timestamp == ctx.timerService().currentWatermark();
        
        if (isEventTime) {
            // 事件时间定时器逻辑
            handleEventTimeTimer(timestamp, ctx, out);
        } else {
            // 处理时间定时器逻辑
            handleProcessingTimeTimer(timestamp, ctx, out);
        }
    }
}

// 高级定时器使用:实现滑动窗口
public class CustomSlidingWindow 
        extends KeyedProcessFunction<String, Event, WindowResult> {
    
    private final long windowSize;
    private final long slideSize;
    private transient MapState<Long, List<Event>> windowState;
    
    public CustomSlidingWindow(long windowSize, long slideSize) {
        this.windowSize = windowSize;
        this.slideSize = slideSize;
    }
    
    @Override
    public void open(Configuration parameters) throws Exception {
        MapStateDescriptor<Long, List<Event>> descriptor = 
            new MapStateDescriptor<>(
                "windows",
                Long.class,
                TypeInformation.of(new TypeHint<List<Event>>() {})
            );
        windowState = getRuntimeContext().getMapState(descriptor);
    }
    
    @Override
    public void processElement(Event event, Context ctx, 
                              Collector<WindowResult> out) throws Exception {
        
        long timestamp = ctx.timestamp();
        
        // 计算事件所属的所有窗口
        long lastStart = timestamp - ((timestamp + slideSize) % slideSize);
        for (long start = lastStart; start > timestamp - windowSize; start -= slideSize) {
            long end = start + windowSize;
            
            // 将事件添加到窗口
            List<Event> events = windowState.get(start);
            if (events == null) {
                events = new ArrayList<>();
                // 注册窗口结束定时器
                ctx.timerService().registerEventTimeTimer(end - 1);
            }
            events.add(event);
            windowState.put(start, events);
        }
    }
    
    @Override
    public void onTimer(long timestamp, OnTimerContext ctx, 
                       Collector<WindowResult> out) throws Exception {
        
        // 计算窗口开始时间
        long windowStart = timestamp + 1 - windowSize;
        
        List<Event> events = windowState.get(windowStart);
        if (events != null) {
            // 输出窗口结果
            WindowResult result = computeWindowResult(
                ctx.getCurrentKey(),
                windowStart,
                timestamp + 1,
                events
            );
            out.collect(result);
            
            // 清理状态
            windowState.remove(windowStart);
        }
    }
}

状态算子

状态管理

Flink 提供的各种状态类型和使用方式。

// ValueState 示例
public class ValueStateExample extends RichFlatMapFunction<Event, Result> {
    private transient ValueState<Counter> counterState;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        ValueStateDescriptor<Counter> descriptor = new ValueStateDescriptor<>(
            "counter",
            TypeInformation.of(Counter.class)
        );
        counterState = getRuntimeContext().getState(descriptor);
    }
    
    @Override
    public void flatMap(Event event, Collector<Result> out) throws Exception {
        Counter counter = counterState.value();
        if (counter == null) {
            counter = new Counter();
        }
        
        counter.increment();
        
        if (counter.getCount() >= 100) {
            out.collect(new Result(counter));
            counter.reset();
        }
        
        counterState.update(counter);
    }
}

// ListState 示例
public class ListStateExample extends KeyedProcessFunction<String, Event, Result> {
    private transient ListState<Event> eventListState;
    private static final int MAX_EVENTS = 1000;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        ListStateDescriptor<Event> descriptor = new ListStateDescriptor<>(
            "event-list",
            Event.class
        );
        eventListState = getRuntimeContext().getListState(descriptor);
    }
    
    @Override
    public void processElement(Event event, Context ctx, 
                              Collector<Result> out) throws Exception {
        
        eventListState.add(event);
        
        // 统计列表大小
        int count = 0;
        for (Event e : eventListState.get()) {
            count++;
        }
        
        if (count >= MAX_EVENTS) {
            // 处理并清理
            List<Event> events = new ArrayList<>();
            eventListState.get().forEach(events::add);
            
            Result result = processEvents(events);
            out.collect(result);
            
            eventListState.clear();
        }
    }
}

// MapState 示例
public class MapStateExample extends KeyedProcessFunction<String, Order, OrderStats> {
    private transient MapState<String, OrderInfo> orderMapState;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        MapStateDescriptor<String, OrderInfo> descriptor = new MapStateDescriptor<>(
            "order-map",
            String.class,
            OrderInfo.class
        );
        orderMapState = getRuntimeContext().getMapState(descriptor);
    }
    
    @Override
    public void processElement(Order order, Context ctx, 
                              Collector<OrderStats> out) throws Exception {
        
        String orderId = order.getOrderId();
        OrderInfo info = orderMapState.get(orderId);
        
        if (info == null) {
            info = new OrderInfo(order);
        } else {
            info.update(order);
        }
        
        orderMapState.put(orderId, info);
        
        // 定期输出统计
        if (shouldOutputStats()) {
            OrderStats stats = calculateStats();
            out.collect(stats);
        }
    }
    
    private OrderStats calculateStats() throws Exception {
        OrderStats stats = new OrderStats();
        
        for (Map.Entry<String, OrderInfo> entry : orderMapState.entries()) {
            stats.aggregate(entry.getValue());
        }
        
        return stats;
    }
}

// ReducingState 示例
public class ReducingStateExample extends RichFlatMapFunction<Transaction, Summary> {
    private transient ReducingState<Transaction> transactionSumState;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        ReducingStateDescriptor<Transaction> descriptor = new ReducingStateDescriptor<>(
            "transaction-sum",
            new TransactionSumReducer(),
            Transaction.class
        );
        transactionSumState = getRuntimeContext().getReducingState(descriptor);
    }
    
    @Override
    public void flatMap(Transaction transaction, Collector<Summary> out) throws Exception {
        transactionSumState.add(transaction);
        
        // 定期输出汇总
        if (shouldOutput()) {
            Transaction sum = transactionSumState.get();
            out.collect(new Summary(sum));
        }
    }
    
    private static class TransactionSumReducer implements ReduceFunction<Transaction> {
        @Override
        public Transaction reduce(Transaction t1, Transaction t2) {
            return new Transaction(
                t1.getUserId(),
                t1.getAmount() + t2.getAmount(),
                Math.max(t1.getTimestamp(), t2.getTimestamp())
            );
        }
    }
}

// AggregatingState 示例
public class AggregatingStateExample 
        extends KeyedProcessFunction<String, Event, AverageResult> {
    
    private transient AggregatingState<Event, AverageResult> avgState;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        AggregatingStateDescriptor<Event, AverageAccumulator, AverageResult> descriptor = 
            new AggregatingStateDescriptor<>(
                "average",
                new AverageAggregator(),
                AverageAccumulator.class
            );
        avgState = getRuntimeContext().getAggregatingState(descriptor);
    }
    
    @Override
    public void processElement(Event event, Context ctx, 
                              Collector<AverageResult> out) throws Exception {
        
        avgState.add(event);
        
        // 每100个事件输出一次平均值
        if (event.getSequenceNumber() % 100 == 0) {
            AverageResult result = avgState.get();
            out.collect(result);
        }
    }
    
    private static class AverageAggregator 
            implements AggregateFunction<Event, AverageAccumulator, AverageResult> {
        
        @Override
        public AverageAccumulator createAccumulator() {
            return new AverageAccumulator();
        }
        
        @Override
        public AverageAccumulator add(Event event, AverageAccumulator acc) {
            acc.add(event.getValue());
            return acc;
        }
        
        @Override
        public AverageResult getResult(AverageAccumulator acc) {
            return acc.getResult();
        }
        
        @Override
        public AverageAccumulator merge(AverageAccumulator a, AverageAccumulator b) {
            return a.merge(b);
        }
    }
}

状态后端

配置不同的状态后端以适应不同场景。

// 配置状态后端
public class StateBackendConfig {
    
    public static void configureStateBackend(StreamExecutionEnvironment env) {
        // 内存状态后端(开发测试用)
        env.setStateBackend(new HashMapStateBackend());
        
        // 文件系统状态后端
        env.setStateBackend(new FsStateBackend("file:///data/flink/checkpoints"));
        
        // RocksDB 状态后端(生产推荐)
        try {
            RocksDBStateBackend rocksDBStateBackend = new RocksDBStateBackend(
                "hdfs://namenode:9000/flink/checkpoints",
                true  // 启用增量检查点
            );
            
            // 配置 RocksDB
            rocksDBStateBackend.setOptions(new OptionsFactory() {
                @Override
                public DBOptions createDBOptions(DBOptions currentOptions, 
                                               Collection<AutoCloseable> handlesToClose) {
                    return currentOptions
                        .setMaxBackgroundJobs(4)
                        .setMaxOpenFiles(-1);
                }
                
                @Override
                public ColumnFamilyOptions createColumnOptions(
                        ColumnFamilyOptions currentOptions, 
                        Collection<AutoCloseable> handlesToClose) {
                    
                    return currentOptions
                        .setCompactionStyle(CompactionStyle.LEVEL)
                        .setLevel0FileNumCompactionTrigger(10)
                        .setMaxBytesForLevelBase(256 * 1024 * 1024);
                }
            });
            
            env.setStateBackend(rocksDBStateBackend);
            
        } catch (IOException e) {
            throw new RuntimeException("Failed to create RocksDB state backend", e);
        }
    }
    
    // 状态 TTL 配置
    public static ValueStateDescriptor<UserInfo> createTTLStateDescriptor() {
        ValueStateDescriptor<UserInfo> descriptor = new ValueStateDescriptor<>(
            "user-info",
            UserInfo.class
        );
        
        // 配置 TTL
        StateTtlConfig ttlConfig = StateTtlConfig
            .newBuilder(Time.hours(24))
            .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
            .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
            .cleanupInRocksdbCompactFilter(1000)
            .build();
        
        descriptor.enableTimeToLive(ttlConfig);
        
        return descriptor;
    }
}

// 大状态优化
public class LargeStateOptimization {
    
    public static class OptimizedStateFunction 
            extends KeyedProcessFunction<String, Event, Result> {
        
        // 使用多个 MapState 代替单个大的 ValueState
        private transient MapState<String, EventStats> statsMapState;
        private transient MapState<Long, List<Event>> timeIndexState;
        
        @Override
        public void open(Configuration parameters) throws Exception {
            // 分片存储统计信息
            MapStateDescriptor<String, EventStats> statsDescriptor = 
                new MapStateDescriptor<>("stats", String.class, EventStats.class);
            statsMapState = getRuntimeContext().getMapState(statsDescriptor);
            
            // 时间索引
            MapStateDescriptor<Long, List<Event>> timeIndexDescriptor = 
                new MapStateDescriptor<>(
                    "time-index",
                    Long.class,
                    TypeInformation.of(new TypeHint<List<Event>>() {})
                );
            timeIndexState = getRuntimeContext().getMapState(timeIndexDescriptor);
        }
        
        @Override
        public void processElement(Event event, Context ctx, 
                                  Collector<Result> out) throws Exception {
            
            // 分片键
            String shardKey = getShardKey(event);
            
            // 更新统计信息
            EventStats stats = statsMapState.get(shardKey);
            if (stats == null) {
                stats = new EventStats();
            }
            stats.update(event);
            statsMapState.put(shardKey, stats);
            
            // 更新时间索引
            long timeKey = event.getTimestamp() / (60 * 1000); // 按分钟分组
            List<Event> events = timeIndexState.get(timeKey);
            if (events == null) {
                events = new ArrayList<>();
            }
            events.add(event);
            timeIndexState.put(timeKey, events);
            
            // 定期清理旧数据
            cleanupOldData(ctx.timerService().currentProcessingTime());
        }
        
        private String getShardKey(Event event) {
            // 根据事件特征计算分片键
            return event.getType() + "_" + (event.getId().hashCode() % 100);
        }
        
        private void cleanupOldData(long currentTime) throws Exception {
            long cutoffTime = currentTime - 24 * 60 * 60 * 1000; // 24小时前
            
            // 清理过期的时间索引
            List<Long> keysToRemove = new ArrayList<>();
            for (Long timeKey : timeIndexState.keys()) {
                if (timeKey * 60 * 1000 < cutoffTime) {
                    keysToRemove.add(timeKey);
                }
            }
            
            for (Long key : keysToRemove) {
                timeIndexState.remove(key);
            }
        }
    }
}

输出算子

基础输出

用于调试和开发的输出算子。

// 基础 print
DataStream<String> stream = env.fromElements("A", "B", "C");
stream.print();  // 输出到标准输出

// 带前缀的 print
stream.print("MyStream");  // 输出格式: MyStream> A

// 输出到标准错误
stream.printToErr();

// 控制输出格式
DataStream<Event> events = env.addSource(new EventSource());
events.map(event -> {
    return String.format("Event[id=%s, type=%s, timestamp=%d]",
        event.getId(), event.getType(), event.getTimestamp());
}).print("Formatted");

writeAsText 算子

将数据写入文本文件。

// 写入本地文件
DataStream<String> stream = env.fromElements("line1", "line2", "line3");
stream.writeAsText("file:///tmp/output.txt", WriteMode.OVERWRITE);

// 写入 HDFS
stream.writeAsText("hdfs://namenode:9000/output/result.txt");

// 自定义输出格式
DataStream<Tuple2<String, Integer>> counts = words
    .flatMap(new Tokenizer())
    .keyBy(0)
    .sum(1);

counts.writeAsText("output/wordcount.txt", WriteMode.OVERWRITE)
      .setParallelism(1);  // 单个文件输出

// 使用 TextOutputFormat
counts.writeUsingOutputFormat(new TextOutputFormat<Tuple2<String, Integer>>(
    new Path("output/wordcount.txt")) {
    
    @Override
    public void writeRecord(Tuple2<String, Integer> record) throws IOException {
        // 自定义格式
        String line = String.format("%s\t%d", record.f0, record.f1);
        stream.write(line.getBytes());
        stream.write('\n');
    }
});

自定义 Sink

实现 SinkFunction

基础的 Sink 接口。

// 基础 SinkFunction
public class CustomSink implements SinkFunction<Event> {
    private final String connectionString;
    
    public CustomSink(String connectionString) {
        this.connectionString = connectionString;
    }
    
    @Override
    public void invoke(Event event, Context context) throws Exception {
        // 简单的输出逻辑
        System.out.printf("Sink received: %s at %d%n", 
            event, context.timestamp());
    }
}

// RichSinkFunction 示例
public class JdbcSink extends RichSinkFunction<Record> {
    private transient Connection connection;
    private transient PreparedStatement insertStatement;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        // 建立连接
        connection = DriverManager.getConnection(
            "jdbc:mysql://localhost:3306/flink_db",
            "username",
            "password"
        );
        
        insertStatement = connection.prepareStatement(
            "INSERT INTO records (id, data, timestamp) VALUES (?, ?, ?)"
        );
    }
    
    @Override
    public void invoke(Record record, Context context) throws Exception {
        insertStatement.setString(1, record.getId());
        insertStatement.setString(2, record.getData());
        insertStatement.setTimestamp(3, new Timestamp(record.getTimestamp()));
        
        insertStatement.executeUpdate();
    }
    
    @Override
    public void close() throws Exception {
        if (insertStatement != null) {
            insertStatement.close();
        }
        if (connection != null) {
            connection.close();
        }
    }
}

// 批量写入优化
public class BatchedJdbcSink extends RichSinkFunction<Record> 
        implements CheckpointedFunction {
    
    private final int batchSize;
    private transient Connection connection;
    private transient PreparedStatement statement;
    private transient List<Record> batch;
    
    public BatchedJdbcSink(int batchSize) {
        this.batchSize = batchSize;
    }
    
    @Override
    public void open(Configuration parameters) throws Exception {
        connection = getConnection();
        statement = connection.prepareStatement(
            "INSERT INTO records (id, data, timestamp) VALUES (?, ?, ?)"
        );
        batch = new ArrayList<>(batchSize);
    }
    
    @Override
    public void invoke(Record record, Context context) throws Exception {
        batch.add(record);
        
        if (batch.size() >= batchSize) {
            flush();
        }
    }
    
    private void flush() throws SQLException {
        for (Record record : batch) {
            statement.setString(1, record.getId());
            statement.setString(2, record.getData());
            statement.setTimestamp(3, new Timestamp(record.getTimestamp()));
            statement.addBatch();
        }
        
        statement.executeBatch();
        connection.commit();
        batch.clear();
    }
    
    @Override
    public void snapshotState(FunctionSnapshotContext context) throws Exception {
        flush();  // 确保所有数据都被写入
    }
    
    @Override
    public void initializeState(FunctionInitializationContext context) throws Exception {
        // 状态恢复时的初始化
    }
    
    @Override
    public void close() throws Exception {
        if (!batch.isEmpty()) {
            flush();
        }
        
        if (statement != null) {
            statement.close();
        }
        if (connection != null) {
            connection.close();
        }
    }
}

实现异步 Sink

高性能的异步输出实现。

// 异步 Sink 实现
public class AsyncElasticsearchSink extends RichSinkFunction<Document> {
    private transient RestHighLevelClient client;
    private transient BulkProcessor bulkProcessor;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        // 创建 ES 客户端
        client = new RestHighLevelClient(
            RestClient.builder(new HttpHost("localhost", 9200, "http"))
        );
        
        // 创建批量处理器
        BulkProcessor.Listener listener = new BulkProcessor.Listener() {
            @Override
            public void beforeBulk(long executionId, BulkRequest request) {
                System.out.printf("Executing bulk [%d] with %d requests%n",
                    executionId, request.numberOfActions());
            }
            
            @Override
            public void afterBulk(long executionId, BulkRequest request, 
                                 BulkResponse response) {
                if (response.hasFailures()) {
                    System.err.println("Bulk had failures: " + 
                        response.buildFailureMessage());
                }
            }
            
            @Override
            public void afterBulk(long executionId, BulkRequest request, 
                                 Throwable failure) {
                System.err.println("Bulk failed: " + failure.getMessage());
            }
        };
        
        bulkProcessor = BulkProcessor.builder(
                (request, bulkListener) -> 
                    client.bulkAsync(request, RequestOptions.DEFAULT, bulkListener),
                listener)
            .setBulkActions(1000)
            .setBulkSize(new ByteSizeValue(5, ByteSizeUnit.MB))
            .setFlushInterval(TimeValue.timeValueSeconds(5))
            .setConcurrentRequests(1)
            .setBackoffPolicy(BackoffPolicy.exponentialBackoff(
                TimeValue.timeValueMillis(100), 3))
            .build();
    }
    
    @Override
    public void invoke(Document document, Context context) throws Exception {
        IndexRequest request = new IndexRequest("documents")
            .id(document.getId())
            .source(document.toJson(), XContentType.JSON);
            
        bulkProcessor.add(request);
    }
    
    @Override
    public void close() throws Exception {
        if (bulkProcessor != null) {
            bulkProcessor.awaitClose(30, TimeUnit.SECONDS);
        }
        
        if (client != null) {
            client.close();
        }
    }
}

// 使用线程池的异步 Sink
public class ThreadPoolAsyncSink extends RichSinkFunction<Event> {
    private transient ExecutorService executorService;
    private transient HttpAsyncClient httpClient;
    private final Semaphore semaphore = new Semaphore(100); // 限流
    
    @Override
    public void open(Configuration parameters) throws Exception {
        executorService = Executors.newFixedThreadPool(10);
        httpClient = HttpAsyncClients.createDefault();
        httpClient.start();
    }
    
    @Override
    public void invoke(Event event, Context context) throws Exception {
        semaphore.acquire();
        
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                sendEventAsync(event);
            } catch (Exception e) {
                System.err.println("Failed to send event: " + e.getMessage());
            } finally {
                semaphore.release();
            }
        }, executorService);
        
        // 可选:等待完成或设置超时
        // future.get(5, TimeUnit.SECONDS);
    }
    
    private void sendEventAsync(Event event) throws Exception {
        HttpPost request = new HttpPost("http://api.example.com/events");
        request.setEntity(new StringEntity(event.toJson()));
        request.setHeader("Content-Type", "application/json");
        
        Future<HttpResponse> future = httpClient.execute(request, null);
        HttpResponse response = future.get();
        
        if (response.getStatusLine().getStatusCode() != 200) {
            throw new RuntimeException("HTTP request failed: " + 
                response.getStatusLine());
        }
    }
    
    @Override
    public void close() throws Exception {
        // 等待所有任务完成
        semaphore.acquire(100);
        
        if (executorService != null) {
            executorService.shutdown();
            executorService.awaitTermination(30, TimeUnit.SECONDS);
        }
        
        if (httpClient != null) {
            httpClient.close();
        }
    }
}

高级特性

侧输出流

使用侧输出流处理不同类型的数据。

// 定义侧输出标签
final OutputTag<String> errorTag = new OutputTag<String>("errors"){};
final OutputTag<LateEvent> lateTag = new OutputTag<LateEvent>("late-events"){};
final OutputTag<Alert> alertTag = new OutputTag<Alert>("alerts"){};

// 在 ProcessFunction 中使用侧输出
public class MultiOutputProcessFunction 
        extends ProcessFunction<Event, ProcessedEvent> {
    
    private final OutputTag<String> errorTag;
    private final OutputTag<Alert> alertTag;
    
    @Override
    public void processElement(Event event, Context ctx, 
                              Collector<ProcessedEvent> out) throws Exception {
        
        try {
            // 主流处理逻辑
            ProcessedEvent processed = processEvent(event);
            
            // 检查是否需要告警
            if (shouldAlert(processed)) {
                ctx.output(alertTag, createAlert(processed));
            }
            
            out.collect(processed);
            
        } catch (Exception e) {
            // 错误数据输出到侧输出流
            ctx.output(errorTag, String.format(
                "Error processing event %s: %s", 
                event.getId(), 
                e.getMessage()
            ));
        }
    }
}

// 在窗口函数中使用侧输出
SingleOutputStreamOperator<WindowResult> mainStream = events
    .keyBy(Event::getKey)
    .window(TumblingEventTimeWindows.of(Time.minutes(5)))
    .allowedLateness(Time.minutes(1))
    .sideOutputLateData(lateTag)
    .process(new ProcessWindowFunction<Event, WindowResult, String, TimeWindow>() {
        @Override
        public void process(String key, Context context, 
                           Iterable<Event> events, 
                           Collector<WindowResult> out) {
            
            List<Event> eventList = new ArrayList<>();
            events.forEach(eventList::add);
            
            // 正常处理
            WindowResult result = computeResult(eventList);
            out.collect(result);
            
            // 检测异常模式
            if (detectAnomaly(eventList)) {
                context.output(alertTag, new Alert(key, "Anomaly detected"));
            }
        }
    });

// 获取侧输出流
DataStream<LateEvent> lateEvents = mainStream.getSideOutput(lateTag);
DataStream<String> errors = mainStream.getSideOutput(errorTag);
DataStream<Alert> alerts = mainStream.getSideOutput(alertTag);

// 处理侧输出流
lateEvents.addSink(new LateEventHandler());
errors.addSink(new ErrorLogger());
alerts.addSink(new AlertNotifier());

迭代算子

实现迭代计算逻辑。

// 基础迭代
DataStream<Long> numbers = env.generateSequence(0, 1000);

IterativeStream<Long> iteration = numbers.iterate();

DataStream<Long> iterationBody = iteration
    .map(new MapFunction<Long, Long>() {
        @Override
        public Long map(Long value) throws Exception {
            return value - 1;
        }
    });

DataStream<Long> feedback = iterationBody
    .filter(new FilterFunction<Long>() {
        @Override
        public boolean filter(Long value) throws Exception {
            return value > 0;
        }
    });

iteration.closeWith(feedback);

DataStream<Long> output = iterationBody
    .filter(new FilterFunction<Long>() {
        @Override
        public boolean filter(Long value) throws Exception {
            return value <= 0;
        }
    });

// 复杂迭代示例:PageRank
public class PageRankExample {
    
    public static DataStream<Page> calculatePageRank(
            DataStream<Page> pages,
            DataStream<Link> links,
            int maxIterations) {
        
        // 初始化 PageRank 值
        DataStream<Page> initialRanks = pages
            .map(page -> {
                page.setRank(1.0);
                return page;
            });
        
        // 开始迭代
        IterativeStream<Page> iteration = initialRanks.iterate(maxIterations);
        
        // 计算新的 PageRank 值
        DataStream<Page> newRanks = iteration
            .join(links)
            .where(Page::getId)
            .equalTo(Link::getSourceId)
            .window(TumblingProcessingTimeWindows.of(Time.seconds(30)))
            .apply(new JoinFunction<Page, Link, Contribution>() {
                @Override
                public Contribution join(Page page, Link link) {
                    return new Contribution(
                        link.getTargetId(),
                        page.getRank() / page.getOutgoingLinks()
                    );
                }
            })
            .keyBy(Contribution::getTargetId)
            .reduce(new ReduceFunction<Contribution>() {
                @Override
                public Contribution reduce(Contribution c1, Contribution c2) {
                    return new Contribution(
                        c1.getTargetId(),
                        c1.getValue() + c2.getValue()
                    );
                }
            })
            .map(contribution -> {
                Page page = new Page(contribution.getTargetId());
                page.setRank(0.15 + 0.85 * contribution.getValue());
                return page;
            });
        
        // 关闭迭代
        DataStream<Page> finalRanks = iteration.closeWith(newRanks);
        
        return finalRanks;
    }
}

// 带终止条件的迭代
public class ConvergenceIterationExample {
    
    public static DataStream<Vector> iterateUntilConverged(
            DataStream<Vector> initialVectors,
            double convergenceThreshold) {
        
        IterativeStream<Vector> iteration = initialVectors.iterate();
        
        // 迭代体
        DataStream<Vector> updated = iteration
            .map(new UpdateFunction())
            .keyBy(Vector::getId);
        
        // 计算收敛度
        DataStream<Tuple2<Vector, Boolean>> withConvergence = updated
            .connect(iteration.keyBy(Vector::getId))
            .flatMap(new CoFlatMapFunction<Vector, Vector, Tuple2<Vector, Boolean>>() {
                private Vector previous;
                
                @Override
                public void flatMap1(Vector current, 
                                    Collector<Tuple2<Vector, Boolean>> out) {
                    boolean converged = false;
                    
                    if (previous != null) {
                        double distance = current.distance(previous);
                        converged = distance < convergenceThreshold;
                    }
                    
                    out.collect(Tuple2.of(current, converged));
                    previous = current;
                }
                
                @Override
                public void flatMap2(Vector value, 
                                    Collector<Tuple2<Vector, Boolean>> out) {
                    // 保存前一次迭代的值
                    previous = value;
                }
            });
        
        // 分离收敛和未收敛的数据
        DataStream<Vector> notConverged = withConvergence
            .filter(t -> !t.f1)
            .map(t -> t.f0);
        
        DataStream<Vector> converged = withConvergence
            .filter(t -> t.f1)
            .map(t -> t.f0);
        
        // 继续迭代未收敛的数据
        iteration.closeWith(notConverged);
        
        // 返回收敛的结果
        return converged;
    }
}

异步 I/O

使用异步 I/O 提高外部系统访问性能。

// 基础异步 I/O
public class AsyncDatabaseRequest 
        extends RichAsyncFunction<Query, Result> {
    
    private transient DatabaseClient client;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        client = new DatabaseClient(getRuntimeContext().getExecutionConfig());
    }
    
    @Override
    public void asyncInvoke(Query query, ResultFuture<Result> resultFuture) {
        CompletableFuture<Result> future = client.queryAsync(query);
        
        future.thenAccept(result -> {
            resultFuture.complete(Collections.singletonList(result));
        }).exceptionally(throwable -> {
            resultFuture.completeExceptionally(throwable);
            return null;
        });
    }
    
    @Override
    public void timeout(Query input, ResultFuture<Result> resultFuture) {
        resultFuture.complete(Collections.singletonList(Result.empty()));
    }
}

// 使用异步 I/O
DataStream<Result> results = AsyncDataStream
    .unorderedWait(
        queries,
        new AsyncDatabaseRequest(),
        60,
        TimeUnit.SECONDS,
        100  // 容量
    );

// 复杂的异步 I/O 示例
public class EnrichmentAsyncFunction 
        extends RichAsyncFunction<Order, EnrichedOrder> {
    
    private transient ExecutorService executorService;
    private transient RestClient restClient;
    private transient RedisAsyncCommands<String, String> redisClient;
    
    @Override
    public void open(Configuration parameters) throws Exception {
        executorService = Executors.newFixedThreadPool(10);
        restClient = new RestClient();
        
        RedisClient redis = RedisClient.create("redis://localhost:6379");
        StatefulRedisConnection<String, String> connection = redis.connect();
        redisClient = connection.async();
    }
    
    @Override
    public void asyncInvoke(Order order, ResultFuture<EnrichedOrder> resultFuture) {
        // 并行查询多个数据源
        CompletableFuture<UserInfo> userFuture = 
            getUserInfoAsync(order.getUserId());
        CompletableFuture<ProductInfo> productFuture = 
            getProductInfoAsync(order.getProductId());
        CompletableFuture<InventoryInfo> inventoryFuture = 
            getInventoryInfoAsync(order.getProductId());
        
        // 组合所有结果
        CompletableFuture
            .allOf(userFuture, productFuture, inventoryFuture)
            .thenApply(v -> {
                EnrichedOrder enriched = new EnrichedOrder(order);
                enriched.setUserInfo(userFuture.join());
                enriched.setProductInfo(productFuture.join());
                enriched.setInventoryInfo(inventoryFuture.join());
                return enriched;
            })
            .thenAccept(enriched -> {
                resultFuture.complete(Collections.singletonList(enriched));
            })
            .exceptionally(throwable -> {
                // 降级处理
                EnrichedOrder degraded = new EnrichedOrder(order);
                degraded.setDegraded(true);
                resultFuture.complete(Collections.singletonList(degraded));
                return null;
            });
    }
    
    private CompletableFuture<UserInfo> getUserInfoAsync(String userId) {
        // 先查缓存
        return redisClient.get("user:" + userId)
            .thenCompose(cached -> {
                if (cached != null) {
                    return CompletableFuture.completedFuture(
                        JSON.parseObject(cached, UserInfo.class)
                    );
                }
                
                // 缓存未命中,查询 API
                return CompletableFuture.supplyAsync(() -> {
                    try {
                        return restClient.getUser(userId);
                    } catch (Exception e) {
                        throw new CompletionException(e);
                    }
                }, executorService);
            });
    }
}

最佳实践

算子链优化

合理配置算子链以提高性能。

// 算子链配置
public class OperatorChainOptimization {
    
    public static void configureChaining(DataStream<Event> stream) {
        // 全局禁用算子链(不推荐)
        // env.disableOperatorChaining();
        
        // 细粒度控制
        stream
            .filter(event -> event.isValid())
            .map(event -> event.normalize())      // 这两个会链在一起
            .startNewChain()                      // 开始新链
            .keyBy(Event::getKey)
            .reduce(new EventReducer())
            .disableChaining()                    // 禁用链接
            .map(result -> result.format())
            .slotSharingGroup("group1")          // 设置 slot 共享组
            .print();
        
        // 设置 UID 和名称
        stream
            .map(new Enricher())
            .uid("enricher")                     // 设置 UID 用于状态恢复
            .name("Event Enricher")              // 设置名称用于监控
            .setParallelism(10);
    }
}

类型信息处理

正确处理类型信息以避免性能问题。

// 类型信息最佳实践
public class TypeInformationBestPractices {
    
    // 使用 TypeHint
    public static void useTypeHint() {
        DataStream<String> stream = env.fromElements("a", "b", "c");
        
        // Lambda 需要类型信息
        DataStream<Tuple2<String, Integer>> result = stream
            .map(s -> Tuple2.of(s, 1))
            .returns(Types.TUPLE(Types.STRING, Types.INT));
        
        // 或使用 TypeHint
        DataStream<List<String>> lists = stream
            .map(s -> Arrays.asList(s))
            .returns(new TypeHint<List<String>>() {});
    }
    
    // 自定义类型信息
    public static class CustomTypeInfo extends TypeInformation<CustomObject> {
        @Override
        public boolean isBasicType() {
            return false;
        }
        
        @Override
        public boolean isTupleType() {
            return false;
        }
        
        @Override
        public int getArity() {
            return 1;
        }
        
        @Override
        public int getTotalFields() {
            return 3;
        }
        
        @Override
        public Class<CustomObject> getTypeClass() {
            return CustomObject.class;
        }
        
        @Override
        public boolean isKeyType() {
            return true;
        }
        
        @Override
        public TypeSerializer<CustomObject> createSerializer(ExecutionConfig config) {
            return new CustomObjectSerializer();
        }
    }
}

资源管理

正确管理外部资源。

// 资源管理最佳实践
public class ResourceManagementBestPractices {
    
    public static class ManagedFunction extends RichMapFunction<Input, Output> {
        private transient Connection connection;
        private transient PreparedStatement statement;
        private transient ObjectMapper mapper;
        
        @Override
        public void open(Configuration parameters) throws Exception {
            // 在 open 中初始化资源
            connection = ConnectionPool.getConnection();
            statement = connection.prepareStatement("SELECT * FROM table WHERE id = ?");
            mapper = new ObjectMapper();
            
            // 注册清理钩子
            getRuntimeContext().registerUserCodeClassLoaderReleaseHookIfAbsent(
                "cleanup-hook",
                () -> cleanup()
            );
        }
        
        @Override
        public Output map(Input input) throws Exception {
            // 使用资源
            statement.setString(1, input.getId());
            ResultSet rs = statement.executeQuery();
            
            if (rs.next()) {
                String json = rs.getString("data");
                return mapper.readValue(json, Output.class);
            }
            
            return null;
        }
        
        @Override
        public void close() throws Exception {
            cleanup();
        }
        
        private void cleanup() {
            // 安全清理资源
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    // 记录日志
                }
            }
            
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    // 记录日志
                }
            }
        }
    }
}

性能优化

算子性能优化技巧

// 性能优化示例
public class PerformanceOptimization {
    
    // 1. 避免在算子中创建大对象
    public static class OptimizedMapper extends RichMapFunction<String, Result> {
        // 重用对象,避免频繁 GC
        private final Result reusableResult = new Result();
        
        @Override
        public Result map(String value) throws Exception {
            reusableResult.reset();
            reusableResult.setValue(value);
            return reusableResult;
        }
    }
    
    // 2. 使用批处理减少开销
    public static class BatchProcessor extends RichFlatMapFunction<Event, Result> {
        private final List<Event> buffer = new ArrayList<>(1000);
        private final int batchSize = 1000;
        
        @Override
        public void flatMap(Event event, Collector<Result> out) throws Exception {
            buffer.add(event);
            
            if (buffer.size() >= batchSize) {
                List<Result> results = processBatch(buffer);
                results.forEach(out::collect);
                buffer.clear();
            }
        }
        
        @Override
        public void close() throws Exception {
            // 处理剩余数据
            if (!buffer.isEmpty()) {
                List<Result> results = processBatch(buffer);
                // 注意:close 中不能使用 Collector
            }
        }
    }
    
    // 3. 合并多个操作
    public static void mergeOperations(DataStream<Event> stream) {
        // 不推荐:多个简单算子
        DataStream<String> result1 = stream
            .filter(e -> e.getType().equals("A"))
            .map(e -> e.getId())
            .filter(id -> id.startsWith("prefix"));
        
        // 推荐:合并为一个算子
        DataStream<String> result2 = stream
            .flatMap(new FlatMapFunction<Event, String>() {
                @Override
                public void flatMap(Event event, Collector<String> out) {
                    if (event.getType().equals("A") && 
                        event.getId().startsWith("prefix")) {
                        out.collect(event.getId());
                    }
                }
            });
    }
    
    // 4. 使用本地聚合减少网络传输
    public static class LocalAggregator 
            implements AggregateFunction<Event, LocalStats, GlobalStats> {
        
        @Override
        public LocalStats createAccumulator() {
            return new LocalStats();
        }
        
        @Override
        public LocalStats add(Event event, LocalStats accumulator) {
            accumulator.update(event);
            return accumulator;
        }
        
        @Override
        public GlobalStats getResult(LocalStats accumulator) {
            return accumulator.toGlobalStats();
        }
        
        @Override
        public LocalStats merge(LocalStats a, LocalStats b) {
            return a.merge(b);
        }
    }
}

常见问题

Q1: 如何选择合适的算子?

答案:

  • 简单转换:使用 map
  • 一对多转换:使用 flatMap
  • 过滤:使用 filter
  • 分组聚合:使用 keyBy + reduce/aggregate
  • 窗口操作:根据需求选择时间窗口或计数窗口
  • 多流操作:简单合并用 union,复杂逻辑用 connect
  • 状态操作:使用 ProcessFunction

Q2: 如何处理大状态?

答案:

// 1. 使用 RocksDB 状态后端
env.setStateBackend(new RocksDBStateBackend("hdfs://checkpoints"));

// 2. 设置状态 TTL
StateTtlConfig ttlConfig = StateTtlConfig
    .newBuilder(Time.hours(24))
    .setUpdateType(StateTtlConfig.UpdateType.OnCreateAndWrite)
    .setStateVisibility(StateTtlConfig.StateVisibility.NeverReturnExpired)
    .build();

// 3. 使用增量聚合
stream.keyBy(...)
      .window(...)
      .aggregate(new IncrementalAggregator());

// 4. 定期清理状态
public class StateCleanupFunction extends KeyedProcessFunction<K, V, R> {
    @Override
    public void processElement(V value, Context ctx, Collector<R> out) {
        // 注册清理定时器
        ctx.timerService().registerProcessingTimeTimer(
            ctx.timerService().currentProcessingTime() + cleanupInterval
        );
    }
    
    @Override
    public void onTimer(long timestamp, OnTimerContext ctx, Collector<R> out) {
        // 清理过期状态
        cleanupExpiredState();
    }
}

Q3: 如何优化算子性能?

答案:

  1. 合理设置并行度:根据数据量和资源调整
  2. 优化算子链:相关算子尽量链在一起
  3. 使用本地聚合:减少网络传输
  4. 避免阻塞操作:使用异步 I/O
  5. 重用对象:减少 GC 压力
  6. 批量处理:减少函数调用开销

总结

Flink DataStream API 提供了丰富而强大的算子体系,通过合理组合这些算子,可以构建出高效、可靠的流处理应用。掌握各类算子的特性和使用场景,是开发高质量 Flink 应用的关键。

核心要点

  1. 转换算子是最基础的操作,包括 map、flatMap、filter 等
  2. 窗口算子用于基于时间或数量的聚合计算
  3. 连接算子实现多流的关联操作
  4. 处理函数提供最底层、最灵活的操作能力
  5. 状态管理是有状态计算的核心
  6. 性能优化需要从多个维度考虑

相关文章

上一篇

下一篇

相关主题

实战项目