加入2个Kafka流时出现问题(使用自定义的timestampextractor)

时间:2018-12-12 09:04:11

标签: scala apache-kafka apache-kafka-streams

我在加入2个kafka流时遇到问题,这些流从事件字段中提取日期。当我没有定义自定义TimeStampExtractor时,联接工作正常,但是当我执行联接时,联接不再起作用。我的拓扑非常简单:

val builder = new StreamsBuilder()

val couponConsumedWith = Consumed.`with`(Serdes.String(),
  getAvroCouponSerde(schemaRegistryHost, schemaRegistryPort))
val couponStream: KStream[String, Coupon] = builder.stream(couponInputTopic, couponConsumedWith)

val purchaseConsumedWith = Consumed.`with`(Serdes.String(),
  getAvroPurchaseSerde(schemaRegistryHost, schemaRegistryPort))
val purchaseStream: KStream[String, Purchase] = builder.stream(purchaseInputTopic, purchaseConsumedWith)

val couponStreamKeyedByProductId: KStream[String, Coupon] = couponStream.selectKey(couponProductIdValueMapper)
val purchaseStreamKeyedByProductId: KStream[String, Purchase] = purchaseStream.selectKey(purchaseProductIdValueMapper)

val couponPurchaseValueJoiner = new ValueJoiner[Coupon, Purchase, Purchase]() {

  @Override
  def apply(coupon: Coupon, purchase: Purchase): Purchase = {
      val discount = (purchase.getAmount * coupon.getDiscount) / 100
      new Purchase(purchase.getTimestamp, purchase.getProductid, purchase.getProductdescription, purchase.getAmount - discount)
  }
}

val fiveMinuteWindow = JoinWindows.of(TimeUnit.MINUTES.toMillis(10))
val outputStream: KStream[String, Purchase] = couponStreamKeyedByProductId.join(purchaseStreamKeyedByProductId,
  couponPurchaseValueJoiner,
  fiveMinuteWindow
  )

outputStream.to(outputTopic)

builder.build()

正如我说的那样,当我不使用自定义TimeStampExtractor时,而是通过将StreamsConfig.DEFAULT_TIMESTAMP_EXTRACTOR_CLASS_CONFIG设置为我的自定义提取器类来执行此代码时,它就像是一个魅力(我已经仔细检查了该类是否正确提取了日期)联接不再起作用。

我正在通过运行单元测试并将以下事件传递给拓扑来测试拓扑:

    val coupon1 = new Coupon("Dec 05 2018 09:10:00.000 UTC", "1234", 10F)
    // Purchase within the five minutes after the coupon - The discount should be applied
    val purchase1 = new Purchase("Dec 05 2018 09:12:00.000 UTC", "1234", "Green Glass", 25.00F)
    val purchase1WithDiscount = new Purchase("Dec 05 2018 09:12:00.000 UTC", "1234", "Green Glass", 22.50F)
    val couponRecordFactory1 = couponRecordFactory.create(couponInputTopic, "c1", coupon1)
    val purchaseRecordFactory1 = purchaseRecordFactory.create(purchaseInputTopic, "p1", purchase1)

    testDriver.pipeInput(couponRecordFactory1)
    testDriver.pipeInput(purchaseRecordFactory1)
    val outputRecord1 = testDriver.readOutput(outputTopic,
      new StringDeserializer(),
      JoinTopologyBuilder.getAvroPurchaseSerde(
        schemaRegistryHost,
        schemaRegistryPort).deserializer())
    OutputVerifier.compareKeyValue(outputRecord1, "1234", purchase1WithDiscount)

不确定选择新密钥的步骤是否已取消正确的日期。我已经测试了很多没有运气的组合:(

任何帮助将不胜感激!

2 个答案:

答案 0 :(得分:1)

我不确定,因为我不知道您要测试多少代码,但我的猜测是:

1)您的代码可以使用默认的时间戳提取器,因为它使用的是将记录发送到管道中的时间作为时间戳记录,因此基本上可以使用,因为在您的测试中,您是在不加暂停。

2)您正在使用TopologyTestDriver进行测试! 请注意,这对于以一个单元测试您的业务代码和拓扑(我有什么作为输入以及根据输出正确的是什么)非常有用,但是这些测试中没有运行Kafka Stream应用程序。

根据您的情况,您可以使用advanceWallClockTime(long)类中的方法TopologyTestDriver来模拟系统的时间步移。

如果要启动拓扑,则必须对嵌入式kafka集群进行集成测试(kafka库中有一个可以正常工作!)。

让我知道是否有帮助:-)

答案 1 :(得分:0)

感谢您的回复。昨天我正在研究这个问题,我想我发现了问题。如您所说,我正在使用TopologyTestDriver来运行测试,并且在初始化TopologyTestDriver类时,它将使用initialWallClockTime,如果不提供值,则TopologyTestDriver将获取currentTimeMillis:

public TopologyTestDriver(Topology topology, Properties config) {
    this(topology, config, System.currentTimeMillis());
} 

还有另一个构造函数,可让您传递initialWallClockTime。我一直在测试此方法,但由于某种原因,它对我不起作用。

总而言之,我的解决方案是使用当前时间戳创建Purchase和Coupon对象。我仍在使用自定义的时间戳提取器,但是我始终使用当前时间戳来代替对日期进行硬编码,因此联接可以正常工作。

对最终解决方案不完全满意,因为我不知道为什么initialWallClockTime对我不起作用,但是至少现在测试可以正常工作。