隨着雲計算的深入落地,大數據技術有了堅實的底層支撐,不斷向前發展並日趨成熟,無論是傳統企業還是互聯網公司,都不再滿足於離線批處理計算,而是更傾向於應用實時流計算,要想在殘酷的企業競爭中立於不敗之地,企業數據必須被快速處理並輸出結果,流計算無疑將是企業Must Have的大殺器。作為充電生態網的領軍企業,特來電在流計算方面很早便開始布局,下面筆者拋磚引玉的談一下流計算及在特來電監控引擎中的應用實踐。
一、由Bit說開去

作為計算機信息中的最小單位,Bit就像工蟻一樣忙碌,任一時刻都只能處於以下三種狀態中的一種:計算、存儲、傳輸,要么在參與計算,要么在去計算的路上,要么等候計算。見微知著,雲計算架構圖中,底層IAAS必然有三個模塊:計算、存儲、網絡,因為微觀的Bit狀態本質,決定了宏觀的架構行為。無論微觀還是宏觀,不難發現計算是永恆的主題,一切都圍繞着計算進行,計算的結果,便會產生數據。

今天如火如荼的大數據,本質也是數據,但是又多了一些新的特征,比如老生常談的“4V”:Volume(數據量大,至少是TB級,甚至是PB級)、Velocity(數據增長速度快)、Variety(數據格式多樣,結構化、半結構化、非結構化)、Value(數據價值大)。大數據之所以能落地,不是依靠表面的“V”,而是“V”后強有力的支撐工具,比如用批處理工具高吞吐的支撐“Volume”,用流計算工具低延遲的支撐“Velocity”,用NoSQL工具高效存儲支撐“Variety”等。針對這幾個“V”,筆者總結了兩個共識:1. 大數據的發展,將逐漸從Volume之大,轉向Velocity之大;2.數據的價值(Value)隨着時間流逝而急劇下降。這就要求必須快速處理數據,所以我們應該專注流計算。
流計算(Streaming Computing)是一種被設計來處理無窮數據集的數據處理系統引擎,流計算面向的對象是無窮數據(一種持續生成,本質上是無窮盡的數據集),與之相對的批處理面對的是有限固定的數據集。子在川上曰:逝者如斯夫!面對滾滾的無窮數據集,流計算該怎樣處理它們呢?答案便是使用窗口,就是對一個無限的流設置一個有限的元素集合,在有界的數據集上進行操作的一種機制,可以分為基於時間(Time-based,比如1分鍾一個窗口進行計算)以及基於數量(Count-based,比如100個數一個窗口進行計算)。
流計算早期是不成熟的,因為在實現上存在一些技術難點:如何保證強一致性、如何做到精確一次(Exactly-Once)的處理數據、如何應對網絡抖動時數據的亂序與延遲到達,如何按數據的發生時間(Event Time)進行計算而不是數據被處理的時間(Processing Time)、如何在流數據上執行Streaming SQL、如何解決數據接收速度大於數據處理速度時帶來的背壓問題等。令人欣慰的是,隨着技術的不斷發展,以上問題都被逐步解決,流計算已經可以媲美並超越傳統批處理計算,總結起來,流計算發展經歷了三個階段:
1.Lambda架構(一套數據、兩個處理引擎、兩套代碼);
2.Kappa架構(一套數據、一個處理引擎、一套代碼,當需要全量計算時,重新起一個流計算實例,從頭開始讀取數據進行處理,並輸出到一個結果存儲中);
3.真正流計算,典型特性有:事件驅動、Exactly-Once、以流計算為主、流計算與批處理完全統一。

大數據是從批處理開始的, MapReduce花開兩枝,一枝是閉源的Google,一枝是開源的Apache,基本上谷歌內部有的大數據工具,都有對應的開源實現,Apache Beam則希望做救世主,讓閉源和開源大數據工具有一個統一的開發界面,為上層應用開發做透明代理,屏蔽底層的各種實現方式。在亂花漸欲迷人眼的各種大數據工具中,我們必須保持一種技術定力,否則很容易陷入技術虛無主義。基於前面分析的原則:要快速處理數據、要專注流計算、要基於真正流計算工具等,我們最終選定了老當益壯的Flink,這也是目前在Apache Beam上運行的最好的開源Runner。
二、老當益壯的Flink

上圖是一些主流開源及閉源大數據工具的起源時間,可以看到Flink(歐洲)和Spark(美國)雖然都發軔於2009年,但Flink其實是比Spark稍早一點的,不過從當前發展形勢看,Spark意欲一統大數據天下,已經對Flink以及其他大數據工具形成了碾壓態勢,Flink可謂“起個大早趕個晚集”,這很大程度上也和歐洲及美國的商業化環境有關。盡管如此,Flink仍能夠倔強成長,一方面是大家不希望看到Spark一家獨大,需要保持平衡;另一方面則是Flink着實有自己的獨門絕技,否則早被Spark拍死了。


上圖左邊是Spark技術棧,右邊是Flink技術棧,不難發現,二者使命相同,都是要提供“One Stack To Rule Them All”的一站式大數據分析解決方案(涵蓋批處理、流計算、機器學習、圖計算等)。除了開源社區,Spark背后有DataBricks商業公司在運作,Flink背后有DataArtisans商業公司在運作,由於生態環境不同,因此願景稍有差異。不過最大的差異在於二者的價值觀:Spark以批處理為主,認為流計算是批處理的特例,使用微批處理(Micro-Batch)來應對流計算;Flink以流計算為主,認為批處理是流計算的特例,使用一段時間內的有限數據集來應對批處理。打個比方,Spark和Flink一起吃面包,Spark正常情況下是一口一個面包的吃,遇到流計算時,把面包切片了,一口一個面包片的吃;Flink正常情況下一口一個面包片的吃,遇到批處理時,幾個面包片一起吃,也能一口吃一個面包了。從長遠來看,Flink的設計理念更契合流計算的標准模型及本質特征,除Google閉源工具外,它是第一個真正實現亂序數據處理的開源流計算引擎,下面簡要看一下Flink的關鍵技術點。
- Exactly-Once
Exactly-Once是流處理系統核心特性之一,它保證每一條消息只被流處理系統處理一次,通過借鑒Chandy和Lamport在1985年發表的一篇關於分布式快照的論文,Flink實現了Exactly-Once特性。它根據用戶自定義的Checkpoint間隔時間,定時在所有數據源中插入一種特殊的快照標記消息(Barrier),這些快照標記消息和其他數據消息一起在DAG(有向無環圖)中流動,但不會被用戶定義的業務邏輯所處理,快照的存儲是異步和增量操作,不會阻塞數據消息的處理。若發生節點掛掉等異常情況時,只需要恢復之前成功存儲的分布式快照狀態,並從數據源重發該快照以后的消息即可,當然這也要求數據輸出端持久化時支持冪等操作。
- 三個時間域

如上圖所示,Flink支持三個時間域:
a) Event Time:事件時間
每個數據都要攜帶一個時間戳,用於標記數據產生時間。
b) Ingestion Time:攝取時間
數據被從消息隊列(比如Kafka),提取到Flink時的時間。
c) Processing Time:處理時間
數據真正被處理時所在機器的時間。
打個比方,筆者在網上訂了一本關於Flink的書(迄今沒有Flink相關的中文書籍出版),這本書的出版日期是Event Time,從快遞員手里接過書的當天日期是Ingestion Time,真正看這本書的當天日期是Processing Time。
一般的流計算系統進行數據處理時,都是基於Processing Time的,這相對來說比較簡單,即不管數據是什么時間產生的,都以數據實際被處理時所在機器的時間為准,這在計數類應用或者計時不敏感類應用中可能沒啥影響,但是如果要嚴格按照數據實際產生時間進行計算的計時類應用,則當數據亂序產生或者延遲到達時,計算結果將會大相徑庭,令人贊嘆的是,Flink能夠輕松應對上述三種時間域的計算。
- 四種窗口
時間域確定了計算數據時的時間類型,而窗口則要確定對多長時間內或多少個數據進行計算,Flink支持四種內置窗口:
a) Tumbling Window:翻滾窗口、固定窗口
每個窗口的大小是固定的(比如1分鍾),各個窗口內的數據互不重疊。
b) Sliding Window:滑動窗口
每個窗口由兩個時間構成,一個是窗口大小時間(比如1分鍾),一個是窗口滑動時間(比如30秒),各個窗口內數據有重疊,比如要每隔30秒統計過去1分鍾的數據。
c) Session Window:會話窗口
用於標記一段用戶持續活躍的周期,由非活躍的間隙分隔開。各個窗口內數據互不重疊,沒有固定的開始時間和結束時間,當它在一個固定的時間周期內不再收到數據,即非活動間隔產生,這個會話窗口就會關閉,后續的數據將被分配到新的窗口中去。
d) Global Window:全局窗口
將所有具有相同Key的元素分配到同一個全局窗口中,該窗口模式僅適用於用戶還需自定義觸發器的情況。
- Watermark:水位
流處理從事件產生,到最終輸出計算結果,中間有一個過程和時間,大部分情況下,流數據都是按照事件產生的時間順序來的,但不排除由於網絡、背壓等原因,導致數據亂序或延遲到達,對於延遲數據,又不能無限期的等下去,必須有個機制來保證一個特定的時間后,觸發window去進行計算,這個機制就是Watermark。
Watermark是數據本身的一個隱藏屬性,通常基於Event Time的數據,自身都包含一個Timestamp,比如1320981071111(2011-11-11 11:11:11.111),則這條數據的Watermark時間可能是:
Watermark(1320981071111)= 1320981068111(2011-11-11 11:11:08.111)
即Timestamp小於1320981068111(2011-11-11 11:11:08.111)的數據,都已經到達了,可以對窗口內數據進行計算了。
三、特來電監控引擎

上圖是特來電監控平台整體架構圖,和一般的大數據分析處理流程類似,主要由以下幾部分組成:
- 數據采集
由部署在各個機器上的監控Agent組成,職責是收集監控數據。收集方式既支持業務通過程序埋點方式主動上報數據,也支持通過監控插件方式,由監控Agent定時主動拉取數據(如性能計數器數據、Windows日志數據、網絡端口連通性數據、Kafka運行數據、RabbitMQ運行數據、Redis運行數據、關系數據庫運行數據、大數據組件運行數據等)。
- 數據輸入緩存(Source)
基於開源的Kafka緩存監控上報的數據,Kafka是流計算的標配,可以說“無Kafka,不流計算”,Kafka最近剛發布了1.0版本,無論是穩定性,還是性能方面,Kafka都是不可替代的消息隊列技術棧。
- 數據計算
監控引擎是監控平台的核心組件,主要做兩件事:一是一定要基於監控數據的Event Time進行計算,並能處理亂序數據以及延遲到達數據;二是要對監控數據做各種維度的聚合計算。利用Flink,基於Event Time進行聚合計算,計算時間可以控制在秒級,是計算密集型場景。

如上所示,使用Flink進行計算時,可以很容易設置計算數據的時間類型,針對監控數據,必須用Event Time,才能保證指定時間的數據都落在同一個窗口。
為了應對數據亂序以及延遲,還需要設置水位,因為每個監控數據自身都攜帶了數據產生的時間戳,所以設置起來也很自然:

監控引擎核心功能在做各種維度的聚合計算,如下所示,如果一分鍾內上報的監控數據如下所示:

則進行維度計算時,主要有如下四種維度計算方式,分別計算出相應維度數據的最大值、最小值、平均值、最后值、求和值以及數據個數:
a) 全部數據維度

將所有數據進行累加求值
b) 指定集群維度

將所有數據按不同集群進行累加求值
c) 指定應用節點維度
將所有數據按不同節點進行累加求值
d) 自定義維度

前面三種維度相當於固定維度,監控數據還支持用戶自定義維度,如果上報數據有自定義的維度,則將自定義維度Key以及Value取出后進行flatMap,和前面三種固定維度進行並列計算。
基於Flink進行聚合計算時,需要先根據監控元數據進行分組,然后計算指定時間窗口內的數據,如下所示:

監控引擎運行時,可以通過Flink的Job Graph,實時查看每個Operator的執行鏈,如下所示:

- 數據輸出緩存(Sink)
監控數據進行聚合計算完畢后,不是立即持久化到存儲,還是先Sink到Kafka,一是讓監控引擎工作更純粹,面對的Source和Sink都是Kafka;二是通過Kafka對數據落盤進行緩沖,減少直接寫存儲可能帶來的監控引擎阻塞,讓流計算更流暢,降低背壓的產生概率。
- 數據存儲
特來電監控平台和其他互聯網監控平台最大的不同,也是特來電監控平台最大亮點,是支持從計算后的聚合數據,鑽取聯查到監控Agent上報的原始數據,這要求既要存儲計算后的聚合數據,也要存儲原始數據,還要存儲聚合數據與原始數據的關聯關系。因為監控數據最大的特點是時間相關性的,因此使用時間序列數據庫InfluxDB存儲計算后的聚合數據,使用HBase存儲原始數據以及原始數據與聚合數據的關聯關系。
- 數據展現
監控數據最終通過Grafana進行展現,可以實時監控雲平台系統層面以及業務層面的運行狀態,為故障檢測、診斷及定位打下了堅實基礎。如前所述,通過Grafana不僅可以查看存儲在InfluxDB中計算完畢的聚合數據,還可以通過Grafana鑽取功能,聯查到存在HBase中的原始數據。
四、技術不止、坑坑不息
技術研究、探索以及應用,總是會伴隨着很多坑,踩坑填坑的過程,就是技術積累的過程,技術人要感謝這些坑,因為它們加深了對技術的認識。
1. 時間戳與Decimal
由於監控Agent是基於C#開發的,監控引擎是基於Scala開發的,DateTime作為C#中內置的數據類型,在Scala中沒有可直接對應的數據類型。考慮到監控Agent上報到Kafka中的數據是Protobuf格式,因此需要根據C#中DateTime對應的.proto文件,生成對應的Java代碼,然后在Scala中反序列化時,調用Protobuf時間類,轉換成可識別的時間戳。
另一個在跨語言時沒有直接對應的數據類型是C#中的Decimal,處理方式和DateTime類似,也是需要定義中間轉換的.proto文件,生成對應語言的類,反序列化時進行調用處理即可。
【注】在處理Protobuf類時,專業的做法是只定義.proto文件,使用gRPC插件動態生成對應的Java類。
2. 從Spark Streaming到Flink
特來電監控引擎的前一版本是基於Spark Streaming實現的,但是在解決亂序數據及延遲到達數據時,遇到了很大的技術障礙,因為Spark Streaming是根據Processing Time進行計算的,如果要基於Event Time進行計算,技術實現難度很大,先后嘗試過Alluxio、Ignite、Structured Streaming,都沒能很好解決該問題。直到嘗試使用Flink,發現這是其內置特性,可以輕松解決以前遇到的問題,后面便果斷放棄Spark Streaming,堅定選擇Flink作為監控引擎的實現工具。
3. 其他經常會遇到的坑還有:應用程序使用的jar包,比Flink自身提供的jar包新,可以通過maven-shade-plugin進行打包;查看Flink的Job Graph時,需要使用Chrome瀏覽器,微軟的IE瀏覽器沒法顯示完整執行鏈;Map作為Protobuf中的特殊集合,不能被Flink直接序列化,需要自定義序列化類或者將類封裝后,實現Serializable Trait;Checkponit時間不要設置的太短,否則會導致頻繁創建快照;生產環境不要把執行鏈斷開,否則會有一些性能問題等等。
五、All In Flink
筆者認為,“老當益壯”的Flink作為“新一代”冉冉升起的真正流計算引擎,將會長期與Spark並駕齊驅,並逐步擴大其應用范圍。我們也會將其逐步應用到實時預警、實時全鏈路分析、風控管理、用戶行為分析,並逐步探索基於Flink的機器學習、監控數據突變分析,希望能在監控預警方面取得進一步突破,為雲平台的穩定運行保駕護航。

六、特來電雲計算與大數據微信公眾號
1.微信公眾號名稱:特來電雲計算與大數據
2.二維碼:

