進擊的實時數倉:Flink在OPPO實時計算平臺的研發與應用實踐

爲了全面推動數倉實時化,OPPO 基於 Flink 打造的實時計算平臺 OStream ,已廣泛服務於實時 ETL/實時報表/實時標籤等應用場景。本文主要圍繞基於Flink打造的實時計算平臺OStream來展開,分享 OStream 平臺的研發之道(包括設計原則、總體架構、Flink 改進優化),業務場景的接入與應用實踐,以及平臺往智能化方向發展的探索與思考。本文整理自OPPO大數據平臺研發負責人張俊在Qcon全球軟件開發大會 2019 廣州站的演講。如果讀者正在考慮或者正在建設實時計算平臺,希望能給大家帶來一些參考。

OPPO大數據平臺介紹

首先介紹一下OPPO的業務跟數據規模。OPPO是一家非常低調的公司,跟互聯網、大數據到底有什麼關係呢?簡單地介紹一下。OPPO有自己的基於安卓的定製系統ColorOS,內置很多互聯網應用,包括應用商店、瀏覽器、信息流等一些熱門應用。這個系統經過幾年的發展,日活現在已超過2億。在業務驅動下,OPPO的數據量級從2013年開始到現在,幾乎每年都是2到3倍的增長速度,所以現在總數據量規模也比較龐大,超過了100PB,每天新增超過200TB。

圖1 OPPO業務與數據規模

在如此大數據量的規模下,不可避免要去構建數據的Pipeline。我們的主要數據來源是手機端的埋點數據,還有一部分是日誌或者是DB的數據。我們基於NiFi開源的項目構建了整個接入系統,然後基於HDFS跟Hive做數據的存儲跟計算。計算層主要有兩部分,第一部分是小時級別的ETL,做數據清洗跟加工;然後還有一個部分是日級的Hive任務,做每天的彙總工作。整個Pipeline有一個調度系統,是基於開源ALflow做的定製版,我們稱之爲OFlow。

圖2 數據處理Pipeline:離線模式

接下來是應用層。數據應用主要分三塊:報表分析、用戶畫像、接口服務。也有一些自研產品。每天可以從Hive裏把數據導到MySQL/Kylin、ES、Redis/HBase去支持產品的應用;還會基於Presto提供一些交互式查詢等服務。這一整個基於離線批處理的Pipeline,經過2到3年的沉澱,幾乎支撐了所有業務的發展。但是我們發現,最近幾年整個互聯網的人口紅利不斷消退,所以我們的業務被迫走向精細化運營的道路。

精細化的一個關鍵點是及時性,需要捕獲用戶即時行爲跟短期興趣。最典型的一個應用場景就是實時推薦,相信國內大部分公司都在做實時化推薦的事情。這樣的及時性對整個數據處理就會有實時化的訴求。這裏所說的實時化,是從過去小時級別、天級別向分鐘級別跟秒級別過渡(現在還沒有到微秒級別的訴求)。

剛纔提到最重要的三塊數據的應用——報表、標籤、接口,分別列出了實際業務的一些場景。其實除了業務,還有一塊容易被我們忽視的,是平臺側對實時化也有訴求。舉個典型的例子,OPPO絕大部分離線調度任務都是在凌晨0點啓動的。這個可以理解,因爲是T+1的處理,到了凌晨0點就可以處理前一天的數據,所以大家都希望越早處理越好。但這對我們平臺來說是一個很大的問題:一到凌晨,整個集羣壓力就非常大,經常半夜被電話叫起來,處理各種集羣問題。對整個平臺來說,如果能把批處理變成流式處理,把集中式的集羣負載分攤到24小時,平臺壓力就會減少。

另外,如果有實時化流式處理,對整個標籤導入,包括質量監控,也是很有幫助的。有了這麼一個實時化的訴求之後,各個業務都在嘗試搭建自己的實時流Pipeline。在搭建Pipeline時,技術選型最重要的兩塊,一是計算引擎,二是存儲引擎。我們都知道,在開源的生態裏,這兩塊現在是羣雄爭霸,有很多的選擇。對於計算引擎來說,有老牌霸主Storm,還有Flink、Spark streaming這樣的新貴;在存儲引擎方面,有Redis、HBase、Elasticsearch等等。我們要構建一個Pipeline,計算引擎最終是要對接存儲引擎的,很容易就會面臨下圖這樣的局面,像一個很複雜的蜘蛛網。

圖3 實時流處理的亂象

既然是一個數據平臺團隊,就有責任去構建一個平臺收斂這些系統,因此會面臨很現實的問題。比如如何說服業務,說現在就需要一個平臺,需要你們接入我這個平臺?怎麼告訴他,我們這個平臺是有價值的?站在平臺角度,我們會認爲這個價值是顯而易見的,平臺化能產生規模效應。規模效應帶來什麼呢?研發資源、硬件資源、運營資源的邊際成本都可以降低。但反過來站在業務的角度,他不一定認同。他可能會覺得,你講的是面,但他關注的是點,他會說:“我的業務快速發展是當下之急,你的規模效應確實是不錯的,但對我業務來說,平臺化會給我帶來遷移的成本、學習的成本,另外還能給我帶來怎樣的價值?”

所以對於平臺的建設來說,很重要的一點是:從一開始建設這個平臺時,就需要站在業務賦能的視角看待這個問題。能不能屏蔽掉底層的一些細節,提高易用性?能不能提供更好的抽象,普適到更多用戶?能不能跟業務簽訂服務等級協議(SLA),給他更好的服務保障?只有說,整個平臺化從點到面地推進,先解決某個業務某個點的需求,慢慢把業務接進來,才能產生規模效應,最終,最大化平臺的價值。這是我們在平臺化推進當中的一個思考。

構建實時計算平臺的技術實踐

基於這樣一個背景,實時計算平臺如何推進呢?建設思路是怎樣的呢?我們認爲,不管構建系統也好,還是說構建一個平臺也好,第一優先考慮是頂層設計。頂層設計什麼呢?我們認爲是兩層,上層是API,下層是Runtime。“冰山理論”說:你所看到的東西,很可能只是它整體的很小一部分,事實上有更多你所看不到的部分隱藏在水面之下。這理論可以套用在很多地方。

對平臺設計來說,我們希望平臺的API儘可能簡化,儘可能抽象,把更多的複雜性留在用戶看不到的Runtime這一層。所以對API考慮是易用性、表達性和靈活性。到Runtime這一層,考覈的是性能、健壯性和可擴展性。這些都是很複雜的分佈式概念。

基於這麼一個思路,平臺API如何選擇?既然說這個API面對用戶,首先要考慮公司的人員分佈與使用習慣是什麼。

圖4 我司人員使用習慣分佈

我們在離線處理時代,幾乎都是用Hive去做的,所以大部分人員都習慣於寫SQL;可能還有一部分,習慣於寫Scala、Java等,他們會希望通過Jar的方式提交數據處理;還有一部分,可能既不會寫SQL,也不會寫程序,會希望有一些簡單易用的界面,直接做數據的分析。

可以發現,SQL是非常重要的平臺一等公民。反過來我們再去考覈一下SQL這個語言,大家都很熟悉,它是一個聲明式語言,經過30、40年的發展,易用性、靈活性、表達性都很高,可以滿足我們剛纔提到的設計原則。

再到Runtime,我們的選擇其實非常多。

圖5 平臺Runtime選擇

這裏僅僅列了幾條,把核心訴求列了一個對比矩陣,發現只有Flink在各方面都滿足需求。其他的引擎,像Spark Streaming,天然的micro-batch方式,沒辦法達到這種低延時的性能;對Kafka來說,它是比較輕量級的框架;對Storm來說,現在處於一個英雄垂暮的階段,很多特性是在它那個時代是沒有考慮過的。所以Flink相對來說是一個比較好的選擇。

可能很多人會說現在Spark在發展,它從Structure streaming演進過來,也可以支持Continuous處理模式,可以支持低延時。但是我們認爲,整個技術框架發展,技術最終肯定是趨同的。爲什麼選擇Flink?還有一個很重要的原因是最近兩年Flink在國內的發展普及程度。包括像阿里團隊,他們也在大力地宣傳跟投入,包括今天QCon大會上大沙和雲邪兩位老師,他們也是阿里團隊社區的資深大V。

回過頭來,Flink這個引擎最核心的優勢是什麼?哪些對我們來說覺得是一個亮點?首先看Flink引擎這一層,它最大的優勢是Runtime,我們希望是高性能的,而它就是低延時、高吞吐。可能很多人會問,低延時跟高吞吐不是矛盾的概念嗎?不應該去做一個權衡嗎?其實Flink也沒有去打破這個權衡,其實它內部有所謂的緩衝超時(buffer timeout)的機制,可以向左走,選擇非常極端的低延時;也可以向右走,選擇極端的高吞吐。那我們怎麼看待它的所謂低延時、高吞吐呢?我是這麼認爲的:第一,不論是極端向左、向右走,最終的性能跟其他框架相比都是有一定優勢的;第二,如果你的場景不需要非常極端走兩邊,可以直接站在中間,它也有比較綜合的性能優勢。大家可以看一下官網,有一些性能對比資料。

接下來就是端到端的“只說一次(exactly-once)”和高容錯的狀態管理,滿足我們剛纔所提到的健壯性要求。再接下來,對我們非常重要的是,基於事件時間和基於晚點數據處理的機制。因爲對手機行業來說,手機上的埋點跟網頁端埋點最大的區別是,數據的上報需要考慮用戶的功耗和網絡消耗。所以經常會出現用戶的一次行爲到這次數據真正的上報之間,有一個很隨機的延遲,不能保證實時把數據報過來。所以基於事件時間去處理,包括處理晚點的數據,對我們來說,能夠正確分析用戶的行爲,這很關鍵。最後,它還可以基於Yarn。因爲我們在過去幾年的發展中都是基於Yarn去做集羣管理,所以我們希望能延續過去的經驗。

我們再來看Flink SQL提供的能力。

圖6 Flink SQL提供的能力

前面兩條可以不用說,它是支持ANSI SQL跟UDF,包括數據類型、內置函數,這個在Flink官網有很詳細的介紹。接下來,它可以自定義Source/Sink,這就滿足了剛纔說的可擴展性,我們可以不斷擴充它的生態,如果它內置沒有包含我們需要的上下游,可以自己做擴展。再接下來,就是之前其他講師演講裏提到過的,像windows、join,還有批流統一的能力,這裏就不復述了。

我們選擇了平臺的API跟Runtime之後會思考:整個平臺從離線、批處理到實時的流處理,能不能是平滑的遷移?什麼是平滑的遷移呢?前面有提到過,我們希望API這一層儘可能抽象跟簡化,能不能在API這一層經過轉變以後,對用戶來說儘可能降低學習成本跟遷移的成本?我們發現這是可行的。所以我們會看到,在離線的時代,API這一層數倉抽象是table,編程接口是SQL+UDF;到了實時時代,我們其實可以保持一致,把更多的複雜性放在Runtime這一層,可以把計算框架、存儲平臺遷移到像Flink、Kafka這樣的引擎上面,但是對於用戶來說,API這一層看到基本的抽象是一致的,這樣學習的成本是比較低的。

圖7 離線到實時處理的平滑遷移

基於這麼一個思路,最終推導出來整個數據Pipeline的實時模式,跟剛纔我們說的離線時代幾乎是一樣的,只不過我們把一些關鍵性的組件替換了,比如說把HDFS替換成了Kafka,把Hive替換成了Flink,還有還有OStream平臺,替換離線任務調度。然後其他的產品,包括基於報表、畫像、接口這樣的產品,都是保持一致的。

圖8 實時模式與離線模式保持一致

有了這麼一個建設思路以後,如何開展整個平臺研發工作呢?首先做平臺,就要把整體的架構畫出來。然後大致分這麼幾個層次:最下面一層是基礎引擎層,這一層大家都很熟悉,無非就是基於Kafka、Flink、ES等這些系統,整個集羣管理通過Yarn來管理,然後Flink需要做檢查點,會用到HDFS;平臺特性這一層,提供網頁版IDE,會有像元數據管理、流作業管理、日誌檢索、監控告警等這些能力;再往上就是API這一層,編程接口支持SQL,SQL是我們的第一等公民,然後還可以支持Jar包提交的方式;再往上,我們把它稱爲“集成工具”。剛纔提到,我們會有一些場景,希望用戶不用寫SQL,可以通過簡單UI配置的方式,就能自動生成SQL。然後我們還會跟內部的CI、CD工具自動打通,自動編譯好Jar包之後,自動把Jar包傳到我們這個平臺,這個正在研發當中。這個就是整體的架構。

圖9 OStream平臺總體架構

前面我提到說SQL是整個平臺的第一等公民,所以第一步要解決基於SQL開發的這麼一個框架。剛纔提到API這一層是基於Table SQL+UDF的方式,具體落地到我們的產品,我們需要有一個類似這樣的界面(如果大家比較熟悉,這是開源的Hill的界面):左側有一系列的table,右邊就是SQL的編輯框,可以提交SQL。這個開發界面,具體落到Runtime這一層需要怎麼支撐呢?核心有兩點:一個是元數據的管理,就是如何創建庫表,如何上傳UDF;還有一個就是流作業的管理,如何編輯SQL,如何提交作業,最終提交給Flink框架執行。

帶着這個問題,我們看一下Flink SQL現在API的情況。

圖10 Flink SQL API編程示例

這是最簡單的Flink SQL編程示例,大概就是20行代碼的樣子。剛纔提到流作業的管理,SQL的編譯、提交,包括元數據的管理、上傳table、註冊table,都可以通過這個方式創建。但是我們還是不希望這樣面向用戶。因爲用戶不希望用編程的方式去實現。所以我們基於Flink簡單地做了擴展。

整個過程大概是這樣子:首先在我們的開發IDE上面,用戶可以寫一個SQL,寫完以後可以提交,提交時創建類似於job的概念——我們會把SQL跟它所需要的資源、配置等封裝起來,變成一個job,然後保存到MySQL。這個時候有一個job store,定期掃描MySQL,看看有哪些新的job,然後調用Flink裏的TableEnvironment模塊,真正去編譯這個SQL,編譯後會生成一個JobGraph(這是Flink認可的一個可執行的單元),最終再向Yarn去提交這個任務。

剛纔提到過元數據管理——table創建出來以後,怎麼被Flink識別?這裏面涉及到編譯過程中,通過元數據中心創建的table,如何被它識別到?我們實現了一個框架,用到了Flink裏面的TableDescriptor概念,創建了一個TableDescriptor,即對錶的描述符,也保存到MySQL裏面。然後通過Flink的ExternalCatalog(剛纔雲邪老師也有提到過),可以把table識別出來,最終通過TableEnvironment去註冊。這樣,Flink就可以識別出外部的表,這個就是整個開發框架的實現。

圖11 元數據中心的界面

左邊是創建表的界面,我們可以創建像Kafka、MySQL、Druid、HDFS等的表。當然,我們其實不希望對用戶暴露DDL,因爲對我們的用戶來說,他們也不希望去寫create table,他們希望通過UI的方式創建表。右邊是UDF的上傳,可以自己寫一個UDF,也可以把一個Jar包提交過來,然後指定你的入口類(main class)是什麼。

圖12 開發IDE界面

還有這個是我們的開發IDE,也是很簡單的界面,其實就是模仿Hill的開發界面。

有了這麼一個開發IDE以後,還要解決什麼問題?真的要把它推向用戶使用的話,還有兩個基本的問題要解決。

一個是UDF,我們在Hive時代,積累了大量內部的UDF,包括加解密、格式轉換,可能會有點問題;還有就是位置、距離的計算等等,其實有大量Hive的UDF。在Flink的處理上,大家也希望能夠繼承下來,直接使用它們。目前來說,Flink還不支持這一點。雲邪老師剛纔說,到1.9可能支持直接調用Hive的UDF,這個我們是很期待的,但目前來說還沒有。我們作爲平臺方,需要把所有的UDF在Flink的框架上重新實現一遍。

第二就是維表的join。有一些業務的維表存在MySQL、HBase、Hive裏面,我們需要去實現這一點。這在Flink1.9也將會實現,但現在的版本還沒有,我們怎麼做呢?

圖13 維表創建頁面

首先,這是我們的維表創建頁面,可以創建一個MySQL的維表。由於大部分場景下,維表不算太大,可以直接把它cache到Flink的任務管理容器(task manage container)裏面,所以我們這裏可以選擇ALL模式,即把所有的數據全量導入到容器。或者選擇LRU(least recently used,最近最少使用)模式,表示這是可以刷新的。還有一個模式(NONE)是乾脆什麼都不做,每次都去查外部的數據源。

這是維表的實現,我們現在如何join呢?

圖14 維表關聯的實現

如上圖,我們在平臺層面會自動改寫SQL。假設用戶寫了一個SQL,裏面有一個join語法,我們在提交之前會加一層SQL的解析,解析出這是針對維表的join(因爲我們知道哪個表是維表),把解析出來的上下文封裝成一個JoinContext。有了JoinContext之後,我們會把SQL解析成更簡單的SQL。我們把join的語法替換掉,替換成類似於另外一張表的形式。

背後怎麼做呢?實際用到了Flink裏可以把table跟stream做無縫轉換的功能:把原始的table轉成stream,然後在stream這一層再用flatmap的形式,每次去調用flatmap時,都會調用我們自定義的一個flatmap函數,這個函數會把剛纔我們解析得到的JoinContext拿過來。這個JoinContext包含什麼內容呢?包含維表到底是哪一張表、地址是哪些、關鍵字是什麼。我們拿到這些信息之後,就可以去MySQL裏面,在初始化階段,open階段,把維表全量加載到cache裏,然後每次做flatmap時,就可以從cache裏查找,最終通過stream跟table之間的轉換,再轉換回一個table,最終改寫成這樣的一個SQL。這就是我們當前的實現,看起來會有點奇怪,所以我們也很期待Flink原生對join的支持。

接下來,這個平臺少不了的,就是日誌檢索。這也是平臺化時需要提供的很常規的功能。我們現在可以對日誌提供基於作業名稱的、基於Yarn App ID的、基於container ID的全文檢索。我們怎麼做的呢?

圖15 日誌採集Pipeline

其實也比較簡單,無論是SQL還是Jar包,通過OStream提交Flink作業時,我們會自動生成這樣一個Log4j的properties配置文件,提供給Yarn以後,最終JobManager跟TaskManager在執行時,就會用到我們自定義的Log4j Appender,把我們的日誌導向統一的Kafka,最終再把所有的日誌導入ES做索引,就做到了全文索引。還有一個問題需要解決:所有的日誌怎麼跟OStream的job關聯起來?比如需要知道每一行日誌到底是屬於哪一個job。這個比較好解決,我們在Flink裏面有配置:在提交Flink作業時,可以給出6個環境變量,這6個環境變量分別注入給TaskManager跟JobManager,我們的Log4j Appender會自動識別進程裏有沒有這樣的環境變量,如果有,把它寫到ES裏,每一行日誌跟作業名稱、App ID都可以關聯起來。

接下來就是指標的監控,這也是平臺裏面需要提供的一個基本能力。有很多指標,但對於用戶來說,最重要的兩個指標是什麼呢?一個是對Flink作業的吞吐,第二個是Kafka消費的lag。這個我們怎麼做呢?

圖16 指標採集Pipeline

首先,Flink的KafkaConsumer是一個源,對於每一個Flink job來說,KafkaConsumer會自動從Kafka的Consumer裏繼承它原生提供的很多指標,比如其中一個指標就是這個Consumer消費Kafka的position、消費的延遲是什麼。對於所有的Flink Operators來說,也會暴露出來這麼一個指標:這個Operator每秒出去了多少條數據。基於這兩種指標,我們可以通過Flink原生的內部Matric的系統/子系統,比如它內部封裝的,像MatricGroup、MatricRegistry,通過我們自己定製的一個MatricReporter,可以把我們剛纔提到的兩個指標導入到Kafka,最終也寫到ES裏去。然後基於這些指標做監控和告警。

圖 17 告警規則

這是告警的頁面,也是比較簡單。基於這個指標可以做同、環比,或者是絕對值的告警。

上面大致介紹了基於Flink做的平臺化的工作。這裏做一個小結,是我們平臺研發過程中積累的小小心得,簡單分享一下。

第一個心得,對於我們這樣的團隊來說,最好的策略是維護純淨的分支。這個跟研發投入密不可分,最好的策略是緊跟社區的步伐,儘可能少改動Flink本身內核的東西。不然Flink整個社區發展很快,慢慢就追不上它的步伐了。怎麼做呢?有兩個策略。第一,任何的開源框架要打造它的生態,必然要去做很多擴展點,我們可以基於這些擴展點,做一些插件開發。比如剛纔提到的元數據管理、如何把外部表對接到Flink裏面去、如何做日誌跟指標的採集,其實都是基於剛纔提到的Catalog、Log4j Appender、Matric Reporter等等的擴展點。第二點是,如果沒有擴展點可以開發插件,我們可以基於API去做二次抽象開發,不要改原生的代碼。就像剛纔提到的SQL的作業、維表的關聯,其實都是基於原生的TableEnvironment、TableDescriptor這些API做的開發。

第二個心得,對於小團隊來說,研發投入是有限的,大家如果想要去參與開源社區,怎麼參加呢?我有些小的建議。首先我們要關注整個社區的動態。最好的方式有兩個,第一個是可以關注一下Flip。每次大的變動在Flip都有介紹,從modification到design再到example,是一個完整的story,可以瞭解這個模塊整個發展路徑。還有一個就是關注它的pull request。這可能是最好的途徑,因爲每個想要往社區提交代碼的,都要發一個pull request,可以關注到每個模塊,比如像table、connect、runtime這些模塊;可以看到作者們在內部的對話,看到社區的進展。第二個可以看作建立一個認知——如果你沒有參與過開源的項目,可能對git branch的機制、怎麼fork一個分支、怎麼merge代碼、怎麼提交PR都沒有認知。可以通過一些小的方式,比如說,可以去幫忙修正一些typo。你會發現Flink文檔也好,註釋也好,有很多語法錯誤,不管是語法拼寫還是語法表達——這些就是typo。當然,提交這種(對於編程)沒什麼意義,但如果只是想建立一些基本認知,可以去做一些嘗試。最後就是所謂快速調試,什麼意思呢?你想去了解Flink某一個模塊,或者是某一個想法想快速驗證一下,不可能說連接外部的Yarn,然後再搭建一個Kafka,然後再往Kafka裏插入一條數據,這樣太慢了。最好的方式就是把端到端的集成化測試本地化起來,在自己的電腦上,不需要依賴任何外部數據源,可以直接跑通。還有,自動化起來:整個端到端,比如說往Kafka插入一條數據、往Yarn提交作業,都可以自動化起來,可以更好地瞭解Flink;甚至可以設一些斷點,跟蹤它的代碼,瞭解Flink裏面的內部機制。

這裏給出了我自己的一個小工程,如果大家感興趣可以瞭解一下。我利用很多像Kafka mini cluster、Yarn mini cluster的機制,在本地IDE可以把整個端到端的流程跑通,而且過程都是自動化的,不需要依賴任何外部的系統。

OStream平臺運營實踐經驗

介紹了研發工作,最後再介紹一下平臺運營方面的實踐。第一個實踐就是,我們認爲最好的方式還是拆分離線跟實時集羣。我們最開始的時候,所有的Flink都跑在離線集羣當中。但我們認爲是有問題的,爲什麼呢?整個離線集羣跑的作業都是短平快的作業,資源分配很不確定。

圖18 離線-實時計算集羣拆分

如果大家對Yarn很熟悉的話,看到這張圖應該會很親切。我們可以看到,Yarn是公平調度機制,每個隊列之間,資源分配都是不確定的。不確定性表現在哪裏呢?大家可以看到上面有綠色的部分和橙色的部分。雖然可以給每個隊列分配資源,但其他的資源隨着每個隊列的情況在不斷動態變化。舉個例子,比如說它有一個steady的fair share,這是固定分配的資源份額;還有instantaneous的fair share,表示如果說有一個隊列,裏面沒有任何作業,那就要分享出來給別人去用。還有一塊就是隊列可以透支資源:比如有一些隊列即使裏面有程序在跑,但沒有消耗完這個份額,這個份額需要被調出來給其他隊列分享,其他隊列可以透支。那這對我們來說是有問題的,線上集羣出現好幾次這樣的問題。假設說批處理跟實時處理是兩個不同的隊列,可能我們在最開始的時候,兩個隊列是50%、50%的資源分配,假設實時作業有一些問題導致它要重啓,重啓的過程中,它的資源會立刻被另外一個隊列搶佔了,因爲另外一個隊列可以透支這個資源。所以對於線上隊列,我們會把搶佔給關閉,這可能是另外一個話題了。如果把搶佔開啓,其實也有很多的不確定性。

對我們來說,實時隊列資源被搶佔,導致重啓之後就沒資源跑起來。所以我們認爲,雖然大家都在談離線跟在線的混步,但我們沒有精力研究混步到底怎麼做,所以最穩妥的方式如果要提供服務的保障,最穩妥的方還是式把這兩個集羣做物理拆分。

還有測試,是從我們原先的實時處理模式系統繼承過來的。大家知道,我們做數據開發,很重要的一點是,它的測試跟普通程序可能不太一樣。很多時候,做數據開發,測試數據的邏輯,需要的不是隨便造幾條數據就可以了,需要的是全量的數據、生產的數據,不是隨便造一些數據。我們以前的方式是這樣的:在發佈測試作業之前,讀寫的是測試數據庫,而測試庫的數據就從生產數據採樣而來。但對業務來說,他們覺得這是有問題的。所以平臺可以這樣做:既然測試時需要依賴生產庫,平臺可以在提交SQL時做SQL改寫,比如自動把insert into改爲指向一個測試的庫,但是讀還是直接從生產庫讀,可以得到及時的、全量的數據,而不會對生產庫造成任何的影響。這是我們的實踐經驗。

圖19 全鏈路延遲監控

最後一個實踐就是全鏈路延遲監控。上面這個鏈路是基於Flink的整個實時處理鏈路。數據從最開始的NiFi開始,經過三次Kafka,最終才真正到Druid裏,然後才能從Druid裏做報表。大家可以看到,在每個環節都有lag,從NiFi到Kafka,從Kafka到Aggregate,再從Kafka到Druid,有3次lag。之前做延遲監控都是針對單個作業,但用戶關心的是他們的數據到這個系統之後,什麼時候才能在報表中體現出來,關心的是全鏈路的延遲,單個延遲對他來說沒有什麼意義。所以我們需要構建血緣關係。

解析ETL、做Aggregate,把整個血緣關係解析出來,形成下面這個通路,從接入通道,到中間的table,再到中間的job,再到最終的Druid,這樣我們可以把這4個lag加起來,變成一個統一的lag,做成一個統一的監控。大家可能還會有一個疑惑:中間ETL到Kafka、Aggregate到Kafka有兩處空白,其實也有lag,爲什麼沒有做監控?我們認爲,Flink有比較好的反壓機制。如果ETL到Kafka、Aggregate到Kafka有延遲,Flink通過反壓機制,可以反映到上游的Kafka消費的lag裏,所以我們可以忽略中間這兩個lag,只計算那4個lag。

上面介紹了我們的研發和運營的工作,接下來分享一下案例。

圖20 交互式查詢界面

這個界面是以前離線時代提供的交互式查詢界面。到了實時數據時代,用戶覺得這樣的界面還是挺好的,還是需要通過UI的方式、拖拽的方式直接分析實時數據。當然,我們會做一些限制:比如說通過拖拽的方式,不可能去查全量的實時數據,可能只能查最新的數據,比如最近一個小時或者幾分鐘的數據。

下面是實時的ETL,是我們線上應用最廣泛的實時流處理應用。

圖21 實時ETL

所有從手機端過來的數據,都會報到同一個通道里。在錄入到數據倉庫之前,需要對這些數據做一個拆分,我們用的就是Flink的SQL方式,比如我們可以寫這4個SQL,對同一個table的數據,通過不同的條件,拆分到不同的下游表。我們用同樣這一套處理邏輯,我們可以同時把數據插入到Kafka或者HDFS,作爲後面離線處理的源數據。

還有就是實時標籤。標籤對我們來說是最重要的數據資產,所以實時標籤是很重要的應用。

圖22 實時標籤

這個就是我們整個實時標籤的通道,從Kafka裏寫一個類似於這樣的SQL,插入一個表,格式做一定的限制——因爲這最終要對接到我們的標籤系統。大家也可以看到,我們用到了嵌套SQL,也用到UDF的函數(包括窗口)去實現。

未來展望和規劃

最後做一些未來的展望:平臺應該怎麼走?

這可能說得比較大。我們認爲一個平臺發展的必然路徑,肯定是要從自動化走向智能化,如果大家比較有信心的話,還會加一個詞“智慧化”。大家對這三個名詞的定義不太一樣,尤其是“智能”,現在各種各樣的地方都有智能這個詞。

我這裏給出我的定義。什麼是自動化?我認爲它是處理機械的、重複的事情,解放我們人的雙手。最典型的自動化應用就是任務調度系統。每天到了一個點,重複性地、不需要人工干涉地自行調度任務,這是自動化。什麼是智能化?一定是要做自適應跟自學習。因爲不斷自學習,所以它的行爲應該是人沒辦法預料到的。可能最典型的應用就是一些AI系統,像阿爾法狗,是可以不斷學習的。最後一個“智慧化”,可能比較虛,現實中可能沒人見過。以前有部美劇《西部世界》,裏面那些Hosts,就是有自我意識跟自我思維的,具備智慧化的。我們先拋開智慧化不談,從自動化到智能化,聽起來還是比較虛,具體落實到我們平臺,我們能做什麼事情呢?

比如說對自動化,我們可以做端到端的自動打通(我在後面再說)、通過規則自動生成SQL、每次提交作業時自動生成告警規則,這些是一些自動化的工作。什麼是智能化?我們其實也可以嘗試,比如說作業的資源它能不能自動伸縮,因爲線上負載一定是曲線的,不是直線。如果說作業資源是直線,難免會有資源的浪費或者不夠用的情況,能不能做自動伸縮。還有,作業出現異常能不能自我修復;作業參數這麼多,不可能說在提交作業時考慮完善,有一個完美的參數配置,那麼能不能在作業運行過程中自動動態調優。

首先看一下端到端的打通。這是一個真實的案例。

圖23 以前SQL的Pipeline

這是我們以前SQL的Pipeline。數據處理完了,從Kafka讀、往Kafka寫;然後通過數據導入的方式導入到存儲引擎,這可能是另外一撥維護存儲引擎的同學做的;最終把它變成線上資產,可能是數據產品人員做的。這3個環節是割裂的,而且需要不同的人去處理。我們考慮能不能把這個過程自動化起來,不要給用戶暴露出來說這是個Kafka table,而是就以展示表、標籤表、接口表展現,這纔是面向場景的。舉例來說,對於展示表,無非有維度字段、指標字段、篩選字段、時間字段等。有這些字段以後,整個數據處理完,寫完SQL以後,數據就可以自動加載到像Druid這樣的平臺裏面去,最終也會自動變成一個報表,整個過程不需要用戶再去操心。這就是線上正在做的例子。剛纔也提到整個表的創建,未來表的創建是這樣的:寫了字段名稱、描述、類型之後,可能還要判斷,如果這是展示的表,則需要指定哪些是維度字段,哪些是指標字段。指定完成之後,當你寫SQL,比如insert into,自動會跟下游像Druid、像報表系統打通,甚至可以嘗試自動把報表創建出來,對用戶來說,這就是端到端的流程。

最後分享一篇論文,是關於剛纔說到智能化的。這是2017年微軟跟Twitter發的一篇論文(他們做了一個叫Dhalion的系統),很有意思,它的關鍵字是self-regulating的,翻譯過來可能是“自我監管”,滿足我們剛纔所提到的智能化的方向。

圖24 線上作業智能化自動伸縮

這是從論文裏摘出來的一張圖,說明線上作業如何實現智能化自動伸縮。它是這麼做的:首先是有一些Detector,像醫生去診斷一樣,去收集指標,根據指標判斷是什麼樣的症狀,比如說是產生反壓了,還是線上的速率有傾斜了;獲得症狀後做一個診斷,通過不同的診斷器,判斷到底是數據傾斜呢,還是有些實例因爲磁盤變壞而變慢了;經過診斷以後,對症下藥,如何解決這個問題。這可能也是自動的,有一些規則說怎麼解決數據傾斜的問題、如何解決某個實例變慢的問題。

整個過程,包括中間的診斷,可以做成規則化,也可以做成基於機器學習,不斷可以自適應的方式。這個框架基於Twitter之前做的Heron系統。我們能不能在Flink裏面做這樣的嘗試?因爲我們發現無論做指標收集也好,反壓機制也好,到通過API對作業做自動伸縮,這些在Flink裏都可以實現。我們能不能做這樣一套框架,自動地、智能地在線上做資源的動態伸縮,這是挺有意思的一件事情。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章