我在加入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)
不确定选择新密钥的步骤是否已取消正确的日期。我已经测试了很多没有运气的组合:(
任何帮助将不胜感激!
答案 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对我不起作用,但是至少现在测试可以正常工作。