序列化:把數據加工成特定的格式
反序列化:把特定格式的數據解析成對象
Avro提供了兩種序列化和反序列化的方式:一種是通過Schema文件來生成代碼的方式,一種是不生成代碼的通用方式,這兩種方式都需要構建Schema文件。
Avro在序列化時可以通過指定編碼器,將數據序列化成標準的JSON格式,也可以序列化成二進制格式。
Avro支持兩種序列化編碼方式:二進制編碼和JSON編碼,使用二進制編碼會高效序列化,並且序列化後得到的結果會比較小。而JSON一般用於調試系統或是基於WEB的應用。對Avro數據序列化/反序列化時都需要對模式以深度優先(Depth-First),從左到右(Left-to-Right)的遍歷順序來執行。
下面通過具體的例子來進行演示:
項目框架
創建一個Maven項目:
- 在pom.xml文件中添加依賴:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
<version>1.9.1</version>
</dependency>
- 在pom.xml文件中配置插件:
<plugins>
<plugin>
<groupId>org.apache.avro</groupId>
<artifactId>avro-maven-plugin</artifactId>
<version>1.9.1</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>schema</goal>
</goals>
<configuration>
<sourceDirectory>${project.basedir}/src/main/resources/</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>
<encoding>utf-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
- 在resources目錄下定義模式文件dept.avsc
{
"namespace":"com.hc.bean",
"type":"record",
"name":"Dept",
"fields":[
{"name":"deptno","type":"int"},
{"name":"dname","type":"string"},
{"name":"loc","type":"string"}
]
}
使用生成的代碼(類)進行序列化和反序列化
第一步:根據schema自動生成對應的Dept類
首先下載avro-tools-1.9.1.jar文件將該文件連同dept.avsc放到同一個目錄下,然後執行下面命令:
java -jar avro-tools-1.9.1.jar compile schema dept.avsc java.
注意:最後面的**java.**指的是生成的avro文件存放在當前目錄下的java文件夾下,點表示當前路徑。
最終在當前目錄生成java/com/hc/bean目錄下有個Dept.java文件。將該java文件和avsc文件一起拷貝到Intellij項目目錄下面:
注:在Intellij中,也可以採用圖形化的方式生成Java代碼:
當然,也可以採用命令行的方式:
mvn clean install -DskipTests=true
不過使用mvn clean install -DskipTests=true命令時,需要添加下面Jackson註解,否則程序報錯。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.10.1</version>
</dependency>
第二步:使用Avro生成的代碼創建Dept對象:
可以使用有參的構造函數和無參的構造函數,也可以使用Builder構造Dept對象:
@Test
public void genBean(){
Dept dept1 = new Dept();
dept1.setDeptno(50);
dept1.setDname("aa");
dept1.setLoc("aaaaaaaaaa");
System.out.println(dept1);
Dept dept2 = new Dept(60, "bb", "bbbbbbbbb");
System.out.println(dept2);
Dept dept3 = Dept.newBuilder()
.setDeptno(70)
.setDname("cc")
.setLoc("ccccccccc")
.build();
System.out.println(dept3);
}
結果:
第三步:序列化:
@Test
public void serialize() throws IOException {
Dept dept = new Dept(60, "bb", "bbbbbbbbb");
DatumWriter<Dept> datumWriter = new SpecificDatumWriter<>(Dept.class); //緩存
DataFileWriter<Dept> dataFileWriter = new DataFileWriter< >(datumWriter);
dataFileWriter.create(dept.getSchema(), new File("dept.avro")); //將數據序列化到指定的文件中
dataFileWriter.append(dept); //可以追加多個對象
dataFileWriter.close();
}
上面代碼中:DatumWrite接口用來把java對象轉換成內存中的序列化格式,SpecificDatumWriter用來生成類並且指定生成的類型。最後使用DataFileWriter來進行具體的序列化,create方法指定目標文件和schema信息,append方法用來寫數據,最後寫完後close文件。
運行程序,結果:在當前目錄生成一個名爲dept.avro的二進制文件。
第四步:反序列化:
反序列化跟序列化很像,相應的Writer換成Reader。
@Test
public void deSerialize() throws IOException {
File file = new File("dept.avro");
DatumReader<Dept> datumReader = new SpecificDatumReader< >(Dept.class);
DataFileReader<Dept> dataFileReader = new DataFileReader< >(file, datumReader);
for(Dept dept : dataFileReader){ //通過迭代的方式一條條讀取出來
System.out.println(dept);
}
}
上面代碼只創建一個Dept對象,是爲了性能優化,每次都重用這個Dept對象,如果文件量很大,對象分配和垃圾收集處理的代價很昂貴。最後使用 for (Dept dept: dataFileReader) 循環遍歷對象
程序運行結果:
不使用生成的代碼(類)進行序列化和反序列化(通用方式)
雖然Avro爲我們提供了根據schema自動生成類的方法,我們也可以自己創建類,不使用Avro的自動生成工具。
第一步:創建Dept對象:
@Test
public void fun4() throws IOException {
Schema schema = new Schema.Parser().parse(new File("src/main/resources/dept.avsc"));//通過流的方式將Schema信息構建出來
GenericData.Record dept = new GenericData.Record(schema);
dept.put("deptno","80");
dept.put("dname","dd");
dept.put("loc","ddddddddddd");
System.out.println(dept);
}
上面代碼首先使用Parser讀取schema信息並且創建Schema類,有了Schema之後可以創建具體的Dept對象了。上面代碼使用GenericRecord表示Dept,GenericRecord會根據schema驗證字段是否正確,如果put進了不存在的字段 dept.put(“deptno”, “80”) ,那麼運行的時候會得到AvroRuntimeException異常。
上面代碼構建Schema也可以:
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsSteam("dept.avsc");
Schema schema = new Schema.Parser().parse(is);
第二步:通用序列化:
@Test
public void serialize() throws IOException {
Schema schema = new Schema.Parser().parse(new File("src/main/resources/dept.avsc"));
GenericRecord dept = new GenericData.Record(schema);
dept.put("deptno", 90);
dept.put("dname", "ee");
dept.put("loc", "eeeeeeeee");
DatumWriter<GenericRecord> datumWriter = new SpecificDatumWriter<>(schema); //泛型參數爲GenericRecord
DataFileWriter<GenericRecord> dataFileWriter = new DataFileWriter<>(datumWriter);
dataFileWriter.create(schema, new File("dept.avro"));
dataFileWriter.append(dept);
dataFileWriter.close();
}
第三步:通用反序列化:
@Test
public void deSerialize() throws IOException {
Schema schema = new Schema.Parser().parse(new File("src/main/resources/dept.avsc"));
File file = new File("dept.avro");
DatumReader<GenericRecord> datumReader = new SpecificDatumReader<GenericRecord>(schema);
DataFileReader<GenericRecord> dataFileReader = new DataFileReader<GenericRecord>(file, datumReader);
GenericRecord dept = null;
while(dataFileReader.hasNext()) {
dept = dataFileReader.next(dept );
System.out.println(dept );
}
}
執行程序,結果:
示例:基於通用序列化反序列化演示模式轉換(數據投影):
原理:序列化的模式文件和反序列化的模式文件不一致。
情況一:模式文件字段重命名
- 新創建模式文件dept2.avsc
{
"namespace":"com.hc.bean",
"type":"record",
"name":"Dept",
"fields":[
{"name":"deptno","type":"int"},
{"name":"deptname","type":"string","aliases":["dname"]},
{"name":"loc","type":"string"}
]
}
-測試代碼:
@Test
public void fun41() throws IOException {//模式文件增加字段
Schema schema = new Schema.Parser().parse(new File("src/main/resources/dept1.avsc"));
File file = new File("src/main/resources/dept.avro");
DatumReader<GenericRecord> datumReader = new SpecificDatumReader<>(schema);
DataFileReader<GenericRecord> dataFileReader = new DataFileReader<>(file, datumReader);
GenericRecord dept = null;
while (dataFileReader.hasNext()) {
dept = dataFileReader.next(dept);
System.out.println(dept);
}
}
- 結果:
情況二:模式文件增加新的字段
- 新創建模式文件dept2.avsc
{
"namespace":"com.hc.bean",
"type":"record",
"name":"Dept",
"fields":[
{"name":"deptno","type":"int"},
{"name":"dname","type":"string"},
{"name":"loc","type":"string"},
{"name":"tel","type":"string","default":"110120"}
]
}
- 測試代碼:
@Test
public void fun42() throws IOException {//模式文件增加字段
Schema schema = new Schema.Parser().parse(new File("src/main/resources/dept2.avsc"));
File file = new File("src/main/resources/dept.avro");
DatumReader<GenericRecord> datumReader = new SpecificDatumReader<>(schema);
DataFileReader<GenericRecord> dataFileReader = new DataFileReader<>(file, datumReader);
GenericRecord dept = null;
while (dataFileReader.hasNext()) {
dept = dataFileReader.next(dept);
System.out.println(dept);
}
}
- 結果:
情況三:模式文件減少新的字段
- 新創建模式文件dept3.avsc
{
"namespace":"com.hc.bean",
"type":"record",
"name":"Dept",
"fields":[
{"name":"deptno","type":"int"},
{"name":"dname","type":"string"}
]
}
- 測試代碼:
@Test
public void fun43() throws IOException {//模式文件增加字段
Schema schema = new Schema.Parser().parse(new File("src/main/resources/dept3.avsc"));
File file = new File("src/main/resources/dept.avro");
DatumReader<GenericRecord> datumReader = new SpecificDatumReader<>(schema);
DataFileReader<GenericRecord> dataFileReader = new DataFileReader<>(file, datumReader);
GenericRecord dept = null;
while (dataFileReader.hasNext()) {
dept = dataFileReader.next(dept);
System.out.println(dept);
}
}
- 結果:
可以發現,上面幾種情況,測試代碼不用發生任何改變。這也正是採用通用模式的最大好處。
附:使用命令行的方式序列化/反序列化
第一步:提供表示數據的json文件:
{"deptno":10,"dname":"RESEARCH","loc":"DALLAS"}
{"deptno":20,"dname":"SALES","loc":"CHICAGO"}
{"deptno":30,"dname":"OPERATION","loc":"BOSTON"}
{"deptno":40,"dname":"ACCOUNTING","loc":"NEW YORK"}
第二步:使用avro工具將json文件轉換成avro文件:
命令:java -jar avro-tools-1.9.1.jar fromjson --schema-file dept.avsc dept.json > dept.avro
可以設置壓縮格式,命令:
java -jar avro-tools-1.9.1.jar fromjson --codec snappy --schema-file dept.avsc dept.json > dept2.avro
第三步:將avro文件反轉換成json文件:
命令:java -jar avro-tools-1.9.1.jar tojson dept.avro
第四步:得到avro文件的meta:
命令:java -jar avro-tools-1.9.1.jar getmeta dept.avro