You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache Flink中BroadcastStream实现NOT IN逻辑的状态优化问题

Flink实现NOT IN关联逻辑的问题

需求背景

需要实现对应如下SQL的Flink作业:

SELECT *
  FROM EVENT_C c
 WHERE c.exclusion_id NOT IN (
    SELECT s.exclusion_id
      FROM EVENT_S s
      JOIN EVENT_A a
        ON s.input_id = a.input_id
     WHERE a.tags = 'some-value'
 )

当前已实现逻辑

已将每个Event作为独立DataStream处理,通过带时间窗口的JoinFunction完成EVENT_S与EVENT_A的关联,并尝试用BroadcastStream传递滚动窗口收集到的exclusion_id,再通过KeyedBroadcastProcessFunction处理EVENT_C记录与这些exclusion_id,核心代码如下:

关联并广播exclusion_id

// Join EventS with EventA, broadcast the results
BroadcastStream<String> exclusionIdBroadcastStream = sStream
        .join(aStream)
        .where(new KeySelector<EventS, Object>() {
            @Override
            public Object getKey(EventS value) throws Exception {
                return value.getInputId();
            }
        })
        .equalTo(new KeySelector<EventA, Object>() {
            @Override
            public Object getKey(EventA value) throws Exception {
                return value.getInputId();
            }
        })
        .window(TumblingEventTimeWindows.of(Time.minutes(5)))
        .apply(new JoinFunction<EventS, EventA, String>() {
            @Override
            public String join(EventS first, EventA second) throws Exception {
                return first.getTurnId();
            }
        })
        .broadcast(turnIdStateDescriptor);

// Process EventC by performing a NOT IN with the broadcast ids
cStream
        .keyBy(new KeySelector<EventC, String>() {
            @Override
            public String getKey(EventC value) throws Exception {
                return value.getUniqueId();
            }
        })
        .connect(exclusionIdBroadcastStream)
        .process(new EventFilterBroadcastProcessFunction())
        .addSink(s3Sink);

KeyedBroadcastProcessFunction实现

public class EventFilterBroadcastProcessFunction extends KeyedBroadcastProcessFunction<String, EventC, String, OutputEvent> {

    private static final MapStateDescriptor<String, String> BROADCAST_DESCRIPTOR =
            new MapStateDescriptor<>("ExclusuionIdBroadcastState", BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.STRING_TYPE_INFO);

    private static final ListStateDescriptor<EventC> EVENT_C_DESCRIPTOR =
            new ListStateDescriptor<>("CustomerIdEvents", TypeInformation.of(EventC.class));

    private transient ListState<EventC> eventsToReprocessState;

    @Override
    public void open(Configuration parameters) throws Exception {
        super.open(parameters);
        this.eventsToReprocessState = this.getRuntimeContext().getListState(EVENT_C_DESCRIPTOR);
    }

    @Override
    public void processElement(EventC event, ReadOnlyContext context, Collector<OutputEvent> out) throws Exception {
        final String turnId = context.getBroadcastState(BROADCAST_DESCRIPTOR).get(event.getTurnId());
        final long expirationTimestamp = nextTimeoutWindow(context.timestamp(), Time.minutes(1));

        if (turnId == null) {
            // Maybe the event is safe to collect, or the turnId has not been broadcast yet. Save it for later.
            this.eventsToReprocessState.add(event);

            context.timerService().registerEventTimeTimer(expirationTimestamp);
        }

    }

    @Override
    public void processBroadcastElement(String turnId, Context context, Collector<OutputEvent> out) throws Exception {
        context.getBroadcastState(BROADCAST_DESCRIPTOR).put(turnId, turnId);
    }

    @Override
    public void onTimer(long timestamp, OnTimerContext ctx, Collector<OutputEvent> out) throws Exception {
        super.onTimer(timestamp, ctx, out);
        int count = 0;
        int size = 0;
        final Iterable<EventC> eventsToReprocess = this.eventsToReprocessState.get();
        if (eventsToReprocess != null) {
            for (EventC event: eventsToReprocess) {
                size++;
                if (!ctx.getBroadcastState(BROADCAST_DESCRIPTOR).contains(event.getTurnId())) {
                    final OutputEvent OutputEvent = generateOutputEvent(event);
                    out.collect(OutputEvent);
                    count++;
                }
            }
            eventsToReprocessState.clear();
        }

    }

    private OutputEvent generateOutputEvent(final EventC event) {
        return OutputEvent.builder()
                ...
                .build();
    }

    public static long nextTimeoutWindow(final long timestamp, final Time interval) {
        return endOfWindow(timestamp, interval) + interval.toMilliseconds();
    }

    public static long beginningOfWindow(final long timestamp, final Time interval) {
        return timestamp - (timestamp % interval.toMilliseconds());
    }

    public static long endOfWindow(final long timestamp, final Time interval) {
        return beginningOfWindow(timestamp, interval) + interval.toMilliseconds();
    }
}

当前方案存在的问题

选择BroadcastStream是为了将exclusion_id视为规则,让所有并行实例基于同一规则集执行NOT IN以提升并行度,但Broadcast状态会持续增长无法删除元素,导致快照体积、CPU及内存占用不断上升。

疑问

  • 是否可以在onTimer方法触发时清理Broadcast状态?
  • 有没有更优的方案实现这类“关联/NOT IN”逻辑?

环境信息:Flink v1.11、Kinesis Data Analytics、JDK 11


内容的提问来源于stack exchange,提问作者Cristian Meneses

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 17:33:19