我如何使用kafka流窗口为烛台图生成

时间:2018-03-14 17:23:02

标签: apache-kafka apache-kafka-streams

我必须使用Kafka Stream从交易结果主题获取交易信息以在每个特定持续时间中绘制烛台图表,它具有交易ID,金额,价格,交易时间,关键是交易ID,这是完全不同的对于每条记录, 我想要做的是根据交易结果进行计算得到 每个持续时间的最高价格,最低价格,开盘价,收盘价,tx close_time,并用它来创建烛台图表。 我使用kafka流窗口来执行此操作:

final KStreamBuilder builder = new KStreamBuilder();
KStream<String, JsonNode> transactionKStream = builder.stream(keySerde, valueSerde, srcTopicName);
KTable<Windowed<String>, InfoRecord> kTableRecords= groupedStream.aggregate(
 InfoRecord::new, /* initializer */
 (k, v, aggregate) -> aggregate.add(k,v), /* adder */
 TimeWindows.of(TimeUnit.SECONDS.toMillis(5)).until(TimeUnit.SECONDS.toMillis(5)),
 infoRecordSerde);

与源主题一样,每个记录都有txId作为键,并且txId永远不会重复,因此,当进行聚合时,结果K-table将具有与K-stream相同的记录但我可以使用获取所有记录的窗口 具体的持续时间。

我认为kTableRecords应该包含特定持续时间内的所有记录,即5秒, 因此,我可以在5秒内循环所有记录,以获得高,低,开(窗口中的第一个记录价格),关闭(窗口中的最后一个记录价格),close_time(tx时间为窗口中的最后一条记录), 所以我只得到这个窗口的一条记录并将这个结果输出到sink kafka主题,但我不知道如何在这些窗口持续时间内这样做。

我认为代码就像:

kTableRecords.foreach((键,值) - &gt; {

// TODO:在这里添加逻辑

})

IDE显示此foreach已被弃用,

但我不知道如何在此窗口或下一个窗口中区分记录 或者我需要一个窗口记录保留时间使用,直到上面的示例代码。

我在这方面已经挣扎了好几天,我仍然不知道完成工作的正确方法,感谢任何人的帮助让我以正确的方式,谢谢

kafka版本是:0.11.0.0

更新

根据Michal的帖子提示,我改变了我的代码,然后做了 聚合器实例中的高,低,开,收价格计算, 但结果让我重新考虑了特定的每个不同的关键 窗口,逻辑为键创建一个新实例,并仅为当前键添加excuta,而不与其他键的值交互, 我真正想要的是给它加油 每个记录的高,低,开,收价格,具有不同的关键 窗口持续时间,所以我需要的是不为每个键创建一个新实例, 它应该为每个特定窗口只创建一个聚合实例 并对持续时间内的所有记录值进行计算,每个持续时间窗口得到一组(高,低,开,收价)。 我读过这个话题: 如何通过连续增加定时窗口来计算窗口化聚合? 所以,我怀疑,我不确定,如果这对我来说是正确的解决方案,谢谢。

顺便说一下,K线表示烛台图表。

更新II:

根据您的更新,我创建代码如下所示:

KStream<String, JsonNode> transactionKStream = builder.stream(keySerde, valueSerde, srcTopicName);

KGroupedStream<String, JsonNode> groupedStream = transactionKStream.groupBy((k,v)-> "constkey", keySerde, valueSerde);

KTable<Windowed<String>, MarketInfoRecord> kTable =
        groupedStream.aggregate(
        MarketInfoRecord::new, /* initializer */
        (k, v, aggregate) -> aggregate.add(k,v), /* adder */
        TimeWindows.of(TimeUnit.SECONDS.toMillis(100)).until(TimeUnit.SECONDS.toMillis(100)),
        infoRecordSerde, "test-state-store");

KStream<String, MarketInfoRecord> newS = kTable.toStream().map(
        (k,v) -> {
            System.out.println("key: "+k+",  value:"+v);
            return KeyValue.pair(k.window().start() + "_" + k.window().end(), v);

        }

);

newS.to(Serdes.String(),infoRecordSerde, "OUTPUT_NEW_RESULT");

如果我在执行组时使用静态字符串作为键,则确保在进行窗口化聚合时, 只为窗口创建了一个聚合器实例,我们可以得到(高,低,开,关) 对于该窗口中的所有记录,但是 作为所有记录的密钥相同,此窗口将多次更新,并为一个窗口生成多个记录,如下所示:

key: [constkey@1521304400000/1521304500000],  value:MarketInfoRecord{high=11, low=11, openTime=1521304432205, closeTime=1521304432205, open=11, close=11, count=1}
key: [constkey@1521304600000/1521304700000],  value:MarketInfoRecord{high=44, low=44, openTime=1521304622655, closeTime=1521304622655, open=44, close=44, count=1}
key: [constkey@1521304600000/1521304700000],  value:MarketInfoRecord{high=44, low=33, openTime=1521304604182, closeTime=1521304622655, open=33, close=44, count=2}
key: [constkey@1521304400000/1521304500000],  value:MarketInfoRecord{high=22, low=22, openTime=1521304440887, closeTime=1521304440887, open=22, close=22, count=1}
key: [constkey@1521304600000/1521304700000],  value:MarketInfoRecord{high=55, low=55, openTime=1521304629943, closeTime=1521304629943, open=55, close=55, count=1}
key: [constkey@1521304800000/1521304900000],  value:MarketInfoRecord{high=77, low=77, openTime=1521304827181, closeTime=1521304827181, open=77, close=77, count=1}
key: [constkey@1521304800000/1521304900000],  value:MarketInfoRecord{high=77, low=66, openTime=1521304817079, closeTime=1521304827181, open=66, close=77, count=2}
key: [constkey@1521304800000/1521304900000],  value:MarketInfoRecord{high=88, low=66, openTime=1521304817079, closeTime=1521304839047, open=66, close=88, count=3}
key: [constkey@1521304800000/1521304900000],  value:MarketInfoRecord{high=99, low=66, openTime=1521304817079, closeTime=1521304848350, open=66, close=99, count=4}
key: [constkey@1521304800000/1521304900000],  value:MarketInfoRecord{high=100.0, low=66, openTime=1521304817079, closeTime=1521304862006, open=66, close=100.0, count=5}

所以我们需要将重复数据删除作为“38945277/7897191”中描述的发布链接,对吗?

所以,我想知道我是否可以这样做:

KGroupedStream<String, JsonNode> groupedStream = transactionKStream.groupByKey();
// as key was unique txId, so this group is just for doing next window operation, the record number is not changed.

KTable<Windowed<String>, MarketInfoRecord> kTable =
   groupedStream.SOME_METHOD(
// just use some method to deliver the records in different windows,
// no sure if this is possible?
TimeWindows.of(TimeUnit.SECONDS.toMillis(100)).until(TimeUnit.SECONDS.toMillis(100))
// use until here to let the record purged if out of the window, 
// please correct me if i am wrong?

我们可以将基于时间的输入记录系列转换为几个窗口组, 每个组都有窗口(或使用窗口开始时间,结束时间组合为字符串键), 所以,对于每个组,键是不同的,但它有几个具有不同值的记录, 然后我们进行聚合(这里不需要使用窗口聚合)​​,已经计算了值,并且 从每个键:值对,即我们可以得到一个结果记录, 并且下一个窗口有不同的windowBased键名,所以这样,下游执行shoud有多个线程( 关键变化)

1 个答案:

答案 0 :(得分:2)

我建议你做的所有计算都不是在foreach中提到的,而是直接在你的聚合器中,即在加法器中:

(k, v, aggregate) -> aggregate.add(k,v), /* adder */

add方法可以完成你提到的所有事情(我建议你先将JsonNode映射到Java对象,让我们称之为Transaction),考虑这个伪代码:

private int low = Integer.MAX; // whatever type you use to represent prices
private int high = Integer.MIN;
private long openTime = Long.MAX; // whatever type you use to represent time
private long closeTime = Long.MIN;
...
public InfoRecord add(String key, Transaction tx) {
  if(tx.getPrice() > this.high) this.high = tx.getPrice();
  if(tx.getPrice() < this.low) this.low = tx.getPrice();
  if(tx.getTime() < this.openTime) {
    this.openTime = tx.getTime();
    this.open = tx.getPrice();
  }
  if(tx.getTime() > this.closeTime) {
    this.closeTime = tx.getTime();
    this.close = tx.getPrice();
  }
  return this;
}

请记住,您实际上可能会为每个窗口输出多条记录,因为窗口可以多次更新(它们永远不会是最终的),如下所述:https://stackoverflow.com/a/38945277/7897191 < / p>

我不知道K-line是什么,但是如果你想要多个持续时间不断增加的窗口,那么模式就是here

更新: 要聚合窗口中的所有记录,只需在执行聚合之前将密钥更改为某个静态值。因此,要创建分组流,您可以使用groupBy(KeyValueMapper),例如:

KGroupedStream<String, JsonNode> groupedStream = transactionKStream.groupBy( (k, v) -> ""); // give all records the same key (empty string)

请注意,这将导致重新分区(因为分区由密钥确定,我们正在更改密钥),下游执行将变为单线程(因为现在只有一个分区)。