如何正確使用 Flink Connector?

本文主要分享 Flink connector 相關內容,分爲以下三個部分的內容:第一部分會首先介紹一下 Flink Connector 有哪些。第二部分會重點介紹在生產環境中經常使用的 kafka connector 的基本的原理以及使用方法。第三部分答疑,對社區反饋的問題進行答疑。

Flink Streaming Connector

Flink 是新一代流批統一的計算引擎,它需要從不同的第三方存儲引擎中把數據讀過來,進行處理,然後再寫出到另外的存儲引擎中。Connector 的作用就相當於一個連接器,連接 Flink 計算引擎跟外界存儲系統。Flink 裏有以下幾種方式,當然也不限於這幾種方式可以跟外界進行數據交換:

  • 第一種 Flink 裏面預定義了一些 source 和 sink。
  • 第二種 Flink 內部也提供了一些 Boundled connectors。
  • 第三種可以使用第三方 Apache Bahir 項目中提供的連接器。
  • 第四種是通過異步 IO 方式。

下面分別簡單介紹一下這四種數據讀寫的方式。

1.jpg

1.預定義的 source 和 sink

Flink 裏預定義了一部分 source 和 sink。在這裏分了幾類。

2.jpg

  • 基於文件的 source 和 sink。

如果要從文本文件中讀取數據,可以直接使用:

env.readTextFile(path)

就可以以文本的形式讀取該文件中的內容。當然也可以使用:

env.readFile(fileInputFormat, path)

根據指定的 fileInputFormat 格式讀取文件中的內容。

如果數據在 Flink 內進行了一系列的計算,想把結果寫出到文件裏,也可以直接使用內部預定義的一些 sink,比如將結果已文本或 csv 格式寫出到文件中,可以使用 DataStream 的 writeAsText(path) 和 writeAsCsv(path)。

  • 基於 Socket 的 Source 和 Sink

提供 Socket 的 host name 及 port,可以直接用 StreamExecutionEnvironment 預定的接口 socketTextStream 創建基於 Socket 的 source,從該 socket 中以文本的形式讀取數據。當然如果想把結果寫出到另外一個 Socket,也可以直接調用 DataStream writeToSocket。

  • 基於內存 Collections、Iterators 的 Source

可以直接基於內存中的集合或者迭代器,調用 StreamExecutionEnvironment fromCollection、fromElements 構建相應的 source。結果數據也可以直接 print、printToError 的方式寫出到標準輸出或標準錯誤。

詳細也可以參考 Flink 源碼中提供的一些相對應的 Examples 來查看異常預定義 source 和 sink 的使用方法,例如 WordCount、SocketWindowWordCount。

2.Bundled Connectors

Flink 裏已經提供了一些綁定的 Connector,例如 kafka source 和 sink,Es sink等。讀寫 kafka、es、rabbitMQ 時可以直接使用相應 connector 的 api 即可。第二部分會詳細介紹生產環境中最常用的 kafka connector。

雖然該部分是 Flink 項目源代碼裏的一部分,但是真正意義上不算作 Flink 引擎相關邏輯,並且該部分沒有打包在二進制的發佈包裏面。所以在提交 Job 時候需要注意, job 代碼 jar 包中一定要將相應的 connetor 相關類打包進去,否則在提交作業時就會失敗,提示找不到相應的類,或初始化某些類異常。

3.jpg

3.Apache Bahir 中的連接器

Apache Bahir 最初是從 Apache Spark 中獨立出來項目提供,以提供不限於 Spark 相關的擴展/插件、連接器和其他可插入組件的實現。通過提供多樣化的流連接器(streaming connectors)和 SQL 數據源擴展分析平臺的覆蓋面。如有需要寫到 flume、redis 的需求的話,可以使用該項目提供的 connector。

4.jpg

4.Async I/O

流計算中經常需要與外部存儲系統交互,比如需要關聯 MySQL 中的某個表。一般來說,如果用同步 I/O 的方式,會造成系統中出現大的等待時間,影響吞吐和延遲。爲了解決這個問題,異步 I/O 可以併發處理多個請求,提高吞吐,減少延遲。

Tips:Async 的原理可參考官方文檔

Flink Kafka Connector

本章重點介紹生產環境中最常用到的 Flink kafka connector。使用 Flink 的同學,一定會很熟悉 kafka,它是一個分佈式的、分區的、多副本的、 支持高吞吐的、發佈訂閱消息系統。生產環境環境中也經常會跟 kafka 進行一些數據的交換,比如利用 kafka consumer 讀取數據,然後進行一系列的處理之後,再將結果寫出到 kafka 中。這裏會主要分兩個部分進行介紹,一是 Flink kafka Consumer,一個是 Flink kafka Producer。

5.jpg

首先看一個例子來串聯下 Flink kafka connector。代碼邏輯裏主要是從 kafka 裏讀數據,然後做簡單的處理,再寫回到 kafka 中。

分別用紅框框出如何構造一個 Source sink Function。Flink 提供了現成的構造FlinkKafkaConsumer、Producer 的接口,可以直接使用。這裏需要注意,因爲 kafka 有多個版本,多個版本之間的接口協議會不同。Flink 針對不同版本的 kafka 有相應的版本的 Consumer 和 Producer。例如:針對 08、09、10、11 版本,Flink 對應的 consumer 分別是 FlinkKafkaConsumer 08、09、010、011,producer 也是。

6.jpg

1.Flink kafka Consumer

  • 反序列化數據

因爲 kafka 中數據都是以二進制 byte 形式存儲的。讀到 Flink 系統中之後,需要將二進制數據轉化爲具體的 java、scala 對象。具體需要實現一個 schema 類,定義如何序列化和反序列數據。反序列化時需要實現 DeserializationSchema 接口,並重寫 deserialize(byte[] message) 函數,如果是反序列化 kafka 中 kv 的數據時,需要實現 KeyedDeserializationSchema 接口,並重寫 deserialize(byte[] messageKey, byte[] message, String topic, int partition, long offset) 函數。

另外 Flink 中也提供了一些常用的序列化反序列化的 schema 類。例如,SimpleStringSchema,按字符串方式進行序列化、反序列化。TypeInformationSerializationSchema,它可根據 Flink 的 TypeInformation 信息來推斷出需要選擇的 schema。JsonDeserializationSchema 使用 jackson 反序列化 json 格式消息,並返回 ObjectNode,可以使用 .get(“property”) 方法來訪問相應字段。

7.jpg

  • 消費起始位置設置

如何設置作業從 kafka 消費數據最開始的起始位置,這一部分 Flink 也提供了非常好的封裝。在構造好的 FlinkKafkaConsumer 類後面調用如下相應函數,設置合適的起始位置。

  • setStartFromGroupOffsets,也是默認的策略,從 group offset 位置讀取數據,group offset 指的是 kafka broker 端記錄的某個 group 的最後一次的消費位置。但是 kafka broker 端沒有該 group 信息,會根據 kafka 的參數"auto.offset.reset"的設置來決定從哪個位置開始消費。
  • setStartFromEarliest,從 kafka 最早的位置開始讀取。
  • setStartFromLatest,從 kafka 最新的位置開始讀取。
  • setStartFromTimestamp(long),從時間戳大於或等於指定時間戳的位置開始讀取。Kafka 時戳,是指 kafka 爲每條消息增加另一個時戳。該時戳可以表示消息在 proudcer 端生成時的時間、或進入到 kafka broker 時的時間。
  • setStartFromSpecificOffsets,從指定分區的 offset 位置開始讀取,如指定的 offsets 中不存某個分區,該分區從 group offset 位置開始讀取。此時需要用戶給定一個具體的分區、offset 的集合。

一些具體的使用方法可以參考下圖。需要注意的是,因爲 Flink 框架有容錯機制,如果作業故障,如果作業開啓 checkpoint,會從上一次 checkpoint 狀態開始恢復。或者在停止作業的時候主動做 savepoint,啓動作業時從 savepoint 開始恢復。這兩種情況下恢復作業時,作業消費起始位置是從之前保存的狀態中恢復,與上面提到跟 kafka 這些單獨的配置無關。

8.jpg

  • topic 和 partition 動態發現

實際的生產環境中可能有這樣一些需求,比如場景一,有一個 Flink 作業需要將五份數據聚合到一起,五份數據對應五個 kafka topic,隨着業務增長,新增一類數據,同時新增了一個 kafka topic,如何在不重啓作業的情況下作業自動感知新的 topic。場景二,作業從一個固定的 kafka topic 讀數據,開始該 topic 有 10 個 partition,但隨着業務的增長數據量變大,需要對 kafka partition 個數進行擴容,由 10 個擴容到 20。該情況下如何在不重啓作業情況下動態感知新擴容的 partition?

針對上面的兩種場景,首先需要在構建 FlinkKafkaConsumer 時的 properties 中設置 flink.partition-discovery.interval-millis 參數爲非負值,表示開啓動態發現的開關,以及設置的時間間隔。此時 FlinkKafkaConsumer 內部會啓動一個單獨的線程定期去 kafka 獲取最新的 meta 信息。針對場景一,還需在構建 FlinkKafkaConsumer 時,topic 的描述可以傳一個正則表達式描述的 pattern。每次獲取最新 kafka meta 時獲取正則匹配的最新 topic 列表。針對場景二,設置前面的動態發現參數,在定期獲取 kafka 最新 meta 信息時會匹配新的 partition。爲了保證數據的正確性,新發現的 partition 從最早的位置開始讀取。

9.jpg

  • commit offset 方式

Flink kafka consumer commit offset 方式需要區分是否開啓了 checkpoint。

如果 checkpoint 關閉,commit offset 要依賴於 kafka 客戶端的 auto commit。需設置 enable.auto.commit,auto.commit.interval.ms 參數到 consumer properties,就會按固定的時間間隔定期 auto commit offset 到 kafka。

如果開啓 checkpoint,這個時候作業消費的 offset 是 Flink 在 state 中自己管理和容錯。此時提交 offset 到 kafka,一般都是作爲外部進度的監控,想實時知道作業消費的位置和 lag 情況。此時需要 setCommitOffsetsOnCheckpoints 爲 true 來設置當 checkpoint 成功時提交 offset 到 kafka。此時 commit offset 的間隔就取決於 checkpoint 的間隔,所以此時從 kafka 一側看到的 lag 可能並非完全實時,如果 checkpoint 間隔比較長 lag 曲線可能會是一個鋸齒狀。

10.jpg

  • Timestamp Extraction/Watermark 生成

我們知道當 Flink 作業內使用 EventTime 屬性時,需要指定從消息中提取時戳和生成水位的函數。FlinkKakfaConsumer 構造的 source 後直接調用 assignTimestampsAndWatermarks 函數設置水位生成器的好處是此時是每個 partition 一個 watermark assigner,如下圖。source 生成的時戳爲多個 partition 時戳對齊後的最小時戳。此時在一個 source 讀取多個 partition,並且 partition 之間數據時戳有一定差距的情況下,因爲在 source 端 watermark 在 partition 級別有對齊,不會導致數據讀取較慢 partition 數據丟失。

11.jpg

2.Flink kafka Producer

  • Producer 分區

使用 FlinkKafkaProducer 往 kafka 中寫數據時,如果不單獨設置 partition 策略,會默認使用 FlinkFixedPartitioner,該 partitioner 分區的方式是 task 所在的併發 id 對 topic 總 partition 數取餘:parallelInstanceId % partitions.length。

  • 此時如果 sink 爲 4,paritition 爲 1,則 4 個 task 往同一個 partition 中寫數據。但當 sink task < partition 個數時會有部分 partition 沒有數據寫入,例如 sink task 爲2,partition 總數爲 4,則後面兩個 partition 將沒有數據寫入。
  • 如果構建 FlinkKafkaProducer 時,partition 設置爲 null,此時會使用 kafka producer 默認分區方式,非 key 寫入的情況下,使用 round-robin 的方式進行分區,每個 task 都會輪循的寫下游的所有 partition。該方式下游的 partition 數據會比較均衡,但是缺點是 partition 個數過多的情況下需要維持過多的網絡連接,即每個 task 都會維持跟所有 partition 所在 broker 的連接。

12.jpg

  • 容錯

Flink kafka 09、010 版本下,通過 setLogFailuresOnly 爲 false,setFlushOnCheckpoint 爲 true,能達到 at-least-once 語義。setLogFailuresOnly,默認爲 false,是控制寫 kafka 失敗時,是否只打印失敗的 log 不拋異常讓作業停止。setFlushOnCheckpoint,默認爲 true,是控制是否在 checkpoint 時 fluse 數據到 kafka,保證數據已經寫到 kafka。否則數據有可能還緩存在 kafka 客戶端的 buffer 中,並沒有真正寫出到 kafka,此時作業掛掉數據即丟失,不能做到至少一次的語義。

Flink kafka 011 版本下,通過兩階段提交的 sink 結合 kafka 事務的功能,可以保證端到端精準一次。詳細原理可以參考:

https://www.ververica.com/blo...

13.jpg

一些疑問與解答

Q:在 Flink consumer 的並行度的設置:是對應 topic 的 partitions 個數嗎?要是有多個主題數據源,並行度是設置成總體的 partitions 數嗎?

A:這個並不是絕對的,跟 topic 的數據量也有關,如果數據量不大,也可以設置小於 partitions 個數的併發數。但不要設置併發數大於 partitions 總數,因爲這種情況下某些併發因爲分配不到 partition 導致沒有數據處理。

Q:如果 partitioner 傳 null 的時候是 round-robin 發到每一個 partition?如果有 key 的時候行爲是 kafka 那種按照 key 分佈到具體分區的行爲嗎?

A:如果在構造 FlinkKafkaProducer 時,如果沒有設置單獨的 partitioner,則默認使用 FlinkFixedPartitioner,此時無論是帶 key 的數據,還是不帶 key。如果主動設置 partitioner 爲 null 時,不帶 key 的數據會 round-robin 的方式寫出,帶 key 的數據會根據 key,相同 key 數據分區的相同的 partition,如果 key 爲 null,再輪詢寫。不帶 key 的數據會輪詢寫各 partition。

Q:如果 checkpoint 時間過長,offset 未提交到 kafka,此時節點宕機了,重啓之後的重複消費如何保證呢?

A:首先開啓 checkpoint 時 offset 是 Flink 通過狀態 state 管理和恢復的,並不是從 kafka 的 offset 位置恢復。在 checkpoint 機制下,作業從最近一次 checkpoint 恢復,本身是會回放部分歷史數據,導致部分數據重複消費,Flink 引擎僅保證計算狀態的精準一次,要想做到端到端精準一次需要依賴一些冪等的存儲系統或者事務操作。


▼ Apache Flink 社區推薦 ▼

Apache Flink 及大數據領域頂級盛會 Flink Forward Asia 2019 重磅開啓,目前正在徵集議題,限量早鳥票優惠ing。瞭解 Flink Forward Asia 2019 的更多信息,請查看:

https://developer.aliyun.com/...

首屆 Apache Flink 極客挑戰賽重磅開啓,聚焦機器學習與性能優化兩大熱門領域,40萬獎金等你拿,加入挑戰請點擊:

https://tianchi.aliyun.com/ma...

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