21

これは、Apache Avro の Java 実装に精通した人がこれを読んでいる場合に備えて、闇の中のショットのようなものです。

私の高レベルの目標は、一連の avro データをネットワーク経由で送信する方法を確立することです (たとえば、HTTP としましょう。ただし、特定のプロトコルはこの目的にとってそれほど重要ではありません)。私のコンテキストでは、このデータを何らかの方法で書き込む必要がある HttpServletResponse があります。

最初に、avro コンテナー ファイルの仮想バージョンに相当するものとしてデータを書き込もうとしました (「応答」が HttpServletResponse 型であると仮定します)。

response.setContentType("application/octet-stream");
response.setHeader("Content-transfer-encoding", "binary");
ServletOutputStream outStream = response.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(outStream);

Schema someSchema = Schema.parse(".....some valid avro schema....");
GenericRecord someRecord = new GenericData.Record(someSchema);
someRecord.put("somefield", someData);
...

GenericDatumWriter<GenericRecord> datumWriter = new GenericDatumWriter<GenericRecord>(someSchema);
DataFileWriter<GenericRecord> fileWriter = new DataFileWriter<GenericRecord>(datumWriter);
fileWriter.create(someSchema, bos);
fileWriter.append(someRecord);
fileWriter.close();
bos.flush();

Avro が実際のファイルとは別にコンテナー ファイルを読み取る方法を実際には提供していないことが判明したことを除いて、これはすべて問題なく適切でした: DataFileReader には 2 つのコンストラクターしかありません。

public DataFileReader(File file, DatumReader<D> reader);

public DataFileReader(SeekableInput sin, DatumReader<D> reader);

ここで、 SeekableInput は avro 固有のカスタマイズされたフォームであり、その作成も最終的にファイルから読み取られます。これを考えると、何らかの方法で InputStream をファイルに強制する方法がない限り (http://stackoverflow.com/questions/578305/create-a-java-file-object-or-equivalent-using-a-byte- array-in-memory-without-a は存在しないことを示唆しており、Java ドキュメントも調べてみました)、OutputStream の反対側のリーダーがその avro コンテナー ファイルを受信した場合、このアプローチは機能しません (反対側の対応する InputStream からそれらを読み取る方法を提供せずに、avro バイナリ コンテナー ファイルを任意の OutputStream に出力することを許可した理由はわかりませんが、それは問題ではありません)。コンテナファイルリーダーの実装には「シーク可能」が必要なようです

さて、そのアプローチが私が望むことをするようには見えません。avro コンテナー ファイルを模倣する JSON 応答を作成するのはどうですか?

public static Schema WRAPPER_SCHEMA = Schema.parse(
  "{\"type\": \"record\", " +
   "\"name\": \"AvroContainer\", " +
   "\"doc\": \"a JSON avro container file\", " +
   "\"namespace\": \"org.bar.foo\", " +
   "\"fields\": [" +
     "{\"name\": \"schema\", \"type\": \"string\", \"doc\": \"schema representing the included data\"}, " +
     "{\"name\": \"data\", \"type\": \"bytes\", \"doc\": \"packet of data represented by the schema\"}]}"
  );

上記の制約を考えると、これがこれにアプローチする最良の方法であるかどうかはわかりませんが、これでうまくいくようです。スキーマ (たとえば、上記の「Schema someSchema」) を「schema」フィールド内の文字列として配置し、そのスキーマに適合するレコードの avro-binary-serialized 形式 (つまり、「GenericRecord」) に配置します。 someRecord") を "data" フィールド内に挿入します。

私は実際に以下で説明する特定の詳細について知りたかったのですが、より大きなコンテキストを提供することも価値があると思いました。しかし、最適とは思えません)お知らせください。

私の質問は、この JSON ベースのアプローチを使用すると仮定すると、レコードの avro バイナリ表現を AvroContainer スキーマの「データ」フィールドに書き込むにはどうすればよいですか? たとえば、私はここまで来ました:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
GenericDatumWriter<GenericRecord> datumWriter = new GenericDatumWriter<GenericRecord>(someSchema);
Encoder e = new BinaryEncoder(baos);
datumWriter.write(resultsRecord, e);
e.flush();

GenericRecord someRecord = new GenericData.Record(someSchema);
someRecord.put("schema", someSchema.toString());
someRecord.put("data", ByteBuffer.wrap(baos.toByteArray()));
datumWriter = new GenericDatumWriter<GenericRecord>(WRAPPER_SCHEMA);
JsonGenerator jsonGenerator = new JsonFactory().createJsonGenerator(baos, JsonEncoding.UTF8);
e = new JsonEncoder(WRAPPER_SCHEMA, jsonGenerator);
datumWriter.write(someRecord, e);
e.flush();

PrintWriter printWriter = response.getWriter(); // recall that response is the HttpServletResponse
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
printWriter.print(baos.toString("UTF-8"));

最初は ByteBuffer.wrap 句を省略しようとしましたが、その後、次の行

datumWriter.write(someRecord, e);

バイト配列を ByteBuffer にキャストできないという例外をスローしました。Encoder クラス (JsonEncoder はそのサブクラス) を呼び出して avro Bytes オブジェクトを書き込む場合、引数として ByteBuffer を指定する必要があるように見えます。したがって、byte[] を java.nio.ByteBuffer.wrap でカプセル化しようとしましたが、データが出力されると、avro 16 進表現を通過せずに、一連のバイトとして出力されました。

"data": {"bytes": ".....some gibberish other than the expected format...}

それは正しくないようです。avroのドキュメントによると、彼らが提供するbytesオブジェクトの例には、jsonオブジェクトを入れる必要があると書かれており、その例は「\u00FF」のように見えますが、そこに入れたものは明らかにその形式ではありません。私が今知りたいことは次のとおりです。

  • avro バイト形式の例は何ですか? 「\uDEADBEEFDEADBEEF...」のように見えますか?
  • バイナリ avro データ (BinaryEncoder による byte[] 配列への出力) を、GenericRecord オブジェクトに貼り付けて JSON で正しく出力できる形式に変換するにはどうすればよいですか? たとえば、GenericRecord "someRecord.put("data", DATA);" で呼び出すことができる Object DATA が必要です。私の avro シリアル化されたデータが内部にある?
  • テキスト JSON 表現が与えられ、AvroContainer 形式の JSON で表されるように GenericRecord を再作成したい場合、そのデータをもう一方の (消費者) 側でバイト配列に戻すにはどうすればよいでしょうか?
  • (前からの質問を繰り返します)これをすべて行うことができるより良い方法はありますか?
4

3 に答える 3

2

Knut が言ったように、ファイル以外のものを使用したい場合は、次のいずれかを実行できます。

  • Knutが言ったように、バイト配列にシューホーンできるものにはSeekableByteArrayInputを使用してください
  • 独自の方法で SeekablInput を実装します。たとえば、奇妙なデータベース構造から取得する場合などです。
  • または、単にファイルを使用します。なぜだめですか?

それらはあなたの答えです。

于 2012-06-20T09:23:02.767 に答える
0

これを解決した方法は、スキーマをデータとは別に出荷することでした。サーバーからスキーマを送信する接続ハンドシェイクを設定し、エンコードされたデータを送受信します。次のように外側のラッパー オブジェクトを作成する必要があります。

{'name':'Wrapper','type':'record','fields':[
  {'name':'schemaName','type':'string'},
  {'name':'records','type':{'type':'array','items':'bytes'}}
]}

最初に、レコードの配列を 1 つずつ、エンコードされたバイト配列の配列にエンコードします。1 つの配列内のすべてが同じスキーマを持つ必要があります。次に、上記のスキーマを使用してラッパー オブジェクトをエンコードします。「schemaName」を、配列のエンコードに使用したスキーマの名前に設定します。

サーバーでは、最初にラッパー オブジェクトをデコードします。ラッパー オブジェクトをデコードすると、schemaName がわかり、デコード方法がわかったオブジェクトの配列が得られます。必要に応じて使用してください。

Socket.ioのようなプロトコルや(for の)WebSocketsエンジンを使用すると、ラッパー オブジェクトを使用せずに済むことに注意してください。その場合は、チャネルごとに特定のスキーマを使用し、各メッセージを送信する前にエンコードします。接続の開始時にスキーマを共有する必要がありますが、これを使用している場合は簡単に実装できます。完了すると、クライアントとサーバーの間で任意の数の厳密に型指定された双方向ストリームが得られます。Socket.IONode.jsWebSockets

于 2016-02-08T16:17:17.533 に答える