Hadoop学习之-Avro

关于Avro

Apache Avro是一个独立于编程语言的数据化序列系统。该项目由Hadoop之父Doug Cutting创建,旨在解决Hadoop中Writable类型的不足:缺乏语言的可移植性。

1.Avro的特点

  1. 独立于编程语言。
  2. 丰富的数据结构类型。
  3. 快速的可压缩的二进制数据形式。(对MapReduce的输入格式至关重要。)
  4. Avro可用于RPC。

2.Avro数据类型和Schema

2-1.基本数据类型

类型 描述 schema示例
null 空值 “null”
boolean 二进制值 “boolean”
int 32位有符号整数 “int”
long 64位有符号整数 “long”
float 单精度(32位)IEEE 754浮点数 “float”
double 双精度(32位)IEEE 754浮点数 “double”
bytes 8位无符号字节序列 “bytes”
string Unicode字符序列 “string”

2-2.Avro复杂数据类型

类型 描述 schema实例
array 已排序的对象集合。 {
“type”: “array”,
“items”: “long”
}
map 未排序的键值对。key必须是string
record 一个任意类型的命名字段集合 {
“type”: “record”,
“name”:“WeatherRecord”,
“doc”: “A weather reading.”,
“fields”: [
{
“name”:“year”,
“type”:“int”
},
{
“name”:“temperature”,
“type”: “int”
},
{
“name”:“stationId”,
“type”: “string”
]
}
ENUM 枚举(一组命名值的集合) {
“type”: “enum”,
“name”:“Cutlery”,
“doc”: “An eating utensil.”,
“symbols”:
[“KNIFE”, “FORK”,“SPOON”]
}
fixed 一组固定数量的8位无符号字节 {
“type”: “fixed”,
“name”:“Md5Hash”,
“size”: 16
}
union schema的并集(并集可以用JSON数组表示) [
“null”,
“string”,
{
“type”: “map”,
“values”: “string”
}
]

2-3.Avro数据的读/写

2-3-1.基于schema的Avro数据读/写

首先我们需要创建一个以.avsc后缀结尾的schema文件,这里我们命名为StringPair.avsc

{
	"namespace":"schemas",
	"name":"StringPair",
	"type":"record",
	"doc":"This is a String Pair.",
	"fields":[
		{"name":"left", "type":"string"},
		{"name":"right", "type":"string"}
	]
}
namespace:可以理解为,文件创建后所在的文件夹名。
name:为文件名称,即类名称,(所以要按照java类的命名规范来命名)。
type:为Avro数据类型。
doc:为文档描述。
fields:为属性信息。

下面就是具体的demo代码:

package avro;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import org.apache.avro.Schema;
import org.apache.avro.Schema.Parser;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.io.Decoder;
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.io.Encoder;
import org.apache.avro.io.EncoderFactory;

public class AvroDemo {
	
	private ByteArrayOutputStream out = new ByteArrayOutputStream();
	
	private Parser parser = new Schema.Parser();
	
	public static void main(String[] args) throws IOException {
		AvroDemo demo = new AvroDemo();
		demo.writeAvroData();
		demo.readAvroData();
	}
	
	public void writeAvroData() throws IOException {
		Parser parser = new Schema.Parser();
		Schema schema = parser.parse(getClass().getResourceAsStream("/StringPair.avsc"));
		GenericRecord datum = new GenericData.Record(schema);
		datum.put("left", "L");
		datum.put("right", "R");
		DatumWriter<GenericRecord> writer = new GenericDatumWriter<GenericRecord>(schema);
		Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
		writer.write(datum, encoder);
		encoder.flush();
		out.close();
	}
	
	public void readAvroData() throws IOException {
		Schema schema = parser.parse(getClass().getResourceAsStream("/StringPair.avsc"));
		ByteArrayInputStream input = new ByteArrayInputStream(out.toByteArray());
		GenericDatumReader<GenericRecord> reader = new GenericDatumReader<GenericRecord>(schema);
		Decoder decoder = DecoderFactory.get().binaryDecoder(input, null);
		GenericRecord result = reader.read(null, decoder);
		input.close();
		System.out.println(result.get("left"));
		System.out.println(result.get("right"));
	}
}

2-3-2.基于schema生成的Avro文件,进行的数据读/写

我们可以通过schema生成StringPair类,有几种方法可以实现,这里以Maven为例。
首先,要通过配置pom.xml文件,得到我们需要的jar(Maven会根据配置信息自动下载所需要的jar文件,具体信息如下)。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>learn.hadoop.eco</groupId>
  <artifactId>HadoopEcoTest</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
  	<dependency>
  		<groupId>org.apache.avro</groupId>
  		<artifactId>avro</artifactId>
  		<version>1.9.0</version>
  	</dependency>
  </dependencies>
  <build>
  	<plugins>
  		<plugin>
  			<groupId>org.apache.avro</groupId>
  			<artifactId>avro-maven-plugin</artifactId>
  			<version>1.9.0</version>
  			<executions>
  				<execution>
  					<phase>generate-sources</phase>
  					<goals>
  						<goal>schema</goal>
  					</goals>
  				</execution>
  			</executions>
  			<configuration>
  			    <!-- 此处的${project.basedir}为项目路径,之后的路径为你自己的目录结构,具体看下面图片-->
  				<sourceDirectory>${project.basedir}/src/main/resources</sourceDirectory>
  				<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
  			</configuration>
  		</plugin>
  		<plugin>
  			<groupId>org.apache.maven.plugins</groupId>
  			<artifactId>maven-compiler-plugin</artifactId>
  			<configuration>
  				<source>1.8</source>
  				<target>1.8</target>
  			</configuration>
  		</plugin>
  	</plugins>
  </build>
</project>

在这里插入图片描述
※注这里配置完成之后会报错,把光标移动到报错的行,按下Ctrl+F1(即可提示快速解决方案↓),任意选一个解决方案,这里我选的第一个解决方案。
在这里插入图片描述
选择解决方案后,pom.xml文件中会自动添加下列内容,文件不再报错,但是如果项目依然报错,解决方法:项目右键→Maven→Upadte Project ...
在这里插入图片描述
如下状态,点击ok即可。
在这里插入图片描述
接下来运行Maven Build生成类文件(Goals为Compile),点击Run,待运行结束后,文件被生成。
在这里插入图片描述
在这里插入图片描述
接下来我们将上面的Demo稍作改动,来实现用schema生成的类进行Avro数据的读/写。

package avro;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import org.apache.avro.io.Decoder;
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.io.Encoder;
import org.apache.avro.io.EncoderFactory;
import org.apache.avro.specific.SpecificDatumReader;
import org.apache.avro.specific.SpecificDatumWriter;

import schemas.StringPair;

public class AvroDemo2 {
	
	private ByteArrayOutputStream out = new ByteArrayOutputStream();
	
	public static void main(String[] args) throws IOException {
		AvroDemo2 demo = new AvroDemo2();
		demo.writeAvroData();
		demo.readAvroData();
	}
	
	public void writeAvroData() throws IOException {
		StringPair datum = new StringPair();
		datum.setLeft("Left");
		datum.setRight("Right");
		SpecificDatumWriter<StringPair> writer = new SpecificDatumWriter<StringPair>(StringPair.class);
		Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
		writer.write(datum, encoder);
		encoder.flush();
		out.close();
	}
	
	public void readAvroData() throws IOException {
		ByteArrayInputStream input = new ByteArrayInputStream(out.toByteArray());
		SpecificDatumReader<StringPair> reader = new SpecificDatumReader<StringPair>(StringPair.class);
		Decoder decoder = DecoderFactory.get().binaryDecoder(input, null);
		StringPair result = reader.read(null, decoder);
		System.out.println(result.getLeft());
		System.out.println(result.getRight());
		input.close();
	}
}

3.Avro数据文件

Avro的数据文件主要用于存储Avro对象序列。这与Hadoop的SequenceFile非常相似,他们之间的最大区别在于,Avro数据文件主要是面向跨语言使用而设计的,因此,我们可以用一种语言写入文件,并用另外一种语言来读取文件。(我们这里以Python为例)

3-1.Python写入 & Java读取

首先,我们需要安装Python,可以自行yum或者直接下载解压版,然后要为Python安装avro执行如下命令即可:

easy_install avro

接下来,我们来写一个python脚本,来创建avro数据文件。

import string
import sys

from avro import schema
from avro import io
from avro import datafile

if __name__ == '__main__':
  if len(sys.argv) != 2:
    sys.exit('Usage: %s <data_file>' % sys.argv[0])
  avro_file = sys.argv[1]
  #注意,avro是以二进制存储文件的
  writer = open(avro_file, 'wb')
  datum_writer = io.DatumWriter()
  schema_object = schema.parse(' \
  { "type": "record", \
    "name": "StringPair", \
    "doc": "a pair of strings.", \
    "fields": [ \
      {"name": "left", "type": "string"}, \
      {"name": "right", "type": "string"} \
    ] \
  }')
  dfw = datafile.DataFileWriter(writer, datum_writer, schema_object)
  for line in sys.stdin.readlines():
    (left, right) = string.split(line.strip(), ',')
    dfw.append({'left':left, 'right':right});
  dfw.close()

上面的脚本执行后,可以在控制台输出left和right的值,用逗号分隔,退出时按Ctrl+D键。
在这里插入图片描述
接下来我们在java端写一个demo,来读出上面用python脚本生成的avro数据文件。

package avro;

import java.io.File;
import java.io.IOException;

import org.apache.avro.file.DataFileReader;
import org.apache.avro.specific.SpecificDatumReader;

import schemas.StringPair;

public class AvroDemo3 {
	
	public static void main(String[] args) throws IOException {
		AvroDemo3 demo = new AvroDemo3();
		demo.readAvroFile();
	}
	
	public void readAvroFile() throws IOException {
		File file = new File("/home/hadoop/demo/python/pairs.avro");
		SpecificDatumReader<StringPair> reader = new SpecificDatumReader<StringPair>(StringPair.class);
		DataFileReader<StringPair> file_reader = new DataFileReader<StringPair>(file, reader);
		StringPair result = null;
		while(file_reader.hasNext()) {
			result = file_reader.next(result);
			System.out.println("left : " + result.getLeft() + ", right : " + result.getRight());
		}
		file_reader.close();
	}
}

输出结果如下:
在这里插入图片描述

3-2.Java写入 & Python读取

这次呢,我们来写一个和上面过程相反的例子,这次我们通过java代码生成文件,再通过python来读取文件内容。

package avro;

import java.io.File;
import java.io.IOException;

import org.apache.avro.file.DataFileWriter;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.specific.SpecificDatumWriter;

import schemas.StringPair;

public class AvroDemo4 {
	
	public static void main(String[] args) throws IOException {
		AvroDemo4 demo = new AvroDemo4();
		demo.writeAvroFile();
	}
	
	public void writeAvroFile() throws IOException {
		File file = new File("/home/hadoop/demo/java/pairs_java.avro");
		DatumWriter<StringPair> writer = new SpecificDatumWriter<StringPair>(StringPair.class);
		DataFileWriter<StringPair> file_writer = new DataFileWriter<StringPair>(writer);
		StringPair datum = new StringPair();;
		file_writer.create(datum.getSchema(), file);
		for(int i =1; i < 10; i++) {
			datum.setLeft("left" + i);
			datum.setRight("right" + i);
			file_writer.append(datum);
		}
		file_writer.flush();
		file_writer.close();
	}
}

文件已经生成,接下来我们写一个python脚本来读取刚才用java程序生成的avro数据文件。

import sys

from avro import schema
from avro import io
from avro import datafile

from json import dumps

if __name__ == '__main__':
  if len(sys.argv) != 2:
    sys.exit('Usage: %s <data_file>' % sys.argv[0])
  avro_file = sys.argv[1]
  reader = open(avro_file, 'rb')
  schema_object = schema.parse(' \
  { "type": "record", \
    "name": "StringPair", \
    "doc": "a pair of strings.", \
    "fields": [ \
      {"name": "left", "type": "string"}, \
      {"name": "right", "type": "string"} \
    ] \
  }')
  datum_reader = io.DatumReader(schema_object)
  dwr = datafile.DataFileReader(reader, datum_reader)
  for line in dwr:
    print dumps(line)
  dwr.close()

在这里插入图片描述

4.Schema解析

Avro的Schema解析非常灵活,读和写可以用不同的schema。

4-1.reader比write所使用的schema的字段多。

reader比writer所使用的schema字段数多

在刚才的StringPair.avsc的基础上,我们再创建一个新的schema文件,这次多添加 一个字段。

{
	"namespace":"schemas",
	"name":"StringPairAdd",
	"type":"record",
	"doc":"This is another String Pair.",
	"fields":[
		{"name":"left", "type":"string"},
		{"name":"right", "type":"string"},
		{"name":"middle", "type":"string", "default":"This is middle"}
	]
}

在之前的AvroDemo类的基础上,我们稍作修改,来看一下效果。

package avro;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import org.apache.avro.Schema;
import org.apache.avro.Schema.Parser;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.io.Decoder;
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.io.Encoder;
import org.apache.avro.io.EncoderFactory;

public class AvroDemo5 {
	
	private ByteArrayOutputStream out = new ByteArrayOutputStream();
	
	private Parser parser = new Schema.Parser();
	
	public static void main(String[] args) throws IOException {
		AvroDemo5 demo = new AvroDemo5();
		demo.writeAvroData();
		demo.readAvroData();
	}
	
	public void writeAvroData() throws IOException {
		Parser parser = new Schema.Parser();
		Schema schema = parser.parse(getClass().getResourceAsStream("/StringPair.avsc"));
		GenericRecord datum = new GenericData.Record(schema);
		datum.put("left", "L");
		datum.put("right", "R");
		DatumWriter<GenericRecord> writer = new GenericDatumWriter<GenericRecord>(schema);
		Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
		writer.write(datum, encoder);
		encoder.flush();
		out.close();
	}
	
	public void readAvroData() throws IOException {
		Schema schema_write = parser.parse(getClass().getResourceAsStream("/StringPair.avsc"));
		Schema schema_read = parser.parse(getClass().getResourceAsStream("/StringPairAdded.avsc"));
		ByteArrayInputStream input = new ByteArrayInputStream(out.toByteArray());
		GenericDatumReader<GenericRecord> reader = new GenericDatumReader<GenericRecord>(schema_write, schema_read);
		Decoder decoder = DecoderFactory.get().binaryDecoder(input, null);
		GenericRecord result = reader.read(null, decoder);
		System.out.println(result.get("left"));
		System.out.println(result.get("right"));
		System.out.println(result.get("middle"));
		input.close();
	}
}

得到输出结果如下:
在这里插入图片描述
※注:这里读取时用的schema比写入时新,所以读取时的schema(StringPairAdded.avsc)中新添加的字段一定要配置default属性,否则会报错。
在这里插入图片描述

4-2.reader比writer所使用的schema字段数少。

这种情况常被称作投影
我们再创建一个schema,这次在StringPari.avsc的基础上我们去掉一个字段。

{
	"namespace":"schemas",
	"name":"ProjectedStringPair",
	"type":"record",
	"doc":"This is a Project String Pair.",
	"fields":[
		{"name":"right", "type":"string"}
	]
}

接下来是Java代码。

package avro;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import org.apache.avro.Schema;
import org.apache.avro.Schema.Parser;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.io.Decoder;
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.io.Encoder;
import org.apache.avro.io.EncoderFactory;

public class AvroDemo6 {
	
	private ByteArrayOutputStream out = new ByteArrayOutputStream();
	
	private Parser parser = new Schema.Parser();
	
	public static void main(String[] args) throws IOException {
		AvroDemo6 demo = new AvroDemo6();
		demo.writeAvroData();
		demo.readAvroData();
	}
	
	public void writeAvroData() throws IOException {
		Parser parser = new Schema.Parser();
		Schema schema = parser.parse(getClass().getResourceAsStream("/StringPair.avsc"));
		GenericRecord datum = new GenericData.Record(schema);
		datum.put("left", "L");
		datum.put("right", "R");
		DatumWriter<GenericRecord> writer = new GenericDatumWriter<GenericRecord>(schema);
		Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
		writer.write(datum, encoder);
		encoder.flush();
		out.close();
	}
	
	public void readAvroData() throws IOException {
		Schema schema_write = parser.parse(getClass().getResourceAsStream("/StringPair.avsc"));
		Schema schema_read = parser.parse(getClass().getResourceAsStream("/ProjectedStringPair.avsc"));
		ByteArrayInputStream input = new ByteArrayInputStream(out.toByteArray());
		GenericDatumReader<GenericRecord> reader = new GenericDatumReader<GenericRecord>(schema_write, schema_read);
		Decoder decoder = DecoderFactory.get().binaryDecoder(input, null);
		GenericRecord result = reader.read(null, decoder);
		System.out.println(result.get("left"));
		System.out.println(result.get("right"));
		System.out.println(result.getSchema());
		input.close();
	}
}

执行结果如下:
在这里插入图片描述
可以看到,left值为null,schema的信息中也没有left字段。

5.其他

除了上述的内容之外,Avro还支持别名,排序,MapReduce等等功能,这里值做简单介绍,不再一一例举。

5-1.别名

Avro可以允许读操作时,与写入操作时不同的字段名称来读取同一列的内容。
※注:为读操作指定别名后,就无法在读取时使用写操作时的列名称。(如下,读取时只能用first和second字段,而不再用left和right)

{
	"type": "record",
	"name": "StringPair",
	"doc": "A pair of strings with aliased field names.",
	"fields": [
		{"name": "first", "type": "string", "aliases": ["left"]},
		{"name": "second", "type": "string", "aliases": ["right"]}
	]
}

5-2.排序

Avro的排序可通过在schema中为字段指定order属性来控制,它有三个值:
①ascending(默认值):升序
②descending:降序
③ignore:排序的时候忽略此字段

{
	"type": "record",
	"name": "StringPair",
	"doc": "A pair of strings, sorted by right field descending.",
	"fields": [
		{"name": "left", "type": "string", "order": "ignore"},
		{"name": "right", "type": "string", "order": "descending"}
	]
}
发布了62 篇原创文章 · 获赞 3 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Leonardy/article/details/93024620