使用ValueTransformerWithKey计算平均值还是可以使用Kafka的聚合函数

时间:2019-06-25 04:38:39

标签: aggregate apache-kafka-streams

我有一个对象流,我想在其中计算该对象中某个字段的平均值,然后将该平均值保存回该对象。我想要一个5分钟的翻滚窗口,并保留1小时。我是Kafka的新手,所以我想知道这是否是解决问题的正确方法。

首先,我创建一个持久性存储:

StoreBuilder<WindowStore<String, Double>> averagesStoreSupplier =
    Stores.windowStoreBuilder(
        Stores.persistentWindowStore(WINDOW_STORE_NAME, Duration.ofHours(1), Duration.ofMinutes(5), true),
        Serdes.String(),
        Serdes.Double());

streamsBuilder.addStateStore(averagesStoreSupplier);

然后我用以下方式致电我的变压器:

otherKTable
    .leftJoin(objectKTable.transformValues(new AveragingTransformerSupplier(WINDOW_STORE_NAME), WINDOW_STORE_NAME), 
            myValueJoiner)
    .to("outputTopic")

这是我的变压器:

public class AveragingTransformerSupplier implements ValueTransformerWithKeySupplier<String, MyObject, MyObject> {

    private final String stateStoreName;

    public TelemetryAveragingTransformerSupplier(final String stateStoreName) {
        this.stateStoreName = stateStoreName;
    }

    public ValueTransformerWithKey<String, MyObject, MyObject> get() {
        return new ValueTransformerWithKey<>() {

            private WindowStore<String, Double> averagesStore;

            @Override
            public void init(ProcessorContext processorContext) {
                averagesStore = Try.of(() ->(WindowStore<String, Double>) processorContext.getStateStore(stateStoreName)).getOrElse((WindowStore<String, Double>)null);
            }

            @Override
            public MyObject transform(String s, MyObject myObject) {
                if (averagesStore != null) {
                    averagesStore.put(s, myObject.getNumber());

                    Instant timeFrom = Instant.ofEpochMilli(0); // beginning of time = oldest available
                    Instant timeTo = Instant.now();
                    WindowStoreIterator<Double> itr = averagesStore.fetch(s, timeFrom, timeTo);

                    double sum = 0.0;
                    int size = 0;
                    while(itr.hasNext()) {
                        KeyValue<Long, Double> next = itr.next();
                        size++;
                        sum += next.value;
                    }

                    myObject.setNumber(sum / size);

                }

                return myObject;
            }

            @Override
            public void close() {
                if (averagesStore != null) {
                    averagesStore.flush();
                }
            }
        };
    }
}

我有几个问题。 首先,我定义WindowStore的方法是否是形成翻滚窗口的正确方法?我将如何创建一个跳跃窗口?

第二,从开始到现在,我都在我的变压器内部从商店中获取所有物品。由于我将其定义为5分钟的窗口和1小时的保留时间,这是否意味着商店中的商品是5分钟的数据快照?保留在这里做什么?

我正在处理琐碎的事,但是不确定是否有更好的方法使用聚合和联接来做到这一点,即使我做得正确也是如此。另外,我还不得不将获取存储的尝试放在尝试中,因为初始化多次被调用,有时会遇到Processor has no access to StateStore异常。

1 个答案:

答案 0 :(得分:0)

在这种情况下,我建议使用DSL代替Processor API。 cf. https://cwiki.apache.org/confluence/display/KAFKA/Kafka+Stream+Usage+Patterns了解详情。

  

我有几个问题。首先,我定义WindowStore的方法是否是形成翻滚窗口的正确方法?我将如何创建一个跳跃窗口?

带窗口的商店可用于跳跃窗口或翻滚窗口-它取决于您在处理器中使用的方式,而不是如何创建商店,以及获得的窗口语义。

  

第二,从开始到现在,我都在我的变压器内部从商店中获取所有物品。由于我将其定义为5分钟的窗口和1小时的保留时间,这是否意味着商店中的商品是5分钟的数据快照?保留在这里做什么?

创建商店时,参数windowSize不能按预期的方式工作。您需要使用Transformerput(key, value, windowStartTimestamp)代码中手动编码开窗逻辑-atm,您正在使用使用put(key, value)的{​​{1}},即当前记录时间戳记,如windowStartTimestamp -我怀疑那是您想要的。保留时间基于窗口时间戳,即旧窗口到期后将被删除。