Apache kafka 使用Kafka流测试窗口聚合

Apache kafka 使用Kafka流测试窗口聚合,apache-kafka,apache-kafka-streams,Apache Kafka,Apache Kafka Streams,我正在玩Kafka Streams的TopologyTestDriver,以便测试我们的数据管道 对于我们所有的简单拓扑,包括使用存储的有状态拓扑,它都发挥了巨大的作用。 我的问题是当我试图使用这个测试驱动程序来测试使用窗口聚合的拓扑时 我复制了一个简单的示例,它在10秒内对使用相同键接收的整数求和 public class TopologyWindowTests { TopologyTestDriver testDriver; String INPUT_TOPIC = "INPUT.TOPI

我正在玩Kafka Streams的TopologyTestDriver,以便测试我们的数据管道

对于我们所有的简单拓扑,包括使用存储的有状态拓扑,它都发挥了巨大的作用。 我的问题是当我试图使用这个测试驱动程序来测试使用窗口聚合的拓扑时

我复制了一个简单的示例,它在10秒内对使用相同键接收的整数求和

public class TopologyWindowTests {

TopologyTestDriver testDriver;
String INPUT_TOPIC = "INPUT.TOPIC";
String OUTPUT_TOPIC = "OUTPUT.TOPIC";

@Before
public void setup(){
    Properties config = new Properties();
    config.put(StreamsConfig.APPLICATION_ID_CONFIG, "test");
    config.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "dummy:1234");
    // EventProcessor is a <String,String> processor
    // so we set those serders
    config.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
    config.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.Integer().getClass());
    testDriver = new TopologyTestDriver(defineTopology(),config,0L);
}

/**
 * topology test
 */
@Test
public void testTopologyNoCorrelation() throws IOException {
    ConsumerRecordFactory<String, Integer> factory = new ConsumerRecordFactory<>(INPUT_TOPIC, new StringSerializer(), new IntegerSerializer());
    testDriver.pipeInput(factory.create(INPUT_TOPIC,"k",2,1L));

    ProducerRecord<String, Integer> outputRecord = testDriver.readOutput(OUTPUT_TOPIC, new StringDeserializer(), new IntegerDeserializer());

    Assert.assertNull(outputRecord);
}

@After
public void tearDown() {
    testDriver.close();
}

/**
 * Defines topology
 * @return
 */
public Topology defineTopology(){
    StreamsBuilder builder = new StreamsBuilder();
    KStream<String,Integer> inputStream = builder.stream(INPUT_TOPIC);

    KTable<Windowed<String>, Integer> groupedMetrics = inputStream.groupBy((key,value)->key,
            Serialized.with(Serdes.String(),Serdes.Integer())).windowedBy(TimeWindows.of(TimeUnit.SECONDS.toMillis(10))).aggregate(
            ()-> 0,
            (String aggKey, Integer newValue, Integer aggValue)->{
                Integer val = aggValue+newValue;
                return val;
            },
            Materialized.<String,Integer,WindowStore<Bytes,byte[]>>as("GROUPING.WINDOW").withKeySerde(Serdes.String()).withValueSerde(Serdes.Integer())
    );

    groupedMetrics.toStream().map((key,value)->KeyValue.pair(key.key(),value)).to(OUTPUT_TOPIC);

    return builder.build();

}
公共类拓扑INDOWTESTS{
拓扑测试驱动程序;
字符串输入\u TOPIC=“INPUT.TOPIC”;
字符串输出\u TOPIC=“OUTPUT.TOPIC”;
@以前
公共作废设置(){
属性配置=新属性();
config.put(StreamsConfig.APPLICATION_ID_config,“test”);
config.put(StreamsConfig.BOOTSTRAP_SERVERS_config,“dummy:1234”);
//EventProcessor是一个处理器
//所以我们让那些农奴
config.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_config,Serdes.String().getClass());
config.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_config,Serdes.Integer().getClass());
testDriver=new-TopologyTestDriver(defineTopology(),config,0L);
}
/**
*拓扑测试
*/
@试验
public void testTopologyNoCorrelation()引发IOException{
ConsumerRecordFactory=new ConsumerRecordFactory(输入主题,new StringSerializer(),new IntegerSerializer());
testDriver.pipeInput(工厂创建(输入主题“k”,2,1L));
ProducerRecord outputRecord=testDriver.readOutput(输出主题,新的StringDeserializer(),新的IntegerDeserializer());
Assert.assertNull(outputRecord);
}
@之后
公共无效拆卸(){
testDriver.close();
}
/**
*定义拓扑
*@返回
*/
公共拓扑定义策略(){
StreamsBuilder builder=新的StreamsBuilder();
KStream inputStream=builder.stream(输入主题);
KTable groupedMetrics=inputStream.groupBy((键,值)->key,
序列化的.with(Serdes.String(),Serdes.Integer()).windowedBy(TimeWindows.of(TimeUnit.SECONDS.toMillis(10)).aggregate(
()-> 0,
(字符串aggKey、整数newValue、整数aggValue)->{
整数val=aggValue+newValue;
返回val;
},
具体化的.as(“GROUPING.WINDOW”).withKeySerde(Serdes.String()).withValueSerde(Serdes.Integer())
);
groupedMetrics.toStream().map((键,值)->KeyValue.pair(键,键(),值)).to(输出主题);
返回builder.build();
}
}

我希望在这个测试用例中,没有任何东西返回到输出主题,除非我将挂钟时间提前10秒。。。但我得到了以下输出

java.lang.AssertionError: expected null, but was:<ProducerRecord(topic=OUTPUT.TOPIC, partition=null, headers=RecordHeaders(headers = [], isReadOnly = false), key=k, value=2, timestamp=0)>
java.lang.AssertionError:应为null,但为:
我是不是遗漏了什么? 我用的是卡夫卡2.0.0

更新

提前谢谢

根据Matthias的回答,我准备了以下测试:

@Test
public void testTopologyNoCorrelation() throws IOException {
    ConsumerRecordFactory<String, Integer> factory = new ConsumerRecordFactory<>(INPUT_TOPIC, new StringSerializer(), new IntegerSerializer());
    testDriver.pipeInput(factory.create(INPUT_TOPIC,"k",2,1L));
    testDriver.pipeInput(factory.create(INPUT_TOPIC,"k",2,1L));

    // Testing 2+2=4
    ProducerRecord<String, Integer> outputRecord1 = testDriver.readOutput(OUTPUT_TOPIC, new StringDeserializer(), new IntegerDeserializer());
    Assert.assertEquals(Integer.valueOf(4),outputRecord1.value());

    // Testing no more events in the window
    ProducerRecord<String, Integer> outputRecord2 = testDriver.readOutput(OUTPUT_TOPIC, new StringDeserializer(), new IntegerDeserializer());
    Assert.assertNull(outputRecord2);
}
@测试
public void testTopologyNoCorrelation()引发IOException{
ConsumerRecordFactory=new ConsumerRecordFactory(输入主题,new StringSerializer(),new IntegerSerializer());
testDriver.pipeInput(工厂创建(输入主题“k”,2,1L));
testDriver.pipeInput(工厂创建(输入主题“k”,2,1L));
//测试2+2=4
ProducerRecord outputRecord1=testDriver.readOutput(输出主题,新的StringDeserializer(),新的IntegerDeserializer());
Assert.assertEquals(Integer.valueOf(4),outputRecord1.value());
//测试窗口中没有更多事件
ProducerRecord outputRecord2=testDriver.readOutput(输出主题,新的StringDeserializer(),新的IntegerDeserializer());
Assert.assertNull(outputRecord2);
}

两条输入消息都是用相同的时间戳发送的,因此我希望输出主题中只有一个事件包含我的值之和。但是,我在输出中接收到2个事件(第一个值为2,第二个值为4),我认为这不是拓扑的理想行为。

默认情况下,Kafka Streams在窗口操作的事件时间上运行,而不是墙上的时钟时间。这保证了确定性处理语义(挂钟时间处理本质上是非确定性的)。有关更多详细信息,请查看文档:

因此,输入记录的时间戳决定将记录放在哪个窗口中。此外,输入记录的时间戳会提前基于这些事件时间戳的内部跟踪“流时间”

还要注意的是,Kafka流遵循一个连续的处理模型,并发出更新的消息,而不是等待窗口结束条件。这对于处理延迟到达(也称为无序数据)非常重要。比较和

更新


这是因为“更新”处理模型。聚合时,每个输入记录更新“当前”结果,并生成“当前结果输出记录”。每个记录(不是每个时间戳)都会发生这种情况。

感谢Matthias的快速响应。我添加了一个新的测试,它显示了当我向输入主题发送两个具有相同时间戳的事件时测试驱动程序的行为。我希望输出中只有两个值的总和,但我得到了两个事件。。。?你能解释一下吗?@DavidO扩展了我的回答我理解你的观点,但这不是我在对Kafka群集运行拓扑时看到的行为。在这种情况下,我只能在每个窗口中看到一个输出事件,不管我发送了多少条记录。所以,我的观点是,有些东西并没有像它应该的那样运行,因为针对测试驱动程序和Kafka集群的行为是不同的。您可以在这里找到整个项目:区别在于
TopologyTestDriver
在处理每个记录后提交(这包括刷新KTable状态存储缓存)。在集群上运行时,默认情况下,Kafka Streams仅每30秒提交一次,因此连续更新是“重复数据消除”的。如果禁用,两种情况下都可以得到相同的行为