在之前三期的實時湖倉系列文章中,我們從業務側、產品側、應用側等幾個方向,為大家介紹了實時湖倉方方面面的內容,包括實時湖倉對於企業數字化佈局的重要性以及如何進行實時湖倉的落地實踐等。 本文將從純技術的角度,為大家解析實時湖倉的存儲原理以及生態選型,為企業建設實時湖倉給出技術方面的參考意見。 實時湖倉能 ...
在之前三期的實時湖倉系列文章中,我們從業務側、產品側、應用側等幾個方向,為大家介紹了實時湖倉方方面面的內容,包括實時湖倉對於企業數字化佈局的重要性以及如何進行實時湖倉的落地實踐等。
本文將從純技術的角度,為大家解析實時湖倉的存儲原理以及生態選型,為企業建設實時湖倉給出技術方面的參考意見。
實時湖倉能解決什麼問題?
大部分人可能都會有這樣一個疑問,企業為什麼要引入實時湖倉?
如下圖所示,引入實時湖倉可以降低運維難度,實現低成本統一存儲、中間狀態可查,以及提升開發效率。
實時湖倉能夠在低成本存儲的同時,極大降低數據指標的時延,從傳統的 T+1 的時延,降低到到分鐘級。
實時湖倉解決方案,利用湖存儲的特性和 Flink 的流批計算能力,統一存儲和計算,解決業務對數據時效性高要求的需求。
實時湖倉存儲原理
下文將從大數據常用技術和大數據存儲常用理論兩個方面為大家解析實時湖倉的存儲原理。
大數據常用技術分析
Hive 事務表
Hive 是基於 Hadoop 的一個數據倉庫工具,用來進行數據提取、轉化、載入,這是一種可以存儲、查詢和分析存儲在 Hadoop 中的大規模數據的機制。Hive 數據倉庫工具能將結構化的數據文件映射為一張資料庫表,並提供 SQL 查詢功能,能將 SQL 語句轉變成 MapReduce 任務來執行。
ACID 事務表中會包含三類文件,分別是 base、delta 以及 delete。
Hbase——LSM-Tree模型
LSM-Tree,即日誌結構合併樹(Log-Structured Merge-Tree)是 Google BigTable 和 HBase 的基本存儲演算法。HBase 是基於 LSM-Tree 模型實現的,所有的數據寫入操作都首先會順序寫到日誌 HLog,再寫入 Memstore。當 MemStore 中數據達到指定大小之後再將這些數據批量寫入磁碟,生成一個新的 HFile 文件,這個動作叫 Memstore flush。
LSM 樹架構有如下幾個非常明顯的優勢:
· 這種寫入方式將一次隨機 IO 寫入轉換成一個順序 IO 寫入(HLog 順序寫入)加上一次記憶體寫入(MemStore 寫入),使得寫入性能得到極大提升
· HFile 中 KeyValue 數據需要按照 Key 排序,排序之後可以在文件級別根據有序的 Key 建立索引樹,極大提升數據讀取效率,然而 HDFS 本身只允許順序讀寫,不能更新,因此需要數據在落盤生成 HFile 之前就完成排序工作,MemStore 就是 KeyValue 數據排序的實際執行者
· MemStore 作為一個緩存級的存儲組件,總是緩存著最近寫入的數據,對於很多業務來說,最新寫入的數據被讀取的概率會更大,最典型的比如時序數據,80%的請求都會落到最近一天的數據上,實際上對於某些場景,新寫入的數據存儲在 MemStore 對讀取性能的提升至關重要
· 在數據寫入 HFile 之前,可以在記憶體中對 KeyValue 數據進行很多更高級的優化。比如,如果業務數據保留版本僅設置為1,在業務更新比較頻繁的場景下,MemStore 中可能會存儲某些數據的多個版本,這樣,MemStore 在將數據寫入 HFile 之前實際上可以丟棄老版本數據,僅保留最新版本數據
MemStore 使用數據結構 ConcurrentSkipListMap 來實際存儲 KeyValue,優點是能夠非常友好地支持大規模併發寫入,同時跳躍表本身是有序存儲的,這有利於數據有序落盤,以及有利於提升 MemStore 中的 KeyValue 查找性能。
KeyValue 寫入 MemStore 並不會每次都隨機在堆上創建一個記憶體對象,然後再放到 ConcurrentSkipListMap 中,這會帶來非常嚴重的記憶體碎片,進而可能頻繁觸發 Full GC。HBase 使用 MemStore-Local Allocation Buffer(MSLAB) 機制預先申請一個大的(2M)的 Chunk 記憶體,寫入的 KeyValue 會進行一次封裝,順序拷貝這個 Chunk 中。
這樣,MemStore 中的數據從記憶體 flush 到硬碟的時候,JVM 記憶體留下來的就不再是小的無法使用的記憶體碎片,而是大的可用的記憶體片段。基於這樣的設計思路,MemStore 的寫入流程可以表述為以下3步:
· 檢查當前可用的 Chunk 是否寫滿,如果寫滿,重新申請一個2M的 Chunk
· 將當前 KeyValue 在記憶體中重新構建,在可用 Chunk 的指定 offset 處申請記憶體,創建一個新的 KeyValue 對象
· 將新創建的 KeyValue 對象寫入 ConcurrentSkipListMap 中
Hbase在實現中,是把整個記憶體在一定閾值後,flush 到 disk 中,形成一個 file。這個 file 的存儲也就是一個小的B+樹,因為 Hbase 一般是部署在 HDFS 上,HDFS 不支持對文件的 update 操作,所以 Hbase 整體記憶體 flush,而不是和磁碟中的小樹 merge update。記憶體 flush 到磁碟上的小樹,定期也會合併成一個大樹,整體上 Hbase 就是用了 LSM-Tree 的思路。
因為小樹先寫到記憶體中,為了防止記憶體數據丟失,寫記憶體的同時需要暫時持久化到磁碟,對應了 HBase 的 HLog(WAL)和 MemStore。
MemStore 上的樹達到一定大小之後,需要 flush 到 HRegion 磁碟中(一般是 Hadoop DataNode),這樣 MemStore 就變成了 DataNode 上的磁碟文件 StoreFile,定期 HRegionServer 對 DataNode 的數據做 merge 操作。徹底刪除無效空間,多棵小樹在這個時機合併成大樹,來增強讀性能。
Hudi
● Hudi 表格式
Hudi 表類型定義瞭如何在 DFS 上對數據進行索引和佈局,以及如何在此類組織上實現上述操作和時間軸活動(即如何寫入數據)。同樣,查詢類型定義了底層數據如何暴露給查詢(即如何讀取數據)。
· Table Types:
1)Copy on Write : 使用列式存儲來存儲數據(parquet), 通過在寫入期間執行同步合併來簡單地更新和重現文件
2)Merge on Read : 使用列式存儲(parquet)+ 行式文件(avro)組合存儲數據,更新記錄到增量文件(avro)中,然後進行同步或非同步壓縮來生成新版本的列式文件
· 查詢類型
1)Snapshot Queries: 在此視圖上的查詢可以查看給定提交或壓縮操作時表的最新快照。對於讀時合併表(MOR表) 該視圖通過動態合併最新文件切片的基本文件(例如 parquet)和增量文件(例如 avro)來提供近實時數據集(幾分鐘的延遲);對於寫時複製表(COW 表),它提供了現有 parquet 表的插入式替換,同時提供了插入/刪除和其他寫側功能
2)Incremental Queries: 對該視圖的查詢只能看到從某個提交/壓縮後寫入數據集的新數據,該視圖有效地提供了更改流,來支持增量數據管道
3)Read Optimized Queries: 在此視圖上的查詢將查看給定提交或壓縮操作中數據集的最新快照,該視圖僅將最新文件切片中的基本/列文件暴露給查詢,並保證與非 Hudi 列式數據集相比,具有相同的列式查詢性能
● Hudi 索引
Hudi 通過索引機制,將給定的 hoodie key(record key + partition path)一致映射到 file id,從而提供高效的上傳功能。一旦記錄的第一個版本被寫入文件, record key 和 file group/file id 之間的映射就永遠不會改變。簡而言之,映射的 file group 包含一組記錄的所有版本。
對於 "寫入即複製 "表來說,這樣就不需要 join 整個數據集來確定要重寫哪些文件,從而實現了快速的上傳/刪除操作。
對於 "讀取合併 "表,這種設計允許 Hudi 對任何給定基礎文件需要合併的記錄數量進行約束。具體來說,一個給定的基礎文件只需要針對屬於該基礎文件的記錄更新進行合併。相比之下,沒有索引組件的設計(如 Apache Hive ACID)最終可能需要針對所有傳入的更新/刪除記錄合併所有基礎文件。
目前,Hudi 支持以下索引類型,在 Spark 引擎上預設為 SIMPLE,在 Flink 和 Java 引擎上預設為 INMEMORY。
· BLOOM:採用由 record keys 構建的 Bloom 過濾器,也可選擇使用 record keys 範圍修剪候選文件
· GLOBAL_BLOOM:使用由 record keys 構建的 Bloom 過濾器,也可選擇使用 record keys 範圍剪切候選文件,表中的所有分區都會強制執行 key 唯一性
· SIMPLE(Spark 引擎預設): Spark 引擎的預設索引類型,根據從存儲表中提取的 key 對輸入記錄執行精益 join,在分區內強制執行 key 的唯一性
· GLOBAL_SIMPLE: 根據從存儲表中提取的 key 對輸入記錄執行精簡 join,在表的所有分區中強制執行 key 的唯一性
· HBASE:管理外部 Apache HBase 表中的索引映射
· INMEMORY(Flink 和 Java 的預設值): 在 Spark 和 Java 引擎中使用記憶體 hashmap,在 Flink 中使用 Flink 記憶體 state 索引
· BUCKET:使用桶 hashing 定位包含記錄的文件組,尤其適用於大規模數據量的表,使用 hoodie.index.bucket.engine 選擇桶引擎類型,即如何生成桶
1)SIMPLE(預設):每個分區的文件組使用固定數量的桶,這些桶不能收縮或擴展,這適用於 COW 表和 MOR 表。由於桶的數量不能改變,而且桶和文件組之間設計了一對一的映射,因此這種索引可能不太適合高度傾斜的分區
2)CONSISTENT_HASHING:支持桶的動態數量和桶的大小調整,以適當調整每個桶的大小。這解決了潛在的數據傾斜問題,即數據量大的分區可以動態調整大小,以擁有多個大小合理的桶,而 SIMPLE 桶引擎類型中每個分區的桶數量是固定的,這隻適用於 MOR 表
· RECORD_INDEX:在 Hudi 元數據表中保存 record keys 到位置映射的索引,記錄索引是全局索引,可確保表中所有分區的密鑰唯一性,支持分片,以實現極高的規模,可以擴展 HoodieIndex 來實現自定義索引
Iceberg
Iceberg 在 V1 的格式中定義瞭如何使用不可變類型的文件(Parquet、ORC、AVRO)來管理大型分析型的表,包括元數據文件、屬性、數據類型、表的模式,分區信息,以及如何寫入與讀取。
而在 V2 的格式中,在 V1 的基礎上增加瞭如何通過這些類型的表實現行級別的更新與刪除功能。其最主要的改變是引入了 delete file 記錄需要刪除的行數據,這樣可以在不重寫原有(數據)文件的前提下,實現行數據的更新與刪除。
Paimon
● Paimon 表格式
得益於 LSM 數據結構的追加寫能力,Paimon 在大規模的更新數據輸入的場景中提供了出色的性能。
Paimon 創新地結合了 湖存儲 + LSM + 列式格式(ORC, Parquet),為湖存儲帶來大規模實時更新能力。Paimon 的 LSM 的文件組織結構如下:
· 高性能更新:LSM 的 Minor Compaction,保障寫入的性能和穩定性
· 高性能合併:LSM 的有序合併效率非常高
· 高性能查詢:LSM 的 基本有序性,保障查詢可以基於主鍵做文件的 Skipping
LSM 是一個面向寫友好的格式,它在寫入的時候可以看到整個流程,但它不用理解具體的流程。大致的思路是,寫入發生在 Flink Sink 中,當檢查點到達時,它會對記憶體中的數據進行排序,並將記錄刷新到 Level0 文件中。
得益於 LSM 這種原生非同步的 Minor Compaction,它可以通過非同步 Compaction 落到最下層,也可以在上層就發生一些 Minor 的 Compaction 和 Minor 的合併,這樣壓縮之後它可以保持 LSM 不會有太多的 level。保證了讀取 merge read 的性能,且不會帶來很大的寫放大。
另外,Flink Sink 會自動清理過期的快照和文件,還可以配置分區的清理策略。所以整個 Paimon 提供了吞吐大的 Append 寫,消耗低的局部 Compaction,全自動的清理以及有序的合併。所以它的寫吞吐很大,merge read 不會太慢。
● Paimon Changelog Producers
Changelog-Producer 配置 Changelog 生產的模式:None,Input,Lookup, Full Compaction。
· None:最適合資料庫系統等消費者
· Input:適合完整的 change log,database CDC
· Lookup:沒有完整的 change log,又不想用 normalized operator,commit 前執行
· Full Compaction:比 Lookup 時延更長,通常是多個 commit
大數據存儲中的常用理論
磁碟存儲
在介紹大數據存儲的常用理論之前,先講講磁碟存儲。
· 磁軌移動到對應的磁軌,這是由馬達控制的機械動作,一般為10ms左右(取決於磁頭位置和目標磁軌的距離),這叫做尋道時間
· 等待對應的扇區旋轉到磁頭位置(磁頭是不動的),按現在主流磁碟轉速7200轉/分鐘,旋轉一周需要8.33ms,這叫等待時間
· 對應扇區在磁頭旋轉而過,數據就被讀寫完成了,一般一個磁軌又63個扇區,一個扇區掠過磁頭的時間為 8.33ms/63=0.13ms,我們叫它傳輸時間
· 存取時間 = 尋道時間(t1) + 等待時間(t2) + 傳輸時間(t3)
● 希捷硬碟
7200轉、256MB緩存、SAS 12Gbps介面,最大持續傳輸速度524MB/s,隨機4K QD16讀取為304 IOPS、隨機4K QD16寫入最大448 IOPS,平均延遲4.16ms,平均運行功耗12W左右。
● 固態硬碟
在固態硬碟中,4K 是最小的讀寫單元固態硬碟。例如,如果我們需要寫入 2K 的數據,我們實際上必須寫入 4K;如果我們需要寫入 13K 的數據,就必須寫入 16K 的數據(這裡不考慮寫入放大)。
對於固態硬碟,順序讀的速度仍然能達到隨機讀的3倍左右。但是隨機寫還是順序寫,差別不大。
行式存儲/列式存儲
● 行式存儲
· 數據是按行存儲的
· 沒有建立索引的查詢將消耗很大的 IO
· 建立索引和視圖需要花費一定的物理空間和時間資源
· 面對大量的查詢,複雜的查詢資料庫必須被大量膨脹才能滿足性能需求
● 列式存儲
· 數據是按列存儲的,每一列單獨存放
· 只訪問查詢涉及的列大量降低系統 IO
· 數據類型一致,數據特征相似高效的壓縮
行式存儲和列式存儲的對比如下:
Merge-On-Read & Copy On Write
● Merge-On-Read (MOR)
· 最適合頻繁寫入/更新的表
· 使用讀取時合併時,不會重寫文件,而是將更改寫入新文件
· 當讀取數據時,更改將應用或合併到原始數據文件中,以在處理過程中形成數據的新狀態
● Copy-On-Write (COW)
· 寫入時複製(COW),最適合頻繁讀取、不頻繁寫入/更新或大批量更新的表
· 使用 COW 時,當對某一行或多行進行刪除或更新時,包含這些行的數據文件會被覆制,但新版本包含更新的行,這會導致寫入速度變慢,取決於有多少數據文件必須重寫,可能會導致併發寫入發生衝突,並可能超過重試次數而失敗
· 如果要更新大量記錄,COW 是理想的選擇,但是,如果只更新幾行,則仍需重寫整個數據文件,這就使得小規模或頻繁的更改代價高昂
· 在讀取方面,COW 是理想的選擇,因為讀取不需要額外的數據處理,讀取查詢有很大的文件可以讀取,吞吐量很高
Merge-On-Read 和 Copy On Write 的對比如下:
WAL&LSM
在 RDBMS 中我們需要B+樹(或者廣義地說,索引),一句話總結,減少尋道時間。在存儲系統中廣泛使用的 HDD 是磁性介質+機械旋轉的,這就使得其順序訪問較快而隨機訪問較慢。使用B+樹組織數據可以較好地利用 HDD 的這種特點,其本質是多路平衡查找樹。
B+樹最大的性能問題是會產生大量的隨機 IO,隨著新數據的插入,葉子節點會慢慢分裂,邏輯上連續的葉子節點在物理上往往不連續,甚至分離的很遠,但做範圍查詢時,會產生大量讀隨機 IO。
為了剋服B+樹的弱點,引入了 LSM 樹的概念,即 Log-Structured Merge-Trees。
Delta Store
基本思想是犧牲寫入性能,換取更高的讀性能。當獲取 CDC 數據後,通過主鍵索引,可以定位到這條記錄原來所在的位置、文件(或者 Block),然後在這個 Block 旁邊放一個 Delta Store,用來保存對於這個 Block 的增量修改。這個 Delta Store 里保存的不是主鍵而是數據記錄在 Block 里的行號(RowId)。
查詢時讀到原始 Block,然後根據 RowId 與 Delta 數據進行合併更新並返回最新數據給上層引擎。由於 Delta 數據是按行號組織的,與 Merge-on-Read 按照 Key 進行合併比,查詢性能好很多。不過這種方式會破壞二級索引,因為對 Block 做修改後,他的索引相當於失效了,想要在更新後再維護索引複雜度會很高。
這種方式寫入性能差(要查詢索引,定位原數據),但讀取的性能好很多。另外因為引入了主鍵索引和 Delta Store,複雜性也較高。
Delete-and-Insert
思路也是犧牲部分寫性能,極大地優化讀的性能。原理也是引入主鍵索引,通過主鍵索引定位原來這條記錄所在位置,找到記錄後只需要給這條記錄打個刪除標記(Delete Bitmap),表示這條記錄是被刪除的,然後所有其它 update 記錄可以當成新增數據插入到新的 Block 裡面。
這樣的好處是讀取時直接把所有的 Block 都可以並行載入,然後只需要根據 Delete Bitmap 標記過濾已經刪除的記錄。
StarRocks 新的支持實時更新的 Primary Key 表就是用到這個 Delete+Insert 的方式實現的,另外還有阿裡雲的 ADB 和 Hologres 也用到在這種方式。
數據湖的一致性
● Flink 二階段提交
● 對比 MySQL 二階段提交
因為最開始 MySQL 里並沒有 InnoDB 引擎。MySQL 自帶的引擎是 MyISAM,但是 MyISAM 沒有 crash-safe 的能力,binlog 日誌只能用於歸檔,而 InnoDB 是另一個公司以插件形式引入 MySQL 的。既然只依靠 binlog 是沒有 crash-safe 能力的,所以 InnoDB 使用另外一套日誌系統,也就是 redo log 來實現 crash-safe 能力。
假設執行一條 SQL 語句:update T set c=c+1 where ID=2;
· 時刻 A Crash
也就是寫入 redo log 處於 prepare 階段之後,寫 binlog 之前,發生了崩潰(crash),由於此時 binlog 還沒寫,redo log 也還沒提交,所以崩潰恢復的時候,這個事務會回滾。
· 時刻 B Crash
看一下崩潰恢復時的判斷規則:
1)如果 redo log 裡面的事務是 完整的,也就是已經有了 commit 標識,則直接提交
2)如果 redo log 裡面的事務只有完整的 prepare,則判斷對應的事務 binlog 是否存在並完整:
a. 如果是,則提交事務;
b. 否則,回滾事務。
這裡,時刻 B 發生 crash 對應的就是 2(a) 的情況,崩潰恢復過程中事務會被提交。
實時湖倉生態選型
在介紹實時湖倉生態選型之前,先看看流計算 + 湖存儲的歷史和發展。
· Storm : 流計算 += 不准確的實時預處理
· Spark : 流計算 += Mini-Batch 預處理
· Flink + HBase/Redis/Mysql : 流計算 += 準確的實時預處理
· Flink + OLAP : 流計算 += 實時數倉,預處理和成本的權衡,高性能 OLAP 帶來了一定的靈活度
· Flink + 數據湖 : 流計算 += 離線數倉部分實時化
· Flink + 流式數據湖 : 流計算 += CDC 流式增量計算,解決更多痛點
· 未來 : Streaming Lakehouse,通用的 Lakehouse 架構
計算
● Spark(Structured Streaming)、Flink
Spark 流計算國外用的比較多,Spark RDD/Dataset 相關 API;Flink 流計算國內生態社區比較完善,絕大部分需求可以直接使用 SQL 完成。
●Trino、Doris、StarRocks
· Trino
1)優點:純 Java 項目;代碼質量較好;Hive 優化細節處理比較完善
2)缺點:執行性能比向量化引擎低;聯邦查詢較慢(缺少自己的元數據管理)
· Doris/StarRocks
1)優點:向量化引擎執行效率高;國內社區活躍;功能迭代快
2)缺點:C++ 門檻較高,遇到問題難排查
存儲
根據計算選擇存儲,有如下推薦:
● Flink + Paimon/Iceberg
Paimon 早期是 Flink 內部存儲,叫作 Table Store,後來進入Apache 孵化,改名為 Paimon。
● Spark + Hudi
Hudi 是從 Spark 解耦出來的,很多功能是和 Spark 綁定的,Spark 支持的最好,有很多存儲過程
● Doris/StarRocks 自帶的存儲
元數據
● Hive MetaStore
· 傳統數倉,用戶非常多
· 存儲有局限性:不同計算引擎的邏輯類型和 Hive 不一致,例如 timestamp 類型屬性只能以 key value 的形式存儲到 Hive 表的 Properties 裡面
· Hive metastore 瓶頸:高併發請求元數據時,經常 CPU 打滿
● Hudi MetaServer
Hudi 裡面開發了一個可以存儲 Hudi 元數據的服務,服務主要基於 Thrift 協議去開發,通過 MyBatis 保存元數據到 MySQL 中。
● HDFS/OSS
元數據存儲在 HDFS 或者在對象存儲上面:.hoodie / .iceberg(metadata) / .paimon(metadata)
● 二次開發
基於 Spring 生態去開發 Web 服務,元數據存儲在 MySQL 中。
湖倉治理
EasyLake
袋鼠雲 EasyLake 湖表治理功能支持數據文件治理,支持快照文件治理,支持 Hudi MOR 增量文件合併,將小文件數量控制在一定的範圍內,提升治理效率。
具體實踐請點擊該鏈接:《如何構建新一代實時湖倉?袋鼠雲基於數據湖的探索升級之路》
https://mp.weixin.qq.com/s/V5CTGJOkeZFuYPJhMk4o_Q
Amoro
Amoro 為用戶、平臺和產品構建湖原生數據倉庫和架構。
Dremio
基於 Apache Parquet、Apache Iceberg 和 Apache Arrow 等社區驅動標準的開放數據湖倉一體,使組織能夠使用一流的處理引擎並消除供應商鎖定。
本文根據《實時湖倉實踐五講第四期》直播內容總結而來,感興趣的朋友們可點擊鏈接觀看直播回放視頻及免費獲取直播課件。
直播課件:https://www.dtstack.com/resources/1055?src=szsm
直播視頻:https://www.bilibili.com/video/BV1EC4y1w7WX/?spm_id_from=333.999.0.0
《數棧產品白皮書》下載地址:https://www.dtstack.com/resources/1004?src=szsm
《數據治理行業實踐白皮書》下載地址:https://www.dtstack.com/resources/1001?src=szsm
想瞭解或咨詢更多有關大數據產品、行業解決方案、客戶案例的朋友,瀏覽袋鼠雲官網:https://www.dtstack.com/?src=szbky