对Avro文件的支持

用户可以使用Greenplum数据库的gphdfs协议来访问一个Hadoop文件系统(HDFS)上的Avro文件。

上级主题: 使用Hadoop分布式文件系统(HDFS)表

关于Avro文件格式

Avro文件把数据定义(模式)和数据本身一起存储在一个文件中,让程序便于动态地理解存储在Avro文件中的信息。Avro模式是JSON格式的,而数据是二进制格式,这让它很紧凑且高效。

下面的Avro模式的例子定义了一个有3个域的Avro记录:

  • name
  • favorite_number
  • favorite_color
  1. {"namespace": "example.avro",
  2. "type": "record", "name": "User",
  3. "fields": [
  4. {"name": "name", "type": "string"},
  5. {"name": "favorite_number", "type": ["int", "null"]},
  6. {"name": "favorite_color", "type": ["string", "null"]}
  7. ]
  8. }

有两行基于该模式的数据:

  1. { "name" : "miguno" , "favorite_number" : 6 , "favorite_color" : "red" }
  2. { "name" : "BlizzardCS" , "favorite_number" : 21 , "favorite_color" : "green" }

有关Avro文件格式的信息请见http://avro.apache.org/docs/1.7.7/

必需的Avro Jar文件

对Avro文件格式的支持要求这些jar文件:

  • avro-1.7.7.jar
  • avro-tools-1.7.7.jar
  • avro-mapred-1.7.5-hadoop2.jar(在Apache Pig中有)

注意: Hadoop 2发布包括了Avro的jar文件$HADOOP_HOME/share/hadoop/common/lib/avro-1.7.4.jar。为了避免冲突,可以把该文件重命名为另一个文件,例如avro-1.7.4.jar.bak。

对于Cloudera 5.4.x Hadoop 发布,只需要下载和安装jar文件avro-mapred-1.7.5-hadoop2.jar。该发布含有其他所需的jar文件。其他文件被包括在gphdfs协议所用的classpath中。

有关下载Avro的jar文件,请见https://avro.apache.org/releases.html

在Greenplum数据库的所有主机上,确保这些jar文件被安装并且在gphdfs使用的classpath上。classpath由脚本$GPHOME/lib/hadoop/hadoop_env.sh指定。

作为一个例子,如果目录$HADOOP_HOME/share/hadoop/common/lib不存在,请作为gpadmin用户在Greenplum数据库的所有主机上创建它。然后,在所有的主机上把jar文件加到该目录中。

hadoop_env.sh脚本文件会把这些jar文件增加到gphdfs协议的classpath。该脚本文件中是这个片段负责这项工作:

  1. if [ -d "${HADOOP_HOME}/share/hadoop/common/lib" ]; then
  2. for f in ${HADOOP_HOME}/share/hadoop/common/lib/*.jar; do
  3. CLASSPATH=${CLASSPATH}:$f;
  4. done

Avro文件格式支持

Greenplum数据库的gphdfs协议支持Avro文件类型作为一个外部表:

  • Avro文件格式 - GPDB认证了Avro 版本 1.7.7
  • 读写Avro文件
  • 读取Avro文件时支持覆盖Avro模式
  • 在写入时压缩Avro文件
  • 写Avro文件时自动生成Avro模式

如果Avro文件含有不被支持的特性或者指定的模式不匹配数据,Greenplum数据库会返回一个错误。

对Avro文件读写

要从一个Avro文件读取或者向其中写入,需要创建一个外部表,并且在LOCATION子句中指定该Avro文件的位置以及在FORMAT子句中指定’AVRO’。例如,这是一个可读外部表的语法。

  1. CREATE EXTERNAL TABLE tablename (column_spec) LOCATION ( 'gphdfs://location') FORMAT 'AVRO'

location可以是单个Avro文件或者一个含有一组Avro文件的目录。如果该位置指定多个文件(一个目录名或者一个含有通配符的文件名),Greenplum数据库使用该目录中第一个文件的模式作为整个目录的模式。在文件名中可以指定通配符 * 来匹配任意数量的字符。

在location中指定的文件后面可以增加参数。可以增加使用http查询字符串语法的参数,它开始于?并且用&分隔域/值对。

对于可读的外部表,唯一合法的参数是schema。在读取Avro文件时,gphdfs使用这个模式取代Avro文件中的模式。见可读外部表的Avro模式覆盖

对于可写的外部表,可以指定schema、namespace以及用于压缩的参数。

表 1. Avro外部表参数
参数可读/可写默认值
schemaURL_to_schema_file读和写无。
对于一个可读的外部表
  • 指定的模式覆盖Avro文件中的模式。见Avro模式覆盖
  • 如果没有指定,Greenplum数据库使用Avro文件模式。
对于一个可写的外部表
  • 创建Avro文件时使用指定的模式。
  • 如果没有指定,Greenplum数据库根据外部表定义创建一个模式。
namespaceavro_namespace只写public.avro

如果指定,是一个合法的Avro namespace

compresstrue或者false只写false
compression_typeblock只写可选。

对于avro格式,如果compresstruecompression_type必须为block

codecdeflate或者snappy只写deflate
codec_level(只对deflate codec有效)1-9之间的整数只写6

控制速度和压缩率之间折中的级别。合法的值是1-9,其中1最快而9的压缩率最高。

这一组参数指定snappy压缩:

  1. 'compress=true&codec=snappy'

这两组参数指定deflate压缩并且等效:

  1. 'compress=true&codec=deflate&codec_level=1'
  2. 'compress=true&codec_level=1'

读取Avro文件时的数据转换

在创建一个到Avro文件数据的可读外部表时,Greenplum数据库会把Avro数据类型转换成Greenplum数据库数据类型。

注意: 在读取一个Avro时,Greenplum数据库把Avro模式顶层的Avro域数据转换成一个Greenplum数据库表列。下面是gphdfs协议如何转换Avro数据类型的例子。

  • 一种Avro简单数据类型,Greenplum数据库把数据转换成一种Greenplum数据库类型。
  • 一种不是map或者record的Avro复杂数据类型,Greenplum数据库把数据转换成一种Greenplum数据库类型。
  • 一种是子记录(内嵌在顶层Avro模式记录中)的Avro 记录,Greenplum数据库把数据转换成XML。

这个表格列举了Avro简单数据类型及转换成的Greenplum数据库类型。

表 2. 对可读外部表的Avro简单数据类型支持
Avro数据类型Greenplum数据库数据类型
null只在一种Avro联合数据类型中被支持。见写Avro文件时的数据转换
booleanboolean
intint 或 smallint
longbigint
floatreal
doubledouble
bytesbytea
stringtext

注意: 在把Avro的int数据类型读取为Greenplum数据库的smallint数据类型时,必须确保Avro的int值不会超过Greenplum数据库的最大smallint值。如果Avro值太大,Greenplum数据库的值将不准确。

gphdfs协议会为smallint执行这样的转换:short result = (short)IntValue;。

这个表格列举了Avro的复杂数据类型及其转换成的Greenplum数据库类型。

表 3. 对可读外部表的Avro复杂数据类型支持
Avro数据类型Greenplum数据库数据类型
enumint

表示符号在模式中位置的整数,以零为基础。

arrayarray

Greenplum数据库数组维度匹配Avro数组维度。元素类型也从Avro数据类型被转换到Greenplum数据库数据类型。

maps不支持
union第一个非空数据类型。
fixedbytea
recordXML数据

Avro模式实例

这里是一个Avro模式的例子。在从该Avro文件读取数据时,gphdfs协议执行这些转换:

  • name以及color数据被转换到Greenplum数据库的string。
  • age数据被转换到Greenplum数据库的int。
  • clist记录被转换到XML。
  1. {"namespace": "example.avro",
  2. "type": "record",
  3. "name": "User",
  4. "fields": [
  5. {"name": "name", "type": "string"},
  6. {"name": "number", "type": ["int", "null"]},
  7. {"name": "color", "type": ["string", "null"]},
  8. {"name": "clist",
  9. "type": {
  10. "type":"record",
  11. "name":"clistRecord",
  12. "fields":[
  13. {"name": "class", "type": ["string", "null"]},
  14. {"name": "score", "type": ["double", "null"]},
  15. {"name": "grade",
  16. "type": {
  17. "type":"record",
  18. "name":"inner2",
  19. "fields":[
  20. {"name":"a", "type":["double" ,"null"]},
  21. {"name":"b", "type":["string","null"]}
  22. ]}
  23. },
  24. {"name": "grade2",
  25. "type": {
  26. "type":"record",
  27. "name":"inner",
  28. "fields":[
  29. {"name":"a", "type":["double","null"]},
  30. {"name":"b", "type":["string","null"]},
  31. {"name":"c", "type":{
  32. "type": "record",
  33. "name":"inner3",
  34. "fields":[
  35. {"name":"c1", "type":["string", "null"]},
  36. {"name":"c2", "type":["int", "null"]}
  37. ]}}
  38. ]}
  39. }
  40. ]}
  41. }
  42. ]
  43. }

这个XML是gpfist协议如何基于之前的模式把Avro数据从clist域转换到XML数据的例子。对于嵌在Avro顶层记录中的记录,gpfist协议把Avro元素名称转换成XML元素名称并且把记录名称转换成该XML元素的一个属性。例如,最顶层元素clist的名称和type属性是Avro记录元素clistRecord的名称。

  1. <clist type="clistRecord">
  2. <class type="string">math</class>
  3. <score type="double">99.5</score>
  4. <grade type="inner2">
  5. <a type="double">88.8</a>
  6. <b type="string">subb0</b>
  7. </grade>
  8. <grade2 type="inner">
  9. <a type="double">77.7</a>
  10. <b type="string">subb20</b>
  11. <c type="inner3">
  12. <c1 type="string">subc</c1>
  13. <c2 type="int& quot;>0</c2>
  14. </c>
  15. </grade2>
  16. </clist>

可读外部表的Avro模式覆盖

在为一个可读外部表指定模式(指定一个Avro文件作为来源)时,Greenplum数据库使用该模式从Avro文件中读取数据。这个指定的模式覆盖Avro文件的模式。

可以把含有一个Avro模式的文件指定为CREATE EXTERNAL TABLE命令中位置参数的一部分,这将会覆盖Avro文件的模式。如果一组Avro文件含有不同的且相关的模式,可以为所有的文件共同指定一个Avro模式来检索数据。

Greenplum数据基于域名称从Avro文件中抽取数据。如果一个Avro文件含同名的域,Greenplum数据库就读出其数据,否则返回一个NULL。

例如,如果一组Avro文件都含有两种不同的模式之一。这是原始模式。

  1. {
  2. "type":"record",
  3. "name":"tav2",
  4. "namespace":"public.avro",
  5. "doc":"",
  6. "fields":[
  7. {"name":"id","type":["null","int"],"doc":""},
  8. {"name":"name","type":["null","string"],"doc":""},
  9. {"name":"age","type":["null","long"],"doc":""},
  10. {"name":"birth","type":["null","string"],"doc":""}
  11. ]
  12. }

这是更新后含有一个comment域的模式。

  1. {
  2. "type":"record",
  3. "name":"tav2",
  4. "namespace":"public.avro",
  5. "doc":"",
  6. "fields":[
  7. {"name":"id","type":["null","int"],"doc":""},
  8. {"name":"name","type":["null","string"],"doc":""},
  9. {"name":"birth","type":["null","string"],"doc":""},
  10. {"name":"age","type":["null","long"],"doc":""},
  11. {"name":"comment","type":["null","string"],"doc":""}
  12. ]
  13. }

可以在一个CREATE EXTERNAL TABLE命令中指定一个含有该Avro模式的文件来从这些Avro文件读取id、name、birth以及comment域。

  1. {
  2. "type":"record",
  3. "name":"tav2",
  4. "namespace":"public.avro",
  5. "doc":"",
  6. "fields":[
  7. {"name":"id","type":["null","int"],"doc":""},
  8. {"name":"name","type":["null","string"],"doc":""},
  9. {"name":"birth","type":["null","string"],"doc":""},
  10. {"name":"comment","type":["null","string"],"doc":""}
  11. ]
  12. }

在这个示例命令中,顾客数据在Avro文件tmp/cust*.avro中。每个文件都使用前面列举的模式之一。文件avro/cust.avsc是一个文本文件,其中包含的Avro模式用来覆盖顾客文件中的模式。

  1. CREATE WRITABLE EXTERNAL TABLE cust_avro(id int, name text, birth date)
  2. LOCATION ('gphdfs://my_hdfs:8020/tmp/cust*.avro
  3. ?schema=hdfs://my_hdfs:8020/avro/cust.avsc')
  4. FORMAT 'avro';

在读取Avro数据时,如果Greenplum数据库读取一个不含有comment域的文件,会为comment数据返回一个NULL。

写Avro文件时的数据转换

在创建一个写数据到一个Avro文件的可写外部表时,每一个表行是一个Avro记录并且每一个表列是一个Avro域。在写一个Avro文件时,默认的压缩算法是deflate。

对于一个可写的外部表,如果没有指定schema选项,Greenplum数据库会根据外部表定义为Avro文件创建一个Avro模式。表列的名称就是Avro域的名称。数据类型是一种联合数据类型,见下面的表:

表 4. Greenplum数据库生成的Avro数据类型
Greenplum数据库数据类型Avro的联合数据类型定义
boolean[“boolean”, “null”]
int[“int”, “null”]
bigint[“long”, “null”]
smallint[“int”, “null”]
real[“float”, “null”]
double[“double”, “null”]
bytea[“bytes”, “null”]
text[“string”, “null”]
array[{array}, “null”]

Greenplum数据库的数组会被转换成与Greenplum数据库数组具有同样维度和元素类型的Avro数组。

其他数据类型[“string”, “null”]

数据是格式化过的字符串。gphdfs协议把数据转成Greenplum数据库文本并且将该文本作为一个Avro字符串写到Avro文件。例如,日期和时间数据会被格式化为日期和时间字符串并且被转换成Avro字符串类型。

可以指定一个带有schema选项的模式。在指定一个模式时,该文件可以在Segment主机上或者是Greenplum数据库可访问的HDFS上的一个文件。对于一个本地文件,该文件必须在所有的Segment主机上位于相同的位置。对于一个HDFS上的文件,该文件必须和数据文件位于同一个集群上。

这个schema选项的例子指定了一个HDFS上的模式。

  1. 'schema=hdfs://mytest:8000/avro/array_simple.avsc'

这个schema选项的例子指定了一个主机文件系统上的模式。

  1. 'schema=file:///mydata/avro_schema/array_simple.avsc'

gphdfs对Avro文件的限制

对于一个Greenplum数据库的可写外部表定义,列不能指定NOT NULL子句。

Greenplum数据库只支持Avro文件中的单一顶层模式,或者用CREATE EXTERNAL TABLE命令中的schema参数指定。如果Greenplum数据库检测到多个顶层模式,则会返回一个错误。

Greenplum数据库不支持Avro的map数据类型并且在遇到时会返回一个错误。

当Greenplum数据库从一个Avro文件读取一个数组时,该数组会被转换成一个字符文本值。例如,数组[1,3]会被转换成’{1,3}’。

支持用户定义的类型(UDT),包括数组UDT。对于一个可写的外部表,该类型会被转换成一个字符串。

例子

从两个Avro域id和ba读取数据的简单CREATE EXTERNAL TABLE命令。

  1. CREATE EXTERNAL TABLE avro1 (id int, ba bytea[])
  2. LOCATION ('gphdfs://my_hdfs:8020/avro/singleAvro/array2.avro')
  3. FORMAT 'avro';

CREATE WRITABLE EXTERNAL TABLE命令指定gphdfs协议用来创建Avro文件的Avro模式。

  1. CREATE WRITABLE EXTERNAL TABLE at1w(id int, names text[], nums int[])
  2. LOCATION ('gphdfs://my_hdfs:8020/tmp/at1
  3. ?schema=hdfs://my_hdfs:8020/avro/array_simple.avsc')
  4. FORMAT 'avro';

写入到一个Avro文件并且为Avro模式指定一个名字空间的CREATE WRITABLE EXTERNAL TABLE命令。

  1. CREATE WRITABLE EXTERNAL TABLE atudt1 (id int, info myt, birth date, salary numeric )
  2. LOCATION ('gphdfs://my_hdfs:8020/tmp/emp01.avro
  3. ?namespace=public.example.avro')
  4. FORMAT 'avro';