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

小白薇薇發表於2018-12-27

摘要: 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。

相關知識點

有問題?點我提問


相關文章