基于Java實現(xiàn)Avro文件讀寫功能
Apache Avro是一個數(shù)據(jù)序列化系統(tǒng)。具有如下基本特性:
豐富的數(shù)據(jù)結(jié)構(gòu)。一種緊湊、快速的二進(jìn)制數(shù)據(jù)格式。一個容器文件,用于存儲持久數(shù)據(jù)。遠(yuǎn)程過程調(diào)用 (RPC)。與動態(tài)語言的簡單集成。 代碼生成不需要讀取或?qū)懭霐?shù)據(jù)文件,也不需要使用或?qū)崿F(xiàn) RPC 協(xié)議。 代碼生成作為一種可選的優(yōu)化,只值得為靜態(tài)類型語言實現(xiàn)。
模式(schema)
Avro 依賴于模式。讀取 Avro 數(shù)據(jù)時,寫入時使用的模式始終存在。 這允許在沒有每個值開銷的情況下寫入每個數(shù)據(jù),從而使序列化既快速又小。 這也便于使用動態(tài)腳本語言,因為數(shù)據(jù)及其模式是完全自描述的。
當(dāng) Avro 數(shù)據(jù)存儲在文件中時,它的模式也隨之存儲,以便以后任何程序都可以處理文件。 如果讀取數(shù)據(jù)的程序需要不同的模式,這很容易解決,因為兩種模式都存在。
在 RPC 中使用 Avro 時,客戶端和服務(wù)器在連接握手中交換模式。 (這可以優(yōu)化,使得對于大多數(shù)調(diào)用,實際上不傳輸模式。)由于客戶端和服務(wù)器都具有對方的完整模式,因此可以輕松解決相同命名字段之間的對應(yīng)關(guān)系,如缺少字段,額外字段等 .
Avro 模式是用 JSON 定義的。 這有助于在已經(jīng)具有 JSON 庫的語言中實現(xiàn)。
與其他系統(tǒng)的比較
Avro 提供類似于 Thrift、Protocol Buffers 等系統(tǒng)的功能。Avro 在以下基本方面與這些系統(tǒng)不同。
動態(tài)類型:Avro 不需要生成代碼。 數(shù)據(jù)總是伴隨著一個模式,該模式允許在沒有代碼生成、靜態(tài)數(shù)據(jù)類型等的情況下完全處理該數(shù)據(jù)。這有助于構(gòu)建通用數(shù)據(jù)處理系統(tǒng)和語言。未標(biāo)記數(shù)據(jù):由于在讀取數(shù)據(jù)時存在模式,因此需要用數(shù)據(jù)編碼的類型信息要少得多,從而導(dǎo)致更小的序列化大小。沒有手動分配的字段 ID:當(dāng)架構(gòu)更改時,處理數(shù)據(jù)時始終存在舊架構(gòu)和新架構(gòu),因此可以使用字段名稱象征性地解決差異。
Java客戶端實現(xiàn)
以下代碼基于maven項目實現(xiàn)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)和復(fù)雜類型(record、enum、array、map、union 和 fixed)組成。 您可以從規(guī)范中了解有關(guān) Avro 模式和類型的更多信息,但現(xiàn)在讓我們從一個簡單的模式示例 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" ] } ] }
此模式定義了代表假設(shè)用戶的記錄。 (請注意,模式文件只能包含單個模式定義。)記錄定義至少必須包括其類型(“type”:“record”)、名稱(“name”:“User”)和字段, 在本例中為 name、favorite_number 和 favorite_color。 我們還定義了一個命名空間(“namespace”:“com.bigdatatoai.avro.generate”),它與 name 屬性一起定義了模式的“全名”(在本例中為 com.bigdatatoai.avro.User)。
字段是通過對象數(shù)組定義的,每個對象都定義了一個名稱和類型(其他屬性是可選的,有關(guān)詳細(xì)信息,請參閱記錄規(guī)范)。 字段的類型屬性是另一個模式對象,它可以是基本類型或復(fù)雜類型。 例如,我們的 User 模式的 name 字段是原始類型字符串,而 favorite_number 和 favorite_color 字段都是聯(lián)合,由 JSON 數(shù)組表示。 unions 是一種復(fù)雜類型,可以是數(shù)組中列出的任何類型; 例如, favorite_number 可以是 int 或 null,本質(zhì)上使它成為一個可選字段。
使用Java代碼生成插件生成的User類進(jìn)行序列化和反序列化
已知我們在maven項目中添加了avro插件,那么我們便可以使用compile命令生成User類。
下述以IDEA為例
現(xiàn)在我們已經(jīng)完成了代碼生成,讓我們創(chuàng)建一些用戶,將它們序列化為磁盤上的數(shù)據(jù)文件,然后讀回文件并反序列化用戶對象。
創(chuàng)建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();
如本例所示,可以通過直接調(diào)用構(gòu)造函數(shù)或使用構(gòu)建器來創(chuàng)建 Avro 對象。 與構(gòu)造函數(shù)不同,生成器將自動設(shè)置模式中指定的任何默認(rèn)值。 此外,構(gòu)建器會按設(shè)置驗證數(shù)據(jù),而直接構(gòu)造的對象在對象被序列化之前不會導(dǎo)致錯誤。 但是,直接使用構(gòu)造函數(shù)通常會提供更好的性能,因為構(gòu)造函數(shù)會在寫入數(shù)據(jù)結(jié)構(gòu)之前創(chuàng)建數(shù)據(jù)結(jié)構(gòu)的副本。
請注意,我們沒有設(shè)置 user1 最喜歡的顏色。 由于該記錄的類型為 [“string”, “null”],我們可以將其設(shè)置為字符串或?qū)⑵浔A魹?null; 它本質(zhì)上是可選的。 同樣,我們將 user3 最喜歡的數(shù)字設(shè)置為 null(使用構(gòu)建器需要設(shè)置所有字段,即使它們?yōu)?null)。
將上述新建的User用戶序列化并保存到磁盤
// 持久化數(shù)據(jù)到磁盤 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反序列化數(shù)據(jù) 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類的情況下直接進(jìn)行序列化和反序列化操作
Avro 中的數(shù)據(jù)始終與其對應(yīng)的模式一起存儲,這意味著無論我們是否提前知道模式,我們都可以隨時讀取序列化項目。 這允許我們在不生成代碼的情況下執(zhí)行序列化和反序列化。
讓我們回顧與上一節(jié)相同的示例,但不使用代碼生成:我們將創(chuàng)建一些用戶,將它們序列化為磁盤上的數(shù)據(jù)文件,然后讀回文件并反序列化用戶對象。
使用user.avsc文件創(chuàng)建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 使用模式來驗證我們是否只指定了有效字段。 如果我們嘗試設(shè)置一個不存在的字段(例如,user1.put(“favorite_animal”, “cat”)),我們將在運(yùn)行程序時收到 AvroRuntimeException。
請注意,我們沒有設(shè)置 user1 最喜歡的顏色。 由于該記錄的類型為 [“string”, “null”],我們可以將其設(shè)置為字符串或?qū)⑵浔A魹?null; 它本質(zhì)上是可選的。
將上述新建的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); }
總結(jié)
到此這篇關(guān)于基于Java實現(xiàn)Avro文件讀寫功能的文章就介紹到這了,更多相關(guān)Java Avro讀寫功能內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!