Protocol buffers Avro模式演化是否需要访问旧模式和新模式?

Protocol buffers Avro模式演化是否需要访问旧模式和新模式?,protocol-buffers,avro,Protocol Buffers,Avro,如果我使用模式版本1序列化一个对象,然后将模式更新为版本2(比如添加一个字段),那么在以后反序列化对象时是否需要使用模式版本1?理想情况下,我只希望使用模式版本2,并使反序列化对象具有在对象最初序列化后添加到模式的字段的默认值 也许一些代码可以更好地解释 方案1: 方案2: 使用通用非代码生成方法: // serialize ByteArrayOutputStream out = new ByteArrayOutputStream(); Encoder encoder = EncoderFact

如果我使用模式版本1序列化一个对象,然后将模式更新为版本2(比如添加一个字段),那么在以后反序列化对象时是否需要使用模式版本1?理想情况下,我只希望使用模式版本2,并使反序列化对象具有在对象最初序列化后添加到模式的字段的默认值

也许一些代码可以更好地解释

方案1:

方案2:

使用通用非代码生成方法:

// serialize
ByteArrayOutputStream out = new ByteArrayOutputStream();
Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
GenericDatumWriter writer = new GenericDatumWriter(schema1);
GenericRecord datum = new GenericData.Record(schema1);
datum.put("firstName", "Jack");
writer.write(datum, encoder);
encoder.flush();
out.close();
byte[] bytes = out.toByteArray();

// deserialize
// I would like to not have any reference to schema1 below here
DatumReader<GenericRecord> reader = new GenericDatumReader<GenericRecord>(schema2);
Decoder decoder = DecoderFactory.get().binaryDecoder(bytes, null);
GenericRecord result = reader.read(null, decoder);
然后将架构更新为版本2,重新生成用户类,并尝试读取文件:

DatumReader<User> reader = new SpecificDatumReader<User>(User.class);
FileInputStream in = new FileInputStream("user.avro");
Decoder decoder = DecoderFactory.get().binaryDecoder(in, null);
User user = reader.read(null, decoder);
序列化:

UserProto.User.Builder user = UserProto.User.newBuilder();
user.setFirstName("Jack");
FileOutputStream out = new FileOutputStream("user.data");
user.build().writeTo(out);
FileInputStream in = new FileInputStream("user.data");
UserProto.User user = UserProto.User.parseFrom(in);
将可选姓氏添加到格式化、regen UserProto和反序列化:

UserProto.User.Builder user = UserProto.User.newBuilder();
user.setFirstName("Jack");
FileOutputStream out = new FileOutputStream("user.data");
user.build().writeTo(out);
FileInputStream in = new FileInputStream("user.data");
UserProto.User user = UserProto.User.parseFrom(in);
正如所料,
user.getLastName()
是空字符串


像这样的事情可以用Avro来完成吗?

我已经试着绕过这个问题。我把它放在这里:

我还尝试使用两个模式,一个模式只是使用Avro的refectionapi将另一列添加到另一个模式中。我有以下模式:

Employee (having name, age, ssn)
ExtendedEmployee (extending Employee and having gender column)
我假设前面有
Employee
对象的文件现在也有
ExtendedEmployee
对象,我试图将该文件读取为:

    RecordHandler rh = new RecordHandler();
    if (rh.readObject(employeeSchema, dbLocation) instanceof Employee) {
        Employee e = (Employee) rh.readObject(employeeSchema, dbLocation);
        System.out.print(e.toString());
    } else if (rh.readObject(schema, dbLocation) instanceof ExtendedEmployee) {
        ExtendedEmployee e = (ExtendedEmployee) rh.readObject(schema, dbLocation);
        System.out.print(e.toString());
    }

这就解决了这里的问题。但是,我想知道是否有一个API,我们可以在其中提供
ExtendedEmployee
模式来读取
Employee
的对象。

Avro和协议缓冲区有不同的方法来处理版本控制,哪种方法更好取决于您的用例

在协议缓冲区中,必须用数字显式标记每个字段,这些数字与字段的值一起存储在二进制表示中。因此,只要您在后续模式版本中从未更改数字的含义,您仍然可以解码在不同模式版本中编码的记录。如果解码器看到一个它无法识别的标签号,它可以简单地跳过它

Avro采用了一种不同的方法:没有标记号,而是二进制布局完全由进行编码的程序决定——这是编写者的模式。(记录的字段只是以二进制编码一个接一个地存储,没有任何标记或分隔符,顺序由写入者的模式决定。)这使得编码更加紧凑,并且省去了在模式中手动维护标记的麻烦。但这确实意味着,对于阅读来说,你必须知道写入数据的确切模式,否则你将无法理解它

如果了解作者的图式对于解读Avro是必不可少的,那么读者的图式就是其上的一层美好。如果您在需要读取Avro数据的程序中执行代码生成,您可以在读取器的模式下执行代码生成,这样您就不必在每次编写器的模式更改时重新生成代码(假设它以可以解决的方式更改)。但这并不能让你不必知道作者的模式

利弊 Avro的方法在一个环境中是很好的,在这个环境中,您有许多已知具有完全相同的模式版本的记录,因为您可以在文件开头的元数据中包含该模式,并且知道接下来的一百万条记录都可以使用该模式进行解码。这在MapReduce环境中经常发生,这解释了为什么Avro来自Hadoop项目


协议缓冲区的方法可能更适合RPC,其中单个对象通过网络发送(作为请求参数或返回值)。如果在这里使用Avro,可能会有不同的客户机和服务器,它们都具有不同的模式版本,因此您必须使用所使用的Avro模式版本标记每个二进制编码的blob,并维护模式注册表。此时,您还可以使用协议缓冲区的内置标记。

要执行您试图执行的操作,您需要通过允许空值使last_name字段成为可选字段。姓氏的类型应该是[“null”,“string”]而不是“string”

Martin感谢您的精彩描述。我读了很多关于在反序列化时需要writers模式以及在avro解析器执行一些投影的情况下拥有readers模式的可能性的文章。有什么例子吗?或者有人能给我指出正确的方向吗?或者是这个ReflectDatumReader实现了这一点?@MartinKleppmann这是怎么回事?(也许5年后我在你的帖子上问了一个问题。那么你列出的优点和缺点仍然有效?)
Employee (having name, age, ssn)
ExtendedEmployee (extending Employee and having gender column)
    RecordHandler rh = new RecordHandler();
    if (rh.readObject(employeeSchema, dbLocation) instanceof Employee) {
        Employee e = (Employee) rh.readObject(employeeSchema, dbLocation);
        System.out.print(e.toString());
    } else if (rh.readObject(schema, dbLocation) instanceof ExtendedEmployee) {
        ExtendedEmployee e = (ExtendedEmployee) rh.readObject(schema, dbLocation);
        System.out.print(e.toString());
    }