当前位置: 首页 > news >正文

Flink DataStream API多流操作与实践

一、概览

多流操作 DataStream API ├─ union() 同类型多流合并 ├─ connect() 两条不同类型流连接 │ ├─ CoMapFunction / CoFlatMapFunction │ ├─ CoProcessFunction │ └─ KeyedCoProcessFunction ├─ broadcast() + connect() 规则流 / 配置流广播 │ ├─ BroadcastProcessFunction │ └─ KeyedBroadcastProcessFunction ├─ intervalJoin() KeyedStream 双流时间区间 Join ├─ join() 窗口 Join ├─ coGroup() 窗口 CoGroup,类似全量分组关联 └─ side output 主流拆出多条旁路流,常用于告警/脏数据

二、union():同类型多流合并

适合多个来源结构完全一致的流,例如多个 Kafka topic、多个业务线的订单流。

DataStream<Order> appOrders = env.fromSource(appSource, wm, "app-orders"); DataStream<Order> webOrders = env.fromSource(webSource, wm, "web-orders"); DataStream<Order> posOrders = env.fromSource(posSource, wm, "pos-orders"); DataStream<Order> allOrders = appOrders .union(webOrders, posOrders);

特点:

  • 只能合并相同数据类型的流。
  • 不做去重、不做排序,只是把多条流合成一条流。
  • 多流 watermark 会取各输入流 watermark 的最小值,因此某一路延迟会拖慢整体事件时间推进。

最佳实践:

  • 多源合并前尽量统一 schema。
  • 如果多个源可能重复生产同一事件,union() 后要基于业务主键做去重。
  • 对空闲 source 配置 idleness,避免某个分区无数据导致 watermark 卡住。
WatermarkStrategy<Order> wm = WatermarkStrategy .<Order>forBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner((e, ts) -> e.getEventTime()) .withIdleness(Duration.ofMinutes(1));

三、connect():两条不同类型流连接处理

connect()可以连接两条不同类型的流,得到ConnectedStreams,再用双输入函数处理。

CoMapFunction/CoFlatMapFunction适合简单双流转换。

DataStream<Order> orders = ...; DataStream<UserEvent> userEvents = ...; DataStream<String> result = orders .connect(userEvents) .flatMap(new CoFlatMapFunction<Order, UserEvent, String>() { @Override public void flatMap1(Order order, Collector<String> out) { out.collect("order: " + order.getOrderId()); } @Override public void flatMap2(UserEvent event, Collector<String> out) { out.collect("event: " + event.getUserId()); } });

其中flatMap1()处理第一条流,flatMap2()处理第二条流,两条流类型可以不同,但输出类型相同。

CoProcessFunction适合需要状态、定时器、复杂逻辑的双流处理,例如订单流关联支付流,订单先到则缓存,支付先到也缓存,超时则输出未支付订单。

DataStream<Order> orders = ...; DataStream<Payment> payments = ...; DataStream<Result> result = orders .keyBy(Order::getOrderId) .connect(payments.keyBy(Payment::getOrderId)) .process(new KeyedCoProcessFunction<String, Order, Payment, Result>() { private ValueState<Order> orderState; private ValueState<Payment> paymentState; @Override public void open(Configuration parameters) { orderState = getRuntimeContext().getState( new ValueStateDescriptor<>("orderState", Order.class)); paymentState = getRuntimeContext().getState( new ValueStateDescriptor<>("paymentState", Payment.class)); } @Override public void processElement1( Order order, Context ctx, Collector<Result> out) throws Exception { Payment payment = paymentState.value(); if (payment != null) { out.collect(Result.matched(order, payment)); paymentState.clear(); } else { orderState.update(order); ctx.timerService().registerEventTimeTimer( order.getEventTime() + 15 * 60 * 1000L); } } @Override public void processElement2( Payment payment, Context ctx, Collector<Result> out) throws Exception { Order order = orderState.value(); if (order != null) { out.collect(Result.matched(order, payment)); orderState.clear(); } else { paymentState.update(payment); } } @Override public void onTimer( long timestamp, OnTimerContext ctx, Collector<Result> out) throws Exception { Order order = orderState.value(); if (order != null) { out.collect(Result.timeout(order)); orderState.clear(); } } });

最佳实践:

  • 双流关联优先使用 keyBy(...).connect(...).process(...)。
  • 两条流必须按相同业务 key 分区,否则同一个订单和支付可能到不同 Task。
  • 状态一定要清理,避免长期膨胀。
  • 有超时语义时,优先使用事件时间定时器。
  • 状态较大时开启 RocksDB StateBackend 和 TTL。

四、broadcast():广播流关联主流

适合“配置流 / 规则流 / 维表变更流”广播到所有并行实例。

典型场景:

  • 风控规则实时更新。
  • 活动配置实时生效。
  • 黑名单、白名单、阈值规则更新。
MapStateDescriptor<String, Rule> ruleStateDesc = new MapStateDescriptor<>("rules", String.class, Rule.class); BroadcastStream<Rule> ruleBroadcastStream = rules.broadcast(ruleStateDesc); DataStream<Alert> alerts = events .keyBy(Event::getUserId) .connect(ruleBroadcastStream) .process(new KeyedBroadcastProcessFunction<String, Event, Rule, Alert>() { @Override public void processElement( Event event, ReadOnlyContext ctx, Collector<Alert> out) throws Exception { ReadOnlyBroadcastState<String, Rule> rules = ctx.getBroadcastState(ruleStateDesc); for (Map.Entry<String, Rule> entry : rules.immutableEntries()) { Rule rule = entry.getValue(); if (rule.match(event)) { out.collect(new Alert(event, rule)); } } } @Override public void processBroadcastElement( Rule rule, Context ctx, Collector<Alert> out) throws Exception { BroadcastState<String, Rule> state = ctx.getBroadcastState(ruleStateDesc); state.put(rule.getRuleId(), rule); } });

最佳实践:

  • 广播流数据量不能太大,因为每个并行实例都会保存一份。
  • 规则要有版本号、更新时间、启停状态。
  • 支持删除规则,不要只支持新增/覆盖。
  • 广播状态适合小规模动态配置,不适合大维表。
  • 大维表优先考虑异步 IO、外部 KV、Lookup Join 或 Table/SQL。

五、intervalJoin():双流时间区间 Join

适合两条 keyed 流按事件时间做区间关联。

例如:订单事件和支付事件,支付时间在订单后 15 分钟内即认为匹配。

DataStream<OrderPayment> result = orders .keyBy(Order::getOrderId) .intervalJoin(payments.keyBy(Payment::getOrderId)) .between(Time.minutes(0), Time.minutes(15)) .process(new ProcessJoinFunction<Order, Payment, OrderPayment>() { @Override public void processElement( Order order, Payment payment, Context ctx, Collector<OrderPayment> out) { out.collect(new OrderPayment(order, payment)); } });

特点:

  • 只能用于 KeyedStream。
  • 基于事件时间。
  • 会根据时间区间缓存两边数据。
  • 不匹配的数据默认不会输出。

最佳实践:

  • 用于明确的时间窗口关联,比如“点击后 10 分钟内下单”。
  • 时间区间不要过大,否则状态会膨胀。
  • 要求两条流都有正确 watermark。
  • 如果需要输出未匹配数据,intervalJoin() 不够灵活,可以用 KeyedCoProcessFunction 自己实现。

六、join():窗口 Join

适合按窗口做内连接。

DataStream<OrderPayment> result = orders .join(payments) .where(Order::getOrderId) .equalTo(Payment::getOrderId) .window(TumblingEventTimeWindows.of(Time.minutes(5))) .apply(new JoinFunction<Order, Payment, OrderPayment>() { @Override public OrderPayment join(Order order, Payment payment) { return new OrderPayment(order, payment); } });

特点:

  • 两边在同一个窗口内且 key 相等时才会 join。
  • 本质是窗口内笛卡尔匹配。
  • 没有匹配的数据不会输出。

最佳实践:

  • 适合批式窗口关联,例如每 5 分钟内订单和支付匹配。
  • 如果关联语义是“订单后 15 分钟内支付”,优先用 intervalJoin()。
  • 窗口 join 对迟到数据敏感,要配置 watermark 和 allowed lateness。

七、coGroup():窗口内双流分组处理

coGroup()比join()更灵活,可以拿到窗口内两边的全部元素。

DataStream<Result> result = orders .coGroup(payments) .where(Order::getOrderId) .equalTo(Payment::getOrderId) .window(TumblingEventTimeWindows.of(Time.minutes(5))) .apply(new CoGroupFunction<Order, Payment, Result>() { @Override public void coGroup( Iterable<Order> orderGroup, Iterable<Payment> paymentGroup, Collector<Result> out) { List<Order> orderList = Lists.newArrayList(orderGroup); List<Payment> paymentList = Lists.newArrayList(paymentGroup); if (paymentList.isEmpty()) { for (Order order : orderList) { out.collect(Result.unpaid(order)); } } else { for (Order order : orderList) { for (Payment payment : paymentList) { out.collect(Result.matched(order, payment)); } } } } });

适合:

  • 需要处理未匹配数据。
  • 需要一对多、多对多关联。
  • 需要自定义窗口内关联逻辑。

八、Side Output:一条流拆出多条旁路流

虽然不是典型“多输入流”,但常用于多流处理架构中,把主流拆成告警流、脏数据流、迟到数据流等。

OutputTag<DirtyRecord> dirtyTag = new OutputTag<DirtyRecord>("dirty") {}; SingleOutputStreamOperator<Event> cleanStream = rawStream .process(new ProcessFunction<RawEvent, Event>() { @Override public void processElement( RawEvent value, Context ctx, Collector<Event> out) { if (value.isValid()) { out.collect(value.toEvent()); } else { ctx.output(dirtyTag, new DirtyRecord(value)); } } }); DataStream<DirtyRecord> dirtyStream = cleanStream.getSideOutput(dirtyTag);

最佳实践:

  • 脏数据、异常数据不要直接丢弃,优先通过 side output 输出到旁路。
  • 迟到数据可以输出到独立 Kafka topic 供补偿处理。
  • 主流程和异常流程解耦,便于监控和回放。

九、选型建议

场景

推荐 API

多条同类型流合并

union()

两条不同类型流简单处理

connect() + CoMapFunction / CoFlatMapFunction

双流复杂状态关联

connect() + KeyedCoProcessFunction

主流关联动态规则流

broadcast() + KeyedBroadcastProcessFunction

两条 keyed 流按时间范围关联

intervalJoin()

两条流按窗口内 key 关联

join()

需要处理未匹配或复杂窗口分组

coGroup()

主流拆告警/脏数据/迟到数据

side output

http://www.jsqmd.com/news/1229283/

相关文章:

  • 【计算机毕业设计】基于Django的华立学院在线教育平台的设计与实现
  • 无锡买金别乱 跑,藏在市区宝藏黄金门店合扬 - 好物测评局
  • 五年产区走访:大漆辨伪的三条实操标准
  • 原来新疆的当季特产竟然这么丰富多样?
  • 小程序计算机毕设之基于 SpringBoot + 微信小程序的校园食堂点餐系统的设计与实现 智慧校园线上订餐服务小程序的设计与实现(完整前后端代码+说明文档+LW,调试定制等)
  • 别再手动触发AI任务!n8n动态调度引擎深度解析:支持QPS 200+的异步队列、重试熔断与成本监控体系
  • CodeGuardian:AI驱动的代码质量与安全扫描解决方案
  • 2026实测:10款好用的AI写小说工具深度测评与避坑指南
  • Sub2API开源AI网关平台:多账户管理与智能调度解析
  • 宝玑扬州官方唯一地址及客户热线2026年7月最新售后服务通知 - 亨得利官方服务中心
  • Jupyter Notebook快捷键核心原理与新手72小时实战指南
  • OData.NET客户端开发实战:使用LINQ查询OData服务的最佳实践
  • 企业微信与DeepSeek大模型深度融合技术实践——智能消息、小程序及多媒体交互全方案
  • 深入解析I2C中断与DMA机制:从轮询到事件驱动的效率革命
  • 2026无锡代理注册公司避坑指南 - 资讯纵览
  • Cobalt Strike BOF开发入门:基于BOF_Collection项目的代码分析
  • Carnac快捷键显示原理:如何智能识别和展示组合键
  • 小安派工:大型职校三千人就餐,多通道校园智慧食堂结算设备安装排布方案
  • 2026.7月贵阳防水彻底根治全攻略 一次性解决漏水多年不复发 - 吉林同城获客
  • Windows系统文件dpapi.dll丢失找不到问题解决
  • 上海大盘金价实时浮动,普通人闲置黄金变现节点实操干货 - 一日一测评
  • 2026天津翡翠回收白皮书:十区直营门店全域覆盖,北方商圈非标翡翠精准鉴评变现实测指南 - 分享测评官
  • 成都市青白江区老旧小区窗户漏风漏音怎么处理?|静华轩隔音窗|老窗密封整改、渗水降噪、低成本居家静音改造 - 一点传媒
  • LangChain文本向量与检索器实践
  • 为什么大模型看不懂文字?
  • 嵌入式RTC闹钟与看门狗寄存器级配置实战指南
  • 《星露谷物语》MOD安装与优化全指南
  • 佛山选关公像要看哪些标准?
  • AI写小说真的能日产万字?2026年7月实测10款好用的写小说ai工具
  • 文学翻译中的文化转译与风格再现