[英]How to drain the window after a Flink join using coGroup()?
我想加入来自两个 Kafka 主题(“左”和“右”)的数据。
匹配记录将使用 ID 连接,但如果缺少“左”或“右”记录,则应在一定超时后将另一记录传递到下游。 因此我选择使用coGroup
功能。
这可行,但有一个问题:如果根本没有消息,则始终至少有一条记录永久保留在内部缓冲区中。 当新消息到达时它会被推出。 否则会卡住。
预期的行为是在达到配置的空闲超时后应该推出所有记录。
一些可能相关的信息
val
就像final var
一些代码片段:
public static final int AUTO_WATERMARK_INTERVAL_MS = 500;
public static final Duration SOURCE_MAX_OUT_OF_ORDERNESS = Duration.ofMillis(4000);
public static final Duration SOURCE_IDLE_TIMEOUT = Duration.ofMillis(1000);
public static final Duration TRANSFORMATION_MAX_OUT_OF_ORDERNESS = Duration.ofMillis(5000);
public static final Duration TRANSFORMATION_IDLE_TIMEOUT = Duration.ofMillis(1000);
public static final Time JOIN_WINDOW_SIZE = Time.milliseconds(1500);
KafkaSource
private static KafkaSource<JoinRecord> createKafkaSource(Config config, String topic) {
val properties = KafkaConfigUtils.createConsumerConfig(config);
val deserializationSchema = new KafkaRecordDeserializationSchema<JoinRecord>() {
@Override
public void deserialize(ConsumerRecord<byte[], byte[]> record, Collector<JoinRecord> out) {
val m = JsonUtils.deserialize(record.value(), JoinRecord.class);
val copy = m.toBuilder()
.partition(record.partition())
.build();
out.collect(copy);
}
@Override
public TypeInformation<JoinRecord> getProducedType() {
return TypeInformation.of(JoinRecord.class);
}
};
return KafkaSource.<JoinRecord>builder()
.setProperties(properties)
.setBootstrapServers(config.kafkaBootstrapServers)
.setTopics(topic)
.setGroupId(config.kafkaInputGroupIdPrefix + "-" + String.join("_", topic))
.setDeserializer(deserializationSchema)
.setStartingOffsets(OffsetsInitializer.latest())
.build();
}
DataStreamSource
然后DataStreamSource
建立在KafkaSource
:
private static DataStreamSource<JoinRecord> createLeftSource(Config config,
StreamExecutionEnvironment env) {
val leftKafkaSource = createLeftKafkaSource(config);
val leftWms = WatermarkStrategy
.<JoinRecord>forBoundedOutOfOrderness(SOURCE_MAX_OUT_OF_ORDERNESS)
.withIdleness(SOURCE_IDLE_TIMEOUT)
.withTimestampAssigner((joinRecord, __) -> joinRecord.timestamp.toEpochSecond() * 1000L);
return env.fromSource(leftKafkaSource, leftWms, "left-kafka-source");
}
keyBy
键控源是在DataSource
实例之上创建的,如下所示:
再次配置“乱序”和“空闲”
再次提取时间戳
val leftWms = WatermarkStrategy .<JoinRecord>forBoundedOutOfOrderness(TRANSFORMATION_MAX_OUT_OF_ORDERNESS) .withIdleness(TRANSFORMATION_IDLE_TIMEOUT) .withTimestampAssigner((joinRecord, __) -> { if (VERBOSE_JOIN) log.info("Left : " + joinRecord); return joinRecord.timestamp.toEpochSecond() * 1000L; }); val leftKeyedSource = leftSource .keyBy(jr -> jr.id) .assignTimestampsAndWatermarks(leftWms) .name("left-keyed-source");
coGroup
加入然后,连接将左键源和右键源组合在一起
val joinedStream = leftKeyedSource
.coGroup(rightKeyedSource)
.where(left -> left.id)
.equalTo(right -> right.id)
.window(TumblingEventTimeWindows.of(JOIN_WINDOW_SIZE))
.apply(new CoGroupFunction<JoinRecord, JoinRecord, JoinRecord>() {
@Override
public void coGroup(Iterable<JoinRecord> leftRecords,
Iterable<JoinRecord> rightRecords,
Collector<JoinRecord> out) {
// Transform
val result = ...;
out.collect(result);
}
生成的joinedStream
被写入控制台:
val consoleSink = new PrintSinkFunction<JoinRecord>();
joinedStream.addSink(consoleSink);
这是预期的行为。 withIdleness
不会尝试处理所有流都空闲的情况。 它仅在仍有事件从至少一个源分区/分片/拆分流出的情况下才有帮助。
要获得您想要的行为(在连续流作业的上下文中),您必须实施自定义水印策略,该策略根据处理时间计时器推进水印。 这是一个使用旧版水印 API 的实现。
另一方面,如果作业已完成,并且您只想在关闭作业之前排空最终结果,则可以在停止作业时使用--drain
选项。 或者,如果您使用有界来源,这将自动发生。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.