Avro介绍[通俗易懂](Apache)
Apache Avro是一個(gè)數(shù)據(jù)序列化系統(tǒng)。
Avro所提供的屬性:
1.豐富的數(shù)據(jù)結(jié)構(gòu)
2.使用快速的壓縮二進(jìn)制數(shù)據(jù)格式
3.提供容器文件用于持久化數(shù)據(jù)
4.遠(yuǎn)程過(guò)程調(diào)用RPC
5.簡(jiǎn)單的動(dòng)態(tài)語(yǔ)言結(jié)合功能,Avro 和動(dòng)態(tài)語(yǔ)言結(jié)合后,讀寫數(shù)據(jù)文件和使用 RPC 協(xié)議都不需要生成代碼,而代碼生成作為一種可選的優(yōu)化只值得在靜態(tài)類型語(yǔ)言中實(shí)現(xiàn)。
Avro的Schema
Avro的Schema用JSON表示。Schema定義了簡(jiǎn)單數(shù)據(jù)類型和復(fù)雜數(shù)據(jù)類型。
基本類型
其中簡(jiǎn)單數(shù)據(jù)類型有以下8種:
| 類型 | 含義 |
|---|---|
| null | 沒(méi)有值 |
| boolean | 布爾值 |
| int | 32位有符號(hào)整數(shù) |
| long | 64位有符號(hào)整數(shù) |
| float | 單精度(32位)的IEEE 754浮點(diǎn)數(shù) |
| double | 雙精度(64位)的IEEE 754浮點(diǎn)數(shù) |
| bytes | 8位無(wú)符號(hào)字節(jié)序列 |
| string | 字符串 |
基本類型沒(méi)有屬性,基本類型的名字也就是類型的名字,比如:
{"type": "string"}
Jetbrains全家桶1年46,售后保障穩(wěn)定
復(fù)雜類型
Avro提供了6種復(fù)雜類型。分別是Record,Enum,Array,Map,Union和Fixed。
Record
Record類型使用的類型名字是 “record”,還支持其它屬性的設(shè)置:
name:record類型的名字(必填)
namespace:命名空間(可選)
doc:這個(gè)類型的文檔說(shuō)明(可選)
aliases:record類型的別名,是個(gè)字符串?dāng)?shù)組(可選)
fields:record類型中的字段,是個(gè)對(duì)象數(shù)組(必填)。每個(gè)字段需要以下屬性:
- name:字段名字(必填)
- doc:字段說(shuō)明文檔(可選)
- type:一個(gè)schema的json對(duì)象或者一個(gè)類型名字(必填)
- default:默認(rèn)值(可選)
- order:排序(可選),只有3個(gè)值ascending(默認(rèn)),descending或ignore
- aliases:別名,字符串?dāng)?shù)組(可選)
一個(gè)Record類型例子,定義一個(gè)元素類型是Long的鏈表:
{
"type": "record",
"name": "LongList",
"aliases": ["LinkedLongs"], // old name for this
"fields" : [
{"name": "value", "type": "long"}, // each element has a long
{"name": "next", "type": ["null", "LongList"]} // optional next element
]
}
Enum
枚舉類型的類型名字是”enum”,還支持其它屬性的設(shè)置:
name:枚舉類型的名字(必填)
namespace:命名空間(可選)
aliases:字符串?dāng)?shù)組,別名(可選)
doc:說(shuō)明文檔(可選)
symbols:字符串?dāng)?shù)組,所有的枚舉值(必填),不允許重復(fù)數(shù)據(jù)。
一個(gè)枚舉類型的例子:
{ "type": "enum",
"name": "Suit",
"symbols" : ["SPADES", "HEARTS", "DIAMONDS", "CLUBS"]
}
Array
數(shù)組類型的類型名字是”array”并且只支持一個(gè)屬性:
items:數(shù)組元素的schema
一個(gè)數(shù)組例子:
{"type": "array", "items": "string"}
Map
Map類型的類型名字是”map”并且只支持一個(gè)屬性:
values:map值的schema
Map的key必須是字符串。
一個(gè)Map例子:
{"type": "map", "values": "long"}
Union
組合類型,表示各種類型的組合,使用數(shù)組進(jìn)行組合。比如[“null”, “string”]表示類型可以為null或者string。
組合類型的默認(rèn)值是看組合類型的第一個(gè)元素,因此如果一個(gè)組合類型包括null類型,那么null類型一般都會(huì)放在第一個(gè)位置,這樣子的話這個(gè)組合類型的默認(rèn)值就是null。
組合類型中不允許同一種類型的元素的個(gè)數(shù)不會(huì)超過(guò)1個(gè),除了record,fixed和enum。比如組合類中有2個(gè)array類型或者2個(gè)map類型,這是不允許的。
組合類型不允許嵌套組合類型。
Fixed
混合類型的類型名字是fixed,支持以下屬性:
name:名字(必填)
namespace:命名空間(可選)
aliases:字符串?dāng)?shù)組,別名(可選)
size:一個(gè)整數(shù),表示每個(gè)值的字節(jié)數(shù)(必填)
比如16個(gè)字節(jié)數(shù)的fixed類型例子如下:
{"type": "fixed", "size": 16, "name": "md5"}
1個(gè)Avro例子
首先定義一個(gè)User的schema:
{
"namespace": "example.avro",
"type": "record",
"name": "User",
"fields": [
{"name": "name", "type": "string"},
{"name": "favorite_number", "type": "int"},
{"name": "favorite_color", "type": "string"}
]
}
User有3個(gè)屬性,分別是name,favorite_number和favorite_color。
json文件內(nèi)容:
{"name":"format","favorite_number":1,"favorite_color":"red"}
{"name":"format2","favorite_number":2,"favorite_color":"black"}
{"name":"format3","favorite_number":666,"favorite_color":"blue"}
使用avro工具將json文件轉(zhuǎn)換成avro文件:
java -jar avro-tools-1.8.0.jar fromjson --schema-file user.avsc user.json > user.avro
可以設(shè)置壓縮格式:
java -jar avro-tools-1.8.0.jar fromjson --codec snappy --schema-file user.avsc user.json > user2.avro
將avro文件反轉(zhuǎn)換成json文件:
java -jar avro-tools-1.8.0.jar tojson user.avro
java -jar avro-tools-1.8.0.jar --pretty tojson user.avro
得到avro文件的meta:
java -jar avro-tools-1.8.0.jar getmeta user.avro
輸出:
avro.codec null
avro.schema {"type":"record","name":"User","namespace":"example.avro","fields":[{"name":"name","type":"string"},{"name":"favorite_number","type":"int"},{"name":"favorite_color","type":"string"}]}
得到avro文件的schema:
java -jar avro-tools-1.8.0.jar getschema user.avro
將文本文件轉(zhuǎn)換成avro文件:
java -jar avro-tools-1.8.0.jar fromtext user.txt usertxt.avro
Avro使用生成的代碼進(jìn)行序列化和反序列化
以上面一個(gè)例子的schema為例講解。
Avro可以根據(jù)schema自動(dòng)生成對(duì)應(yīng)的類:
java -jar /path/to/avro-tools-1.8.0.jar compile schema user.avsc .
user.avsc的namespace為example.avro,name為User。最終在當(dāng)前目錄生成的example/avro目錄下有個(gè)User.java文件。
├── example
│ └── avro
│ └── User.java
使用Avro生成的代碼創(chuàng)建User:
User user1 = new User();
user1.setName("Format");
user1.setFavoriteColor("red");
user1.setFavoriteNumber(666);
User user2 = new User("Format2", 66, "blue");
User user3 = User.newBuilder()
.setName("Format3")
.setFavoriteNumber(6)
.setFavoriteColor("black").build();
可以使用有參的構(gòu)造函數(shù)和無(wú)參的構(gòu)造函數(shù),也可以使用Builder構(gòu)造User。
序列化:
DatumWrite接口用來(lái)把java對(duì)象轉(zhuǎn)換成內(nèi)存中的序列化格式,SpecificDatumWriter用來(lái)生成類并且指定生成的類型。
最后使用DataFileWriter來(lái)進(jìn)行具體的序列化,create方法指定文件和schema信息,append方法用來(lái)寫數(shù)據(jù),最后寫完后close文件。
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();
反序列化:
反序列化跟序列化很像,相應(yīng)的Writer換成Reader。這里只創(chuàng)建一個(gè)User對(duì)象是為了性能優(yōu)化,每次都重用這個(gè)User對(duì)象,如果文件量很大,對(duì)象分配和垃圾收集處理的代價(jià)很昂貴。如果不考慮性能,可以使用 for (User user : dataFileReader) 循環(huán)遍歷對(duì)象
File file = new File("users.avro");
DatumReader<User> userDatumReader = new SpecificDatumReader<User>(User.class);
DataFileReader<User> dataFileReader = new DataFileReader<User>(file, userDatumReader);
User user = null;
while(dataFileReader.hasNext()) {
user = dataFileReader.next(user);
System.out.println(user);
}
打印出:
{"name": "Format", "favorite_number": 666, "favorite_color": "red"}
{"name": "Format2", "favorite_number": 66, "favorite_color": "blue"}
{"name": "Format3", "favorite_number": 6, "favorite_color": "black"}
Avro不使用生成的代碼進(jìn)行序列化和反序列化
雖然Avro為我們提供了根據(jù)schema自動(dòng)生成類的方法,我們也可以自己創(chuàng)建類,不使用Avro的自動(dòng)生成工具。
創(chuàng)建User:
首先使用Parser讀取schema信息并且創(chuàng)建Schema類:
Schema schema = new Schema.Parser().parse(new File("user.avsc"));
有了Schema之后可以創(chuàng)建record:
GenericRecord user1 = new GenericData.Record(schema);
user1.put("name", "Format");
user1.put("favorite_number", 666);
user1.put("favorite_color", "red");
GenericRecord user2 = new GenericData.Record(schema);
user2.put("name", "Format2");
user2.put("favorite_number", 66);
user2.put("favorite_color", "blue");
使用GenericRecord表示User,GenericRecord會(huì)根據(jù)schema驗(yàn)證字段是否正確,如果put進(jìn)了不存在的字段 user1.put(“favorite_animal”, “cat”) ,那么運(yùn)行的時(shí)候會(huì)得到AvroRuntimeException異常。
序列化:
序列化跟生成的User類似,只不過(guò)schema是自己構(gòu)造的,不是User中拿的。
Schema schema = new Schema.Parser().parse(new File("user.avsc"));
GenericRecord user1 = new GenericData.Record(schema);
user1.put("name", "Format");
user1.put("favorite_number", 666);
user1.put("favorite_color", "red");
GenericRecord user2 = new GenericData.Record(schema);
user2.put("name", "Format2");
user2.put("favorite_number", 66);
user2.put("favorite_color", "blue");
DatumWriter<GenericRecord> datumWriter = new SpecificDatumWriter<GenericRecord>(schema);
DataFileWriter<GenericRecord> dataFileWriter = new DataFileWriter<GenericRecord>(datumWriter);
dataFileWriter.create(schema, new File("users2.avro"));
dataFileWriter.append(user1);
dataFileWriter.append(user2);
dataFileWriter.close();
反序列化:
反序列化跟生成的User類似,只不過(guò)schema是自己構(gòu)造的,不是User中拿的。
Schema schema = new Schema.Parser().parse(new File("user.avsc"));
File file = new File("users2.avro");
DatumReader<GenericRecord> datumReader = new SpecificDatumReader<GenericRecord>(schema);
DataFileReader<GenericRecord> dataFileReader = new DataFileReader<GenericRecord>(file, datumReader);
GenericRecord user = null;
while(dataFileReader.hasNext()) {
user = dataFileReader.next(user);
System.out.println(user);
}
打印出:
{"name": "Format", "favorite_number": 666, "favorite_color": "red"}
{"name": "Format2", "favorite_number": 66, "favorite_color": "blue"}
一些注意點(diǎn)
Avro解析json文件的時(shí)候,如果類型是Record并且里面有字段是union并且允許空值的話,需要進(jìn)行轉(zhuǎn)換。因?yàn)閇“bytes”, “string”]和[“int”,”long”]這2個(gè)union類型在json中是有歧義的,第一個(gè)union在json中都會(huì)被轉(zhuǎn)換成string類型,第二個(gè)union在json中都會(huì)被轉(zhuǎn)換成數(shù)字類型。
所以如果json值的null的話,在avro提供的json中直接寫null,否則使用只有一個(gè)鍵值對(duì)的對(duì)象,鍵是類型,值的具體的值。
比如:
{
"namespace": "example.avro",
"type": "record",
"name": "User",
"fields": [
{"name": "name", "type": "string"},
{"name": "favorite_number", "type": ["int","null"]},
{"name": "favorite_color", "type": ["string","null"]}
]
}
在要轉(zhuǎn)換成json文件的時(shí)候要寫成這樣:
{"name":"format","favorite_number":{"int":1},"favorite_color":{"string":"red"}}
{"name":"format2","favorite_number":null,"favorite_color":{"string":"black"}}
{"name":"format3","favorite_number":{"int":66},"favorite_color":null}
Spark讀取Avro文件
直接遍歷avro文件,得到GenericRecord進(jìn)行處理:
val conf = new SparkConf().setMaster("local").setAppName("AvroTest")
val sc = new SparkContext(conf)
val rdd = sc.hadoopFile[AvroWrapper[GenericRecord], NullWritable, AvroInputFormat[GenericRecord]](this.getClass.getResource("/").toString + "users.avro")
val nameRdd = rdd.map(s => s._1.datum().get("name").toString)
nameRdd.collect().foreach(println)
使用Avro需要注意的地方
筆者使用Avro的時(shí)候暫時(shí)遇到了下面2個(gè)坑。先記錄一下,以后遇到新的坑會(huì)更新這篇文章。
1.如果定義了unions類型的字段,而且unions中有null選項(xiàng)的schema,比如如下schema:
{
"namespace": "example.avro",
"type": "record",
"name": "User2",
"fields": [
{"name": "name", "type": "string"},
{"name": "favorite_number", "type": ["null","int"]},
{"name": "favorite_color", "type": ["null","string"]}
]
}
這樣的schema,如果不使用Avro自動(dòng)生成的model代碼進(jìn)行insert,并且insert中的model數(shù)據(jù)有null數(shù)據(jù)的話。然后用spark讀avro文件的話,會(huì)報(bào)org.apache.avro.AvroTypeException: Found null, expecting int … 這樣的錯(cuò)誤。
這一點(diǎn)很奇怪,但是使用Avro生成的Model進(jìn)行insert的話,sprak讀取就沒(méi)有任何問(wèn)題。 很困惑。
2.如果使用了Map類型的字段,avro生成的model中的Map的Key默認(rèn)類型為CharSequence。這種model我們insert數(shù)據(jù)的話,用String是沒(méi)有問(wèn)題的。但是spark讀取之后要根據(jù)Key拿這個(gè)Map數(shù)據(jù)的時(shí)候,永遠(yuǎn)得到的是null。
stackoverflow上有一個(gè)頁(yè)面說(shuō)到了這個(gè)問(wèn)題。http://stackoverflow.com/questions/19728853/apache-avro-map-uses-charsequence-as-key
需要在map類型的字段里加上”avro.java.string”: “String”這個(gè)選項(xiàng), 然后compile的時(shí)候使用-string參數(shù)即可。
比如以下這個(gè)schema:
{
"namespace": "example.avro",
"type": "record",
"name": "User3",
"fields": [
{"name": "name", "type": "string"},
{"name": "favorite_number", "type": ["null","int"]},
{"name": "favorite_color", "type": ["null","string"]},
{"name": "scores", "type": ["null", {"type": "map", "values": "string", "avro.java.string": "String"}]}
]
}
總結(jié)
以上是生活随笔為你收集整理的Avro介绍[通俗易懂](Apache)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 国内常用公共DNS服务器、各省运营商DN
- 下一篇: ABAP的OPEN SQL和Hybris