Apache flink 流模拟期间缺乏再现性

Apache flink 流模拟期间缺乏再现性,apache-flink,Apache Flink,我有一个Flink stream程序,它在一个键控的、窗口化的流上执行某些操作。 操作符使用Context#globalState() (见附件) 在实际的实时流中,我没有任何问题。 但是,我有一些特殊的场景需要模拟流, 为此,我需要加载一定数量的数据并按时间戳排序。 我必须分配水印的操作符的实现方式可以处理这两种情况 我的问题是,除非我以parallelism=1执行流模拟, 我没有可复制的结果。 我按下按钮,我的源可以以比窗口处理功能更快的速度发出事件, 也许一个给定密钥的几个窗口以一种不一

我有一个Flink stream程序,它在一个键控的、窗口化的流上执行某些操作。 操作符使用
Context#globalState()
(见附件)

在实际的实时流中,我没有任何问题。 但是,我有一些特殊的场景需要模拟流, 为此,我需要加载一定数量的数据并按时间戳排序。 我必须分配水印的操作符的实现方式可以处理这两种情况

我的问题是,除非我以parallelism=1执行流模拟, 我没有可复制的结果。 我按下按钮,我的源可以以比窗口处理功能更快的速度发出事件, 也许一个给定密钥的几个窗口以一种不一定保持时间顺序的方式排队。 由于window process函数根据时间顺序修改状态, 可能存在使州不一致的种族条件

在模拟版本中,甚至不允许迟到,所以这应该不是问题。 并行度=1总是产生相同的结果 (一些单元测试会对此进行检查)

有人能确认流模拟是否是Flink支持的用例吗? 如果是的话,上述行为如果被证实是否是一个bug


更新1 以下是流管道的高级概述:

input = source with default parallelism (message bus could have multiple partitions)
keySelector = CustomKeySelector

timestampedStream = input
    .assignTimestampsAndWatermarks(WaterMarker)
    .setParallelism(1) // see Remark 1

streamFork1 = timestampedStream.flatMap(FlatMapFunction1)
streamFork2 = timestampedStream.flatMap(FlatMapFunction2)

streamFork1
    .keyBy(keySelector)
    .window(SlidingEventTimeWindow)
    .process(ProcessWindowFunction1) // stateful (global)
    .addSink(MessageBusSink)

internalStream = streamFork2
    .keyBy(keySelector)
    .window(SlidingEventTimeWindow)

internalStream
    .process(ProcessWindowFunction2) // stateful (window)
    .addSink(DatabaseSink1)
    .setParallelism(1)

internalStream
    .process(ProcessWindowFunction3) // stateful (global)
    .addSink(DatabaseSink2)
    .setParallelism(1)
备注1:来源不在我的控制之下, 所以我无法在源位置分配时间戳。 这就是为什么我需要分配平行度为1的水印, 因为流中的一些分区实际上可能是空的 (至少有一段时间)

还有我的水印的逻辑 (类实现了带有周期性水印的赋值器, 备注如下:

我的系统的吞吐量可能很低, 但我需要关闭窗口,即使每分钟都没有新数据到达, 这就是为什么我需要使用
forceAdvanceMultiplier
的逻辑。 我相信线程安全在这里不是问题,
但我可能错了。

在大多数情况下,这应该是可行的——在处理历史或模拟数据时,可以获得可重复的、确定性的结果。但这也很容易导致非决定论。没有更多的信息,人们只能推测原因

Flink中的不确定性来自两件事中的一件:(1)编写对并行管道之间的竞争敏感的作业,以及(2)使用处理时间而不是事件时间(您可能认为是事件和系统时钟之间的竞争)

对于任何给定的键,窗口操作符只有一个单线程实例,并且随着水印的上升,它将按顺序触发其事件时间窗口。如果单个密钥内的结果不一致,则我怀疑(1)水印的实现不正确,或者在某种程度上取决于系统时钟而不是事件时间戳,或者(2)单个密钥的事件来自多个源实例,而且窗口计算对这些独立源之间的竞争非常敏感

即使每个键内的结果是一致的,如果你将这些每个键的结果组合成某种全局结果,那么在最后的组合阶段引入一些非确定性就足够容易了

更新:

我相信你的水印会产生一些不确定数量的后期事件,然后导致无法重现的结果。让我解释一下

源代码是并行读取的,如果我理解正确,它是按时间戳排序的。然而,对于水印,并行性则降低到1。此时,多个(可能的)有序流被交织在一起,导致单个无序流。然后水印使用maxEventTime作为当前水印,而不从中减去一些延迟来说明无序性,这会导致无序事件延迟


您可以通过向窗口添加侧输出来显示任何延迟事件来确认此诊断。最简单的修复方法是将源代码的并行度设置为1。

(1)水印确实取决于实时版本的系统时钟,尽管它不应影响模拟版本,但系统时钟如何会带来问题?(2) 我想窗户后面只有一个水槽,但我得检查一下。哪些信息对故障排除有用?我可以发布流中使用的运算符的高级定义。对于初学者,我希望看到时间戳提取器和水印赋值器的实现,以及流中使用的运算符的高级定义。我更新了问题。我想,你所说的“平行管道”可能是个问题,但我不确定弗林克是如何处理一个有键、有窗的流的“分叉”的。谢谢你,我还没想过。我的模拟源代码确实具有parallelism=1,但实际上我在分配水印之前对其进行了转换(
singleInput.map(…).filter(…)
)。这些转换的操作符有默认的并行性,所以它们会分割流(可能是循环),对吗?我可能必须确保事情按预期进行,但我的初始测试看起来很有希望。再次感谢。
private final TemporalUnit slideTime;

private Instant maxEventTime = null;
private Instant maxEventTimeTruncated = Instant.ofEpochMilli(0L);
private Instant lastWatermarkTimeTruncated = Instant.ofEpochMilli(0L);
private long forceAdvanceMultiplier = 1L;

public TimestampExtractorAndPeriodicWatermarker(TemporalUnit slideTime) {
    this.slideTime = slideTime;
}

@Nullable
@Override
public Watermark getCurrentWatermark() {
    if (maxEventTime == null) {
        return null;
    }

    Instant truncatedInstant = Instant.now().truncatedTo(slideTime);

    if (Duration.between(maxEventTimeTruncated, maxEventTime).compareTo(slideTime.getDuration()) >= 0) {
        // generate watermark when the newest event time is >= max (truncated) event time + slide time
        lastWatermarkTimeTruncated = truncatedInstant;
        maxEventTimeTruncated = maxEventTime.truncatedTo(slideTime);
        return new Watermark(maxEventTime.toEpochMilli());

    } else if (truncatedInstant.compareTo(lastWatermarkTimeTruncated) > 0) {
        // generate watermark every "slide" time if no new events arrive
        lastWatermarkTimeTruncated = truncatedInstant;
        Instant timeToForceAdvanceFlinkTime = this.maxEventTime
                .truncatedTo(slideTime)
                .plus(slideTime.getDuration().multipliedBy(forceAdvanceMultiplier++));

        return new Watermark(timeToForceAdvanceFlinkTime.toEpochMilli());
    }

    return null;
}

@Override
public long extractTimestamp(T t, long l) {
    long elemTS = t.getTimestamp();
    if (maxEventTime == null) {
        maxEventTime = Instant.ofEpochMilli(elemTS);
    } else {
        maxEventTime = Instant.ofEpochMilli(Math.max(elemTS, maxEventTime.toEpochMilli()));
    }
    forceAdvanceMultiplier = 1L;
    return elemTS;
}