Scala 连接2个kafka流时出现问题(使用自定义时间戳提取器)
我在加入2个卡夫卡流时遇到问题,无法从我的活动字段中提取日期。当我没有定义自定义TimeStampExtractor时,联接工作正常,但当我定义自定义TimeStampExtractor时,联接不再工作。我的拓扑结构非常简单:Scala 连接2个kafka流时出现问题(使用自定义时间戳提取器),scala,apache-kafka,apache-kafka-streams,Scala,Apache Kafka,Apache Kafka Streams,我在加入2个卡夫卡流时遇到问题,无法从我的活动字段中提取日期。当我没有定义自定义TimeStampExtractor时,联接工作正常,但当我定义自定义TimeStampExtractor时,联接不再工作。我的拓扑结构非常简单: val builder = new StreamsBuilder() val couponConsumedWith = Consumed.`with`(Serdes.String(), getAvroCouponSerde(schemaRegistryHost, s
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()
正如我所说的,当我不使用自定义时间戳提取器时,这段代码就像一个符咒,但当我通过将StreamsConfig.DEFAULT\u TIMESTAMP\u提取器\u CLASS\u 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)
不确定选择新密钥的步骤是否删除了正确的日期。我已经测试了很多组合,但运气不佳:(
非常感谢您的帮助!我不确定这一点,因为我不知道您对代码进行了多少测试,但我的猜测是: 1) 您的代码使用默认的时间戳提取器,因为它使用您将记录作为时间戳记录发送到管道中的时间,所以基本上它可以工作,因为在您的测试中,您可以不停顿地一个接一个地发送数据 2) 您正在使用
TopologyTestDriver
进行测试!
请注意,它对于将业务代码和拓扑作为一个单元进行测试非常有用(我有哪些作为输入,哪些是正确的相应输出),但这些测试中没有运行Kafka Stream应用程序
在您的情况下,您可以使用TopologyTestDriver
类中的advanceWallClockTime(long)
方法来模拟系统时间行走
如果要启动拓扑,必须对嵌入式kafka群集进行集成测试(kafka库中有一个运行正常!)
如果有帮助,请告诉我:-)谢谢您的回复。我昨天在做这个,我想我发现了问题。正如您所说,我正在使用TopologyTestDriver运行测试,当您初始化TopologyTestDriver类时,它使用initialWallClockTime,如果您不提供值,TopologyTestDriver将获取currentTimeMillis:
public TopologyTestDriver(Topology topology, Properties config) {
this(topology, config, System.currentTimeMillis());
}
还有一个构造函数允许您传入initialWallClockTime。我一直在测试这个方法,但出于某种原因,它不适合我
总之,我的解决方案是使用当前时间戳创建购买和优惠券对象。我仍然在使用我的自定义时间戳提取器,但不是硬编码日期,我总是得到当前的时间戳,这样连接就可以正常工作
我对我的最终解决方案不太满意,因为我不知道为什么initialWallClockTime对我不起作用,但至少现在测试工作正常 您的自定义提取器是否返回当前时间戳?另外,查看
KStreamKStreamJoinProcessor#process()
以了解处理器的功能。感谢您回复@MatthiasJ.Sax。我的自定义提取器从事件的timestamp字段中提取时间戳。我发现了问题(请看下面的答案)再次感谢!最初的挂钟时间不应对IMHO产生任何影响。连接是在事件时间定义的,如果您从负载中提取时间戳,它应该可以工作。对于使用默认时间戳提取器:这似乎是可行的,因为您的输入记录也将从使用挂钟时间初始化的工厂获取时间戳。总的来说,这似乎没有什么意义。我也不知道你所说的“我仍在使用我的自定义时间戳提取器,但我总是获取当前时间戳,而不是硬编码日期,这样连接工作正常。”这是我最初的想法@MatthiasJ.Sax,但似乎挂钟时间对流连接的方式有影响(至少使用TopologyTestDriver)。我注意到,当我试图找到问题时,我在时间戳提取器中返回currentTimeMillis,测试开始工作。无论如何,我已将测试推送到github repo,以便您可以更好地了解我试图解释的内容: