- 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進行數據存儲時,最好還是需要在一下兩個前提下:
- 數據的量級較大
- 數據結構不太複雜(如嵌套層次太多等)
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的工具:
- 核心部分:Schema Registry – 管理和維護Schema與ID之間的管理
- 序列化部分:聯合與Schema Registry的 Avro Serializer & Avro Deserializer
- 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的速度(只是簡單測試,所以不公佈結果了)。