摘要:本文整理自上海數⽲資訊科技有限公司⼤資料架構師楊涵冰,在 Flink Forward Asia 2022 流批一體專場的分享。本篇內容主要分為六個部分:
- 序
- 傳統方案與流批⼀體
- 資料的流批一體方案
- 邏輯的流批一體方案
- 資料一致性方案
- 流、批、呼叫一體方案
一、序
1.1. 一些問題
我們在整個實時流模型開發的過程中,經常會遇到一些問題:
- 在對現有模型策略精耕細作之前,還有沒有什麼資料沒有被使⽤?
- 離線特徵邏輯是否已經⾜夠完整,為什麼實時特徵邏輯需要重新梳理與補充邏輯?
- 不確定使⽤場景,⽆法區分點查和跑批,能不能同時覆蓋?
- 流式處理邏輯難以理解,為什麼要流 Join,不能直接“取數”嗎?
- 實時模型策略空跑測試需要很⻓時間,能不能縮短?
- 模型策略開發訓練很快,上線時開發所需的實時特徵卻需要很久,能不能加速?特別是當我們要進行一些深度學習模型開發的時候,我們需要的實時資料會很多,且結構複雜,這個時候就更加難以使用傳統實時特徵的方式來進行解決。那麼我們要如何將它上線呢?
1.2. 一些方案
針對以上的問題,我們提出了一些方案:
- 資料上,儲存所有狀態變化資料,還原任意時刻的資料切片狀態。
- 邏輯上,使用 Flink 流批一體,以流為主,邏輯一致,無需驗證口徑。
- 執行上,使用流、批、呼叫一體化方案,自適應不同的場景。
- 開發上,使用“取數”而不是流合併,封裝實時流特有概念,降低實時開發門檻。
- 測試上,支援任意時間段回溯測試,增加實時開發測試速度。
- 上線上,自助式的流批一體模型開發上線,減少溝通環節,增加上線效率。
二、傳統方案與流批一體
有兩個很經典的傳統方案分別是 Lambda 架構和 Kappa 架構。
Lambda 架構擁有實時鏈路和離線鏈路兩個不同的資料鏈路。其中,實時鏈路是一個實時作業,它會將資料實時寫入 Serving DB。離線鏈路則是一個離線作業,它會透過批處理的方式將資料寫入 Serving DB。最後線上應用透過 Serving DB 進行訪問。
Kappa 架構的實時鏈路和離線鏈路都使用了相同的流式處理系統,最後線上應用也是透過 Serving DB 進行訪問。
那麼 Lambda 和 Kappa 有什麼優缺點呢?
Lambda 架構的優點包括架構簡單;很好的結合了離線批處理和實時流處理的優點;穩定且實時計算成本可控;離線資料易於訂正。缺點包括實時、離線資料難以保持一致結果,需要維護兩套系統。
Kappa 架構的優點包括只需要維護實時處理模組;可以透過訊息重放進行資料回溯;無需離線和實時資料合併。缺點包括強依賴於訊息中介軟體快取能力;實時資料處理時存在丟失資料可能。
Kappa 在拋棄了離線資料處理模組的時候,同時也拋棄了離線計算更穩定可靠的特點。Lambda 雖然保證了離線計算的穩定性,但雙系統的維護成本高且兩套程式碼的運維很困難。
在資料來源來自於 Kafka 的場景下,Kappa 看上去沒有什麼太多問題。但在網際網路金融場景下,我們主要的資料來源都來自事務性資料,比如說 MySQL,它的實時流資料可以透過 Binlog 進行同步,但最終資料還是要以 MySQL 記憶體儲的資料為準。如果使用 Kappa,整個鏈路就會變成一個純增量鏈路,累積的誤差將難以進行修正。此時我們需要 Lambda 架構的離線修正能力。
我們提出的方案是 Lambda+Kappa,左邊是 Lambda,右邊是 Kappa。
左邊的 Lambda 部分,我們以 MySQL 為例,MySQL 的 Binlog 會被同步到 Kafka,然後我們將 Kafka 的訊息變化資料存入 HBase。同時它的全量資料會透過 Sqoop 抽取進入 EMR,透過 Spark 任務進行資料對比修正,然後將修正資料和切片資料存入 HBase。
右邊的 Kappa 部分,也是我們交給使用者書寫 Flink 的部分。需要注意一下,Flink 流處理和 Flink 批處理的程式碼是一樣的。在實時流處理的過程中,Flink 它會直接消費 Kafka 的實時流資料,可以得到最低延遲。在離線批處理的過程中,它的資料則來自於 HBase 的重放。
上圖是流批一體方案的資料流。可以看到 MySQL 的 Binlog 進 Kafka,然後它的實時資料會透過資料同步進入事件中心,離線修正及切片也會每日同步進入事件中心。Flink 作業在實時觸發過程中,透過 Kafka 來獲取相關資料。在離線過程中,則透過事件中心獲取相關資料,同時它也可以透過事件中心獲取一些其他事件流的歷史資料。最後由後設資料中心進行統一的後設資料服務。
三、資料的流批一體方案
實時執行時,我們可以獲取當前時刻各資料來源的流水資料及切片狀態資料。在離線分析和回溯時,我們可以透過獲取回溯時刻各資料來源流水的資料以及切片狀態資料。此時實時和離線獲取的資料結構及資料內容是完全保持一致的。我們透過標準化的時序資料接入與獲取,這樣可以天然杜絕穿越問題。
我們用事件中心承載了整個資料儲存方案。首先使用 Lambda 架構儲存所有變化資料,實時寫入,離線修正。由於我們儲存的是所有變化資料,它的儲存量會比較大,所以我們使用冷熱混存與重加熱機制來追求最佳價效比。然後我們仿造 Flink 的水印機制,在事件中心實現了一個特有的水印機制,確保當前值同步完成,從而可以以“取數”代替流 Join。
除此之外,我們還提供了訊息轉發機制。透過非同步轉同步支援觸發訊息接收及觸發輪詢式呼叫,並賦予該介面回溯能力。這樣無論透過訊息還是呼叫,我們就都可以支援,且使用模型的開發人員也無需再關心繫統對接細節。
MySQL 的 Binlog 會同步進 Kafka;Kafka 的資料就會被直接使用;RabbitMQ 訊息透過轉發作業轉發進 Kafka;訊息轉發服務的 API 請求也會轉發成 Kafka。接著由一個 Flink 作業去消費這些 Kafka 資料,並將其存到 HBase 熱存。
此時 MySQL 會有一個額外的離線鏈路,透過 Sqoop 抽取到 EMR,進行快照與修正,然後將資料存進 HBase 熱存。HBase 熱存透過 replica 機制將資料同步到 HBase 冷存。當訪問到冷存資料時,會有一個重新加熱的機制,把 HBase 冷存資料重新加熱回熱層。
從上圖可以看到,HBase 熱存裡有四張表,其中第一張是主資料,下面三張是索引表,它用一個持續的結構進行儲存。我們在 HBase 熱存中僅儲存 32 天內的資料,超過這個時間的資料需要透過冷存獲取。
索引表裡的第二張索引表(標記了 watermark 字樣)就是我們的用於實現索引機制的表。
當一個 Flink 作業在實時觸發的時候,它實際上是直接使用的 Kafka 流資料,只是我們透過後設資料中心把相關的邏輯統一封裝了。Flink 的使用者無需關心資料是來自 Kafka 還是 HBase,因為對他來說是一樣的。
在回溯的時候會自動使用 HBase 熱存,如果讀到冷存資料,它也會自動觸發一個重新加熱的機制。除此之外,當你需要直接取其他資料流資料的時候,也可以直接在 HBase 中取數。
我們在實時流開發中經常比較頭疼的就是多流 Join。這裡我們以雙流 Join 舉個例子,多流 Join 是一樣的,以此類推。
假設我們要對兩個流進行 Join,也可以簡單理解為兩張表,透過某外來鍵進行行關聯。當任何一張表發生變更時,我們都需要至少觸發一次最終完整 Join 後的記錄。
我們將兩個流分別記錄為 A 和 B,並且假設 A 流先到。那麼在開啟事件中心水印機制的情況下,A 流觸發時,A 流的當前事件已經被記錄在事件中心中。此時分為兩種情況:
- 在時間中心中可以取到 B 流的相關資料,那麼說明 A 流當前事件記錄進事件中心,到執行至讀取 B 流相關資料的時間段內,B 流已經完成了事件中心的記錄,此時的資料已經完整。
- 在事件中心中無法取到 B 流的相關資料,那麼由於事件中心水印機制,說明此時 B 流相關事件尚未觸發。而由於 A 流當前事件已經被寫入事件中心,那麼當 B 流相關事件被觸發時,一定能獲得 A 流的當前事件資料,此時資料也是完整的。
由此,透過事件中心水印機制,即可確保用“取數”取代流 Join 後至少會有一次擁有完整資料的計算。
轉發機制主要是為了對一些傳統系統進行相容,它分為兩種。第一種是觸發訊息接收式,比如外部系統發起一個請求,我們的訊息轉發系統接收到請求後,會把請求轉發成一個 Kafka 訊息,並且將訊息存到事件中心中。
之後 Flink 作業接收到 Kafka 訊息後會進行運算,並將結果傳送到 RabbitMQ 等使用者能夠直接訂閱的訊息系統中,然後外部系統接收相關的訊息結果進行後續的操作。
第二種是觸發輪詢式,外部系統會發起請求並輪詢結果。這裡需要注意一點,當處理時間小於單次請求超時時間的時候,輪詢的動作就會退化為單次同步請求。這裡和之前的方案是一樣的,區別是 Flink 作業會將資料寫入到一個 Kafka,然後由事件中心獲取 Kafka 資料並進行儲存,最後提供相關的服務。
透過這種方式我們還額外使我們的介面具備了資料回溯能力。
四、邏輯的流批一體方案
邏輯的流批一體是由 Flink 天生帶來的,它可以使離線開發試運⾏與實時執⾏、離線回溯程式碼完全⼀致。另外,我們封裝了實時流特有的概念,降低實時開發門檻。封裝了複雜的觸發邏輯和複雜的“取數”邏輯。
除此之外,我們可以提供自助式的開發上線,減少溝通環節,增加上線效率。最後我們額外提供了熱更新的引數,並支援獨立的引數變更流程。使模型策略人員和運營人員有更好的互動。
我們使用的 PyFlink,我們使用它是原因模型策略人員通常使用 Python 進行相關的邏輯開發。
從上圖我們可以看到,整個程式碼被分為三個部分:觸發、主邏輯、輸出。觸發部分我們可以引用一些已經封裝好的觸發邏輯,主邏輯部分我們也可以引用一些已經封裝好取數,或者其他函式邏輯,輸出部分我們也可以引用一些已經封裝好的輸出邏輯,同時我們也支援多路輸出。
上圖展示的是整體資料流,首先觸發邏輯會觸發到主邏輯,主邏輯可以引入一些取數邏輯,最後會有一個輸出邏輯。模型策略人員主要開發的是主邏輯,對於觸發邏輯、取數邏輯、輸出邏輯一般直接選擇就可以了。
觸發邏輯、取數邏輯、輸出邏輯,它的底層封裝會隨著流批環境自動變化,同時確保輸入和輸出不變。邏輯本身在絕大多數情況下不需要考慮流批環境的變化,當然在某些特殊情況也是需要考慮的。而由模型策略人員開發的主邏輯部分則完全無需考慮流批環境變化,已經被完全封裝好了。
熱更新引數操作流分為兩個角色:模型人員、運營人員。模型人員需要定義一些引數,並對這些引數進行說明,最後在程式碼中使用這些引數。運營人員需要去閱讀引數的定義及引數的說明,然後進行相關變更的提交,最後透過稽核進行生效。
我們按照分工和職能可以把整個系統分為三類人:
- 第一類是平臺管理人員,他可以規整化並接入資料來源;封裝觸發事件和“取數”邏輯;封裝輸出鏈路;封裝並標準化場景。
- 第二類是模型人員,他可以選擇觸發事件、“取數”邏輯或直接使用標準化場景;選擇一種或多種輸出鏈路,輸出執行結果。
- 第三類是運營人員,他可以觀測模型執行結果;熱更新模型引數。
站在模型策略人員的視角,典型的使用流程為如下操作:
- 第一步,需要選擇一個觸發流。
- 第二步,編寫取數和預處理邏輯,也可以直接引入已經發布的取數或處理邏輯程式碼。
- 第三步,設定回溯邏輯並試執行,它可以按照樣本表或時間切片進行回溯。
- 第四步,獲取試執行結果,在分析平臺中進一步分析與訓練。
- 第五步,訓練完成後釋出模型,在作業中選擇訓練完成的模型。如果有需要,可以設定熱更新引數及初始化相關引數。
- 第六步,釋出作業,上線完成。
整個過程自助化非常高,可以減少很多溝通環節,快速訓練、測試、釋出模型。
五、資料一致性方案
實時流處理是一種非同步處理方式,如果沒有特殊需求一致性級別一般均為最終一致,但也可以透過一些額外方案來實現更高的一致性要求。分為以下四種方案:
- 最終一致:經過一段時間後能訪問到更新的資料。整個流批一體方案預設保證最終一致。
- 觸發流強一致(可延遲):它會保障觸發流重的當前資料及早於當前的資料,在對觸發流的取數過程中能取到。使用水印方案,當水印不滿足時進行延遲。
- 取數強一致(可延遲):它會保障取數時早於使用者提出時間要求的資料均能取到。使用水印方案,水印不滿足時進行延遲。
- 取數強一致(無延遲):它會保障取數時早於使用者提出時間要求的資料均能取到。當水印不滿足時,它會直接從資料來源增量補足。這裡需要注意,增量取數會對資料來源帶來壓力,要謹慎。
從上圖我們可以看到,資料來源觸發了一個事件。由於它是一個非同步系統,它會同時觸發事件中心的儲存作業和 Flink 消費的作業,所以當 Flink 消費的時候它有可能讀不到事件中心當次事件的儲存。事件中心沒有完成寫入就取不到資料,只有當事件中心完成寫入的時候,才能取到最新的資料。
整體的時序和最終一致時序一樣,區別在於 Flink 作業會進行事件中心水印機制的判斷。如果不滿足,它會進行延遲,直至滿足相關的水印機制,就能獲得最新的資料了。
取數強一致(可延遲)時序和最終一致時序也很類似,只是因為是取數流,所以它觸發的 Kafka 和資料的 Kafka 是分開的。他的處理方案也是透過事件中心的水印機制,如果不滿足就延遲直至滿足,才能獲取相關的資料。
前半部分和最終一致時序一樣,但它在水印機制不滿足的時候,就不再等待和延遲了,它會直接從資料來源增量獲取資料。顯然,這種情況會對資料來源造成壓力,因此這種情況要謹慎。
在絕大多數場景,比如反欺詐、經營等對時效性並沒有那麼敏感的場景下,最終一致已經足夠滿足需要了,這也是我們實踐中絕大多數情況使用的方式。
觸發流強制一致(可延遲)是在對觸發流統計誤差要求很高的場景下使用。一般除了狀態初始化外,我們也可以直接使用 Flink 自帶的 state 機制來解決。
取數強一致(可延遲),它在對取數流統計誤差要求很高的情況下使用。比如一些金融場景下,需要對全歷史訂單進行統計,那麼就要求不能有誤差,所以就需要用這種方式。
取數強一致(無延遲),由於會對資料來源造成外的壓力,這個方案只會在極少情況下使用。一般對時效性要求有如此高的時候,我們會優先考慮直接線上上應用處理。只有線上上應用無法處理的大資料量情況下才會考慮使用,一般極少使用。
六、流、批、呼叫一體方案
在模型策略上線後,我們必然要透過某種方式才能為線上系統提供服務。對不同的呼叫方式進行封裝,我們可以在模型策略程式碼不修改的前提下,自適應各類不同場景的呼叫需求。主要分為以下四種:
- 第一種,特徵儲存服務方案。在 Flink 作業進行預運算以後,將運算結果寫入特徵儲存服務平臺,並透過該資料服務平臺對外服務。
- 第二種,介面觸發--輪詢方案。它呼叫並輪詢事件中心的訊息轉發介面,直到 Flink 作業返回運算結果。
- 第三種,介面觸發--訊息接收方案。它呼叫事件中心的訊息轉發介面來觸發 Flink 作業運算,接收 Flink 作業返回的運算結果訊息。
- 第四種,直接訊息接受方案。線上系統無需關心觸發,直接使用 Flink 作業返回的運算結果訊息進行相關的運算。
從上圖中在中間可以看到有三個 Flink,這三個 Flink 節點的程式碼是一樣的,不需要修改,就是同一個作業。
整個資料流豎著看我們分成三條線。
第一條是實時,它就是 Kafka 的實時觸發。觸發 Flink 程式碼,然後運算結果會存 Kafka,最後儲存到特徵儲存。
第二條和第三條都是離線,分別是離線初始化和離線修正。它們都是透過事件中心 HBase 來批次觸發 Flink 任務,然後將結果寫入 EMR,EMR 將資料同步進特徵儲存。
除此之外,如果對其他流提出一些需求,不管是實時還是離線,都可以從事件中心的 HBase 中進行取數。
我們需要注意一下,特徵儲存服務方案因為是一個預運算的非同步方案,所以它的時效性和一致性需求也是最終一致。從上圖我們可以看到,Kafka 觸發以後會進行運算,然後寫入到特徵儲存。如果外部呼叫太早,Flink 作業還沒完成運算以及寫入特徵儲存,就無法獲取更新的資料,只有等到 Flink 作業執行完畢,並寫入了特徵儲存系統,才能得到更新的資料。
這是透過一種非同步轉同步的方式將一個非同步的 Flink 作業變成同步的請求。外部系統透過請求觸發來呼叫我們的訊息轉發機制,訊息轉發機制會將訊息轉發到 Kafka 觸發 Flink 的運算,Flink 運算完畢會將資料寫進寫進 Kafka,最後寫進事件中心 HBase。
需要注意一下,如果整個過程沒有超過單次請求的超時時間,那麼此時觸發輪詢會退化為單次觸發的同步呼叫,即變成一個簡單的同步呼叫。如果超過了,就需要觸發方進行輪詢,透過事件查詢查詢事件中心的 HBase 是否有結果。
從上圖可以看到,外部呼叫會觸發訊息轉發與查詢服務,然後訊息轉發與查詢服務會觸發一個事件,Flink 會消費這個事件並進行相關計算,最後寫進事件中心。同時訊息轉發與查詢服務會不斷嘗試從事件中心獲取運算結果,如果一直獲取失敗,第一次的呼叫就會超時,需要你第二次輪詢,直到輪詢到計算結果。這是一種非同步轉同步的方式。
前面是一樣的,訊息轉發轉發到 Flink 作業,但 Flink 就不再寫進 Kafka 了,會直接寫到外部系統能夠使用的訊息系統中。然後外部系統進行相應的事件監聽,獲取執行結果。
整個資料流就會變得比前面的時序簡單很多,它就是一個非常傳統的非同步呼叫時序,只是中間會有一個訊息轉發服務會幫你把同步請求轉發成訊息來觸發計算。
這個就比較傳統,外部系統無需關心觸發流,觸發流會由作業自行使用。Flink 作業接收觸發並運算完畢後,將結果直接寫到 RabbitMQ 等外部系統能夠接收的訊息佇列,然後外部系統會直接消費訊息並進行訂閱接收,進行後續操作。
這是一個非常典型的非同步時序,全程非同步,資料流從 Kafka 到 Flink 到 RabbitMQ 最後到外部訂閱。
流、批、呼叫一體化提供的服務方案:
- 特徵儲存服務方案。透過特徵儲存服務提供持久化的特徵儲存,提供 API 點查及特徵圈選服務。
- 介面觸發—輪詢方案。透過事件中心的訊息轉發與訊息查詢服務,將同步呼叫轉換成非同步訊息處理,最後對外封裝的時候就是一個簡單的同步請求。
- 介面觸發—訊息接收方案。透過事件中心的訊息轉發服務,與介面觸發—輪詢方案的區別是最終提供的是訊息,將訊息發回相關的應用系統。
- 直接訊息接收方案。支援複雜的事件觸發,提供事件訊息服務。
我們以 Flink 作為核心引擎,以事件中心作為中間層以及儲存,使呼叫、實時流、離線跑批都可以用相同的方式來進行處理。這樣模型、策略無論被如何使用,都無需修改即可執行。
點選檢視直播回放和演講 PPT
更多內容
活動推薦
阿里雲基於 Apache Flink 構建的企業級產品-實時計算Flink版現開啟活動:
99 元試用 實時計算Flink版(包年包月、10CU)即有機會獲得 Flink 獨家定製衛衣;另包 3 個月及以上還有 85 折優惠!
瞭解活動詳情:https://www.aliyun.com/product/bigdata/sc