流批一體的近實時數倉的思考與設計

大資料技術前線發表於2023-05-19

來源:Apache Flink


摘要:基於對資料時間旅行的思考,引出了對目前三種數倉形態和兩種數倉架構的思考。結合資料湖在 Flink 的應用和資料湖後設資料型別的思考,探索了基於資料湖的 Flink SQL 流批一體的實踐,在流批一體 SQL 表達一致、結果一致性、流批任務分離、混合排程依賴等進行了設計和探索。同時,歡迎大家多分享具體實踐,一起共築新的資料實踐方式。


01

資料的時間旅行和業務對資料的本質要求


大規模的資料處理興起於 Hadoop 生態的發展,關鍵在於分散式儲存和分散式計算的發展,造就瞭如今近百種有關大資料的生態技術。數倉理論和建模理論基於大資料技術體系得以快速發展,其中離線數倉的標準化建設得到了廣泛應用。資料的本質是一種行為的具象,業務在對資料的需求,核心在於對行為的可探索和可觀察。基於此,我們需要明確一點,大資料技術是否完全滿足了業務對資料需求在時間維度上的確定性了呢,這點是值得思考的。那麼我們先來看一下資料的時間旅行。

流批一體的近實時數倉的思考與設計

業務期望的資料:使用者空間下的時間資料,t1時間資料,使用者自然時間點或自然時間段的明細或者統計資料。

傳輸延遲:App 使用者,資料傳送到閘道器或者日誌服務系統,或者 Server 日誌落檔案系統所產生的延遲。Event 進入到儲存空間,可以代表資料已經是確定的,基本可觀察,一般情況下,這個延遲很小。但是,在某些情況,比如 APP 的日誌產生之後,但是因為網路等問題一直沒有傳送,或者 Server 當機,導致延遲傳送或者最終丟失。總體而言,傳輸延遲屬於不可控延遲,暫時沒有什麼好的技術方案來解決。

儲存空間:資料承載於實際的儲存中,離線數倉承載於具體的分散式檔案系統,實時數倉基於 Kafka 的訊息佇列系統,近實時數倉承載於資料湖儲存中。這裡可以抽象來看離線數倉,Event 承載於分散式檔案系統,以小時分割槽為例,某個小時的分割槽本質是自然時間產生的檔案的集合,時間精度退化為小時級別。

計算延遲:資料進入儲存之後,與進入計算空間的時間差,t3-t2。實時數倉中,計算延遲是資料的 ProcessTime-IngestTime。離線數倉中,計算延遲是排程產生例項執行時間-資料進入儲存空間的時間差。本質離線數倉和實時數倉的計算延遲在抽象上看是一致的。計算延遲在不同的數倉體系下,產生的時效不同,我們會劃分為三種主流的數倉體系,秒級的實時數倉,分鐘級的近實時數倉,小時級的離線數倉。可以看出,數倉的時效性差異,因為傳輸延遲的不可控,退化為計算延遲的差異。

02

離線、近實時、實時三種數倉

在時間維度下的成因


在離線數倉和實時數倉,常常會提到資料的有界和無界,認為離線數倉的資料是有界的,實時數倉的訊息流是無界的。準確與否在於資料的確定性考量。

離線數倉的確定性,在於檔案自然生成時間的確定性和不可更改性,某個小時的自然檔案生成,近似等於事件時間在自然時間的確定性,反例就是我們能看到資料漂移的情況,事件時間會或多或少落入上個小時或者下個小時的自然檔案生成時間。那麼離線數倉的確定性,實質是資料的 IngestTime 的確定性,具有天然的檔案屬性,易於分割。當我們說離線數倉計算的資料是準確的時候,預設了傳輸延遲帶來的影響很小或者預設了當前小時的資料指標的標準是檔案的自然形成時間。

流批一體的近實時數倉的思考與設計

實時數倉,常常會提及不確定性或者說 Lambda 架構實際是對實時數倉的不確定性的替代方案。這種不確定性的原因是什麼呢?這裡分為四類情況說明,一是 ETL 的處理,從視窗上來說,是單條資料即為一個視窗,視窗的產生和銷燬在一個 Event 中完成,y=window(data)。二是基於 EventTime 的時間視窗,如果再定義延遲時間,y=window(datas, datas.EventTime, delay),第三種和第四種分別就是 IngestTime 和 ProcessTime 的時間視窗函式。對比離線數倉,可以看出,基於 IngestTime 的時間視窗和離線數倉的時間語義最為一致。離線數倉在時間視窗上,可以看做為資料進入檔案的自然時間所對應的小時視窗,資料所承載的檔案的確定性,保證了小時視窗的資料確定性,y=window(files)。

流批一體的近實時數倉的思考與設計

近實時數倉,比如基於 Iceberg 的資料湖建立的近實時數倉,在於離線數倉對比中,實際是將基於小時檔案細分到分鐘級別的快照檔案上來,y=window(snapshots)。對比實時數倉,因為 Kafka 的 IngestTime 目前在精確性上是不精確的,基於快照的檔案劃分,在精確性上有一定的保證,同時在降低時效程度,從秒退化為分鐘,很多場景是可以容忍的。

流批一體的近實時數倉的思考與設計

三種在時間維度對比上看,一是在某個時間,統計的本質對業務的需求都是近似的,這個本質是傳輸延遲所帶來的,但是這個在實踐中,不影響資料的可用性和統計學意義。二是不同數倉的劃分,是儲存和計算技術發展所帶來的。三是離線數倉的確定性模糊了傳輸延遲,實時數倉的不確定性,是對傳輸延遲的一種取捨,人為的限定了 EventTime 的最大延遲時間,從而保證了指標的時效性,都是具有實踐的意義所在。

03

Lambda 和 Kappa 架構

在時間維度下的取捨


當離線數倉剛剛發展的時候,只有一種數倉架構,也是基於大資料分散式處理剛剛發展的原因。隨著實時技術的發展,大家在時效性上有了更多要求,但是同離線數倉對比的時候,在資料的準確性上,因為統計的視窗不同,必然會導致某個時刻的指標結果的不嚴格一致。

為了解決這種不嚴格一致的情況,Lambda 架構(由 Storm 的作者 Nathan Marz 提出的)產生的,實時確保時效,離線確保準確。最終會以確保離線三個時間視窗的統計一個事件時間視窗的結果,來回補實時數倉以為 EventTime 視窗,因為時效性丟棄的延遲資料的結果,從而保證業務上對 EventTime 視窗的要求,或者預設為離線的 IngestTime 所產生的檔案分割槽近似認為 EventTime 的時間視窗。這種帶來的弊端,維護兩套資料路線,而大家總在想辦法解決。

Kappa 架構的提出,得益於實時計算的效率提升,但是因為在批處理技術短板,生產實踐推廣受限。Kappa 架構是基於實時 EventTime 的一種資料視窗處理,因為 Kafka 的 IngestTime 不精確和為了同離線數倉對比而權衡考慮,EventTime 在傳輸延遲上的不可控,導致 Kappa 架構的準確性就會出現折扣。雖然是業務上最準確的時間範圍,可行性上確不佳。

近些年來,不斷髮展的 MPP 架構的 OLAP 查詢引擎,並不會涉及到時間視窗的計算取捨,OLAP 引擎本質是基於 ProcessTime 來加速查詢的一種技術手段,是數倉不可分割的一部分,但是傳輸延遲的不可控沒有解決,但是將計算延遲下推到了查詢時,透過快速查詢來解決儘可能減少計算延遲,同時保證了查詢的靈活性,自助分析探索上有著廣泛的應用。

從數倉架構的發展上看,不斷在圍繞結果的確定性,技術的可行性,資料的時效性,查詢的靈活性上,不斷的權衡,各個元件也是依據實際需求而發展起來的。

04

數倉一體的可行性思考


基於三種數倉體系和兩種架構的思考,每個設計都是兼顧一種或多種考量,那麼能不能實現一種機制,能夠較好的滿足數倉需求體系建設呢?從目前的技術發展上看,是有一定的可能性的。架構體系的發展一是基於技術基礎,二是不斷吸收元件的優點,做加法。

除去實時、近實時、離線數倉的劃分,從技術的視角去看數倉建設的可行性。那麼我們就要選取一些重要的點,取捨掉一些不可能的實現。

第一點是結果的確定性,這點是基於離線數倉發展的思考。不確定性帶來的問題是資訊的不對稱,確定性的結果是可以模糊一定的指標含義的。

第二點是資料的時效性,高時效必然能夠滿足低時效,反之不然。另外資料的時效性,本身是基礎元件的技術發展所限制的。

第三點是開發的便利性,排在時效性後面的考慮是,便利性是基於應用層面建設的,難度一般是弱於基礎元件的,可以透過不斷實踐最佳化,達到一個良好的使用體驗。
第四點是查詢的靈活性和高響應,OLAP 的基礎設計保證了查詢速度,那麼 OLAP 的技術架構體現是可以複用或者擴充的。

那麼基於上面四點考慮,可以在實時數倉的基礎上,優先解決掉確定性問題。這個是很重要的一個命題,要保證計算結果同離線數倉的一致性。這一點的實現方面,可以參考離線數倉,模糊 EventTime 和 IngestTime,用檔案的 start 和 end 作為確定性的依據,檔案的中間實時計算,確保時效性。那麼基於Flink,就需要實現一種基於檔案自然分割的 Watermark 機制,作為計算視窗劃分的依據。

在確定性問題之後,需要解決計算的成本和使用的成本,這裡比較重要的是儲存層,實時數倉依賴 Kafka,Kafka 發展不具備數倉一些重要的點,成本是一個方面,查詢是一個方面,Kafka 無法架構在各種 OLAP 引擎或者計算引擎上面。這裡,近實時數倉的依賴,比如資料湖或者 Paimon,資料湖分鐘級的時效。不過,從發展的角度上看,是一種可行的解決方案。資料湖兼顧了流計算和批計算,同時,如果未來 OLAP 引擎如果能夠在資料湖上實現類似 MPP 架構的查詢效率,這也是有可能的,比如短期可以用資料冗餘,將資料湖格式的資料轉換一份到 OLAP 對應的引擎上實現加速查詢。

第三個方面,流式計算的管理和依賴機制,借鑑於離線數倉的管理方式,需要一套完備的資料依賴管理,任務容錯回跑機制。實時數倉一般是基於單個任務式的管理,離線數倉是基於任務流的管理,那麼實時數倉的發展,也必然要實現任務流的管理方式,覆蓋整個開發鏈路。

為了實現一種統計的數倉架構,那麼需要的發展工作如下:一是著重發展儲存層,比如資料湖,既要比較好的適應流和批引擎,又要能夠高度適應 OLAP 查詢引擎。二是在實時數倉或者近實時數倉,引入類似離線數倉的排程依賴管理和補數和容錯回跑機制,或者在離線排程上相容流任務依賴排程,實現任務流級別的管理和流批一體的數倉實現。三是在引擎層著重發展Flink批處理能力。

流批一體的近實時數倉的思考與設計

最終的任務執行方式同時包含三種:實時模式、離線模式、業務模式,分別對應著不同的資料準確性級別。也可以任選其一或者其二作為執行方式。

05

基於 Flink 和資料湖

的流批一體近實時數倉設計示例


數倉任務在離線排程和實時任務的簡單抽象示例:
資料來源=>同步任務/實時任務 =>
stg_table(partition=hour) =>計算任務(insert overwrite partition=hour)=>
dwd_table(partition=hour)=>計算任務(insert overwrite partition=hour)=>
dws_table(partition=hour)=>同步任務=>OLAP 加速=>資料服務

如果儲存層是基於資料湖(以 Paimon 為例):
離線排程產生的表的版本資訊,commit_kind: insert overwrite 型別的。同時離線任務的驅動,是基於排程依賴的驅動,one by one 的排程。

如果是基於流式計算,比如分鐘級生成snapshot那麼會演變為:
資料來源=>同步任務/實時任務 =>
stg_table(version=snapshot_id) =>計算任務(insert into version=snapshot_id)=>
dwd_table(version=snapshot_id)=>計算任務(insert into version=snapshot_id)=>
dws_table(version=snapshot_id)=>同步任務=>OLAP 加速=>資料服務
那麼啟動多個任務,任務是持續的執行。commit_kind: insert into型別的。

那麼要想實現流批一體的近實時數倉,需要解決如下問題:

1. Flink 任務支援批次計算能力要持續不斷的加強

從 Flink 1.16/1.17 的版本釋出情況,在批處理能力上有比較大的提升,同時,社群也在持續不斷的加強批處理能力以及同 hive 的相容能力。

2. 如何使用同一份 Flink SQL,既可以用於批任務排程,又可以用於流任務執行呢

兩張表:dwd_partition_word_count,dws_partition_word_count,計算 word count


















CREATE TABLE tablestore.tablestore_test.dwd_partition_word_count (  logdate String,    user_id bigint) PARTITIONED BY (logdate)WITH (    'bucket' = '3');
CREATE TABLE tablestore.tablestore_test.dws_partition_word_count (  logdate String,    user_id bigint,    cnt BIGINT,    PRIMARY KEY (logdate,user_id) NOT ENFORCED) PARTITIONED BY (logdate)WITH (    'bucket' = '3');

批任務的 Flink SQL:






insert overwrite tablestore.tablestore_test.dws_partition_word_count PARTITION(logdate=${start_date}) select user_id,count(1) as cnt from tablestore.tablestore_test.dwd_partition_word_count where logdate=${start_date} group by user_id;-- 或者insert overwrite tablestore.tablestore_test.dws_partition_word_countselect logdate, user_id,count(1) as cnt from tablestore.tablestore_test.dwd_partition_word_count where logdate=${start_date} group by logdate,user_id;

流任務的 Flink SQL:




insert into tablestore.tablestore_test.dws_partition_word_count select logdate,user_id,count(1) as cnt from tablestore.tablestore_test.dwd_partition_word_count group by logdate,user_id;

如何用一個 Flink SQL 來實現流批模型下的不同呢?

不同點:Insert into 和 Insert overwrite 的問題,這個透過在提交執行模式的時候,如果是批任務,則是 Insert Overwrite,如果是流任務,則轉為 Insert into,這個在技術上沒有什麼難點。

不同點:Where 條件的資料範圍問題。抽象來看,流任務和批任務的時間範圍在表達上是可以統一的




insert overwrite tablestore.tablestore_test.dws_partition_word_countselect logdate, user_id,count(1) as cnt from tablestore.tablestore_test.dwd_partition_word_count where logdate>=${start_date} and logdate<=${end_date} group by logdate,user_id;

比如跑 4 月 22 號一天的資料,執行的批 SQL 為:


insert overwrite tablestore.tablestore_test.dws_partition_word_countselect logdate, user_id,count(1) as cnt from tablestore.tablestore_test.dwd_partition_word_count where logdate>='20230422' and logdate<='20230422' group by logdate,user_id;

如果用流模式跑,執行的 SQL 可以為:

select logdate, user_id,count(1) as cnt from tablestore.tablestore_test.dwd_partition_word_count where logdate>='19700101' and logdate<='99990101' group by logdate,user_id;



insert overwrite/into 和時間範圍,可以由平臺執行的時候自動轉換和引數輸入。

3. 批任務的排程和流任務的計算如何分離

任務完成開發,在批模式下,用排程任務驗證了邏輯無誤,那麼之後可以用流模式,一直持續不斷的執行。一是計算邏輯變更或者歷史資料修復怎麼辦,二是可不可以支援流批雙跑。其實本質是一個問題。如果計算邏輯變更,那麼可以修改流批一體的 SQL 邏輯,然後流任務重啟應用新的計算邏輯。同時,流批一體的 SQL,在排程上回跑歷史資料,重新刷寫資料。

重刷歷史資料的時候,流任務會不會讀取到重刷的歷史資料進行計算。

這個問題主要是透過上述說的資料湖版本 commit kind 解決。批任務只應用 insert overwrite,流任務應用 insert into.如果流任務檢測到 insert overwrite 的版本提交,直接跳過,不做實際的資料讀取和處理。只處理 insert into  的資料。實際批任務的執行,對流任務不會產生影響。

目前在資料湖流式讀取上,只需要加個開關選項就可以實現。

4. 流任務的 Insert into 如何實現主鍵寫入

如果流任務的 Insert into 不能實現主鍵寫入,那麼分割槽資料的重複性無法解決,那麼就只能流批雙跑來解決資料的重複性問題。也就是,下游如果是主鍵冪等寫入,insert into 和 insert overwrite 語義等同。

這個可以透過資料湖主鍵表(比如 Paimon 的主鍵表)實現。Paimon 的主鍵表已初步具備生產可用性。

5. 流批任務的排程依賴

如果一個流任務,下游接的是批任務排程,如果實現排程依賴呢?

比較優雅的實現可以是,在流任務寫入下游表的時候,假如資料的 Watermark 寫入到下游表的屬性中,如果最晚的資料已經是當前小時的 05 分,那麼當前小時的下游排程任務,透過檢查表的屬性時間,就可以判斷批任務的排程例項是否應該拉起。或者也可以基於流任務的執行延遲做檢查依賴。

基於上述的實現和解決,我們基本就可以實現流批一體的 Flink SQL 在批模式和流模式下執行,如果排程依賴做的比較完善的情況下,可以實現流批混跑。同時補數或者雙跑對流任務的穩定性不會產生影響。

實際開發,就可以用批任務先開發驗證,然後用流模式拉起,資料產出基本是分鐘級別的。出問題可以用批任務修正。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027827/viewspace-2953290/,如需轉載,請註明出處,否則將追究法律責任。

相關文章