14

如果我使用模式版本 1 序列化对象,然后将模式更新到版本 2(例如通过添加字段) - 以后反序列化对象时是否需要使用模式版本 1?理想情况下,我只想使用模式版本 2,并让反序列化对象具有在对象最初序列化后添加到模式中的字段的默认值。

也许一些代码会更好地解释......

架构1:

{"type": "record",
 "name": "User",
 "fields": [
  {"name": "firstName", "type": "string"}
 ]}

架构2:

{"type": "record",
 "name": "User",
 "fields": [
  {"name": "firstName", "type": "string"},
  {"name": "lastName", "type": "string", "default": ""}
 ]}

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

// 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);

导致 EOFException。jsonEncoder在 AvroTypeException 中使用结果。

我知道如果我将 schema1 和 schema2 都传递给GenericDatumReader构造函数,它将起作用,但我不想保留所有先前模式的存储库,并且还以某种方式跟踪用于序列化每个特定对象的模式。

我还尝试了代码生成方法,首先使用从 schema1 生成的 User 类序列化到文件:

User user = new User();
user.setFirstName("Jack");
DatumWriter<User> writer = new SpecificDatumWriter<User>(User.class);
FileOutputStream out = new FileOutputStream("user.avro");
Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
writer.write(user, encoder);
encoder.flush();
out.close();

然后将模式更新到版本 2,重新生成 User 类,并尝试读取文件:

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);

但它也会导致 EOFException。

只是为了比较,我正在尝试做的似乎与protobufs一起工作......

格式:

option java_outer_classname = "UserProto";
message User {
    optional string first_name = 1;
}

连载:

UserProto.User.Builder user = UserProto.User.newBuilder();
user.setFirstName("Jack");
FileOutputStream out = new FileOutputStream("user.data");
user.build().writeTo(out);

添加可选的 last_name 以格式化、重新生成 UserProto 和反序列化:

FileInputStream in = new FileInputStream("user.data");
UserProto.User user = UserProto.User.parseFrom(in);

正如预期的那样,user.getLastName()是空字符串。

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

4

3 回答 3

38

Avro 和 Protocol Buffers 有不同的处理版本控制的方法,哪种方法更好取决于您的用例。

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

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

如果知道作者的模式对于解码 Avro 至关重要,那么读者的模式就是在它之上的一层美好。如果您在需要读取 Avro 数据的程序中进行代码生成,您可以从阅读器的模式中进行代码生成,这样您就不必在每次编写器的模式更改时都重新生成它(假设它的更改方式可以得到解决)。但这并不能使您不必了解作者的架构。

优点缺点

Avro 的方法在您拥有大量已知具有完全相同架构版本的记录的环境中非常有用,因为您可以在文件开头的元数据中包含架构,并且知道接下来的一百万条记录都可以使用该模式进行解码。这在 MapReduce 环境中经常发生,这就解释了为什么 Avro 会脱离 Hadoop 项目。

Protocol Buffers 的方法可能更适合 RPC,其中单个对象通过网络发送(作为请求参数或返回值)。如果您在这里使用 Avro,您可能有不同的客户端和不同的服务器,它们都具有不同的架构版本,因此您必须使用它正在使用的 Avro 架构版本标记每个二进制编码的 blob,并维护架构注册表。那时你可能已经使用了 Protocol Buffers 的内置标记。

于 2012-10-17T20:37:26.907 回答
2

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

于 2013-05-04T00:24:26.260 回答
0

我试图规避这个问题。我把它放在这里:

我也尝试过使用两个模式,一个模式只是使用 Avro 的反射 API 将另一列添加到另一个模式。我有以下架构:

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

于 2012-09-21T07:12:29.067 回答