详解 AVRO 格式
- 1.Avro 介绍
- 2.schema
- 2.1 原始类型
- 2.2 复杂类型
- 2.2.1 Records
- 2.2.2 Enums
- 2.2.3 Arrays
- 2.2.4 Maps
- 2.2.5 Unions
- 2.2.6 Fixed
- 3.Avro 的文件存储格式
- 3.1 数据编码
- 3.1.1 原始类型
- 3.1.2 复杂类型
- 3.2 存储格式
- 3.3 存储格式
- 4.小结
1.Avro 介绍
Apache Avro 是 Hadoop 中的一个子项目,也是一个数据序列化系统,其数据最终以二进制格式,采用行式存储的方式进行存储。
Avro提供了:
- ✅ 丰富的数据结构。
- ✅ 可压缩、快速的二进制数据格式。
- ✅ 一个用来存储持久化数据的容器文件。
- ✅ 远程过程调用。
- ✅ 与动态语言的简单集成,代码生成不需要读取或写入数据文件,也不需要使用或实现 RPC 协议。代码生成是一种可选的优化,只值得在静态类型语言中实现。
基于以上这些优点,Avro 在 Hadoop 体系中被广泛使用。除此之外,在 Hudi、Iceberg 中也都有用到 Avro 作为元数据信息的存储格式。
2.schema
Avro 依赖 schema
(模式)来实现数据结构的定义,schema
通过 json
对象来进行描述表示,具体表现为:
- 一个
json
字符串命名一个定义的类型。 - 一个
json
对象,其格式为{"type":"typeName" ... attributes ...}
,其中typeName
为 原始类型名称 或 复杂类型名称。 - 一个
json
数组,表示嵌入类型的联合。
schema
中的类型由 原始类型(也就是 基本类型)(null
、boolean
、int
、long
、float
、double
、bytes
和 string
)和 复杂类型(record
、enum
、array
、map
、union
和 fixed
)组成。
2.1 原始类型
原始类型包括如下几种:
null
:没有值boolean
:布尔类型的值int
: 32 32 32 位整形long
: 64 64 64 位整形float
: 32 32 32 位浮点double
: 64 64 64 位浮点bytes
: 8 8 8 位无符号类型string
:unicode
字符集序列
原始类型没有指定的属性值,原始类型的名称也就是定义的类型的名称,因此,schema
中的 "string"
等价于 {"type":"string"}
。
2.2 复杂类型
Avro 支持 6 种复杂类型:records
、enums
、arrays
、maps
、unions
和 fixed
。
2.2.1 Records
reocrds
使用类型名称 "record"
,并支持以下属性
name
:提供记录名称的json
字符串(必选)namespace
:限定名称的json
字符串doc
:一个json
字符串,为用户提供该模式的说明(可选)aliases
:字符串的json
数组,为该记录提供备用名称fields
:一个json
数组,罗列所有字段(必选),每个字段又都是一个json
对象,并包含如下属性:name
:字段的名称(必选)doc
:字段的描述(可选)type
:一个schema
,定义如上default
:字段的默认值order
:指定字段如何影响记录的排序顺序,有效值为"ascending"
(默认值)、"descending"
和"ignore"
。aliases
:别名
一个简单示例:
{
"type": "record",
"name": "LongList",
"aliases": ["LinkedLongs"],
"fields", [
{"name": "value", "type": "long"},
{"name": "next", "type": ["null", "LongList"]}
]
}
2.2.2 Enums
Enum
使用类型名称 enum
,并支持以下属性
name
:提供记录名称的json
字符串(必选)namespace
:限定名称的json
字符串aliases
:字符串的json
数组,为该记录提供备用名称doc
:一个json
字符串,为用户提供该模式的说明(可选)symbols
:一个json
数组,以json
字符串的形式列出符号。在枚举中每个符号必须唯一,不能重复,每个符号都必须匹配正则表达式"[A-Za-z_][A-Za-z0-9_]*"
。default
:该枚举的默认值。
示例:
{
"type": "enum",
"name": "Suit",
"symbols": ["SPADES", "HEARTS", "DIAMONDS", "CLUBS"]
}
2.2.3 Arrays
item
:数组中元素的schema
一个例子:声明一个 value
为 string
的 array
{
"type": "array",
"items": "string",
"default": []
}
2.2.4 Maps
values
:map
的值(value
)的schema
,其key
被假定为字符串
一个例子:声明一个 value
为 long
类型,(key
类型为 string
)的 map
{
"type": "map",
"values": "long",
"default": {}
}
2.2.5 Unions
union
使用 json
数组表示,例如 [null, "test"]
声明一个模式,它可以是空值或字符串。
需要注意的是:当为 union
类型的字段指定默认值时,默认值的类型必须与 union
第一个元素匹配,因此,对于包含 "null"
的 union
,通常先列出 "null"
,因为此类型的 union
的默认值通常为空。
另外,union
不能包含多个相同类型的 schema
,类型为 record
、fixed
和 enum
除外。
2.2.6 Fixed
Fixed
使用类型名称 "fixed"
并支持以下属性:
name
:提供记录名称的json
字符串(必选)namespace
:限定名称的json
字符串aliases
:字符串的json
数组,为该记录提供备用名称doc
:一个json
字符串,为用户提供该模式的说明(可选)size
:一个整数,指定每个值的字节数(必须)
例如,16 字节的数可以声明为:
{
"type": "fixed",
"name": "md5",
"size": 16
}
3.Avro 的文件存储格式
3.1 数据编码
3.1.1 原始类型
- 对于
null
类型:不写入内容,即 0 字节长度的内容表示。 - 对于
boolean
类型:以 1 字节的 0 或 1 来表示false
或true
。 - 对于
int
、long
:以zigzag
的方式编码写入。 - 对于
float
:固定 4 字节长度,先通过floatToIntBits
转换 32 位整数,然后按小端编码写入。 - 对于
double
:固定 8 字节长度,先通过doubleToLongBits
转换为 64 位整型,然后按小端编码写入。 - 对于
bytes
:先写入长度(采用zigzag
编码写入),然后是对应长度的二进制数据内容。 - 对于
string
:同样先写入长度(采用zigzag
编码写入),然后再写入字符串对应utf8
的二进制数据。
3.1.2 复杂类型
- 对于
enums
:只需要将enum
的值所在的Index
作为结果进行编码即可,例如,枚举值为["A","B","C","D"]
,那么 0 就表示"A"
,3 表示"D"
。 - 对于
maps
:被编码为一系列的块。每个块由一个长整数的计数表示键值对的个数(采用zigzag
编码写入),其后是多个键值对,计数为 0 的块表示map
的结束。每个元素按照各自的schema
类型进行编码。 - 对于
arrays
:与map
类似,同样被编码为一系列的块,每个块包含一个长整数的计数,计数后跟具体的数组项内容,最后以 0 计数的块表示结束。数组项中的每个元素按照各自的schema
类型进行编码。 - 对于
unions
:先写入long
类型的计数表示每个value
值的位置序号(从零开始),然后再对值按对应schema
进行编码。 - 对于
records
:直接按照schema
中的字段顺序来进行编码。 - 对于
fixed
:使用schema
中定义的字节数对实例进行编码。
3.2 存储格式
在一个标准的 avro
文件中,同时存储了 schema
的信息,以及对应的数据内容。具体格式由三部分组成:
-
魔数:固定 4 字节长度,内容为字符
'O'
,'b'
,'j'
,以及版本号标识,通常为 1 1 1。 -
元数据信息:文件的元数据属性,包括
schema
、数据压缩编码方式等。整个元数据属性以一个map
的形式编码存储,每个属性都以一个 KV 的形式存储,属性名对应key
,属性值对应value
,并以字节数组的形式存储。最后以一个固定 16 字节长度的随机字符串标识元数据的结束。 -
数据内容:而数据内容则由一个或多个数据块构成。每个数据块的最前面是一个
long
型(按照zigzag
编码存储)的计数表示该数据块中实际有多少条数据,后面再跟一个long
型的计数表示编码后的( N N N 条)数据的长度,随后就是按照编码进行存储的一条条数据,在每个数据块的最后都有一个 16 字节长度的随机字符串标识块的结束。
整体存储内容如下图所示:
3.3 存储格式
我们通过一个实际例子来对照分析下。
首先定义 schema
的内容,具体为 4 个字段的表,名称(字符串)、年龄(整型)、技能(数组)、其他(map
类型),详细如下所示:
{
"type":"record",
"name":"person",
"fields": [
{
"name": "name",
"type": "string"
},
{
"name": "age",
"type": "int"
},
{
"name": "skill",
"type": {
"type":"array",
"items": "string"
}
},
{
"name": "other",
"type": {
"type": "map",
"values": "string"
}
}
]
}
再按照上面的 schema
定义两条数据(person.json
):
{"name":"hncscwc","age":20,"skill":["hadoop","flink","spark","kafka"],"other":{"interests":"basketball"}}
{"name":"tom","age":18, "skill":["java","scala"],"other":{}}
通过 avro-tools
可以生成一个 avro
文件:
java -jar avro-tools-1.7.4.jar fromjson --schema-file person.avsc person.json > person.avro
通过二进制的方式查看生成的 avro
文件内容:
另外,对于一个已存在的文件,也可以通过 avro-tools
工具查看 schema
内容、数据内容。
[root@localhost avro]$ java -jar avro-tools-1.7.4.jar getschema ./person.avro
{
"type" : "record",
"name" : "person",
"fields" : [ {
"name" : "name",
"type" : "string"
}, {
"name" : "age",
"type" : "int"
}, {
"name" : "skill",
"type" : {
"type" : "array",
"items" : "string"
}
}, {
"name" : "other",
"type" : {
"type" : "map",
"values" : "string"
}
} ]
}
[root@localhost avro]$ java -jar avro-tools-1.7.4.jar tojson ./person.avro
{"name":"hncscwc","age":20,"skill":["hadoop","flink","spark","kafka"],"other":{"interests":"basketball"}}
{"name":"tom","age":18,"skill":["java","scala"],"other":{}}
4.小结
本文对 avro
的格式定义、编码方式、以及实际存储的文件格式进行了详细说明,最后也以一个实际例子进行了对照说明。另外, 在官网中还涉及 rpc
的使用、mapreduce
的使用,这里就没有展开说明,有兴趣的可移步官网进行查阅。