【问题标题】:How to unit test the Kafka Streams and Producer APIs together如何一起对 Kafka Streams 和 Producer API 进行单元测试
【发布时间】:2020-05-19 14:24:08
【问题描述】:

目前,我有一个基本的 Kafka 流应用程序,它涉及一个只有源和处理器但没有接收器的拓扑。本质上,Topology 只处理消息的消费。至于产生消息,我们在传递给拓扑的 ProcessorSupplier 实例中调用 Producer API,特别是在重写的 process 方法中。虽然我知道 Producer API 在这里是多余的,因为我可以简单地在拓扑中添加一个接收器,但我处于必须以这种方式设置我的流应用程序的位置。至于测试,我试用了kafka-streams-test-utils 包中的TopologyTestDriver 类。但是,我不仅要测试拓扑,还要测试对 Producer API 的调用。使用TopologyTestDriver 需要我模拟我的Producer 实例,因为它与Streams API 是分开的。结果,由于消息没有“转发”,我无法读取来自TopologyTestDriver 的消息以进行单元测试。

这是我的process 方法的简化版本:

@Override
public void process(String key, String value) {
    // some data processing stuff that I leave out for simplicity sake
    String topic = "...";
    Properties props = ...;
    //Producer<String, String> producer = new KafkaProducer<>(props);
    ProducerRecord<String, String> record = new ProducerRecord(topic, key, value);
    producer.send(record);
}

这是我的示例单元测试的简化:

@Test
public void process() {
    Topology topology = new Topology();
    topology.addSource("source", "input-topic");
    topology.addProcessor("processor", ..., "source");
    Properties props = ...;

    TopologyTestDriver testDriver = new TopologyTestDriver(topology, props);

    ConsumerRecordFactory<String, String> factory = new ConsumerRecordFactory<>(new StringSerializer(), new StringSerializer());
    // the following line will work fine as long as the producer is mocked
    testDriver.pipeInput(factory.create("input-topic", "key", "value"));

    // since the producer is mocked, no message can be read from the output topic
    ProducerRecord<String, String> outputRecord = testDriver.readOutput("output-topic", new StringDeserializer(), new StringDeserializer());

    assertNull(outputRecord); // returns true
}

总结一下我的问题,有没有办法编写一个单元测试来测试拓扑中消息的消费和生产,该拓扑使用生产者 API 将消息写入传出主题?

【问题讨论】:

  • Spring 有一个嵌入式 Kafka,可用作执行此操作的基础架构。

标签: java junit apache-kafka apache-kafka-streams kafka-producer-api


【解决方案1】:

您不应使用自定义 Producer,而应将接收器添加到您的 Topology。对Producer.send() 的调用是异步的,因此您可能会丢失数据。为避免数据丢失,您需要使调用同步,即获取由send() 返回的Future,并在process() 返回之前等待其完成。但是,这对您的吞吐量影响很大,不建议这样做。

如果您添加接收器,您可以避免这些问题,因为 Kafka Streams 现在可以了解发送到输出主题的数据,因此不会发生数据丢失,而 Kafka Streams 可以使用性能更高的异步调用。

除了正确性问题之外,您似乎为您在当前代码中处理的每条消息创建了一个新的KafkaProducer,这是非常低效的。此外,使用接收器将简化您的代码。当然,您还可以使用 TopologyTestDriver 获得适当的测试功能。

【讨论】:

  • 嘿@Matthias J. Sax,感谢您的回复。我只想指出,我们并没有在每次收到新消息时创建自定义Producer,这只是为了表明我们正在使用一个来发送消息。我确实同意使用接收器将极大地简化代码,但是,我陷入了必须从不同集群中读取数据并将数据发送到主题的情况。我已经读到在这种情况下应该使用 MirrorMaker 将数据从一个集群复制到另一个集群。如果我没有要复制的主题,在这种情况下我还能做些什么吗?
  • 我明白了——是的,推荐使用 MM 或其他一些复制工具。否则,您需要使所有send() 调用同步不希望的内容。 -- 如果你真的想继续使用你的自定义生产者,你需要“模拟”生产者以某种方式使用——也许你可以使用“供应商模式”并在测试代码中切换供应商。
  • 只是想知道,context.forward 是同步的并且不容易丢失数据吗?另外,对于制作人的“嘲讽”,你会推荐EmbeddedKafka这个方法还是没关系?例如,我可以只使用MockProducer 对象吗?
  • 如果使用 sink,Kafka Streams 将使用异步发送调用,但是,在提交偏移量之前,它会调用 producer.flush() 以确保写入所有数据;这样可以避免数据丢失。 -- 你可以使用EmbeddedKafka,但它更重,我认为没有必要。只需使用模拟生产者就足够了。
  • 那么在我的情况下使用producer.flush() 有意义吗?如果我错了,请纠正,但似乎刷新生产者与使 send 调用异步。但是,它的性能仍然不如使用context.foward()。还有一个澄清,context.forward() 是否调用context.commit()?根据我的理解,似乎 forward 方法调用了flush然后提交,并且还有一些额外的逻辑来确保记录仍然按照它们被读取的顺序写入?
猜你喜欢
  • 2017-08-30
  • 1970-01-01
  • 2018-04-02
  • 1970-01-01
  • 2019-03-15
  • 1970-01-01
  • 2019-09-04
  • 2018-02-22
  • 2020-04-06
相关资源
最近更新 更多