使用Kafka和Flink構建實時資料處理系統

jasonli4發表於2018-09-05

引言

在很多領域,如股市走向分析, 氣象資料測控,網站使用者行為分析等,由於資料產生快,實時性強,資料量大,所以很難統一採集併入庫儲存後再做處理,這便導致傳統的資料處理架構不能滿足需要。流計算的出現,就是為了更好地解決這類資料在處理過程中遇到的問題。與傳統架構不同,流計算模型在資料流動的過程中實時地進行捕捉和處理,並根據業務需求對資料進行計算分析,最終把結果儲存或者分發給需要的元件。本文將從實時資料產生和流向的各個環節出發,通過一個具有實際意義的案例,向讀者介紹如何使用 Apache Kafka 和 Apache Flink 構建一個實時的資料處理系統,當然本文只是拋磚引玉,因為構建一個良好健壯的實時資料處理系統並不是一篇文章可以說清楚的。在閱讀本文前,假設您已經對 Apache Kafka 分散式訊息系統有了基本的瞭解,並且可以使用 Flink SQL 編寫業務邏輯。接下來,就讓我們一起看看如何構建一個簡易的實時資料處理系統吧。

關於Kafka

Kafka 是一個分散式的,高吞吐量,易於擴充套件地基於主題釋出/訂閱的訊息系統,最早是由 Linkedin 開發,並於 2011 年開源並貢獻給 Apache 軟體基金會。一般來說,Kafka 有以下幾個典型的應用場景:
  • 作為訊息佇列。由於 Kafka 擁有高吞吐量,並且內建訊息主題分割槽,備份,容錯等特性,使得它更適合使用在大規模,高強度的訊息資料處理的系統中。
  • 流計算系統的資料來源。流資料產生系統作為 Kafka 訊息資料的生產者將資料流分發給 Kafka 訊息主題,流資料計算系統 (Flink,Storm,Spark Streaming等) 實時消費並計算資料。這也是本文將要介紹的應用場景。
  • 系統使用者行為資料來源。這種場景下,系統將使用者的行為資料,如訪問頁面,停留時間,搜尋日誌,感興趣的話題等資料實時或者週期性的釋出到 Kafka 訊息主題,作為對接系統資料的來源。
  • 日誌聚集。Kafka 可以作為一個日誌收集系統的替代解決方案,我們可以將系統日誌資料按類別彙集到不同的 Kafka 訊息主題中。
  • 事件源。在基於事件驅動的系統中,我們可以將事件設計成合理的格式,作為 Kafka 訊息資料儲存起來,以便相應系統模組做實時或者定期處理。由於 Kafka 支援大資料量儲存,並且有備份和容錯機制,所以可以讓事件驅動型系統更加健壯和高效。
當然 Kafka 還可以支援其他的應用場景,在這裡我們就不一一羅列了。關於 Kafka 更詳細的介紹,請讀者參考Kafka 官網。需要指出的是,本文使用的 Kafka 版本是基於 Scala 2.10 版本構建的 0.8.2.1 版本。

關於Flink

Flink支援多種資料來源:Kafka、MQ、SLS、Datahub 等,原生支援寫入到 MQ、OTS、常見關聯式資料庫等儲存介質,提供了不同的抽象級別以開發流式或批處理應用。
1534815616178-0df91477-37ea-4f20-baa1-a2
  • 最底層級的抽象僅僅提供了有狀態流。它將通過過程函式(Process Function)嵌入到DataStream API中。它允許使用者可以自由地處理來自一個或多個流資料的事件,並使用一致、容錯的狀態。除此之外,使用者可以註冊事件時間和處理事件回撥,從而使程式可以實現複雜的計算。
  • 實際上,大多數應用並不需要上述的低層級抽象,而是針對 核心API(Core APIs) 進行程式設計,比如DataStream API(有界或無界流資料)以及DataSet API(有界資料集)。這些流暢的API為資料處理提供了通用的構建模組,比如由使用者定義的多種形式的轉換(transformations),連線(joins),聚合(aggregations),視窗操作(windows),狀態(state)等等。這些API處理的資料型別以類(classes)的形式由各自的程式語言所表示。
低層級的 過程函式 與 DataStream API 相整合,使其可以對某些特定的操作進行低層級的抽象。DataSet API 為有界資料集提供了額外的原語,例如迴圈與迭代。
  • Table API 是以  為中心的宣告式DSL,其中表可能會動態變化(在表達流資料時)。Table API遵循(擴充套件的)關係模型:表具有附加的模式(類似於關聯式資料庫中的表),同時API提供可比較的操作,例如select、project、join、group-by、aggregate等。Table API程式宣告式地定義了 什麼邏輯操作應該執行 而不是準確地確定 這些操作程式碼的看上去如何 。 儘管Table API可以通過多種型別的使用者定義的函式進行擴充套件,其仍不如 核心API 更具表達能力,但是使用起來卻更加簡潔(程式碼量更少)。除此之外,Table API程式還可以在執行之前通過應用優化規則的優化器。
你可以在表與 DataStream/DataSet 之間無縫切換,以允許程式將 Table API 與 DataStream 以及 DataSet 混合使用。
  • Flink提供的最高層級的抽象是 SQL 。這一層抽象在語法與表達能力上與 Table API 類似,但是是以SQL查詢表示式的形式表現程式。SQL抽象與Table API互動密切,同時SQL查詢可以直接在Table API定義的表上執行。

案例介紹與Flink SQL程式設計實現

1.案例介紹

該案例中,我們假設某論壇需要根據使用者對站內網頁的點選量,停留時間,以及是否點贊,來近實時的計算網頁熱度,進而動態的更新網站的今日熱點模組,把最熱話題的連結顯示其中。

2.案例分析

對於某一個訪問論壇的使用者,我們需要對他的行為資料做一個抽象,以便於解釋網頁話題熱度的計算過程。
首先,我們通過一個向量來定義使用者對於某個網頁的行為即點選的網頁,開始時間,停留時間,以及是否點贊,可以表示如下:
(page001.html,0, 1, 0.5, 1)
向量的第一項表示網頁的 ID,第二項表示使用者網頁開始點選時間,第三項表示從進入網站到離開對該網頁的點選次數,第四項表示停留時間,以秒為單位,第五項是代表是否點贊,1 為贊,-1 表示踩,0 表示中立。
其次,我們再按照各個行為對計算網頁話題熱度的貢獻,給其設定一個權重,在本文中,我們假設點選次數權重是 0.8,因為使用者可能是由於沒有其他更好的話題,所以再次瀏覽這個話題。停留時間權重是 0.8,因為使用者可能同時開啟多個 tab 頁,但他真正關注的只是其中一個話題。是否點贊權重是 1,因為這一般表示使用者對該網頁的話題很有興趣。
最後,我們定義用下列公式計算某條行為資料對於該網頁熱度的貢獻值。
f(x,y,z)=0.8x+0.8y+z
那麼對於上面的行為資料 (page001.html, 1, 0.5, 1),利用公式可得:
H(page001)=f(x,y,z)= 0.8x+0.8y+z=0.8*1+0.8*0.5+1*1=2.2
讀者可以留意到,在這個過程中,我們忽略了使用者本身,也就是說我們不關注使用者是誰,而只關注它對於網頁熱度所做的貢獻。

3.生產行為資料資訊

在本案例中我們將使用一段程式來模擬使用者行為,該程式每隔 5 秒鐘會隨機的向 user-behavior-topic 主題推送 0 到 50 條行為資料訊息,顯然,這個程式扮演訊息生產者的角色,在實際應用中,這個功能一般會由一個系統來提供。為了簡化訊息處理,我們定義訊息的格式如下:
網頁 ID|開始點選時間|點選次數|停留時間 (分鐘)|是否點贊
ID(varchar)|firsttime(timestamp)|count(bigint)|timegap(bigint)|positive(boolean)

4.編寫Flink SQL編寫程式實時處理資料

在弄清楚了要解決的問題之後,就可以開始編碼實現了。對於本案例中的問題,在實現上的基本步驟如下:
  • 建立源表和結果表
  • 建立子查詢,使用 ROW_NUMBER() 視窗函式來對資料根據排序列進行排序並標上排名
  • 外層查詢中,對排名進行過濾,只取前N條,如N=5,那麼就是取 Top 5 的資料,即網頁熱度排名


-- Kafka源表
create table input(
id             varchar,
count          bigint,
timegap        bigint,
positive       boolean,
firsttime      timestamp,
WATERMARK wk FOR firsttime as withOffset(firsttime, 2000)
)with(
type=`kafka`
endpoint=`xxx`
......
);
-- Hbase儲存表
create table output(
window_start  TIMESTAMP,
window_end    TIMESTAMP,
id            bigint,
contribution  double
)with(
type=`ALIHBASE`
endpoint=`xxx`
......
);
-- 5秒鐘視窗貢獻度統計
CREATE VIEW group_view AS
SELECT id,
      TUMBLE_START(firsttime, INTERVAL `5` SECOND) AS start_time,
      (0.8*count+0.8*timegap+positive) as contribution
FROM input
GROUP BY id, TUMBLE(firsttime, INTERVAL `1` MINUTE);

-- 統計每5秒 top5 貢獻值ID,並輸出
insert into output
select
(
   SELECT
   id, firsttime, contribution,
   ROW_NUMBER() OVER (PARTITION BY firsttime ORDER BY contribution DESC) as rownum,
   FROM
   group_view
)
WHERE rownum <= 5;

注意事項

利用 Flink 構建一個高效健壯的流資料計算系統,我們還需要注意以下方面。
  • 需要合理的設定時間視窗,即需要保證Flink的計算視窗合理地統計到熱度最高的話題,理論上Flink的視窗可以是無界的,也可以是很小的時間視窗,但是合理的視窗大小設計對業務邏輯的影響。
  • 雖然本文案例中,我們只是把 (近) 實時計算結果列印出來,但是實際上很多時候這些結果會被儲存到資料庫,HDFS, 或者傳送回 Kafka, 以供其他系統利用這些資料做進一步的業務處理,Flink可以直接實現這些功能。
  • 由於流計算對實時性要求很高,所以任何由於 JVM Full GC 引起的系統暫停都是不可接受的。Flink 採用類似 DBMS 的 sort 和 join 演算法,直接操作二進位制資料,從而使序列化/反序列化帶來的開銷達到最小。所以 Flink 的內部實現更像 C/C++ 而非 Java。如果需要處理的資料超出了記憶體限制,則會將部分資料儲存到硬碟上。如果要操作多塊MemorySegment就像操作一塊大的連續記憶體一樣,Flink會使用邏輯檢視(AbstractPagedInputView)來方便操作。
  • Flink內部支援exactly-once,要想達到端到端(Soruce到Sink)的exactly-once,需要Blink外部Soruce和Sink的支援,比如Source要支援精準的offset,Sink要支援兩階段提交,也就是繼承TwoPhaseCommitSinkFunction。
  • Flink中當所有輸入的barrier沒有完全到來的時候,早到來的event在exactly-once的情況向會進行快取(不進行處理),而at-least-once的模式下即使所有輸入的barrier沒有完全到來的時候,早到來的event也會進行處理。也就是說對於at-least-once模式下,對於下游節點而言,本來資料屬於checkpoint n的資料在checkpoint n-1裡面也可能處理過了。所以我們建議,Flink的checkpoint模式設定為exactl-once模式。

結束語

本文包含了整合Flink和 Kafka 分散式訊息系統的基本知識,但是需要指出的是,在實際問題中,我們可能面臨更多的問題,如效能優化,記憶體不足,以及其他未曾遇到的問題。希望通過本文的閱讀,讀者能對使用 Flink SQL 和 Kafka 構建實時資料處理系統有一個基本的認識,為讀者進行更深入的研究提供一個參考依據。讀者在閱讀本文的時候發現任何問題或者有任何建議,請不吝賜教,留下您的評論,我會及時回覆。希望我們可以一起討論,共同進步。


相關文章