如果任何精通 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 只有两个构造函数:
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 容器文件(我不确定为什么他们允许将 avro 二进制容器文件输出到任意 OutputStream 而不提供从另一端的相应 InputStream 读取它们的方法,但这不是重点)。看来容器文件阅读器的实现需要“可搜索”
好的,所以看起来这种方法不会做我想要的。创建一个模仿 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”)作为字符串放在“模式”字段中,然后放入适合该模式的记录的 avro-binary-serialized 形式(即“GenericRecord someRecord") 在“数据”字段中。
我实际上想知道下面描述的具体细节,但我认为提供更大的背景也是值得的,这样如果有更好的高级方法我可以采取(这种方法有效但只是感觉不太理想)请告诉我。
我的问题是,假设我采用这种基于 JSON 的方法,如何将我的 Record 的 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 作为参数。因此,我尝试用 java.nio.ByteBuffer.wrap 封装 byte[],但是当数据被打印出来时,它被打印为一系列字节,而不是通过 avro 十六进制表示:
"data": {"bytes": ".....some gibberish other than the expected format...}
这似乎不对。根据 avro 文档,他们给出的示例字节对象说我需要放入一个 json 对象,其示例看起来像“\u00FF”,而我放入的内容显然不是那种格式。我现在想知道的是:
- 什么是 avro 字节格式的示例?它看起来像“\uDEADBEEFDEADBEEF...”吗?
- 如何将我的二进制 avro 数据(由 BinaryEncoder 输出到 byte[] 数组中)强制转换为可以粘贴到 GenericRecord 对象中并在 JSON 中正确打印的格式?例如,我想要一个对象数据,我可以为其调用一些 GenericRecord "someRecord.put("data", DATA);" 里面有我的 avro 序列化数据?
- 当给定文本 JSON 表示并想要重新创建由 AvroContainer 格式 JSON 表示的 GenericRecord 时,我如何将该数据读回另一端(消费者)端的字节数组?
- (重申之前的问题)有没有更好的方法可以做这一切?