一、背景
本次賽題思路源自於真實工作場景的一個線上專案,該專案在經過一系列最佳化後已穩定上線,在該專案開發的過程中資料平臺組和技術負責人提供了許多資源和指導意見,而專案的結果也讓我意識到了流計算在實際生產中最佳化的作用,進而加深了我對大資料應用的理解。
1.1 成員簡介
陸冠興:資料開發工程師,目前在網際網路券商大資料部門工作,主要負責業務資料開發、資料平臺建設、資料資產建設等相關工作,對流計算應用開發有一定經驗。
1.2 內容概述
本次賽題的主要內容,是透過引入流計算引擎 Flink+訊息佇列 Kafka,使用 ETL 模式取代原有架構的 ELT 模式計算出使用者的實時資產,解決原有架構下計算和讀取壓力大的問題,實現存算分離;並以計算結果進一步做為資料來源構建實時資產走勢等資料應用,體現了更多的資料價值。
1.3 一些概念
在股票交易系統中,使用者需要先進行開戶得到一個賬戶,該賬戶包含賬戶現金和賬戶持倉兩部分,之後就可以透過該賬戶進行流水操作,同時也可進交易操作。
流水
- 出入金流水 = 往賬戶現金中存入/取出現金
- 出入貨流水 = 往賬戶持倉中存入/取出股票
交易
- 買入股票 = 現金減少,股票持倉增加
- 賣出股票 = 現金增加,股票持倉減少
總資產的計算
- 使用者總資產=賬戶現金+賬戶持倉股票市值
- 賬戶持倉股票市值 = 所持倉股票數量 * 對應的最新報價(實時變化)
1.4 傳統架構的實現&痛點
當使用傳統業務架構處理一個總資產的查詢介面時,大致需要經過的步驟如下:
- 使用者從客戶端發起資產請求到後端
- 後端程式去業務 DB 裡查詢所有使用者現金錶、使用者持倉股票表以及最新股票報價表資料
- 後端程式根據查詢到的資料計算出使用者持倉的市值,加上使用者現金得到出使用者最新總資產
- 將算出的總資產結果返回客戶端展示
但隨著請求量的增加,在該架構下資料庫和計算效能都會很快達到瓶頸,主要原因是上面的第 2 步和第 3 步的計算流程較長並且未得到複用:
- 每次客戶端的請求到來時,後端程式都需要向業務的 DB 發起多個查詢請求去查詢表,這個對於資料庫是有一定壓力
- 查詢得到的資料庫資料還需要計算才能得到結果,並且每來一個請求觸發計算一次,這樣的話 CPU 開銷很大
二、技術方案
2.1 ETL 的架構&流計算
這裡一個更合理的架構方案是使用 ETL 的架構對此做最佳化。
對於 ELT 架構,主要體現在 T(轉換)的這個環節的順序上,ELT 是最後再做轉換,而 ETL 是先做轉換它的優點是因為先做了轉換,能夠方便下游直接複用計算的結果。
那麼回到總資產計算的這個例子,因為它的基本計算邏輯確定,而下游又有大量的查詢需求,因此這個場景下適合把 T 前置,也就是採用 ETL 的架構。
在使用 ETL 架構的同時,這裡選擇了 Flink 作為流計算引擎,因為 Flink 能帶來如下好處:
- 僅在對應上游資料來源有變更時觸發算出對應的計算,避免了像批計算每個批次都需要去拉取全量資料來源的開銷
- 由於是事件觸發計算最新的結果,所以實時性會比批計算會好很多
那麼新的架構實現可以大致如圖,首先這裡圖中右邊部分,透過引入 Flink 可先把計算的結果寫到中間的資料倉儲中;再把這個已算好資料提供給圖中左邊介面進行一個查詢,並且因為資料倉儲裡面已經是算好的結果,所以介面幾乎可以直接讀取裡面的資料無需再處理。
2.2 架構實現
實現這裡主要分為三部分:資料接入、資料 ETL、提供資料。
2.2.1 資料接入
出於效能和 SQL 化的能力以及對 Flink 的相容性考慮,這裡主要使用的接入方案是 Flink CDC,整個 SQL 部分只需要確定資料來源例項和庫表的一些資訊,以及要接入到的目標資料倉儲資訊,我們可在程式碼中 create 對應的 SQL,然後執行 insert 便可以完成整個接入。
一個從業務 MySQL 資料庫接入數倉 Kafka 訊息佇列的 demo 程式碼如下:
<img src="https://img.alicdn.com/imgextra/i4/O1CN010g7tGi297fevcN2aT_!!6000000008021-2-tps-404-214.png" alt="7" style="zoom:50%;" /><img src="https://img.alicdn.com/imgextra/i3/O1CN017vJaaF23BtcJSVMTy_!!6000000007218-2-tps-456-212.png" alt="7" style="zoom:50%;" />
2.2.2 資料 ETL
在資料完成接入後,我們就可以開始業務邏輯,也就是使用者總資產的計算了。
根據前面提到的計算公式,需要先對“賬戶持倉資料”和“股票報價資料”做一個關聯,然後進行一次賬戶維度的聚合算出使用者持倉市值,再和“賬戶現金資料”關聯算出總資產,對應的 SQL 程式碼如下:
然而,在實際的執行中我們發現,資料的輸出結果似乎很不穩定,變動頻繁,輸出的資料量很大,這裡透過之前社群一些 Flink 的分享[1] 發現,這類實時流資料的 regular join 可能會有資料量放大和不準確的問題,原因是因為 Flink 有時會把上游的一條資料拆成兩條資料(一條回撤,一條新值),然後再發給下游。
那在到我們總資產計算的這個場景中,可以看到在我們的 SQL,確實在關聯之前和關聯之後都會往下游輸出資料;另外,再做聚合 SUM 的時候,上游的一個變化也可能觸發兩個不同的 SUM 結果;這些計算中間結果,都在不斷地往下游輸出,導致下游的資料量和資料的穩定性出現了一定的問題,因此這裡要對這些回撤進行一個定的最佳化。
根據之前一些社群的分享經驗來看,這裡對應的一個解決方案是開啟 mini-batch;原理上使用 mini-batch 是為了實現一個攢批,在同一個批次中把相同 KEY 的回撤資料做一個抵消,從而減少對下游的影響;所以這邊裡可以按照官方的文件做了對應的一個配置,那麼資料量和穩定性的問題也就得到了初步的一個緩解。
2.2.3 提供資料
這部分的主要目的是將 ETL 計算好的結果進行儲存,便於下游介面直接查詢或者再做進一步的流計算使用,因此一般可以選擇儲存到資料庫和訊息佇列中;
2.3 擴充套件資料應用
在完成基本資料模組的計算後,我們可以從資料的價值角度出發並探索更多可能,例如對已經接入的資料,可以再做一個二次的資料開發或挖掘,這樣就可得到其它視角的資料,並進一步實現資料中臺獨特的價值。
以使用者總資產為例,在我們在計算出使用者總資產這個資料之後,我們可以再以此作為資料來源,從而實現使用者的實時總資產走勢。
使用 Flink 自帶的狀態管理和運算元的定時功能,我們可以大致按如下步驟進行實現:
- 接收上游不斷更新的全量使用者資產資料,並在 Flink 內部不斷維護最新的使用者資產截面
- 配置定時器,定期地掃描最新的使用者資產截面,配上系統設定的時間戳,得到當前截面的資產快照資料
- 將當前截面的資產快照資料輸出到下游的資料庫或訊息佇列中
2.4 資料穩定性的挑戰
在專案實際上線過程中,我們還遇到了一些引入流計算後帶來的挑戰,有時這些問題會對資料的準確性和穩定性造成一定影響,其中首當其衝的是 DB 事務給 CDC 帶來的困擾,尤其是業務 DB 的一個大事務,會在短時間內對錶的資料帶來比較大的衝擊。
如圖,假如業務 DB 出現了一個交易的大事務,會同時修改現金錶和持倉表的資料,但下游處理過程是分開並且解耦的,而且各自處理的過程也不一致,就有可能出現錢貨資料變動不同步的情況,那麼在此期間算出的總資產就是不準確的。
那麼這裡針對這種情況,我們也有一些應對方案:首先一個方案和前面處理回撤流的思路類似,是透過視窗進行攢批次的一個處理,尤其是 session 視窗比較適合這個場景。
例如下圖中的程式碼,在計算出使用者資產之後不是立刻輸出結果,而是先做一個 session 視窗,把流之間最大可能延遲的變動包含進去,即把 session 視窗裡面最新的結果作為一個比較穩定的結果作為輸出;當然這裡的 gap 不能太長,太長的話視窗可能會一直無法截斷輸出,需要根據實際情況選擇合適的 gap 大小。
另一個方案的話可以是對此類大事務做一個識別,當上遊觸發一個很大的變動時,可以給 ETL 程式做一個提醒或預警感知,這樣的話 ETL 程式就可以對輸出資料做一個暫時的遮蔽,等到資料穩定之後再恢復輸出。
再有的話就可以是提升效能和算力,假設處理資料的機器效能越強,那在同樣時間資料被處理就越會更快,各流之間的延遲就越小。
三、總結
在這個場景中,我們透過引入 ETL 模式和 Flink 流計算引擎,實現了計算和儲存的分離,將計算的負擔從後端程式轉移到了 Flink 流計算引擎上,方便的實現算力的動態擴縮容,還減少了對業務資料庫讀取的壓力。除此之外,流計算出的實時結果還可以進一步給下游(使用者實時走勢)使用,實現了更多的資料應用價值。
參考:
[1] FFA2021 核心技術的分享 《Flink Join 運算元最佳化》
掃碼進入賽事官網瞭解更多資訊:
更多內容
活動推薦
阿里雲基於 Apache Flink 構建的企業級產品-實時計算Flink版現開啟活動:
99 元試用 實時計算Flink版(包年包月、10CU)即有機會獲得 Flink 獨家定製衛衣;另包 3 個月及以上還有 85 折優惠!
瞭解活動詳情:https://www.aliyun.com/product/bigdata/sc