這個連接器提供了對由Apache Kafka提供的事件流的訪問。
Flink 提供了特殊的Kafka Connectors來從Kafka topic中讀取數據或者將數據寫入到Kafkatopic中,Flink的Kafka Consumer與Flink的檢查點機制相結合,提供exactly-once處理語義。為了做到這一點,Flink并不完全依賴于Kafka的consumer組的offset跟蹤,而是在自己的內部去跟蹤和檢查。
請根據你自己的使用情況和環境來選擇一個包和類名,對于大部分用戶,FlinkKafkaConsumer08
(flink-connector-kafka的一部分)是最合適的。
Maven Dependency | Supported since | Consumer and Producer Class name | Kafka version | Notes |
---|---|---|---|---|
flink-connector-kafka-0.8_2.10 | 1.0.0 | FlinkKafkaConsumer08 FlinkKafkaProducer08 | 0.8.x | Uses the SimpleConsumer API of Kafka internally. Offsets are committed to ZK by Flink. |
flink-connector-kafka-0.9_2.10 | 1.0.0 | FlinkKafkaConsumer09 FlinkKafkaProducer09 | 0.9.x | Uses the new Consumer API Kafka. |
flink-connector-kafka-0.10_2.10 | 1.2.0 | FlinkKafkaConsumer010 FlinkKafkaProducer010 | 0.10.x | This connector supports Kafka messages with timestamps both for producing and consuming. |
然后,在你的工程中導入connector
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka-0.8_2.10</artifactId>
<version>1.3.0</version>
</dependency>
注意,目前streaming的connectors還不是Flink二進制發布包的一部分,請參考此處來了解在集群執行中與它們連接在一起。
安裝Apache Kafka
·按照Kafka 的快速入門說明,來下載代碼和啟動服務(每次啟動一個應用前都需要啟動一個Zookeeper和一個kafka服務)
·如果Kafka和Zookeeper服務運行在一個遠程服務器上,那么config/server.properties中的advertised.host.name配置必須要設置成那臺服務器的IP地址。
Kafka Consumer
Flink的kafka consumer叫做FlinkKafkaConsumer08(對于Kafka 0.9.0.X來說是09 等),它提供了對一個或者多個Kafka topic的訪問。
FlinkKafkaConsumer08、09等的構造函數接收以下參數:
1、topic名稱或者名稱列表
2、反序列化來自kafka的數據的DeserializationSchema/KeyedDeserializationSchema
3、Kafka consumer的一些配置,下面的配置是必需的:
"bootstrap.servers"(以逗號分隔的Kafka brokers列表)
"zookeeper.connect"(以逗號分隔的Zookeeper 服務器列表)
"group.id"(consumer組的id)
例如:
Java 代碼:
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9092");
// only required for Kafka 0.8
properties.setProperty("zookeeper.connect", "localhost:2181");
properties.setProperty("group.id", "test");
DataStream<String> stream = env
.addSource(new FlinkKafkaConsumer08<>("topic", new SimpleStringSchema(), properties));
Scala 代碼:
val properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9092");
// only required for Kafka 0.8
properties.setProperty("zookeeper.connect", "localhost:2181");
properties.setProperty("group.id", "test");
stream = env
.addSource(new FlinkKafkaConsumer08[String]("topic", new SimpleStringSchema(), properties))
.print
當前FlinkKafkaConsumer的實現會建立一個到Kafka客戶端的連接來查詢topic的列表和分區。
為此,consumer需要能夠訪問到從提交Job任務的服務器到Flink服務器的consumer,如果你在客戶端遇到任何Kafka Consumer的問題,你都可以在客戶端日志中看到關于請求失敗的日志。(這段翻譯得不太好,待我查看完源碼后再重新翻譯)
反序列化模式 DeserializationSchema
Flink的Kafka Consumer需要知道如何將Kafka中的二進制轉換成Java或者Scala的對象,而DeserializationSchema
則是允許用戶來指定這樣一個模式,T deserialize(byte[] message)
方法被每個kafka消息調用,并傳入kafka的值。
從AbstractDeserializationSchema
開始是非常有用的,它描述了Java/Scala類型的產生到Flink的類型系統。用戶實現基本的DeserializationSchema
的話,需要自己去實現getProducedType(...)
方法。
為了獲取Kafka消息中的key和value,KeyedDeserializationSchema需要有下面這個反序列化方法 T deserialize(byte[] messageKey, byte[] message, String topic, int partition, long offset)
為了方便起見,Flink提供了下面的模式:
1、TypeInformationSerializationSchema
(以及TypeInformationKeyValueSerializationSchema
) ,這個是以Flink的TypeInfoSchema
為基礎創建的,如果數據的讀寫都是由Flink來做的話,這是非常有用的,這種Schema是高性能的Flink具體的替代其他通用序列化方法的方法。
2、JsonDeserializationSchema
(以及JSONKeyValueDeserializationSchema
),它能夠將序列化的JSON轉換成ObjectNode
對象,在這個對象中字段可以通過調用objectNode.get("field").as(Int/String/...)()
來獲取。keyValue類型的ObjectNode
包含一個"key"和一個包含所有字段的"value",同時還有可選的用來展示這些消息的offset/partition/topic的"metadata"字段。
當遇到損壞的消息,無法被序列化時,這里有兩個選擇,要么在deserialize(...)方法中拋出一個異常,這會導致作業失敗和重啟,要么返回一個null值,來允許Flink Kafka consumer默默地跳過這些錯誤消息。值得注意的是,由于consumer的容錯性(見下面的詳細部分),在處理損壞數據時失敗的Job會讓consumer再次嘗試反序列化損壞的消息。因此,如果反序列化任然失敗,那么consumer將陷入不斷的失敗重啟中。
Kafka Consumer 開始位置配置
Flink Kafka Consumer允許配置Kafka分區的開始位置是如何確定的。
例如:
Java 代碼:
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
FlinkKafkaConsumer08<String> myConsumer = new FlinkKafkaConsumer08<>(...);
myConsumer.setStartFromEarliest(); // start from the earliest record possible
myConsumer.setStartFromLatest(); // start from the latest record
myConsumer.setStartFromGroupOffsets(); // the default behaviour
DataStream<String> stream = env.addSource(myConsumer);
...
Scala 代碼:
val env = StreamExecutionEnvironment.getExecutionEnvironment()
val myConsumer = new FlinkKafkaConsumer08[String](...)
myConsumer.setStartFromEarliest() // start from the earliest record possible
myConsumer.setStartFromLatest() // start from the latest record
myConsumer.setStartFromGroupOffsets() // the default behaviour
val stream = env.addSource(myConsumer)
...
所有版本的Flink Kafka Consumer都有下面的切確配置方法來配置開始位置。
setStartFromGroupOffsets
(默認的行為):從consumer 分組(在consumer中group.id的配置項)提交到Kafka broker(Kafka 0.8是Zookeeper)的偏移位置開始讀取分區。如果分區中沒有偏移位置,那么會采用auto.offset.reset
的配置信息。
setStartFromEarliest()
/setStartFromLatest()
:從最早的或者最近的記錄開始,在這種模式下,在Kafka中commit的偏移位置將會被忽略并且不會再用作開始位置。
你也可以指定一個確切的偏移位置,Kafka Consumer必須從這個位置開始讀取每個分區的信息,代碼如下:
Java 代碼:
Map<KafkaTopicPartition, Long> specificStartOffsets = new HashMap<>();
specificStartOffsets.put(new KafkaTopicPartition("myTopic", 0), 23L);
specificStartOffsets.put(new KafkaTopicPartition("myTopic", 1), 31L);
specificStartOffsets.put(new KafkaTopicPartition("myTopic", 2), 43L);
myConsumer.setStartFromSpecificOffsets(specificStartOffsets);
Scala 代碼:
val specificStartOffsets = new java.util.HashMap[KafkaTopicPartition, java.lang.Long]()
specificStartOffsets.put(new KafkaTopicPartition("myTopic", 0), 23L)
specificStartOffsets.put(new KafkaTopicPartition("myTopic", 1), 31L)
specificStartOffsets.put(new KafkaTopicPartition("myTopic", 2), 43L)
myConsumer.setStartFromSpecificOffsets(specificStartOffsets)
上面的例子中配置了consumer從myTopic的分區0,1,2的指定偏移位置開始,這個偏移位置必須是讀取每個分區的下一個記錄。注意:如果consumer需要讀取的分區在給定的偏移信息的map中,沒有指定的偏移位置,那么將會在這個特定的分區中采用默認的分組偏移的行為(即采用setStartFromGroupOffsets()
)。
注意:這些開始位置配置方法并不會影響作業失敗自動重啟或者通過savepoint手動重啟的開始位置,在恢復中,每個Kafka分區的開始位置由保存在savepoint或者checkpoint中的偏移來決定的。
Kafka Consumers 和Fault Tolerance
Flink的checkpoint啟用之后,Flink Kafka Consumer將會從一個topic中消費記錄并以一致性的方式周期性地檢查所有Kafka偏移量以及其他操作的狀態。Flink將保存流程序到狀態的最新的checkpoint中,并重新從Kafka中讀取記錄,記錄從保存在checkpoint中的偏移位置開始讀取.
checkpoint的時間間隔定義了程序在發生故障時可以恢復多少.
為了使用容錯性的Kafka Consumer,拓撲結構的checkpoint需要在執行環境中啟用,代碼如下:
Java 代碼:
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.enableCheckpointing(5000); // 每5000毫秒檢查一次
Scala 代碼:
val env = StreamExecutionEnvironment.getExecutionEnvironment()
env.enableCheckpointing(5000) // 每5000毫秒檢查一次
同時需要注意的是Flink只能在有足夠的slots時才會去重啟topology,所以如果topology由于TaskManager丟失而失敗時,任然需要有足夠的slot可用。Flink on YARN支持YARN container丟失自動重啟。
如果checkpoint不可用,那么Kafka Consumer將會周期性地將offset提交到Zookeeper中。
Kafka Consumer Offset提交行為配置
Flink Kafka Consumer允許配置offset提交回Kafka brokers(Kafka 0.8是寫回Zookeeper)的行為,注意Flink Kafka Consumer 并不依賴于這個提交的offset來進行容錯性保證,這個提交的offset僅僅作為監控consumer處理進度的一種手段。
配置offset提交行為的方式有多種,主要取決于Job的checkpoint機制是否啟動。
1、checkpoint禁用:如果checkpoint禁用,Flink Kafka Consumer依賴于Kafka 客戶端內部的自動周期性offset提交能力。因此,為了啟用或者禁用offset提交,僅需在給定的Properties配置中設置enable.auto.commit
(Kafka 0.8是auto.commit.enable
)/auto.commit.interval.ms
為適當的值即可。
2、checkpoint啟用:如果checkpoint啟用,當checkpoint完成之后,Flink Kafka Consumer將會提交offset保存到checkpoint State中,這就保證了kafka broker中的committed offset與 checkpoint stata中的offset相一致。用戶可以在Consumer中調用setCommitOffsetsOnCheckpoints(boolean)
方法來選擇啟用或者禁用offset committing(默認情況下是啟用的)。注意,在這種情況下,配置在Properties中的自動周期性offset提交將會被完全忽略。
Kafka Consumer與Timestamp抽取器和Watermark發射器
在許多情況下,記錄的timestamp都是顯式或者隱式地嵌入在記錄本身中,此外,用戶可能想周期性地發射水印或者不規則地發射,例如:根據Kafka 流中包含當前事件時間的特殊記錄的水印。為此,Flink Kafka Consumer允許這些指定:AssignerWithPeriodicWatermarks
或者 AssignerWithPunctuatedWatermarks
。
你可以指定你自定義的timestamp抽取器或者watermark發射器如這里描述,或者使用一個預定義的,之后你就可以按照下面的方式將它們傳遞到你的consumer中:
Java 代碼:
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9092");
// only required for Kafka 0.8
properties.setProperty("zookeeper.connect", "localhost:2181");
properties.setProperty("group.id", "test");
FlinkKafkaConsumer08<String> myConsumer =
new FlinkKafkaConsumer08<>("topic", new SimpleStringSchema(), properties);
myConsumer.assignTimestampsAndWatermarks(new CustomWatermarkEmitter());
DataStream<String> stream = env
.addSource(myConsumer)
.print();
Scala 代碼:
val properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9092");
// only required for Kafka 0.8
properties.setProperty("zookeeper.connect", "localhost:2181");
properties.setProperty("group.id", "test");
val myConsumer = new FlinkKafkaConsumer08[String]("topic", new SimpleStringSchema(), properties);
myConsumer.assignTimestampsAndWatermarks(new CustomWatermarkEmitter());
stream = env
.addSource(myConsumer)
.print
在內部,一個分配器實例會被每個Kafka分區執行,當一個實例被指定,這個extractTimestamp(T element, long previousElementTimestamp)
方法就會被調用來為記錄分配一個timestamp并且Watermark getCurrentWatermark()
(周期性的)或者 Watermark checkAndGetNextWatermark(T lastElement, long extractedTimestamp)
(不規則的)會被調用來決定新生成的watermark是否需要發射,跟哪兒timestamp一起發射。
Kafka Producer
Flink的Kafka Producer叫做FlinkKafkaProducer08
(Kafka 0.9.x版本是FlinkKafkaProducer09
),它允許寫流記錄到一個或者多個Kafka topic中。
例如:
Java 代碼 Kafka 0.8+:
DataStream<String> stream = ...;
FlinkKafkaProducer08<String> myProducer = new FlinkKafkaProducer08<String>(
"localhost:9092", // broker list
"my-topic", // target topic
new SimpleStringSchema()); // serialization schema
// the following is necessary for at-least-once delivery guarantee
myProducer.setLogFailuresOnly(false); // "false" by default
myProducer.setFlushOnCheckpoint(true); // "false" by default
stream.addSink(myProducer);
Java 代碼 Kafka 0.9+
DataStream<String> stream = ...;
FlinkKafkaProducer010Configuration myProducerConfig = FlinkKafkaProducer010.writeToKafkaWithTimestamps(
stream, // input stream
"my-topic", // target topic
new SimpleStringSchema(), // serialization schema
properties); // custom configuration for KafkaProducer (including broker list)
// the following is necessary for at-least-once delivery guarantee
myProducerConfig.setLogFailuresOnly(false); // "false" by default
myProducerConfig.setFlushOnCheckpoint(true); // "false" by default
Scala 代碼 Kafka 0.8+
val stream: DataStream[String] = ...
val myProducer = new FlinkKafkaProducer08[String](
"localhost:9092", // broker list
"my-topic", // target topic
new SimpleStringSchema) // serialization schema
// the following is necessary for at-least-once delivery guarantee
myProducer.setLogFailuresOnly(false) // "false" by default
myProducer.setFlushOnCheckpoint(true) // "false" by default
stream.addSink(myProducer)
Scala 代碼 0.9+
val stream: DataStream[String] = ...
val myProducerConfig = FlinkKafkaProducer010.writeToKafkaWithTimestamps(
stream, // input stream
"my-topic", // target topic
new SimpleStringSchema, // serialization schema
properties) // custom configuration for KafkaProducer (including broker list)
// the following is necessary for at-least-once delivery guarantee
myProducerConfig.setLogFailuresOnly(false) // "false" by default
myProducerConfig.setFlushOnCheckpoint(true) // "false" by default
上面例子展示了創建一個Flink Kafka Producer來寫流數據到指定的Kafka topic的基本用法:
對于更高級的用法,這里有其他變換的構造函數來允許提供下面的功能:
1、提供自定義的屬性:Producer允許為內部的KafkaProducer提供自定義的屬性配置,請參考Apache Kafka 文檔來了解如何配置Kafka Producer的詳細信息。
2、自定義分區器:為了給記錄指定分區,你可以為構造函數提供一個FlinkKafkaPartitioner
的實現,這個分區器將會被流中的每個記錄調用,來決定記錄要被發送到目標topic的哪個確切分區。
3、高級的序列化模式:與consumer類似,Producer允許使用一個叫KeyedSerializationSchema
的高級序列化模式,這個模式允許分開地序列化key和value。同時允許重寫目標topic,因此一個Producer可以發送數據到多個topic。
Kafka Producer和容錯性
在Flink checkpoint開啟的情況下,Flink Kafka Producer可以提供至少一次(at-least-once)的發送保證。
除了啟用Flink的checkpoint之外,你還需要是當地配置setLogFailuresOnly(boolean) 和setFlushOnCheckpoint(boolean)方法,如前面章節的例子所示:
1、setLogFailuresOnly(boolean):啟用這個配置將允許producer記錄失敗日志而不是捕獲和拋出它們,這個本質上會認為記錄已經成功,即使記錄沒有寫入目標Kafka topic中,對于at-least-once模式來說,這個配置必須禁用。
2、setFlushOnCheckpoint(boolean):啟用這個配置,Flink的checkpoint會等待在checkpoint成功之前被Kafka識別的時間內傳輸的記錄,這就保證了所有checkpoint之前的記錄都被寫入Kafka 中,在at-least-once模式下,這個配置必須啟用。
注意:默認情況下,重試次數設置為“0”,這也就意味著setLogFailuresOnly設置為false,producer失敗的話會立即報錯,包括leader的切換也會報錯。這個值默認情況下設置為0是為了避免重試導致重復的消息進入目標topic中。對于大多數頻繁切換broker的生產環境中,我們建議將重試次數設置為一個比較高的值。
注意:現在Kafka還沒有事務型的producer,所以Flink無法保證精確地(exactly-once)分發消息到一個Kafka topic中。
在Kafka 0.10中使用Kafka timestamp和 Flink的event time
Apache Kafka 0.10之后的版本中,Kafka的消息可以攜帶timestamp,指出了事件發生的時間(參考Apache Flink的event time)或者消息被寫入到Kafka broker的時間。
如果Flink的TimeCharacteristic
設置為TimeCharacteristic.EventTime (StreamExecutionEnvironment.setStreamTimeCharacteristic(TimeCharacteristic.EventTime))
的話,FlinkKafkaConsumer010
會發射附有timestamp的記錄。Kafka consumer并不會發射watermark,為了發射watermark,原理如"Kafka Consumers and Timestamp Extraction/Watermark Emission"所述,使用assignTimestampsAndWatermarks
方法。
當使用Kafka中的timestamp時,無需定義timestamp抽取器,extractTimestamp()
方法中的previousElementTimestamp
參數已經包含了Kafka消息所攜帶的timestamp。
一個Kafka consumer的timestamp抽取器如下所示:
public long extractTimestamp(Long element, long previousElementTimestamp) {
return previousElementTimestamp;
}
如果setWriteTimestampToKafka(true)
配置了的話,FlinkKafkaProducer010
會僅發射記錄的timestamp。
FlinkKafkaProducer010.FlinkKafkaProducer010Configuration config = FlinkKafkaProducer010.writeToKafkaWithTimestamps(streamWithTimestamps, topic, new SimpleStringSchema(), standardProps);
config.setWriteTimestampToKafka(true);
Kafka Connector 度量
Flink的Kafka Connector通過Flink的度量系統提供了一些度量指標來分析connector的行為,producer通過Flink的度量系統輸出所有支持的版本的Kafka內部度量指標。consumer則輸出所有的Kafka 0.9版本的度量指標,Kafka在它的文檔中列出了所有的度量指標。
除此之外,所有的consumer都會為每個分區暴露出current-offsets和committed-offsets,current-offsets指出了當前分區的偏移位置,這個指出了我們最近檢索并成功發射的元素的偏移位置,committed-offsets是最近提交的偏移位置。
Flink中的Kafka Consumer提交offsets到Zookeeper(Kafka 0.8)或者Kafka Broker中(Kafka 0.9+),如果checkpoint禁用的話。offset會周期性地提交。啟用checkpoint的話,一旦所有流topology中的操作已經聲明它們已經創建為它們的State創建了一個checkpoint,offset會提交。這些為用戶提供了提交offset到Zookeeper或者Broker的at-least-once語義。對于offsetcheckpoint到Flink,系統提供了精確的(exactly once)保證。
提交到ZK或者Broker中的offset可以被用來追蹤Kafka consumer的讀取進度,提交的offset與每個分區中最近的offset的差異叫做consumer lag,如果Flink topology從topic中消費數據的速度小于新數據添加的速度,那么lag會增加,consumer會落后。對于大多數生產發布我們建議監控這個度量來避免不斷增加的延遲。
啟用Kerberos認證(對于0.9及以上版本)
Flink為Kafka Connector認證到一個配置了Kerberos的Kafka集群提供了一流的支持,僅僅需要在flink-conf.yaml
配置Flink來為Kafka啟用Kerberos認證即可,如下:
1、通過如下設置來配置Kerberos憑證:
security.kerberos.login.use-ticket-cache
:默認情況下,這個是true并且Flink會由kinit管理的票據緩存中使用Kerberos憑證。注意,當在部署到YARN的Flink 的Kafka Connectors中,Kerberos認證使用票據緩存是不起作用的,這種情況在作業部署到Mesos中也會出現,因為使用票據緩存的認證在Mesos部署中還未支持。
security.kerberos.login.keytab
和 security.kerberos.login.principal
:為了使用Kerberos keytabs, 請為這兩個配置設置一個值。
2、將KafkaClient追加到security.kerberos.login.contexts
中:這會告訴Flink來為Kafka 認證所用的Kafka登錄場景提供配置的Kerberos憑證。
一旦基于Kerberos的Flink安全啟用,你就可以通過Flink Kafka Consumer或者Producer認證到Kafka中,在提供的屬性配置中引入下面兩個配置,這兩個配置會傳入到內部的Kafka 客戶端。