《hadoop权威指南》学习笔记-hadoop I/O之Avro

本文是《hadoop权威指南》中关于Avro的学习笔记,重点介绍了Avro如何实现不同语言间的无障碍文件读写。Avro通过生成统一格式确保不同语言间的数据交换,包括模式文件(.avsc)定义数据结构,数据文件(.avro)存储实际数据。内容涵盖内存序列化、反序列化、模式解析以及排序和别名特性。
摘要由CSDN通过智能技术生成

这篇主要讲一讲我看完Avro这部分的心得体会。Avro其实就是为了让不同语言可以无障碍的进行文件读写。也就是说,虽然读写这部分内容(不论在内存还是文件中)的语言各不相同,API也可不相同,但我们总可以通过Avro提供的方式实现这些语言之间的无障碍沟通。主要原理就是,不论什么语言写入的对象最后通过Avro都会生成Avro的统一格式,这样在最底层形成了统一的格式,我们只要给不同的语言以不同的API方式去翻译就可以了(就像英语是全世界的通用语言,各种语言的翻译机制不同,但大家都可以用英语来沟通)。

Avro有两种文件,一种文件是用来指定每条数据(类型)的构建模型的,另一种文件是数据文件,就是存储在指定的类型下具体的数据。前者我们叫做模式文件,后缀为.avsc,后者我们叫做数据文件,后缀为.avro。

下面来介绍一下具体的应用。

一、内存中的序列化和反序列化

下面就是一个模式文件的格式,像C语言中的结构体,只规定了数据的结构,没有具体值

{
    "type": "record",
    "name": "StringPair",
    "doc": "A pair of strings.",
    "fields": [
      {"name": "left", "type": "string"},
      {"name": "right", "type": "string"}
    ]
}

现附上原书的代码,我会删减掉一部分以便与分析:

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;

import org.apache.avro.AvroTypeException;
import org.apache.avro.Schema;
import org.apache.avro.file.DataFileReader;
import org.apache.avro.file.DataFileWriter;
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.DatumReader;
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;
import org.apache.avro.specific.SpecificDatumReader;
import org.apache.avro.specific.SpecificDatumWriter;
import org.apache.avro.util.Utf8;
import org.junit.Ignore;
import org.junit.Test;

public class AvroTest {
  
  @Test
  public void testPairGeneric() throws IOException {
// vv AvroParseSchema
    Schema.Parser parser = new Schema.Parser();
    Schema schema = parser.parse(getClass().getResourceAsStream("StringPair.avsc"));
//初始化一个schema,有了这个类,我们在传值的时候才能和模式文件中的具体位置对应起来
// ^^ AvroParseSchema
    
// vv AvroGenericRecordCreation
    GenericRecord datum = new GenericData.Record(schema);
    datum.put("left", "L");
    datum.put("right", "R");
//GenericDatumWriter需要传递给schema,因为他会随着schema去决定从数据对象来的哪个值应该被输出,说白了这样相当于构成了键值对
// ^^ AvroGenericRecordCreation
    
// vv AvroGenericRecordSerialization
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    DatumWriter<GenericRecord> writer = new GenericDatumWriter<GenericRecord>(schema);
    Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
    writer.write(datum, encoder);
    encoder.flush();//encoder.flush()是刷新序列化工具
    out.close();//out.close()关闭输出流
/**写入内存:
*(1)创建二进制输出流,
*(2)通过schema创建GenericRecord对象,并按照键值对应的关系写入数据
*(3)创建DantumWriter<GenericRecord>对象与schema对象绑定,这样才能使writer对象知道哪个键对应哪个值
*(4)创建Encoder对象与输出流绑定用于解析
*(5)利用writer对象把datum中的数据翻译成encoder可以理解的类型,然后由encoder写到输出流*/
// ^^ AvroGenericRecordSerialization
    
// vv AvroGenericRecordDeserialization
    DatumReader<GenericRecord> reader = new GenericDatumReader<GenericRecord>(schema);
    Decoder decoder = DecoderFactory.get().binaryDecoder(out.toByteArray(), null);
    GenericRecord result = reader.read(null, decoder);
    assertThat(result.get("left").toString(), is("L"));
    assertThat(result.get("right").toString(), is("R"));
// ^^ AvroGenericRecordDeserialization
/**读步骤:
*(1)建立一个DatumReader<GenericRecord>对象和schema联系起来
*(2)创建Decoder对象联系输出流
*(3)通过Decoder对象把内容读取到DatumReader<GenericRecord>对象中,创建GenericRecord对象*/

  @Test
  public void testPairSpecific() throws IOException {
    
// vv AvroSpecificStringPair
   StringPair datum = new StringPair();
    datum.left = "L";
    datum.right = "R";/*]*/

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    /*[*/DatumWriter<StringPair> writer =
      new SpecificDatumWriter<StringPair>(StringPair.class);/*]*/
    Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
    writer.write(datum, encoder);
    encoder.flush();
    out.close();
    
    DatumReader<StringPair> reader =
      new SpecificDatumReader<StringPair>(StringPair.class);/*]*/
    Decoder decoder = DecoderFactory.get().binaryDecoder(out.toByteArray(), null);
    StringPair result = reader.read(null, decoder);
    assertThat(result./*[*/left/*]*/.toString(), is("L"));
    assertThat(result./*[*/right/*]*/.toString(), is("R"));
// ^^ AvroSpecificStringPair
  }
/**这个例子就是根据java类来创建一个实例用来代替GenericRecord对象,再使用SpecificDatumWriter类来把实例的内容写到数据流中,使用SpecificReader类来读取数据,放*在这里的作用就是表明不仅可以通过模式文件来构建模式,也可以直接以java类的形式构建,而且写到内存中的形式和先前的例子一样,同样可以被其他语言读取*/
  @Test
  public void testDataFile() throws IOException {
    Schema schema = new Schema.Parser().parse(getClass().getResourceAsStream("StringPair.avsc"));
    
    GenericRecord datum = new GenericData.Record(schema);
    datum.put("left", "L");
    datum.put("right", "R");

// vv AvroDataFileCreation
    File file = new File("data.avro");
    DatumWriter<GenericRecord> writer = new GenericDatumWriter<GenericRecord>(schema);
    DataFileWriter<GenericRecord> dataFileWriter =
      new DataFileWriter<GenericRecord>(writer);
    dataFileWriter.create(schema, file);
    dataFileWriter.append(datum);
    dataFileWriter.close();
/**(1)创建数据文件路径
*(2)创建DatumWriter实例
*(3)通过DatumWriter实例创建DataFileWriter实例
*(4)利用DataFileWriter.create实例来创建数据文件
*(5)利用DataFileWriter.append把Avro对象写入数据文件*/

    
// vv AvroDataFileGetSchema
    DatumReader<GenericRecord> reader = new GenericDatumReader<GenericRecord>();
    DataFileReader<GenericRecord> dataFileReader =
      new DataFileReader<GenericRecord>(file, reader);
    assertThat("Schema is the same", schema, is(dataFileReader.getSchema()));
// ^^ AvroDataFileGetSchema
/**读取数据对象文件的时候与写相似,只是不用指定模式,
(1)创建DatumTeader实例
(2)创建一个DataFileReader为这个文件添加一个reader*/
// vv AvroDataFileRead
    assertThat(dataFileReader.hasNext(), is(true));
    GenericRecord result = dataFileReader.next();
    assertThat(result.get("left").toString(), is("L"));
    assertThat(result.get("right").toString(), is("R"));
    assertThat(dataFileReader.hasNext(), is(false));
// ^^ AvroDataFileRead
    
    file.delete();
  }
}


数据文件的开头部分包括Avro模式和同步符,接着就是序列化后的数据块,每一个数据块有同步符来分割。将Avro对象写到数据文件的方法和写到数据流的方法类似。这是我画的数据文件的结构图(很丑):

二、模式的解析

{
    "type": "record",
    "name": "StringPair",
    "doc": "A pair of strings with an added field.",
    "fields": [
     {"name": "left", "type": "string"},
     {"name": "right", "type": "string"},
     {"name": "description", "type": "string", "default": "}
    ]
}

description只是新增的一个字段,这一部分的内容主要是在讲,当读模式和写模式不通的时候该如何处理(毕竟随着新旧版本的更替这是经常发生的)。

 public void testSchemaResolution() throws IOException {
    Schema schema = new Schema.Parser().parse(getClass().getResourceAsStream("StringPair.avsc"));
    Schema newSchema = new Schema.Parser().parse(getClass().getResourceAsStream("NewStringPair.avsc"));

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    
    DatumWriter<GenericRecord> writer = new GenericDatumWriter<GenericRecord>(schema);
    Encoder encoder = EncoderFactory.get().binaryEncoder(out, null /* reuse */);
    GenericRecord datum = new GenericData.Record(schema); // no description
    datum.put("left", "L");
    datum.put("right", "R");
    writer.write(datum, encoder);
    encoder.flush();
    
// vv AvroSchemaResolution
    DatumReader<GenericRecord> reader =
      /*[*/new GenericDatumReader<GenericRecord>(schema, newSchema);/*]*/
    Decoder decoder = DecoderFactory.get().binaryDecoder(out.toByteArray(), null);
    GenericRecord result = reader.read(null, decoder);//由于写入模式在数据文件头就有规定,所以在此可以传入空值
    assertThat(result.get("left").toString(), is("L"));
    assertThat(result.get("right").toString(), is("R"));
    /*[*/assertThat(result.get("description").toString(), is(""));
//这个实例旨在说明当用新的模式去读取旧的模式(写入时的模式)时的做法,新的模式只是比旧的模式多了一个field(即description)
}

当新模式变为

{
"type": "record",
"name": "StringPair",
"doc": "The right field of a pair of strings.",
"fields": [
{"name": "right", "type": "string"}
]
}

相当于减少了一个字段,减少了一个字段后的读取方式和先前还是一样的,故可以判断读取方式都是一样的

三、排序、别名

其实还可以在字段的属性里添加排序好别名

排序

{
"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"}
]
}
作用:排序的作用一贯是map处理完数据后要进行排序
别名

{
    "type":"record",
    "name":"com.sweetop.styhadoop.StringPair",
    "doc":"A pair of strings",
    "fields":[
        {"name":"first","type":"string","aliases":["left"]},
        {"name":"second","type":"string","aliases":["right"]}
    ]
}

作用:,将写的schema的field名字转换成读的schema的field,使用别名schema去读数据,这里不能再用left,right,而要用first,second,参考这篇博客。原书最后还提供了一个Avro在mapreduce应用的实例,以后再添加吧。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值