基於 Flink 流計算實現的股票交易實時資產應用

01 背景

本次賽題思路源自於真實工作場景的一個線上項目,該項目在經過一系列優化後已穩定上線,在該項目開發的過程中數據平臺組和技術負責人提供了許多資源和指導意見,而項目的結果也讓我意識到了流計算在實際生產中優化的作用,進而加深了我對大數據應用的理解。

1.1 成員簡介

陸冠興:數據開發工程師,目前在互聯網券商大數據部門工作,主要負責業務數據開發、數據平臺建設、數據資產建設等相關工作,對流計算應用開發有一定經驗。

1.2 內容概述

本次賽題的主要內容,是通過引入流計算引擎 Flink + 消息隊列 Kafka,使用 ETL 模式取代原有架構的 ELT 模式計算出用戶的實時資產,解決原有架構下計算和讀取壓力大的問題,實現存算分離;並以計算結果進一步做爲數據源構建實時資產走勢等數據應用,體現了更多的數據價值。

1.3 一些概念

在股票交易系統中,用戶需要先進行開戶得到一個賬戶,該賬戶包含賬戶現金和賬戶持倉兩部分,之後就可以通過該賬戶進行流水操作,同時也可進交易操作。

1.4 傳統架構的實現 & 痛點

當使用傳統業務架構處理一個總資產的查詢接口時,大致需要經過的步驟如下:

但隨着請求量的增加,在該架構下數據庫和計算性能都會很快達到瓶頸,主要原因是上面的第 2 步和第 3 步的計算流程較長並且未得到複用:

02 技術方案

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 代碼如下:

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 自帶的狀態管理和算子的定時功能,我們可以大致按如下步驟進行實現:

                              

2.4 數據穩定性的挑戰

在項目實際上線過程中,我們還遇到了一些引入流計算後帶來的挑戰,有時這些問題會對數據的準確性和穩定性造成一定影響,其中首當其衝的是 DB 事務給 CDC 帶來的困擾,尤其是業務 DB 的一個大事務,會在短時間內對錶的數據帶來比較大的衝擊。

如圖,假如業務 DB 出現了一個交易的大事務,會同時修改現金錶和持倉表的數據,但下游處理過程是分開並且解耦的,而且各自處理的過程也不一致,就有可能出現錢貨數據變動不同步的情況,那麼在此期間算出的總資產就是不準確的。

那麼這裏針對這種情況,我們也有一些應對方案:首先一個方案和前面處理回撤流的思路類似,是通過窗口進行攢批次的一個處理,尤其是 session 窗口比較適合這個場景。

例如下圖中的代碼,在計算出用戶資產之後不是立刻輸出結果,而是先做一個 session 窗口,把流之間最大可能延遲的變動包含進去,即把 session 窗口裏面最新的結果作爲一個比較穩定的結果作爲輸出;當然這裏的 gap 不能太長,太長的話窗口可能會一直無法截斷輸出,需要根據實際情況選擇合適的 gap 大小。

另一個方案的話可以是對此類大事務做一個識別,當上遊觸發一個很大的變動時,可以給 ETL 程序做一個提醒或預警感知,這樣的話 ETL 程序就可以對輸出數據做一個暫時的屏蔽,等到數據穩定之後再恢復輸出。

再有的話就可以是提升性能和算力,假設處理數據的機器性能越強,那在同樣時間數據被處理就越會更快,各流之間的延遲就越小。

03 總結

在這個場景中,我們通過引入 ETL 模式和 Flink 流計算引擎,實現了計算和存儲的分離,將計算的負擔從後端程序轉移到了 Flink 流計算引擎上,方便的實現算力的動態擴縮容,還減少了對業務數據庫讀取的壓力。除此之外,流計算出的實時結果還可以進一步給下游(用戶實時走勢)使用,實現了更多的數據應用價值。

參考鏈接:

[1] FFA2021 核心技術的分享 《Flink Join 算子優化》:

https://files.alicdn.com/tpsservice/43b8f111c62623cb43a0a57a320929cb.pdf

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/b4Hl6EUHBgrC5DH_nzMf8Q