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 を format に追加し、UserProto を再生成し、逆シリアル化します。

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

予想どおり、user.getLastName()空の文字列です。

このようなことは Avro で実行できますか?

4

3 に答える 3

38

Avro と Protocol Buffers には、バージョン管理を処理するためのさまざまなアプローチがあり、どちらのアプローチが優れているかは、ユース ケースによって異なります。

Protocol Buffers では、すべてのフィールドに数値を明示的にタグ付けする必要があり、それらの数値はフィールドの値とともにバイナリ表現で保存されます。したがって、後続のスキーマ バージョンで数値の意味を変更しない限り、別のスキーマ バージョンでエンコードされたレコードをデコードできます。デコーダが認識できないタグ番号を検出した場合、単純にスキップできます。

Avro は別のアプローチを採用しています。タグ番号はありません。代わりに、バイナリ レイアウトはエンコードを行うプログラムによって完全に決定されます。これがライターのスキーマです。(レコードのフィールドは、タグ付けやセパレーターを使用せずにバイナリ エンコーディングで単純に次々に格納され、順序はライターのスキーマによって決定されます。) これにより、エンコーディングがよりコンパクトになり、手動でタグを維持する必要がなくなります。スキーマ。ただし、データを読み取るためには、データが書き込まれた正確なスキーマを知っている必要があります。そうしないと、意味を理解できません。

ライターのスキーマを知ることが Avro のデコードに不可欠である場合、リーダーのスキーマはその上にあるナイス層です。Avro データを読み取る必要があるプログラムでコード生成を行っている場合、リーダーのスキーマからコード生成を行うことができます。これにより、ライターのスキーマが変更されるたびに再生成する必要がなくなります (解決されます)。しかし、ライターのスキーマを知らなくても済むわけではありません。

長所短所

Avro のアプローチは、まったく同じスキーマ バージョンを持つことがわかっているレコードが多数ある環境に適しています。ファイルの先頭にあるメタデータにスキーマを含めるだけで、次の百万レコードがすべてそのスキーマを使用してデコードされます。これは、MapReduce コンテキストで頻繁に発生します。これが、Avro が Hadoop プロジェクトから派生した理由を説明しています。

Protocol Buffers のアプローチは、個々のオブジェクトがネットワーク経由で (要求パラメーターまたは戻り値として) 送信される RPC にはおそらく適しています。ここで Avro を使用する場合、異なるスキーマ バージョンを持つ異なるクライアントと異なるサーバーが存在する可能性があるため、バイナリ エンコードされたすべての BLOB に、使用している Avro スキーマ バージョンでタグを付け、スキーマのレジストリを維持する必要があります。その時点で、Protocol Buffers の組み込みのタグ付けを使用した方がよいでしょう。

于 2012-10-17T20:37:26.907 に答える
0

私はこの問題を回避しようとしました。ここに置いています:

また、Avro のリフェクション API を使用して、1 つのスキーマをもう 1 つのスキーマに別の列を追加するだけの 2 つのスキーマを使用してみました。次のスキーマがあります。

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

これにより、ここで問題が解決されます。ExtendedEmployeeただし、オブジェクトを読み取るスキーマも提供できる API があるかどうかを知りたいですEmployee

于 2012-09-21T07:12:29.067 に答える