avro數據序列化/反序列化

序列化:把數據加工成特定的格式
反序列化:把特定格式的數據解析成對象

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
在這裏插入圖片描述
在這裏插入圖片描述

發佈了310 篇原創文章 · 獲贊 720 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章