Flink入坑指南 第三章:第一個作業

摘要: Flink入坑指南系列文章,從實際例子入手,一步步引導用戶零基礎入門實時計算/Flink,併成長爲使用Flink的高階用戶。本文屬個人原創,僅做技術交流之用,筆者才疏學淺,如有錯誤,歡迎指正。轉載請註明出處,侵權必究。

Flink接口

Flink支持三種接口,也就是三種寫作業的方式:

  • SQL:實時計算產品與開源Flink相比,提供更全面的語法支持,目前95%的用戶都是使用SQL解決其問題
  • TableAPI:表達能力與SQL相同
  • DataStream API:底層API,需要一定基礎
    本系列文章會從流式SQL開始介紹。重點是幫助用戶理解批和流SQL的區別,使用戶能快速上手,在Flink上寫出正確的流SQL。

不會介紹實時計算/Flink SQL的語法細節,關於SQL語法或各內置函數的用法,請參考文檔:幫助手冊
有志於瞭解FlinkSQL實現原理或研究Flink代碼的同學,可以參考《Apache Flink 漫談系列 - SQL概覽》

有問題?點我提問

明確需求

接上一章內容,本章計算一個指標:

  1. 當天0點開始,全網的成交量
    使用系統:
  • 上游(SOURCE):Kafka
  • 下游(SINK):MySQL

開始寫第一個作業

原始數據

ctime category_id shop_id item_id price
2018-12-04 15:44:54 cat_01 shop_01 item_01 10
2018-12-04 15:45:46 cat_02 shop_02 item_02 11.1
2018-12-04 15:46:11 cat_01 shop_03 item_03 12.4

主要邏輯

源表:

-- 源表DDL
create table src(
    ctime timestamp,       -- 交易時間戳
    category_id varchar,   -- 類目id
    shop_id varchar,       -- 店鋪id
    item_id varchar,       -- 商品id
    price double           -- 價格
)

-- 結果表DDL
create table sink(
    cdate date,            -- 日期
    gmv_daily double       -- 從零點開始,每天的全網成交金額
)

批SQL寫法

一般批的寫法:

SELECT 
    date_format(ctime, '%Y%m%d') as cdate, -- 將數據從時間戳格式(2018-12-04 15:44:54),轉換爲date格式(20181204)
       SUM(price) AS gmv_daily
 FROM src
 GROUP BY date_format(ctime, '%Y%m%d') ; --按照天做聚合

結果:

cdate gmv_daily
20181204 33.5

特點:

  • 每次執行前,數據庫中都保存了當天已經入庫的全量數據
  • 每次執行SQL,都會拿到__一個__返回值,然後SQL執行結束

Flink SQL寫法

SELECT 
    date_format(ctime, '%Y%m%d') as cdate, -- 將數據從時間戳格式(2018-12-04 15:44:54),轉換爲date格式(20181204)
       SUM(price) AS gmv_daily
 FROM src
 GROUP BY date_format(ctime, '%Y%m%d') ; --按照天做聚合

特點:

  • Flink SQL是個常駐進程,一個SQL文件,就對應與一個Flink作業。如果用戶不殺掉這個作業,這個SQL就會一直存在
  • 每來一條數據,這個Flink作業都會輸出一個值。如果MySQL結果表中沒有加主鍵,那看到的結果如下:
cdate gmv_daily
20181204 10.0
20181204 21.1
20181204 33.5

如果把MySQL結果表中的cdate字段作爲主鍵,那麼每來一條數據,這個Flink作業都會輸出一個值,三條數據的主鍵相同,因此會覆蓋之前的結果,等三條數據都經過Flink計算後,得到的結果如下:

cdate gmv_daily
20181204 33.5

原理介紹

這個例子中,批和流的SQL相同,從最終結果看也相同。但是批引擎(比如MySQL/Hive等)的執行模式,和流引擎(如Flink)是完全不同的。這就導致同一個SQL在處理數據的行爲上,會有很多區別。如果要深入使用Flink SQL,並且保證結果的正確性,成爲Flink SQL調優專家,就需要對Flink底層實現有一定的瞭解。接下來每一章的例子之後,都會介紹一下本章所用的基礎原理。但不會講實現細節,需要了解實現細節的同學,可以follow flink源代碼

1. 從批和流SQL的行爲開始 - 持續查詢

從直觀上看,批和流SQL行爲非常不同:

  • 批SQL每執行一次,只返回一次結果,針對計算時刻數據庫中數據的快照做計算
  • 流SQL只要啓動,就會一直執行,在有數據的情況下會不停的產生結果
    這裏涉及到的流計算中新增的一個非常重要且基礎的概念持續查詢。簡單理解,持續查詢的特點,Flink 作業會一直執行其SQL的邏輯,每到一條新數據,都會觸發下游計算,從而源源不斷的產生輸出。

該SQL中有兩個關鍵操作:

  • Group By:分組操作,在批SQL和流SQL中,group by的行爲是相同的,都是按某幾個字段對數據進行分組。
  • SUM:求和操作,在批SQL和流SQL中,SUM求和操作的語義上相同的,都是對每個分組的某個字段,做求和操作。但是批SQL和流SQL的實現方式是不同的:

    • 批:已經知道了所有數據,把每個分組的求和字段拿出來相加即可。
    • 流:數據是一條條進入系統的,並不知道全部數據,來一條加一條,產出一條結果。

2. group by + SUM()- 狀態

Flink SQL持續計算過程中,數據源源不斷流入,以本文中例子來看,三條數據先後進入Flink,Flink中需要按cdate做一個全局group by,然後對每個cdate中所有數據的price做一個聚合運算(SUM),過程如下:

  1. item_01: sum1=0+10
  2. item_02: sum2=sum1+11.1=21.1
  3. item_03: sum3=sum2+12.3=33.4

這就產生了個問題:每條數據的SUM計算,都要依賴上一條數據的計算結果。Flink在計算的時候,會保留這些中間結果麼?答案是:會保存。而這些中間結果,就是一個作業的狀態(state)的一部分。

關於state的幾個關鍵問題:

  • __是不是所有的作業都有狀態?__是,但是隻有聚合操作/JOIN等,state中才會保存中間結果。簡單的運算,如filter等,state中不需要保存中間結果。State官方文檔,請參閱:Apache Flink Doc,Flink Committer解析請參考 《Apache Flink 漫談系列 - State》
  • state會保存多長時間? state會一直保存麼?不會。流計算中state都是有過期時間的。實時計算產品中,默認是36小時。
  • __state過期是什麼意思?__state過期,是指36小時前的狀態都會被刪掉。這樣做是爲了節省系統存儲空間,在大窗口join計算過程中,需要保存很多數據,如果都存下來,集羣磁盤會滿。
  • __state過期規則是什麼?__state過期規則

    • 不同group by分組的state互不相關
    • 與該group by分組上次state更新的時間有關,如果 __現在時間 - 某個key的state最近更新的時間>state過期時間__,則這個group by分組的state會被清理掉。
  • state過期時間能調麼?能,實時計算產品中,單作業可以配置這個參數:state.backend.rocksdb.ttl.ms=129600000,單位毫秒。
  • __如果state過期會對作業造成什麼影響__:
    以這個例子來說,極端一點,假設我們把state過期參數設成5分鐘。如果3條原始數據進入Flink的時間__相差5分鐘以上__,以ptime定義數據進入flink的時間,如下所示:
ctime category_id shop_id item_id price ptime
2018-12-04 15:44:54 cat_01 shop_01 item_01 10 2018-12-04 15:45:00
2018-12-04 15:45:46 cat_02 shop_02 item_02 11.1 2018-12-04 15:45:10
2018-12-04 15:46:11 cat_01 shop_03 item_03 12.4 2018-12-04 15:52:00

此時:

  1. item_01(ptime=2018-12-04 15:45:00): sum1=0+10
  2. item_02(ptime=__2018-12-04 15:45:10__): sum2=sum1+11.1=21.1
  3. item_03(ptime=__2018-12-04 15:52:00__): sum3=0+12.3=12.3
    item_02和item_03之間超過5min,因此state中sum2的值被清掉,導致item_03到來時,sum3的值計算錯誤。

該例子中:

  • ctime就是數據產生的時間,流計算中的術語叫event time(事件時間)
  • ptime是數據進入Flink的事件,流計算中術語叫process time(處理時間)
    這兩個時間域是流計算的基礎概念。要正確使用流計算,還需要理解以下這兩個概念,相關文章:

《Streaming System 第一章:Streaming 101》
《Streaming System 第二章:The What- Where- When- and How of Data Processing》

SQL優化

上述SQL中,每來一條數據就要就要計算一次,在輸入數量大的情況下,容易產生性能瓶頸。每來一條數據,後端都會read和write一次state,發生序列化和反序列化操作,甚至是磁盤的 I/O 操作。對複雜場景,比如JOIN/TopN等,因此狀態的相關操作通常都會成爲整個任務的性能瓶頸。
如何避免這個問題呢?使用microbatch策略。microbatch顧名思義,就是攢批。不是來一條處理一條,而是攢一批再處理。相關配置如下:
​​


#攢批的間隔時間,使用 microbatch 策略時需要加上該配置,且建議和 blink.miniBatch.allowLatencyMs 保持一致
blink.microBatch.allowLatencyMs=5000
# 使用 microbatch 時需要保留以下兩個 minibatch 配置
blink.miniBatch.allowLatencyMs=5000
# 防止OOM,每個批次最多緩存多少條數據
blink.miniBatch.size=20000。

相關知識點

有問題?點我提問

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