數(shù)據(jù)湖實(shí)時計(jì)算:從批處理思維中跳出來
數(shù)據(jù)湖實(shí)時計(jì)算:從批處理思維中跳出來
傳統(tǒng)數(shù)據(jù)倉庫時代,ETL流程通常是按天或按小時調(diào)度,數(shù)據(jù)從產(chǎn)生到可用之間存在明顯延遲。當(dāng)企業(yè)轉(zhuǎn)向數(shù)據(jù)湖架構(gòu),實(shí)時計(jì)算的需求隨之而來——業(yè)務(wù)部門不再滿足于昨天發(fā)生了什么,而是想知道此刻正在發(fā)生什么。但很多團(tuán)隊(duì)把實(shí)時計(jì)算簡單理解成“把批處理跑快一點(diǎn)”,結(jié)果在數(shù)據(jù)湖上搭建的實(shí)時管道頻繁出問題,延遲依然居高不下,數(shù)據(jù)質(zhì)量也難以保證。真正做好數(shù)據(jù)湖實(shí)時計(jì)算,需要從架構(gòu)設(shè)計(jì)、存儲選型到計(jì)算引擎的配合,徹底跳出批處理的慣性思維。
實(shí)時寫入與數(shù)據(jù)湖的天然矛盾
數(shù)據(jù)湖的核心優(yōu)勢在于低成本存儲海量原始數(shù)據(jù),但這一優(yōu)勢建立在文件系統(tǒng)之上。傳統(tǒng)HDFS或?qū)ο蟠鎯Υ罅啃∥募膶懭氩⒉挥押?,而流式?shù)據(jù)天然就是持續(xù)不斷的小批量到達(dá)。如果每個微批次都生成一個獨(dú)立的小文件,幾分鐘后數(shù)據(jù)湖里就會堆滿成千上萬個碎片,后續(xù)查詢性能急劇下降。解決這個矛盾的關(guān)鍵在于引入緩沖層——在數(shù)據(jù)寫入數(shù)據(jù)湖之前,先用消息隊(duì)列或流式存儲(如Kafka、Pulsar)做短暫的匯集,再以分鐘級或秒級粒度合并成大小適中的文件寫入數(shù)據(jù)湖。這種方式既保留了數(shù)據(jù)湖的存儲經(jīng)濟(jì)性,又避免了小文件風(fēng)暴。另一個常見做法是使用支持實(shí)時更新的湖存儲格式,比如Delta Lake、Apache Iceberg或Hudi,它們能夠在文件層面做增量合并,讓數(shù)據(jù)湖本身具備一定的upsert能力,從而減少對額外緩沖層的依賴。
計(jì)算引擎的選擇取決于時效性要求
數(shù)據(jù)湖上的實(shí)時計(jì)算并非只有一個技術(shù)棧。如果業(yè)務(wù)對延遲的要求在分鐘級,比如每小時更新一次用戶畫像標(biāo)簽,那么基于Spark Structured Streaming的微批次模式就足夠勝任。Spark的優(yōu)勢在于生態(tài)成熟,能與數(shù)據(jù)湖中的Parquet、ORC格式無縫對接,而且團(tuán)隊(duì)通常已有Spark的使用經(jīng)驗(yàn)。但如果業(yè)務(wù)要求秒級甚至毫秒級響應(yīng),比如實(shí)時風(fēng)控或在線推薦,就需要轉(zhuǎn)向Flink這樣的純流處理引擎。Flink能夠做到事件級別的精確一次語義,并且支持狀態(tài)管理和事件時間處理,在數(shù)據(jù)湖場景下,F(xiàn)link可以直接將計(jì)算結(jié)果寫入Iceberg或Hudi表,實(shí)現(xiàn)流式數(shù)據(jù)入湖。需要注意的是,F(xiàn)link對狀態(tài)后端和檢查點(diǎn)配置有較高要求,如果數(shù)據(jù)量巨大且狀態(tài)膨脹,需要合理規(guī)劃RocksDB的存儲和內(nèi)存資源,否則容易導(dǎo)致任務(wù)不穩(wěn)定。
數(shù)據(jù)一致性是容易被忽視的硬骨頭
批處理模式下,數(shù)據(jù)不一致可以通過重跑整個分區(qū)來糾正。實(shí)時計(jì)算則不同,數(shù)據(jù)一旦流入下游,修正成本極高。數(shù)據(jù)湖實(shí)時計(jì)算中常見的一致性問題包括:重復(fù)數(shù)據(jù)、亂序事件、以及部分寫入失敗導(dǎo)致的臟數(shù)據(jù)。解決這些問題需要從多個層面入手。在存儲層面,使用支持ACID事務(wù)的湖格式可以保證一批數(shù)據(jù)要么全部可見要么全部不可見,避免下游讀到半成品。在計(jì)算層面,F(xiàn)link的精確一次語義結(jié)合Kafka的冪等生產(chǎn)者,能夠從源頭到終點(diǎn)確保每條數(shù)據(jù)只被處理一次。但更隱蔽的問題是亂序——網(wǎng)絡(luò)延遲或上游系統(tǒng)重試可能導(dǎo)致事件時間戳錯亂。處理亂序數(shù)據(jù)通常需要設(shè)置合理的watermark延遲閾值,并在業(yè)務(wù)邏輯中容忍一定程度的延遲。對于金融、電商等對一致性敏感的行業(yè),還可以在實(shí)時管道中加入校驗(yàn)對賬環(huán)節(jié),定期將實(shí)時結(jié)果與離線批處理結(jié)果做對比,及時發(fā)現(xiàn)偏差。
冷熱分層與查詢模式的匹配
數(shù)據(jù)湖上的實(shí)時計(jì)算往往不只是寫入,還包括查詢。很多團(tuán)隊(duì)把實(shí)時數(shù)據(jù)一股腦寫入數(shù)據(jù)湖,結(jié)果導(dǎo)致查詢性能災(zāi)難。一個務(wù)實(shí)的做法是冷熱分層:熱數(shù)據(jù)存放在高性能存儲(如SSD或內(nèi)存級緩存)中,供實(shí)時看板或在線服務(wù)查詢;冷數(shù)據(jù)下沉到廉價(jià)的對象存儲,用于歷史分析和機(jī)器學(xué)習(xí)訓(xùn)練。這種分層并不需要兩套系統(tǒng)——借助Apache Hudi或Iceberg的時間分區(qū)和文件合并策略,可以在同一個數(shù)據(jù)湖內(nèi)完成數(shù)據(jù)從熱到冷的自動遷移。例如,最近一小時的數(shù)據(jù)以未壓縮的格式存放在快速存儲上,超過一小時的數(shù)據(jù)自動合并壓縮并轉(zhuǎn)移到低成本存儲。查詢引擎(如Presto或Trino)需要感知這種分層,在查詢計(jì)劃中優(yōu)先掃描熱數(shù)據(jù)分片,避免全表掃描帶來的延遲。
從Lambda架構(gòu)到Kappa架構(gòu)的演進(jìn)
早期數(shù)據(jù)湖實(shí)時計(jì)算的主流方案是Lambda架構(gòu):一條批處理鏈路負(fù)責(zé)全量數(shù)據(jù)的準(zhǔn)確計(jì)算,一條流處理鏈路負(fù)責(zé)低延遲的增量計(jì)算,最終由服務(wù)層合并結(jié)果。這種架構(gòu)雖然能同時滿足準(zhǔn)確性和時效性,但維護(hù)兩套代碼和兩套調(diào)度邏輯的成本很高,而且兩套鏈路的結(jié)果經(jīng)常對不齊。近年來,隨著Flink和Kafka在數(shù)據(jù)湖生態(tài)中的成熟,Kappa架構(gòu)逐漸成為更受青睞的選擇——只用一套流處理引擎,通過重放歷史數(shù)據(jù)來實(shí)現(xiàn)全量計(jì)算。在Kappa架構(gòu)下,數(shù)據(jù)湖本身作為歷史數(shù)據(jù)的存儲層,流處理任務(wù)可以從Kafka的某個offset開始重跑,或者直接從數(shù)據(jù)湖中讀取歷史文件進(jìn)行回溯計(jì)算。這種方式簡化了技術(shù)棧,也消除了批流結(jié)果不一致的根源。但Kappa架構(gòu)對消息隊(duì)列的保留時長和數(shù)據(jù)湖的讀取性能有更高要求,如果歷史數(shù)據(jù)量極大,重跑任務(wù)可能需要數(shù)小時,這時可以結(jié)合批處理做定期快照來加速恢復(fù)。
運(yùn)維監(jiān)控與成本控制
數(shù)據(jù)湖實(shí)時計(jì)算一旦上線,運(yùn)維壓力往往比離線任務(wù)大得多。流任務(wù)需要7x24小時運(yùn)行,任何網(wǎng)絡(luò)抖動、存儲限流或數(shù)據(jù)傾斜都可能造成任務(wù)積壓甚至失敗。建立有效的監(jiān)控體系是第一步:除了常規(guī)的任務(wù)延遲和吞吐量指標(biāo),還要關(guān)注檢查點(diǎn)耗時、狀態(tài)大小、以及數(shù)據(jù)湖寫入的文件數(shù)。文件數(shù)異常增長往往是數(shù)據(jù)傾斜或分區(qū)策略不當(dāng)?shù)男盘?。成本方面,?shí)時計(jì)算的計(jì)算資源消耗通常高于批處理,因?yàn)槿蝿?wù)需要持續(xù)運(yùn)行。優(yōu)化手段包括:合理設(shè)置并行度避免資源浪費(fèi),對不常用的實(shí)時管道做降級處理(比如夜間降低并發(fā)),以及利用Kubernetes的彈性伸縮能力按需分配資源。有些團(tuán)隊(duì)會將實(shí)時計(jì)算的中間結(jié)果緩存到Redis或內(nèi)存網(wǎng)格中,減少重復(fù)計(jì)算,這也能顯著降低計(jì)算成本。