Avro(非RPC部分)个人知识总结与感悟

  • Avro core中的基本代码结构
  • Avro 之 可切分 & 可压缩
  • Avro 与 大数据存储
  • Avro在Kafka中的应用 之Confluent
  • Avro在Kafka中的应用 之Flume&Flink
    在这里插入图片描述

Avro core中的基本代码结构

首先说明一下core源码中的package目录

  • Record类: Avro提供的 【三种Record操作方式】
    • generic: 负责基于Schema(String)的映射操作
    • specific: 负责基于代码生成类的映射操作
    • reflect: 负责基于java反射的映射操作(这部分没看,不清楚)
  • 序列化类:
    • io: 负责基础数据类型 & 复合数据类型的io操作内容 【建议阅读源码】
    • file: 负责Header & Codec的内容(可切分 & 可压缩的体现) 【建议阅读源码】
  • 其他类: util & default(Schema & Protocol)
    • Schema: 负责将avsc文件进行解析处理
    • Protocol: 负责将avpr文件进行解析处理

Avro 之 可切分 & 可压缩

private static byte[] generateSync() {
  // return md5(UUID + "@" + currentTime)
}

private void writeBlock() throws IOException {
  if (blockCount > 0) {
    bufOut.flush();
    ByteBuffer uncompressed = buffer.getByteArrayAsByteBuffer();
    DataBlock block = new DataBlock(uncompressed, blockCount);
    block.setFlushOnWrite(flushOnEveryBlock);
    block.compressUsing(codec);
    block.writeBlockTo(vout, * sync *);
    buffer.reset();
    blockCount = 0;
  }
}

@Override // 如何实现随机position从新定位
public void sync(long position) throws IOException {
  seek(position);
  // work around an issue where 1.5.4 C stored sync in metadata
  if ((position == 0) && (getMeta("avro.sync") != null)) {
    initialize(sin);                            // re-init to skip header
    return;
  }
  try {
    int i=0, b;
    InputStream in = vin.inputStream();
    vin.readFixed(syncBuffer); // 读取 sync长度 字节数
  do {
    int j = 0;
    for (; j < SYNC_SIZE; j++) {
      if (getHeader().sync[j] != syncBuffer[(i+j)%SYNC_SIZE])
        break;
    }
    if (j == SYNC_SIZE) {                       // matched a complete sync
      blockStart = position + i + SYNC_SIZE;
      return;
    }
    b = in.read();
    syncBuffer[i++%SYNC_SIZE] = (byte)b;
  } while (b != -1);
  } catch (EOFException e) {
    // fall through
  }
  // if no match or EOF set start to the end position
    blockStart = sin.tell();
  //System.out.println("block start location after EOF: " + blockStart );
    return;
}

以上源码内容说明:

  • sync marker 生成:md5(UUID + “@” + currentTime)
  • sync marker 写入 meta
  • sync marker 写入 Block:对整个Block进行压缩再写入;并在尾部写入sync marker
  • sync 定位:指定任意position,即可找到该position之后的,并最近的一个sync marker的位置
    在这里插入图片描述

Avro 与 大数据存储

个人认为首先,Avro这种序列化方式在大数据存储中并不占有什么优势,因为考虑到大数据中主要的内容是 数据分析 ,所以,相对来说 列式存储 会更加的合适,而不是像Avro这样的行式存储。
列式存储中,像Parquet、ORC等都能在 空间利用率 上有良好的表现;不仅如此,列式存储还能为上游提供一定的 谓词下推 的语义。这些多是Avro这样的行式存储所不能提供的(准确的说,Avro的设计定位就不在这里)。
所以,Avro与Parquet和ORC等就没有对比的必要,而与之有对比价值的通常是JSON和Protobuf。
从序列化结果上看(不考虑序列化速度、序列化大小等因素),仅观察Schema与Value的映射关系,会有下面的发现:

  • Json: Schema: Value = 1: 1
  • Protobuf: Schema: Value = 0: M
  • Avro: Schema: Value = 1: M

显然 相对于 结构化数据 的存储,Json会太过冗余,Protobuf则需要单独对Schema进行额外维护,Avro则非常合适。

虽然如此,但是我个人还是表示,在使用Avro进行数据存储时,最好还是需要在一下两个前提下:

  1. 数据的量级较大
  2. 数据结构不太复杂(如嵌套层次太多等)

Avro在Kafka中的应用 之Confluent

像上面说的一样,Avro中Schema: Value = 1: M,但是在Kafka中,Record是基本单位。也就是说,

  • 如果在Kafka中,Record是JSON格式存储的,则Record的解析就不依赖于其他Context
  • 如果在Kafka中,Record是Avro或者Protobuf格式存储的,则Record的解析就依赖于其他Context

又由于Kafka是大数据场景下的MQ,可想而知,数据的量级不小,所以存储问题是使用该服务时必要考虑的问题。所以在Kafka中使用JSON,不太合适。

其次如果在Kafka中使用Avro或者Protobuf,则不得不考虑元数据信息管理的问题,即Schema管理。那么如何管理?Confluent公司给出了他们的方案,同时他们的选择是Avro。

Confluent公司提供了一个开源的服务(包) – Schema Registry,同时为Kafka提供了定制化的Avro序列化工具包。
思路和过程如下:

  • Schema Registry 是一个常驻服务,对外提供REST API,即外界使用无需依赖和被侵入。
  • 向Kafka中写入Avro格式的数据时 会有这些相关操作
    • 向Schema Registry发起Schema注册请求,并以ID作为响应(Schema Registry管理维护Schema与ID的映射关系)
    • 将数据按照Avro的方式进行序列化,并在最开头填上ID标识
  • 从Kafka中消费Avro格式的数据时 会有这些相关操作
    • 首先,读取byte[] 的开头部分 – ID,并根据ID信息请求Schema Registry以获取对应的Schema信息
    • 通过Schema解析剩余的Value内容

从以上过程可以看出,Confluent这样的设计,需要依赖于Avro的Generic的功能,即直接依赖于Schema String就可以进行序列化和反序列化的操作,而无需强制要求代码生成,这也是为何不使用Protobuf的原因。

总的来说,Confluent公司提供了以下关于Kafka存储Avro的工具:

  1. 核心部分:Schema Registry – 管理和维护Schema与ID之间的管理
  2. 序列化部分:联合与Schema Registry的 Avro Serializer & Avro Deserializer
  3. Connector部分:联合与Schema Registry的 包装Avro Deserializer的connect插件

Avro在Kafka中的应用 之Flume&Flink

近期需求 – 需要对IoT的数据进行实时分析。整体设计
在这里插入图片描述

简单说明:

  • Flume:将格式化(tab符分割)的IoT日志文件按照Avro进行序列化采集
  • Flink:数据计算引擎,以SqlClient方式进行使用,相对来说只需要SQL和配置文件定义即可

其中Flume中自定义Avro Interceptor了,即数据通过Source采集之后,立即按照指定的Schema进行序列化(Schema管理在Schema Manager),之后传向Channel,最后至Kafka Sink。
另外Flink中的SqlClient(Flink-1.6.1),当前对Connector的支持较为有限,对于HDFS的Connector虽然已经提供,但是目前还没有集成至SqlClient中(其实就是缺少了Factory),所以也进行了部分调整。
剩余其他都是原生支持模块,可以直接拼接使用。

目前来看,Flume的中Source端(Interceptor其实算Source端)可能存在性能瓶颈,表现形式为:Source,Channel,Sink均单线程下,各端的处理速度均约等于Source的速度。所以个人简单认为Source端的速度限制了Channel和Sink的速度(只是简单测试,所以不公布结果了)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章