kafka stream groupBy通过聚合产生意外值

时间:2019-10-17 00:39:14

标签: apache-kafka apache-kafka-streams ktable

我的问题是关于Kafka流Ktable.groupBy.aggregate。以及所得的合计值。

情况

我正在尝试每天汇总分钟事件。

我有一个分钟事件生成器(此处未显示),可以为一些房屋生成事件。有时,事件值不正确,因此必须重新发布分钟事件。 分钟事件在“分钟”主题中发布。

我正在使用kafka流groupByaggregate进行每天和每天这些事件的汇总

问题

通常一天中只有1440分钟,因此聚合值绝不能超过1440。 而且,永远都不会出现事件数量为负的聚合。

...但是还是会发生,我们不了解代码中的错误。

示例代码

这里是一个示例简化代码来说明该问题。有时会抛出IllegalStateException。


        StreamsBuilder builder = new StreamsBuilder();

        KTable<String, MinuteEvent> minuteEvents = builder.table(
                "minutes",
                Consumed.with(Serdes.String(), minuteEventSerdes),
                Materialized.<String, MinuteEvent, KeyValueStore<Bytes, byte[]>>with(Serdes.String(), minuteEventSerdes)
                        .withCachingDisabled());

        // preform daily aggregation
        KStream<String, MinuteAggregate> dayEvents = minuteEvents
                // group by house and day
                .filter((key, minuteEvent) -> minuteEvent != null && StringUtils.isNotBlank(minuteEvent.house))
                .groupBy((key, minuteEvent) -> KeyValue.pair(
                        minuteEvent.house + "##" + minuteEvent.instant.atZone(ZoneId.of("Europe/Paris")).truncatedTo(ChronoUnit.DAYS), minuteEvent),
                        Grouped.<String, MinuteEvent>as("minuteEventsPerHouse")
                                .withKeySerde(Serdes.String())
                                .withValueSerde(minuteEventSerdes))
                .aggregate(
                        MinuteAggregate::new,
                        (String key, MinuteEvent value, MinuteAggregate aggregate) -> aggregate.addLine(key, value),
                        (String key, MinuteEvent value, MinuteAggregate aggregate) -> aggregate.removeLine(key, value),
                        Materialized
                                .<String, MinuteAggregate, KeyValueStore<Bytes, byte[]>>as(BILLLINEMINUTEAGG_STORE)
                                .withKeySerde(Serdes.String())
                                .withValueSerde(minuteAggSerdes)
                                .withLoggingEnabled(new HashMap<>())) // keep this aggregate state forever
                .toStream();

        // check daily aggregation
        dayEvents.filter((key, value) -> {
            if (value.nbValues < 0) {
                throw new IllegalStateException("got an aggregate with a negative number of values " + value.nbValues);
            }
            if (value.nbValues > 1440) {
                throw new IllegalStateException("got an aggregate with too many values " + value.nbValues);
            }
            return true;
        }).to("days", minuteAggSerdes);

这是此代码段中使用的示例类:

    public class MinuteEvent {
        public final String house;
        public final double sensorValue;
        public final Instant instant;

        public MinuteEvent(String house,double sensorValue, Instant instant) {
            this.house = house;
            this.sensorValue = sensorValue;
            this.instant = instant;
        }
    }

    public class MinuteAggregate {
        public int nbValues = 0;
        public double totalSensorValue = 0.;
        public String house = "";

        public MinuteAggregate addLine(String key, MinuteEvent value) {
            this.nbValues = this.nbValues + 1;
            this.totalSensorValue = this.totalSensorValue + value.sensorValue;
            this.house = value.house;
            return this;
        }

        public MinuteAggregate removeLine(String key, MinuteEvent value) {
            this.nbValues = this.nbValues -1;
            this.totalSensorValue = this.totalSensorValue - value.sensorValue;
            return this;
        }

        public MinuteAggregate() {
        }
    }

如果有人可以告诉我们我们在这里做错了什么,为什么我们会有这些意想不到的价值,那就太好了。

附加说明

  • 我们将流作业配置为运行4个线程properties.put(StreamsConfig.NUM_STREAM_THREADS_CONFIG, 4);
  • 我们被迫使用Ktable.groupBy().aggregate(),因为分钟值可以是 使用不同的sensorValue 重新发布,用于已发布的Instant。并且每日汇总也相应进行了修改。 Stream.groupBy().aggregate()没有addersubstractor

1 个答案:

答案 0 :(得分:0)

我认为,计数实际上可能是负的临时值。

原因是,您的第一个KTable中的每次更新都会向下游发送 two 消息-下游聚合中要减去的旧值,以及要向其中添加的新值下游聚合。两条消息将在下游聚合中独立处理。

如果当前计数为零,并且在加法之前进行了减法运算,则计数将暂时变为负数。