Java Avro读写功能
BigDataToAI 人气:0Apache Avro是一个数据序列化系统。具有如下基本特性:
丰富的数据结构。一种紧凑、快速的二进制数据格式。一个容器文件,用于存储持久数据。远程过程调用 (RPC)。与动态语言的简单集成。 代码生成不需要读取或写入数据文件,也不需要使用或实现 RPC 协议。 代码生成作为一种可选的优化,只值得为静态类型语言实现。
模式(schema)
Avro 依赖于模式。读取 Avro 数据时,写入时使用的模式始终存在。 这允许在没有每个值开销的情况下写入每个数据,从而使序列化既快速又小。 这也便于使用动态脚本语言,因为数据及其模式是完全自描述的。
当 Avro 数据存储在文件中时,它的模式也随之存储,以便以后任何程序都可以处理文件。 如果读取数据的程序需要不同的模式,这很容易解决,因为两种模式都存在。
在 RPC 中使用 Avro 时,客户端和服务器在连接握手中交换模式。 (这可以优化,使得对于大多数调用,实际上不传输模式。)由于客户端和服务器都具有对方的完整模式,因此可以轻松解决相同命名字段之间的对应关系,如缺少字段,额外字段等 .
Avro 模式是用 JSON 定义的。 这有助于在已经具有 JSON 库的语言中实现。
与其他系统的比较
Avro 提供类似于 Thrift、Protocol Buffers 等系统的功能。Avro 在以下基本方面与这些系统不同。
动态类型:Avro 不需要生成代码。 数据总是伴随着一个模式,该模式允许在没有代码生成、静态数据类型等的情况下完全处理该数据。这有助于构建通用数据处理系统和语言。未标记数据:由于在读取数据时存在模式,因此需要用数据编码的类型信息要少得多,从而导致更小的序列化大小。没有手动分配的字段 ID:当架构更改时,处理数据时始终存在旧架构和新架构,因此可以使用字段名称象征性地解决差异。
Java客户端实现
以下代码基于maven项目实现Java读写Avro
首先在maven项目中添加下述依赖:
<dependency> <groupId>org.apache.avro</groupId> <artifactId>avro</artifactId> <version>1.11.0</version> </dependency>
以及下述插件
<plugin> <groupId>org.apache.avro</groupId> <artifactId>avro-maven-plugin</artifactId> <version>1.11.0</version> <executions> <execution> <phase>generate-sources</phase> <goals> <goal>schema</goal> </goals> <configuration> <sourceDirectory>${project.basedir}/src/main/avro/</sourceDirectory> <outputDirectory>${project.basedir}/src/main/java/</outputDirectory> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin>
定义一个schema
Avro 模式是使用 JSON 定义的。 模式由基本类型(null、boolean、int、long、float、double、bytes 和 string)和复杂类型(record、enum、array、map、union 和 fixed)组成。 您可以从规范中了解有关 Avro 模式和类型的更多信息,但现在让我们从一个简单的模式示例 user.avsc 开始:
{ "namespace": "com.bigdatatoai.avro.generate", "type": "record", "name": "User", "fields": [ { "name": "name", "type": "string" }, { "name": "favorite_number", "type": [ "int", "null" ] }, { "name": "favorite_color", "type": [ "string", "null" ] } ] }
此模式定义了代表假设用户的记录。 (请注意,模式文件只能包含单个模式定义。)记录定义至少必须包括其类型(“type”:“record”)、名称(“name”:“User”)和字段, 在本例中为 name、favorite_number 和 favorite_color。 我们还定义了一个命名空间(“namespace”:“com.bigdatatoai.avro.generate”),它与 name 属性一起定义了模式的“全名”(在本例中为 com.bigdatatoai.avro.User)。
字段是通过对象数组定义的,每个对象都定义了一个名称和类型(其他属性是可选的,有关详细信息,请参阅记录规范)。 字段的类型属性是另一个模式对象,它可以是基本类型或复杂类型。 例如,我们的 User 模式的 name 字段是原始类型字符串,而 favorite_number 和 favorite_color 字段都是联合,由 JSON 数组表示。 unions 是一种复杂类型,可以是数组中列出的任何类型; 例如, favorite_number 可以是 int 或 null,本质上使它成为一个可选字段。
使用Java代码生成插件生成的User类进行序列化和反序列化
已知我们在maven项目中添加了avro插件,那么我们便可以使用compile命令生成User类。
下述以IDEA为例
现在我们已经完成了代码生成,让我们创建一些用户,将它们序列化为磁盘上的数据文件,然后读回文件并反序列化用户对象。
创建User用户
// 新建user三种方式 // 方式1 User user1 = new User(); user1.setName("Alyssa"); user1.setFavoriteNumber(256); // 方式2 User user2 = new User("Ben", 7, "red"); // 方式3 User user3 = User.newBuilder() .setName("Charlie") .setFavoriteColor("blue") .setFavoriteNumber(null) .build();
如本例所示,可以通过直接调用构造函数或使用构建器来创建 Avro 对象。 与构造函数不同,生成器将自动设置模式中指定的任何默认值。 此外,构建器会按设置验证数据,而直接构造的对象在对象被序列化之前不会导致错误。 但是,直接使用构造函数通常会提供更好的性能,因为构造函数会在写入数据结构之前创建数据结构的副本。
请注意,我们没有设置 user1 最喜欢的颜色。 由于该记录的类型为 [“string”, “null”],我们可以将其设置为字符串或将其保留为 null; 它本质上是可选的。 同样,我们将 user3 最喜欢的数字设置为 null(使用构建器需要设置所有字段,即使它们为 null)。
将上述新建的User用户序列化并保存到磁盘
// 持久化数据到磁盘 DatumWriter<User> userDatumWriter = new SpecificDatumWriter<User>(User.class); DataFileWriter<User> dataFileWriter = new DataFileWriter<User>(userDatumWriter); dataFileWriter.create(user1.getSchema(), new File("users.avro")); dataFileWriter.append(user1); dataFileWriter.append(user2); dataFileWriter.append(user3); dataFileWriter.close();
从磁盘读取users.avro并反序列化输出
// 从User反序列化数据 DatumReader<User> userDatumReader = new SpecificDatumReader<User>(User.class); DataFileReader<User> dataFileReader = new DataFileReader<User>(new File("users.avro"), userDatumReader); User user = null; while (dataFileReader.hasNext()) { user = dataFileReader.next(user); System.out.println(user); }
在不生成User类的情况下直接进行序列化和反序列化操作
Avro 中的数据始终与其对应的模式一起存储,这意味着无论我们是否提前知道模式,我们都可以随时读取序列化项目。 这允许我们在不生成代码的情况下执行序列化和反序列化。
让我们回顾与上一节相同的示例,但不使用代码生成:我们将创建一些用户,将它们序列化为磁盘上的数据文件,然后读回文件并反序列化用户对象。
使用user.avsc文件创建User用户
Schema schema = new Schema.Parser().parse(new File("java-example/src/main/avro/com/bigdatatoai/avro/user.avsc")); GenericRecord user1 = new GenericData.Record(schema); user1.put("name", "Alyssa"); user1.put("favorite_number", 256); GenericRecord user2 = new GenericData.Record(schema); user2.put("name", "Ben"); user2.put("favorite_number", 7); user2.put("favorite_color", "red");
由于我们不使用代码生成,我们使用 GenericRecords 来表示用户。 GenericRecord 使用模式来验证我们是否只指定了有效字段。 如果我们尝试设置一个不存在的字段(例如,user1.put(“favorite_animal”, “cat”)),我们将在运行程序时收到 AvroRuntimeException。
请注意,我们没有设置 user1 最喜欢的颜色。 由于该记录的类型为 [“string”, “null”],我们可以将其设置为字符串或将其保留为 null; 它本质上是可选的。
将上述新建的User持久化到磁盘
File file = new File("users2.avro"); DatumWriter<GenericRecord> datumWriter = new GenericDatumWriter<GenericRecord>(schema); DataFileWriter<GenericRecord> dataFileWriter = new DataFileWriter<GenericRecord>(datumWriter); dataFileWriter.create(schema, file); dataFileWriter.append(user1); dataFileWriter.append(user2); dataFileWriter.close();
从磁盘读取users.avro并反序列化输出
DatumReader<GenericRecord> datumReader = new GenericDatumReader<GenericRecord>(schema); DataFileReader<GenericRecord> dataFileReader = new DataFileReader<GenericRecord>(file, datumReader); GenericRecord user = null; while (dataFileReader.hasNext()) { user = dataFileReader.next(user); System.out.println(user); }
总结
加载全部内容