概述
Flink DataStream API 提供了丰富的算子(Operators)来处理流数据。算子是 Flink 流处理的核心组件,通过组合不同的算子,可以构建复杂的流处理拓扑。本文将系统地介绍 Flink 中的各类算子,包括其功能、使用场景和最佳实践。
算子分类体系
Flink 算子可以按照不同维度进行分类:
按功能分类
- 转换算子:map、flatMap、filter、reduce 等
- 分区算子:keyBy、rebalance、broadcast 等
- 窗口算子:window、windowAll、timeWindow 等
- 连接算子:connect、union、join、coGroup 等
- 状态算子:process、aggregate、fold 等
- 输出算子:print、addSink、writeAsText 等
按并行度特征分类
- 一对一算子:map、filter 等(保持并行度)
- 重分区算子:keyBy、rebalance 等(改变数据分布)
- 全局算子: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 算子
用于调试和开发的输出算子。
// 基础 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: 如何优化算子性能?
答案:
- 合理设置并行度:根据数据量和资源调整
- 优化算子链:相关算子尽量链在一起
- 使用本地聚合:减少网络传输
- 避免阻塞操作:使用异步 I/O
- 重用对象:减少 GC 压力
- 批量处理:减少函数调用开销
总结
Flink DataStream API 提供了丰富而强大的算子体系,通过合理组合这些算子,可以构建出高效、可靠的流处理应用。掌握各类算子的特性和使用场景,是开发高质量 Flink 应用的关键。
核心要点
- 转换算子是最基础的操作,包括 map、flatMap、filter 等
- 窗口算子用于基于时间或数量的聚合计算
- 连接算子实现多流的关联操作
- 处理函数提供最底层、最灵活的操作能力
- 状态管理是有状态计算的核心
- 性能优化需要从多个维度考虑
相关文章
上一篇
- Flink DataStream API - DataStream API 基础入门
下一篇
- Flink DataStream API 高级用法 - 容错机制与性能优化
相关主题
- Flink DataStream API 高级特性 - 状态管理与复杂事件处理
- Flink 三种异步IO - 异步IO优化