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的速度(只是簡單測試,所以不公佈結果了)。

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