使用KStream组进行序列化

时间:2019-02-10 05:18:45

标签: apache-kafka apache-kafka-streams

我试图在KStream上执行计数操作,并且在理解序列化在这里如何工作方面遇到了一些困难。我有一个信息流正在推送人员信息,例如姓名年龄。使用完此流后,我试图创建一个具有人们年龄计数的KTable。

输入:     {“名称”:“ abc”,“年龄”:“ 15”}

输出:     30、10     20、4     10、8     35、22     ...

属性

props.put(StreamsConfig.APPLICATION_ID_CONFIG, "person_processor");
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");

处理器

KStream<Object, Person> people = builder.stream("people");
people.print(Printed.<Object, Person>toSysOut().withLabel("consumer-1"));

输出     [消费者1]:空,[B @ 7e37bab6

问题1 我了解该主题中的数据以字节为单位。我没有为键或值设置任何Serdes。 KStream是否将输入从字节转换为Person并在此处打印Person的地址?

问题2 当我添加下面的值Serdes时,我得到了更有意义的输出。此处的字节信息是否已转换为String,然后转换为Person?为什么现在正确打印该值?

props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());

[consumer-1]: null, {"name" : "abc","age" : "15"}

问题3 现在,在对年龄进行计数时,在将String转换为Person时出现运行时错误。如果groupBy将age设置为Key并将count设置为Long,为什么会发生String到Person的转换?

KTable<Integer, Long> integerLongKTable = people.groupBy((key, value) -> value.getAge())
    .count();

Exception in thread "person_processor-9ff96b38-4beb-4594-b2fe-ae191bf6b9ff-StreamThread-1" java.lang.ClassCastException: java.lang.String cannot be cast to com.example.kafkastreams.KafkaStreamsApplication$Person
at org.apache.kafka.streams.kstream.internals.KStreamImpl$1.apply(KStreamImpl.java:152)
at org.apache.kafka.streams.kstream.internals.KStreamImpl$1.apply(KStreamImpl.java:149)

编辑1

仔细阅读@Matthias J. Sax的响应后,我从该位置使用Serializer和DeSerializer创建了一个PersonSerde,然后得到了SerializationException ...

{{3}}

static class Person {

    String name;
    String age;

    public Person(String name, String age) {

      this.name = name;
      this.age = age;
    }

    void setName(String name) {

      this.name = name;
    }

    String getName() {

      return name;
    }

    void setAge(String age) {

      this.age = age;
    }

    String getAge() {

      return age;
    }

    @Override
    public String toString() {

      return "Person {name:" + this.getName() + ",age:" + this.getAge() + "}";
    }
  }

public class PersonSerde implements Serde {

  @Override
  public void configure(Map map, boolean b) {

  }

  @Override
  public void close() {

  }

  @Override
  public Serializer serializer() {

    Map<String, Object> serdeProps = new HashMap<>();

    final Serializer<Person> personSerializer = new JsonPOJOSerializer<>();
    serdeProps.put("JsonPOJOClass", Person.class);
    personSerializer.configure(serdeProps, false);

    return personSerializer;
  }

  @Override
  public Deserializer deserializer() {

    Map<String, Object> serdeProps = new HashMap<>();

    final Deserializer<Person> personDeserializer = new JsonPOJODeserializer<>();
    serdeProps.put("JsonPOJOClass", Person.class);
    personDeserializer.configure(serdeProps, false);

    return personDeserializer;
  }
}

props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, personSerde.getClass());

KTable<String, Long> count = people.selectKey((key, value) -> value.getAge()).groupByKey(Serialized.with(Serdes.String(), personSerde))
      .count();

错误

Caused by: org.apache.kafka.common.errors.SerializationException: Error serializing JSON message
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.example.kafkastreams.KafkaStreamsApplication$Person and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:313)

编辑5

因此,当我将ValueMaps映射为String时,count可以正常工作。但是当我在自定义对象上使用它时,它会失败

KStream<String, Person> people = builder.stream("person-topic", Consumed.with(Serdes.String(), personSerde));
people.print(Printed.<String, Person>toSysOut().withLabel("person-source"));

KStream<String, Person> agePersonKStream = people.selectKey((key, value) -> value.getAge());
agePersonKStream.print(Printed.<String, Person>toSysOut().withLabel("age-person"));

KStream<String, String> stringStringKStream = agePersonKStream.mapValues((person -> person.name));
stringStringKStream.print(Printed.<String, String>toSysOut().withLabel("age-name"));

KTable<String, Long> stringLongKTable = stringStringKStream.groupByKey(Serialized.with(Serdes.String(), Serdes.String())).count();
stringLongKTable.toStream().print(Printed.<String, Long>toSysOut().withLabel("age-count"));

如果没有将MapValues映射到名称的3个步骤,则第4步将失败。

1 个答案:

答案 0 :(得分:0)

  

问题1我了解该主题中的数据以字节为单位。我没有为键或值设置任何Serdes。 KStream是否将输入从字节转换为Person并在此处打印Person的地址?

如果您未在SerdeStreamsConfig中指定任何builder.stream(..., Consumers.with(/*serdes*/)),则字节不会转换为Person对象,但该对象将是类型byte[]。因此,print()将调用byte[].toString(),从而产生您所看到的隐秘输出([B@7e37bab6)。

  

问题2当我添加下面的值Serdes时,我得到了更有意义的输出。此处的字节信息是否已转换为String,然后转换为Person?为什么现在正确打印该值?

Serde.String()中指定StreamsConfig时,字节将转换为String类型。看来StringSerde能够以有意义的方式反序列化字节-但这完全是巧合。看来您的数据实际上是用JSON序列化的,这可以解释为什么StringSerde()可以将字节转换为String

  

问题3现在,当对年龄进行计数时,在将String转换为Person时出现运行时错误。如果groupBy将age设置为Key并将count设置为Long,为什么会发生String到Person的转换?

这是预期的。由于字节已转换为String对象(如您指定的Serdes.String()),因此无法执行强制转换。

最后的评论:

如果仅使用print(),则不会获得类强制转换异常,因为在这种情况下,不会执行任何强制转换操作。 Java仅在需要时插入强制转换操作。

对于groupBy(),您使用value.getAge(),因此Java在此处插入强制类型转换(它知道期望的类型为Person,因为它是通过KStream<Object, Person> people = ...指定的。对于{{ 1}}仅调用print()上定义的toString(),因此不需要强制转换。

Java中的泛型类型会提示编译器,并用Object代替(或在编译期间需要时强制转换)。因此,对于Objectprint()变量可以毫无问题地指向Object,并且byte[]被成功调用。对于toString(),编译器将groupBy()强制转换为Object才能调用Person -但是,这失败了,因为实际类型是getAge()。 / p>

要使代码正常工作,您需要创建一个String类并将其指定为值Serde。