【问题标题】:Unable to decode Custom object at Avro Consumer end in Kafka无法在 Kafka 的 Avro Consumer 端解码自定义对象
【发布时间】:2019-03-13 19:50:49
【问题描述】:

我有一个具体的类,我在字节数组中序列化以发送到 Kafka 主题。 对于序列化,我使用的是 ReflectDatumWriter 。 在发送 bytes[] 之前,我在查看了一些在线教程后将架构 ID 与架构 ID 放在前 4 个字节中。

我能够发送消息,但在 Avro 控制台使用者中使用它时,我得到的响应为:

./bin/kafka-avro-console-consumer --bootstrap-server 0:9092 --property schema.stry.url=http://0:8081 --property print.key=true --topic 测试

"1" "\u0000" 
"1" "\u0000" 
"1" "\u0000" 
"1" "\u0000" 
"1" "\u0000"
"1" "\u0000" 
"1" "\u0000" 
"1" "\u0000" 
"1" "\u0000" 
"1" "\u0000"

    MParams ddb = new MParams();
    ddb.setKey("ss");

    for (int i = 0; i < 10; i++) {
        ProducerRecord record = new ProducerRecord<String, byte[]>("Test", "1", build(1, Producer.serialize(ddb)));
        Future resp = kafkaFullAckProducer.send(record);

        System.out.println("Success" + resp.get());
    }
}

public static <T> byte[] serialize(T data) {
    Schema schema = null;
    if (data == null) {
        throw new RuntimeException("Data cannot be null in AvroByteSerializer");
    }
    try {
        schema = ReflectData.get().getSchema(data.getClass());
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        DatumWriter<T> writer = new ReflectDatumWriter<T>(schema);
        writer.write(data, new EncoderFactory().directBinaryEncoder(out, null));
        byte[] bytes = out.toByteArray();
        return bytes;
    } catch (java.io.IOException e) {
        throw new RuntimeException("Error serializing Avro message", e);
    }
}

public static byte[] build(Integer schemaId, byte[] data) {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    out.write(0);
    try {
        out.write(ByteBuffer.allocate(4).putInt(schemaId).array());
        out.write(data);
        byte[] bytes = out.toByteArray();
        out.close();
        return bytes;
    } catch (IOException e) {
        throw new RuntimeException("Exception in avro record builder , msg :" + e.getMessage());
    }



@Data
public class MParams extends MetricParams{

    // POJO version

    @Nullable
    private String key;


}

@JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY, property = "@c")
@Union(value= {MParams.class})
public abstract class MetricParams {

}

工作序列化器 sn-p

public byte[] serialize(String topic, T record) {
        Schema schema;
        int id;
        try {
            schema = ReflectData.get().getSchema(record.getClass());
            id = client.register(topic + "-value", schema);
        } catch (IOException | RestClientException e) {
            throw new RuntimeException(e);
        }
        return serializeImpl(id, schema, record);
    }

    protected byte[] serializeImpl(int id, Schema schema, T object) throws SerializationException {
        if (object == null) {
            return null;
        }
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            out.write(0x0);
            out.write(ByteBuffer.allocate(4).putInt(id).array());

            BinaryEncoder encoder = EncoderFactory.get().directBinaryEncoder(out, null);
            DatumWriter<T> writer = new ReflectDatumWriter<T>(schema);
            writer.write(object, encoder);
            encoder.flush();
            byte[] bytes = out.toByteArray();
            out.close();
            return bytes;
        } catch (IOException | RuntimeException e) {
            throw new SerializationException("Error serializing Avro message", e);
        }
    }

反序列化器:

protected T deserialize(Schema schema, byte[] payload) throws SerializationException {
        // Even if the caller requests schema & version, if the payload is null
        // cannot include it. The caller must handle
        // this case.
        if (payload == null) {
            return null;
        }

        int id = -1;
        try {
            ByteBuffer buffer = getByteBuffer(payload);
            id = buffer.getInt();
            int length = buffer.limit() - 1 - 4;

            int start = buffer.position() + buffer.arrayOffset();
            DatumReader<T> reader = new ReflectDatumReader<T>(schema);
            T res = reader.read(null, new DecoderFactory().binaryDecoder(buffer.array(), start, length, null));
            return res;
        } catch (IOException | RuntimeException e) {
            throw new SerializationException("Error deserializing Avro message for id " + id, e);
        }
    }

    private ByteBuffer getByteBuffer(byte[] payload) {
        ByteBuffer buffer = ByteBuffer.wrap(payload);
        if (buffer.get() != 0x0) {
            throw new SerializationException("Unknown magic byte!");
        }
        return buffer;
    }

【问题讨论】:

  • “查看一些在线教程” -> 可以附上链接吗?
  • 关于如何发送 Avro 序列化消息的详细信息在这里:docs.confluent.io/current/schema-registry/docs/…
  • @RobinMoffatt 我尝试查找在线链接,但无法回忆
  • @RobinMoffatt 能否请您指向包含有关将 Avro 序列化程序用于自定义 POJO 的文本的文档
  • 已经链接到的架构注册表文档应该包含它,但这是假设您实际使用的是 Confluent 架构注册表,而不是普通的 Avro 编码字节

标签: java apache-kafka avro confluent-schema-registry


【解决方案1】:

为了进行序列化,我使用的是 ReflectDatumWriter 。在发送字节 [] 之前,我将架构 ID 与架构 ID 放在前 4 个字节中

不清楚您为什么要尝试bypass the KafkaAvroSerializer class's default behavior。 (在您的情况下,请从该示例中删除 Schema.Parser,并使用您的 Reflect 记录类型而不是 GenericRecord

你可以把你的具体类作为生产者的第二种类型,只要它实现了基础 Avro 类,它就应该被正确序列化(意味着正确计算 ID,而不是你创建的某个数字,并转换为字节),注册到注册表,然后发送到Kafka

最重要的是,注册表中的模式 ID 不一定是 1,这样一来,控制台使用者可能会尝试错误地反序列化您的消息,从而导致错误的输出

换句话说,试试

ProducerRecord<String, MParams> record = new ProducerRecord<>(...)

【讨论】:

  • 我最终删除了带有模式 ID 的字节转换,这不是正确的方法。我按照您建议的方式使用 ProducerRecord 初始化,使用 DatumWriter 使用通用自定义反序列化器获取值
  • Avro 已经可以在单个对象类的上下文中随着时间的推移而发展。模式注册表中的主题存储多个版本,并且默认情况下在单个主题/主题的所有版本中强制执行向后兼容性
  • 仍然会为每个对象类型应用版本。随意阅读这篇博文 - confluent.io/blog/put-several-event-types-kafka-topic
  • 如果你真的需要泛型,你可以使用ProducerRecord&lt;String, GenericRecord&gt;,同样在消费者方面。从那里,您可以检查有关 Avro 数据的命名空间和类型的 if 语句
  • RefectDataSpecificRecord的具体实现,扩展了GenericRecordavro.apache.org/docs/1.8.1/api/java/org/apache/avro/reflect/…
猜你喜欢
  • 1970-01-01
  • 2016-07-01
  • 2016-01-16
  • 2021-12-28
  • 2019-04-04
  • 1970-01-01
  • 2023-03-09
  • 2017-12-14
  • 2014-08-26
相关资源
最近更新 更多