Java ApacheKafka命令根据其值对消息进行窗口化

Java ApacheKafka命令根据其值对消息进行窗口化,java,stream,apache-kafka,messaging,Java,Stream,Apache Kafka,Messaging,我正试图找到一种方法,在一个主题分区内对消息重新排序,并将排序后的消息发送到一个新的主题 我有一个Kafka publisher,它发送以下格式的字符串消息: {system\u timestamp}-{event\u name}{parameters} 例如: 1494002667893-client.message?chatName=1c&messageBody=hello 1494002656558-chat.started?chatName=1c&chatPatricip

我正试图找到一种方法,在一个主题分区内对消息重新排序,并将排序后的消息发送到一个新的主题

我有一个Kafka publisher,它发送以下格式的字符串消息:
{system\u timestamp}-{event\u name}{parameters}

例如:

1494002667893-client.message?chatName=1c&messageBody=hello
1494002656558-chat.started?chatName=1c&chatPatricipants=3
此外,我们为每条消息添加一些消息密钥,以将它们发送到相应的分区

我想做的是根据消息的{system timestamp}部分在1分钟内重新排序事件,因为我们的发布者不保证消息将按照{system timestamp}值发送

例如,我们可以首先向主题传递一条具有更大{system timestamp}值的消息

我调查了Kafka Stream API,发现了一些关于消息窗口和聚合的示例:

Properties streamsConfiguration = new Properties();
        streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "stream-sorter");
        streamsConfiguration.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        streamsConfiguration.put(StreamsConfig.ZOOKEEPER_CONNECT_CONFIG, "localhost:2181");
        streamsConfiguration.put(StreamsConfig.KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName());
        streamsConfiguration.put(StreamsConfig.VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName());

 KStreamBuilder builder = new KStreamBuilder();
 KStream<String, String> stream = builder.stream("events");
 KGroupedStream<String>, String> groupedStream = stream.groupByKey();//grouped events within partion.

    /* commented since I think that I don't need any aggregation, but I guess without aggregation I can't use time windowing.
KTable<Windowed<String>, String> windowedEvents = stream.groupByKey().aggregate(
                () -> "",  // initial value
                (aggKey, value, aggregate) -> aggregate + "",   // aggregating value
                TimeWindows.of(1000), // intervals in milliseconds
                Serdes.String(), // serde for aggregated value
                "test-store"
        );*/
属性流配置=新属性();
streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG,“流分类器”);
streamsConfiguration.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG,“localhost:9092”);
streamsConfiguration.put(StreamsConfig.ZOOKEEPER\u CONNECT\u CONFIG,“localhost:2181”);
streamsConfiguration.put(StreamsConfig.KEY\u SERDE\u CLASS\u CONFIG,Serdes.String().getClass().getName());
streamsConfiguration.put(StreamsConfig.VALUE\u SERDE\u CLASS\u CONFIG,Serdes.String().getClass().getName());
KStreamBuilder builder=新的KStreamBuilder();
KStream stream=builder.stream(“事件”);
KGroupedStream,String>groupedStream=stream.groupByKey()//分区内的分组事件。
/*评论,因为我认为我不需要任何聚合,但我想没有聚合,我无法使用时间窗口。
KTable windowedEvents=stream.groupByKey().aggregate(
()->“”,//初始值
(aggKey,value,aggregate)->aggregate+“”,//聚合值
TimeWindows.of(1000),//以毫秒为单位的间隔
Serdes.String(),//用于聚合值的serde
“测试存储”
);*/
但是我接下来应该如何处理这个分组流呢?我没有看到任何可用的'sort()(e1,e2)->e1.compareTo(e2)'方法,windows也可以应用于aggregation()、reduce()、count()等方法,但我认为我不需要任何消息数据操作

如何在1分钟的时间窗口中重新排序邮件并将其发送到另一个主题?

以下是一个提纲:

创建以下处理器实现:

  • 在process()方法中,对于每条消息:

    • 从消息值读取时间戳
    • 将(timestamp,message key)对作为键,将消息值作为值插入KeyValueStore。注意:这还提供重复数据消除。您需要提供一个自定义Serde来序列化密钥,以便时间戳位于第一位(按字节排列),以便范围查询首先按时间戳排序
  • 在标点()方法中:

    • 使用从0到时间戳-60'000(=1分钟)的范围内的获取读取存储
    • 使用context.forward()按顺序发送提取的消息,并将其从存储中删除
这种方法的问题是,如果没有新的MSG到达以提前“流时间”,则不会触发标点()。如果这种情况存在风险,您可以创建一个外部调度程序,向主题的每个(!)分区发送周期性的“勾号”消息,您的处理器应该忽略这些消息,但在缺少“真实”MSG的情况下,它们会触发标点。 KIP-138将通过添加对系统时间标点的明确支持来解决此限制:

以下是我在项目中对流进行排序的方式

  • 已创建具有源、处理器和接收器的拓扑
  • 处理器内
  • 进程(键、值)->将每个记录添加到列表(实例变量)
  • Init()->schedule(窗口缓冲区时间、墙上时钟时间)->标点(时间戳)对列表(实例变量)中窗口缓冲区时间项的列表进行排序,并进行迭代和转发。清除列表(实例变量)

  • 这个逻辑对我来说很好。

    再仔细考虑一下,当生产者在同一毫秒内用同一个键发出多条消息时,这个逻辑就不起作用了。因此,密钥应该是(时间戳,一些唯一的密钥)对,或者值应该是一个集合,这应该没有问题,因为消息的消费顺序与它们在指向同一分区时产生的顺序相同——我们知道,通过使用相同的键,使用StateStore备份该集合是明智的,以便在应用程序崩溃时不会丢失缓冲事件。下面是一个可能丢失缓冲区的示例。我已承诺如何使用状态存储对事件进行排序->