Kafka流错误:SerializationException:LongDeserializer接收的数据大小不为8

时间:2019-02-28 10:56:18

标签: java apache-kafka apache-kafka-streams

我正在尝试Kafka Streams。编写一个简单的应用程序,在其中计算重复的消息。

消息:

2019-02-27-11:16:56 :: session:prod-111656 :: Msg => Hello World: 2491
2019-02-27-11:16:56 :: session:prod-111656 :: Msg => Hello World: 2492

我正在尝试按session:prod-xxxx拆分此类消息。使用它作为密钥。并且session:prod-xxxx+Hello World: xxxx使用它作为值。然后按键分组,看看在每个会话中哪些消息重复。

代码如下:

KStream<String, String> textLines = builder.stream("RegularProducer");
KTable<String, Long> ktable = textLines.map(
    (String key, String value) -> {
        try {
            String[] parts = value.split("::");
            String sessionId = parts[1];
            String message = ((parts[2]).split("=>"))[1];
            message = sessionId+":"+message;
            return new KeyValue<String,String>(sessionId.trim().toLowerCase(), message.trim().toLowerCase());
        } catch (Exception e) {
            return new KeyValue<String,String>("Invalid-Message".trim().toLowerCase(), "Invalid Message".trim().toLowerCase());
        }
    })
    .groupBy((key,value) -> value)
    .count().filter(
            (String key, Long value) -> {
                return value > 1;
            }
    );

ktable.toStream().to("RegularProducerDuplicates", 
Produced.with(Serdes.String(), Serdes.Long()));
Topology topology = builder.build();
topology.describe();
KafkaStreams streams = new KafkaStreams(topology, props);
streams.start();

KTable主题RegularProducerDuplicates被生成。但是,当我使用console-consumer查看它时,它崩溃并显示错误。然后,我在控制台使用者上使用--skip-message-on-error标志。现在我看到成千上万的这样的行

session:prod-111656 : hello world: 994  [2019-02-28 16:25:18,081] ERROR Error processing message, skipping this message:  (kafka.tools.ConsoleConsumer$)
org.apache.kafka.common.errors.SerializationException: Size of data received by LongDeserializer is not 8

有人可以帮助我这里出什么事吗?

3 个答案:

答案 0 :(得分:2)

您的Kafka Streams应用程序正常并且可以正常运行。

错误kafka-console-consumer中(kafka.tools.ConsoleConsumer是实现脚本逻辑的类)。

在反序列化期间,它不能正确处理null。当它获得null作为消息的值或键时,它将设置默认值(代表null字符串的字节数组)。如果您检查源代码,则可以找到以下功能

def write(deserializer: Option[Deserializer[_]], sourceBytes: Array[Byte]) {
  val nonNullBytes = Option(sourceBytes).getOrElse("null".getBytes(StandardCharsets.UTF_8))
  val convertedBytes = deserializer.map(_.deserialize(null, nonNullBytes).toString.
    getBytes(StandardCharsets.UTF_8)).getOrElse(nonNullBytes)
  output.write(convertedBytes)
}

如何查看反序列化得到的sourceBytes为null(sourceBytes==null)时如何设置默认值:

val nonNullBytes = Option(sourceBytes).getOrElse("null".getBytes(StandardCharsets.UTF_8))

在您的情况下为"null".getBytes(StandardCharsets.UTF_8)。然后,尝试使用org.apache.kafka.common.serialization.LongDeserializer(您的值反序列化器)进行反序列化。 LongDeserializer从一开始就检查字节数组的大小。现在它是4(null的字节表示形式),并且引发了异常。

例如,如果您使用StringDeserializer,它将无法正确地反序列化它,但是至少它不会引发异常,因为它不检查字节数组的长度。

长话短说:ConsoleConsumer的格式化程序,负责打印,用于漂亮的打印,设置一些默认值,某些反序列化器(LongDeserializer,IntegerDeserializer)无法处理

关于,为什么您的应用程序会为某些键产生null值:

KTable:filter的语义与KStream::filter不同。根据javadoc的KTable:

  

对于每个删除的记录(即不满足给定的记录)   谓词)转发墓碑记录。

对于您的filter,当count <= 1传递密钥的null值时。

答案 1 :(得分:1)

用于值的反序列化器可能不适用于String,而将用于Long。 在cli中创建消费者时,请指定它。

bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 \
    --topic name \
    --from-beginning \
    --formatter kafka.tools.DefaultMessageFormatter \
    --property print.key=true \
    --property print.value=true \
    --skip-message-on-error \
    --property key.deserializer=org.apache.kafka.common.serialization.StringDeserializer \
    --property value.deserializer=org.apache.kafka.common.serialization.StringDeserializer

在创建使用者时,请检查最后两行,注意您的(键,值)的类型 在我的情况下,它们都是字符串,如果值是long类型,则使用最后一行作为: -属性value.deserializer = org.apache.kafka.common.serialization.LongDeserializer

答案 2 :(得分:0)

我遇到了同样的问题,发现如果将filter移到toStream之后,则不会产生空值(墓碑)。