Avro架构演变是否需要访问新旧架构?

bil*_*ils 11 protocol-buffers avro

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

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

schema1:

{"type": "record",
 "name": "User",
 "fields": [
  {"name": "firstName", "type": "string"}
 ]}
Run Code Online (Sandbox Code Playgroud)

SCHEMA2:

{"type": "record",
 "name": "User",
 "fields": [
  {"name": "firstName", "type": "string"},
  {"name": "lastName", "type": "string", "default": ""}
 ]}
Run Code Online (Sandbox Code Playgroud)

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

// 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);
Run Code Online (Sandbox Code Playgroud)

导致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();
Run Code Online (Sandbox Code Playgroud)

然后将架构更新到版本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);
Run Code Online (Sandbox Code Playgroud)

但它也会导致EOFException.

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

格式:

option java_outer_classname = "UserProto";
message User {
    optional string first_name = 1;
}
Run Code Online (Sandbox Code Playgroud)

连载:

UserProto.User.Builder user = UserProto.User.newBuilder();
user.setFirstName("Jack");
FileOutputStream out = new FileOutputStream("user.data");
user.build().writeTo(out);
Run Code Online (Sandbox Code Playgroud)

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

FileInputStream in = new FileInputStream("user.data");
UserProto.User user = UserProto.User.parseFrom(in);
Run Code Online (Sandbox Code Playgroud)

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

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

Mar*_*ann 32

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

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

Avro采用了不同的方法:没有标签号,而二进制布局完全取决于执行编码的程序 - 这是编写器的架构.(A记录的字段被简单地存储了一个又一个的二进制编码,没有任何标记或分离,且订单由作家的模式决定的.)这使得编码更加紧凑,并且无需手动维护标签为您节省架构.但它确实意味着,对于阅读,您必须知道数据写入的确切模式,否则您将无法理解它.

如果知道编写器的模式对于解码Avro至关重要,那么读者的模式就是它的一层好处.如果你在一个需要读取的Avro数据的程序做的代码生成,你可以做代码生成过读者的架构,从而节省您不必每一个作家的模式变化(假设一次的方式,可以改变使其再生得到解决).但它不会让你不必知道作者的架构.

优点缺点

Avro的方法在一个环境中是很好的,在这种环境中你有许多已知具有完全相同的模式版本的记录,因为你可以只在文件开头的元数据中包含模式,并且知道下一百万条记录都可以使用该架构解码.这在MapReduce上下文中发生了很多,这解释了为什么Avro从Hadoop项目中走出来.

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