Avro - 简单介绍,Java版 - 序列化与反序列化官方例子

英文原文 http://avro.apache.org/docs/1.7.7/gettingstartedjava.html

Henvealf

Avro 简单介绍

数据序列化系统。

  • 丰富的数据结构类型;
  • 快速可压缩的二进制数据形式,对数据二进制序列化后可以节约数据存储空间和网络传输带宽;
  • 存储持久数据的文件容器;
  • 可以实现远程过程调用RPC;
  • 简单的动态语言结合功能。

模式

Avro 依赖于 schema. 当 Avro 数据读取时, 就是用当前写入时的模式。结构和数据值放在一块,能让序列化变得又快有小。当数据和模式放在一起的时候,有利于在动态脚本语言上使用。

当 Avro 数据存储在文件中,模式也一块存在里面。所以文件之后可能会被任何的程序处理。如果程序读取的数据和期望的模式不同,Avro 也能很好的处理。

在使用 RPC 时,客户端于服务端在连接握手时交换模式,这样客户端于服务端就都有了对方的全部模式,这样他们就能简单的处理两个模式之间的差异。

Avro 使用 json 定义模式。

maven 连接

<dependency>
  <groupId>org.apache.avro</groupId>
  <artifactId>avro</artifactId>
  <version>1.7.7</version>
</dependency>

Avro 的 Maven 插件,用于执行代码生成

<plugin>
  <groupId>org.apache.avro</groupId>
  <artifactId>avro-maven-plugin</artifactId>
  <version>1.7.7</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.6</source>
    <target>1.6</target>
  </configuration>
</plugin>

定义一个 schema

  • 使用 Json 来定义。
  • 分为基本类型(null, boolean, int, long, float, double, bytes, and string)和 复杂类型(record, enum, array, map, union, and fixed)

下面是一个简单的模式

{"namespace": "example.avro",
 "type": "record",
 "name": "User",
 "fields": [
     {"name": "name", "type": "string"},
     {"name": "favorite_number",  "type": ["int", "null"]},
     {"name": "favorite_color", "type": ["string", "null"]}
 ]
}
  • 上面的模式定义了一个假象的用户,
  • 一个文件只能包含一个单独的模式定义
  • 一个 record 定义至少包括他的类型(”type”: “record”),一个名字(”name”: “User”),还有 fields 。
  • 然后 namespace,命名空间,使用上栗,schema 的完整名字就是 example.avro.User

  • fields 中的内容使用一个数组来定义,数组中的元素中就是 name 与 type,当然有其他选项可选。type 属性的值是其他属性对象。可以是复杂或者基本类型。
  • name 就为基本属性,favorite_* 就为复杂属性,意思其类型要么是 int/string,要么就是null。

生成代码来序列化与反序列化。

编译模式

可以使用代码,根据上面的模式自动创建出类。直接使用 avro 工具 jar 便可以:

java -jar /path/to/avro-tools-1.7.7.jar compile schema <schema file> <destination>
java -jar /path/to/avro-tools-1.7.7.jar compile schema user.avsc .

下面我们就能在当前目录看到使用命名空间定义的目录 ./example/avro ,目录最深处就会有一个 User.class

如果你使用了 Avro Maven plugin,就会自动执行代码生成。

创建 Users

现在,就可以在代码中创建对应的 User 对象,并将其序列化在本地文件中。然后反序列将数据读取出来。

User user1 = new User();

user1.setName("Alyssa");
user1.setFavoriteNumber(256);
// Leave favorite color null

// Alternate constructor
User user2 = new User("Ben", 7, "red");

// Construct via builder
User user3 = User.newBuilder()
             .setName("Charlie")
             .setFavoriteColor("blue")
             .setFavoriteNumber(null)
             .build();

三种创建方式。

builders 会在创建时候自动设置在 schema 中的默认值,builders会在被设置的时候检查,而使用对象构造器,如果字段写错,序列化的时候造成一个error。

然而,使用构造器直接生成会提供更高的性能,而 builder 会在写入钱创建一个数据结构的拷贝。

序列化

// Serialize user1, user2 and user3 to disk
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();

我们创建一个 DatumWriter,将 Java 对象转化为内存中的序列格式。SpecificDatumWriter 使用 之前生成的类并且从中提取出指定的模式。

下一步,我们创建了一个 DataFileWriter,将已经序列化的数据,写入调用 dataFileWriter.create 时指定的文件中。最后使用 dataFileWriter.append 将我们的user对象写入到序列化文件中。

反序列化

// Deserialize Users from disk
DatumReader<User> userDatumReader = new SpecificDatumReader<User>(User.class);
DataFileReader<User> dataFileReader = new DataFileReader<User>(file, userDatumReader);

// 使用这种方式可以避免因为对象太多而造成的对 gc  的不良影响。
User user = null;

while (dataFileReader.hasNext()) {
    // Reuse user object by passing it to next(). This saves us from
    // allocating and garbage collecting many objects for files with
    // many items.
    user = dataFileReader.next(user);
    System.out.println(user);
}

输出结果为:

{"name": "Alyssa", "favorite_number": 256, "favorite_color": null}
{"name": "Ben", "favorite_number": 7, "favorite_color": "red"}
{"name": "Charlie", "favorite_number": null, "favorite_color": "blue"}

编译并运行示例代码

$ mvn compile # includes code generation via Avro Maven plugin
$ mvn -q exec:java -Dexec.mainClass=example.SpecificMain

不使用代码生成来序列以及反序列化。

Avro 中的 schema 和数据经常放在一起。

创建 Users

首先,我们使用一个 Parser 读取我们的 schema 定义并创建一个 Schema 对象。

Schema schema = new Schema.Parser().parse(new File("user.avsc"));

然后使用该 schema ,我们来创建一些 Users

GenericRecord user1 = new GenericData.Record(schema);
user1.put("name", "Alyssa"); 
user1.put("favorite_number", 256);
// Leave favorite color null

GenericRecord user2 = new GenericData.Record(schema);
user2.put("name", "Ben");
user2.put("favorite_number", 7);
user2.put("favorite_color", "red");

GenericRecords 就代表 users。 GenericRecords 使用 schema 去验证我们指定的字段,如果我们指定的字段在 schema 中不存在,就会抛出 AvroRuntimeException 。

序列化

我们的序列化与序列化过程与上面使用代码生成时的过程几乎一样,不一样的是 reader 和 writer 所使用的类。

// Serialize user1 and user2 to disk
File file = new File("users.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();

发现这里用的是 GenericDatumWriter。不多说了。

反序列化

// Deserialize users from disk
DatumReader<GenericRecord> datumReader = new GenericDatumReader<GenericRecord>(schema);
DataFileReader<GenericRecord> dataFileReader = new DataFileReader<GenericRecord>(file, datumReader);

GenericRecord user = null;

while (dataFileReader.hasNext()) {
    // Reuse user object by passing it to next(). This saves us from
    // allocating and garbage collecting many objects for files with
    // many items.
    user = dataFileReader.next(user);
    System.out.println(user);
}

输出为:

{"name": "Alyssa", "favorite_number": 256, "favorite_color": null}
{"name": "Ben", "favorite_number": 7, "favorite_color": "red"}

可以发现在这里也就仅仅换了一个类 GenericDatumReader.

编译并运行代码

$ mvn compile
$ mvn -q exec:java -Dexec.mainClass=example.GenericMain

End!!

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐