Scala 区分AVRO联合类型

Scala 区分AVRO联合类型,scala,apache-kafka,avro,confluent-schema-registry,Scala,Apache Kafka,Avro,Confluent Schema Registry,我正在使用“自动”反序列化器使用来自Kafka的Avro序列化消息,如: props.put( ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "io.confluent.kafka.serializers.KafkaAvroDeserializer" ); props.put("schema.registry.url", "https://example.com"); 这是一个很好的工作方式,并且是在 我面临的问题是,我实际上只

我正在使用“自动”反序列化器使用来自Kafka的Avro序列化消息,如:

props.put(
    ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
    "io.confluent.kafka.serializers.KafkaAvroDeserializer"
);
props.put("schema.registry.url", "https://example.com");
这是一个很好的工作方式,并且是在

我面临的问题是,我实际上只是想转发这些消息,但要进行路由,我需要一些来自内部的元数据。一些技术限制意味着我无法在生成的类文件中编译以使用
kafkaavroodeserializerconfig.SPECIFIC\u AVRO\u READER\u CONFIG=>true
,因此我使用的是一个常规解码器,而不必绑定到Kafka中,特别是将字节作为
数组[Byte]读取
并将它们传递给手动构造的反序列化程序:

var maxSchemasToCache = 1000;
var schemaRegistryURL = "https://example.com/"
var specificDeserializerProps = Map(
  "schema.registry.url" 
      -> schemaRegistryURL,
  KafkaAvroDeserializerConfig.SPECIFIC_AVRO_READER_CONFIG 
      -> "false"
);
var client = new CachedSchemaRegistryClient(
                     schemaRegistryURL, 
                     maxSchemasToCache
                 );
var deserializer = new KafkaAvroDeserializer(
                         client,
                         specificDeserializerProps.asJava
                   );
消息是一种“容器”类型,真正有趣的第一部分是a
union{a,B,C}msg
记录字段中的大约25种类型:

record Event {
    timestamp_ms created_at;
    union {
        Online,
        Offline,
        Available,
        Unavailable,
        ...
        ...Failed,
        ...Updated
    } msg;
}
因此,我成功地将
数组[Byte]
读入
记录
,并将其输入反序列化程序,如下所示:

var genericRecord = deserializer.deserialize(topic, consumerRecord.value())
                       .asInstanceOf[GenericRecord];
var schema = genericRecord.getSchema();
var msgSchema = schema.getField("msg").schema();
        List<Schema> unionTypes = new ArrayList<>();
        DatumReader<GenericRecord> datumReader = new CustomReader<GenericRecord>(schema, unionTypes);
        DataFileReader<GenericRecord> dataFileReader = new DataFileReader<GenericRecord>(eventFile, datumReader);
        GenericRecord event = null;

        while (dataFileReader.hasNext()) {
            event = dataFileReader.next(event);
        }

        System.out.println(unionTypes);
但问题是,我无法通过union识别、区分或“解析”
msg
字段的“类型”:

System.out.printf(
    "msg.schema = %s msg.schema.getType = %s\n", 
    msgSchema.getFullName(),  
    msgSchema.getType().name());
=> msg.schema = union msg.schema.getType = union
在这种情况下,如何区分类型?汇合注册中心知道,这些东西有名称,它们有“类型”,即使我将它们视为
genericords


我在这里的目标是知道
record.msg
是“类型”
Online | Offline | Available
,而不仅仅是知道它是一个
union

,在研究了AVRO Java库的实现之后,可以肯定地说,考虑到当前的API,这是不可能的。我发现了以下在解析时提取类型的方法,使用自定义的
GenericDatumReader
子类,但是在我在生产代码中使用类似的东西之前,它需要很多改进:d

下面是子类:

import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.io.ResolvingDecoder;

import java.io.IOException;
import java.util.List;

public class CustomReader<D> extends GenericDatumReader<D> {
    private final GenericData data;
    private Schema actual;
    private Schema expected;

    private ResolvingDecoder creatorResolver = null;
    private final Thread creator;
    private List<Schema> unionTypes;

    // vvv This is the constructor I've modified, added a list of types
    public CustomReader(Schema schema, List<Schema> unionTypes) {
        this(schema, schema, GenericData.get());
        this.unionTypes = unionTypes;
    }

    public CustomReader(Schema writer, Schema reader, GenericData data) {
        this(data);
        this.actual = writer;
        this.expected = reader;
    }

    protected CustomReader(GenericData data) {
        this.data = data;
        this.creator = Thread.currentThread();
    }

    protected Object readWithoutConversion(Object old, Schema expected, ResolvingDecoder in) throws IOException {
        switch (expected.getType()) {
            case RECORD:
                return super.readRecord(old, expected, in);
            case ENUM:
                return super.readEnum(expected, in);
            case ARRAY:
                return super.readArray(old, expected, in);
            case MAP:
                return super.readMap(old, expected, in);
            case UNION:
                // vvv The magic happens here
                Schema type = expected.getTypes().get(in.readIndex());
                unionTypes.add(type);
                return super.read(old, type, in);
            case FIXED:
                return super.readFixed(old, expected, in);
            case STRING:
                return super.readString(old, expected, in);
            case BYTES:
                return super.readBytes(old, expected, in);
            case INT:
                return super.readInt(old, expected, in);
            case LONG:
                return in.readLong();
            case FLOAT:
                return in.readFloat();
            case DOUBLE:
                return in.readDouble();
            case BOOLEAN:
                return in.readBoolean();
            case NULL:
                in.readNull();
                return null;
            default:
                return super.readWithoutConversion(old, expected, in);
        }
    }
}
import org.apache.avro.Schema;
导入org.apache.avro.generic.GenericData;
导入org.apache.avro.generic.GenericDatumReader;
导入org.apache.avro.io.ResolvingDecoder;
导入java.io.IOException;
导入java.util.List;
公共类CustomReader扩展了GenericDatumReader{
私人最终通用数据;
私有模式;
期望私有模式;
私有解析解码器creatorResolver=null;
私有最终线程创建者;
私有列表类型;
//这是我修改过的构造函数,添加了一个类型列表
公共CustomReader(架构、列表类型){
这(schema,schema,GenericData.get());
this.unionTypes=unionTypes;
}
公共CustomReader(模式编写器、模式读取器、GenericData数据){
这(数据);
this.actual=作者;
这个。预期的=读者;
}
受保护的CustomReader(GenericData数据){
这个数据=数据;
this.creator=Thread.currentThread();
}
受保护的对象readWithoutConversion(对象旧,应为架构,ResolvingDecoder in)引发IOException{
开关(应为.getType()){
病例记录:
返回super.readRecord(旧的、预期的、in);
案例列举:
返回super.readEnum(应为,in);
案例阵列:
返回super.readArray(旧的,预期的,in);
案例图:
返回super.readMap(旧的、预期的、in);
案例联盟:
//魔术就在这里发生
Schema type=expected.getTypes().get(in.readIndex());
unionTypes.add(type);
返回super.read(old,type,in);
固定案例:
返回super.readFixed(旧的、预期的、in);
大小写字符串:
返回super.readString(旧的、预期的、in);
大小写字节:
返回super.readBytes(旧的、预期的、以英寸为单位);
案例INT:
返回super.readInt(旧的、预期的、in);
案例长度:
在.readLong()中返回;
案例浮动:
在.readFloat()中返回;
双格:
在.readDouble()中返回;
大小写布尔值:
在.readBoolean()中返回;
大小写为空:
in.readNull();
返回null;
违约:
返回super.readwithout转换(旧的、预期的、in);
}
}
}
我已经为代码中有趣的部分添加了注释,因为它主要是样板文件

然后您可以像这样使用此自定义读取器:

var genericRecord = deserializer.deserialize(topic, consumerRecord.value())
                       .asInstanceOf[GenericRecord];
var schema = genericRecord.getSchema();
var msgSchema = schema.getField("msg").schema();
        List<Schema> unionTypes = new ArrayList<>();
        DatumReader<GenericRecord> datumReader = new CustomReader<GenericRecord>(schema, unionTypes);
        DataFileReader<GenericRecord> dataFileReader = new DataFileReader<GenericRecord>(eventFile, datumReader);
        GenericRecord event = null;

        while (dataFileReader.hasNext()) {
            event = dataFileReader.next(event);
        }

        System.out.println(unionTypes);
List unionTypes=new ArrayList();
DatumReader DatumReader=新的CustomReader(模式、类型);
DataFileReader DataFileReader=新的DataFileReader(eventFile,datumReader);
GenericRecord事件=null;
while(dataFileReader.hasNext()){
event=dataFileReader.next(事件);
}
System.out.println(UnionType);
这将为每个分析的
union
打印该
union
的类型。请注意,您必须根据记录中的联合数等,找出该列表中您感兴趣的元素


不太好tbh:D

经过大量挖掘,我终于想出了一个一次性解决方案:

val records: ConsumerRecords[String, Array[Byte]] = consumer.poll(100);
for (consumerRecord <- asScalaIterator(records.iterator)) {
  var genericRecord = deserializer.deserialize(topic, consumerRecord.value()).asInstanceOf[GenericRecord];
  var msgSchema = genericRecord.get("msg").asInstanceOf[GenericRecord].getSchema();
  System.out.printf("%s \n", msgSchema.getFullName());
val记录:ConsumerRecords[String,Array[Byte]]=consumer.poll(100);

对于(consumerRecord要检索字段值的模式,可以使用

new GenericData().induce(genericRecord.get("msg"))

因此,为了清楚起见,您对接收到的特定类型的消息感兴趣,是吗?您希望
type=Online
而不是
type=union
?不清楚“自动反序列化器”是什么意思……您可以定义自己的接受
数组[Byte]
并反序列化为GenericRecord,如您所示。或者您可以使用Kafkaavroderializer来获取GenericRecord,默认情况下,因为它已经知道如何处理它。是的,确切地说,将更新问题以澄清。即使您确实编译为Java,我很好奇您会怎么做