Spark Streaming + Kafka Integration Guide (Kafka broker version 0.10.0 or higher)官網翻譯

在設計上,Spark Streaming集成Kafka對於 0.10版本的類似於0.8版本(現在只講Direct Stream,其它不管,這裏沒有receiver)。
Spark StreamingKafka對於 0.10版本的集成提供了更簡化的並行度,在Kafka分區和Spark分區之間是 1:1 的對應關係,能夠去訪問偏移量和元數據。在有receiver的Spark Streaming裏,Spark的分區和Kafka的分區根本不是一回事,但是現在是Direct Stream,那麼兩個分區就是一樣了,一個Spark裏的partition去消費一個Kafka裏的partition更好。(以前是receiver的方式,現在是Direct Stream的方式,具體可以看上面提到的0.8.2.1官網)。
但是,由於較新的集成使用新的 Kafka consumer API而不是之前的simple API,因此在使用上存在顯著的差異。
此版本的集成被標記爲實驗性的,因此API可能會發生更改。

Linking

依賴

groupId = org.apache.spark
artifactId = spark-streaming-kafka-0-10_2.12
version = 2.4.3

不要手動添加org.apache.kafka相關的依賴,如kafka-clients。spark-streaming-kafka-0-10已經包含相關的依賴了,並且不同版本可能在難以診斷的方式上不兼容。

Creating a Direct Stream

創建一個Direct 流
請注意,導入的命名空間包括版本org.apache.spark.streaming.kafka010

import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.streaming.kafka010._
import org.apache.spark.streaming.kafka010.LocationStrategies.PreferConsistent
import org.apache.spark.streaming.kafka010.ConsumerStrategies.Subscribe

val kafkaParams = Map[String, Object](
  "bootstrap.servers" -> "localhost:9092,anotherhost:9092",
  "key.deserializer" -> classOf[StringDeserializer],
  "value.deserializer" -> classOf[StringDeserializer],
  "group.id" -> "use_a_separate_group_id_for_each_stream",
  "auto.offset.reset" -> "latest",
  "enable.auto.commit" -> (false: java.lang.Boolean)
)

val topics = Array("topicA", "topicB")
val stream = KafkaUtils.createDirectStream[String, String](
  streamingContext,
  PreferConsistent,
  Subscribe[String, String](topics, kafkaParams)
)

stream.map(record => (record.key, record.value))

流中的每個項目都是ConsumerRecord。

kafka的參數,請參考kafka官網。如果,你的spark批次時間超過了kafka的心跳時間(30s),需要增加 heartbeat.interval.mssession.timeout.ms。如果批處理時間是5min,那麼就需要調整 group.max.session.timeout.ms。注意,例子中是將enable.auto.commit設置爲了false。

LocationStrategies

本地策略

新版本Kafka的消費者API會預先獲取消息(數據)到buffer緩衝區。因此,爲了提升性能,在Executor端緩存消費者(這些數據)(而不是每個批次重新創建)是非常有必要的,並且優先在擁有適合的消費者所在的主機上調度安排分區。

在大多數情況下,你需要像上面所示,使用LocationStrategies.PreferConsistent,這個參數會將分區儘量均勻地分配到可用的executors上去。如果你的executors和Kafka brokers在相同機器上(生產環境基本不可能),請使用PreferBrokers,它將優先將分區調度到kafka分區leader所在的主機上。最後,分區間負荷有明顯的傾斜,請使用PreferFixed。這個允許你指定一個明確的分區到主機的映射(沒有指定的分區將會使用連續一致的地址)。

消費者緩存的默認最大值是64。如果你希望處理超過(64 * executors數目)kafka分區數,spark.streaming.kafka.consumer.cache.maxCapacity這個參數可以幫助你修改這個值。

如果你想禁止kafka消費者緩存,可以將spark.streaming.kafka.consumer.cache.enabled修改爲false。
Cache是將topicpartition和groupid作爲key的,所以每次調用creaDirectStream的時候要使用單獨的group.id。

ConsumerStrategies

消費策略

新的Kafka消費者API有許多不同的方法來指定主題,其中一些需要需要考慮post-object-instantiation設置。 ConsumerStrategies提供了一種抽象,它允許spark能夠獲得正確配置的消費者,即使從Checkpoint重啓之後。

ConsumerStrategies.Subscribe,如上所示,允許您訂閱固定的topic主題集合。 SubscribePattern允許您使用正則表達式來指定感興趣的主題。 請注意,與0.8版本集成不同,使用SubscribeSubscribePattern應該在在運行的stream流期間添加分區的時候進行響應。 最後,Assign允許您指定固定的分區集合。 所有這三種策略都有重載的構造函數,允許您指定特定分區的起始偏移量。

如果您具有上述選項無法滿足的特定消費者設置需求,則ConsumerStrategy是您可以擴展的公共類。

Creating an RDD

創建一個RDD

如果您的用例更適合批處理,則可以爲定義的偏移範圍創建RDD。

// Import dependencies and create kafka params as in Create Direct Stream above

val offsetRanges = Array(
  // topic, partition, inclusive starting offset, exclusive ending offset
  OffsetRange("test", 0, 0, 100),
  OffsetRange("test", 1, 0, 100)
)

val rdd = KafkaUtils.createRDD[String, String](sparkContext, kafkaParams, offsetRanges, PreferConsistent)

注意,在這裏是不能使用PreferBrokers的,因爲不是流處理的話就沒有driver端的消費者幫助你尋找broker元數據。 如有必要,請使用PreferFixed和您自己的元數據查找。

Obtaining Offsets

獲取偏移

stream.foreachRDD { rdd =>
  val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
  rdd.foreachPartition { iter =>
    val o: OffsetRange = offsetRanges(TaskContext.get.partitionId)
    println(s"${o.topic} ${o.partition} ${o.fromOffset} ${o.untilOffset}")
  }
}

請注意,只有在createDirectStream結果調用的第一個方法中完成對HasOffsetRanges的類型轉換纔會成功,而不是在方法鏈的後面。 請注意,在任何repartition或shuffle操作之後,RDD分區和Kafka分區之間的一對一映射不會保留,例如: reduceByKey()或window()。

Storing Offsets

失敗時的Kafka傳遞語義取決於存儲偏移的方式和時間。 Spark輸出操作至少一次。 因此,如果你想獲得僅僅一次的語義,你要麼在冪等輸出後存儲offset偏移量(主要用這個),要麼在原子事務中將offset偏移量與輸出一起存儲(這個不太用)。
關於語義可以看Spark Streaming官網:
http://spark.apache.org/docs/latest/streaming-programming-guide.html#fault-tolerance-semantics
在這裏插入圖片描述
通過這種集成,你有3種方式,來提高可靠性(以及代碼複雜性),用於存儲偏移量。

Checkpoints

(這個沒用,不用這個)

如果啓用Spark檢查點,則偏移量將存儲在檢查點中。 這很容易實現,但也有缺點。 因爲你將獲得重複輸出,所以你的輸出操作必須是冪等的; 事務不是一種選項。 此外,如果應用程序代碼已更改,那麼你是無法從檢查點進行恢復的。 對於計劃的升級,您可以通過在舊代碼的同時運行新代碼來緩解這種情況(因爲你的輸出都必須是冪等的,它們不會發生衝突)。 但是對於需要更改代碼的計劃外故障,除非您有其他方法來識別已知良好的起始偏移,否則您將丟失數據。

Kafka itself

Kafka有一個偏移提交API,用於在特殊的Kafka主題中存儲偏移量。 默認情況下,新消費者將定期自動提交偏移量。 這幾乎肯定不是您想要的,因爲消費者成功輪詢的消息可能尚未導致Spark輸出操作,從而導致未定義的語義。就是說雖然消費了這些數據,但是卻沒用輸出,那麼有什麼用呢。 就是說消息可能已經被消費者從kafka拉去出來,但是spark還沒處理,這種情況下會導致一些錯誤。這就是上面的stream示例將“enable.auto.commit”設置爲false的原因。 但是,您可以使用commitAsync API,在你知道輸出已被存儲之後,去向Kafka提交偏移量。與checkpoint方式相比,該種方式的好處是Kafka是一個持久化的存儲,而不需要考慮代碼的更新。然而,Kafka是非事物性的,所以你的輸出操作仍需要具有冪等性。

stream.foreachRDD { rdd =>
  val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges

  // some time later, after outputs have completed
  stream.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
}

有了上面代碼的hasofsetranges之後,只有在對createdDirectStream的結果調用時(而不是在轉換之後),才能成功對canCommitoffset進行強制轉換。commitasync調用是threadsafe,但如果需要有意義的語義,則必須在輸出之後發生。

Your own data store

對於支持事務的數據存儲,可以在同一個事務中保存偏移量,這樣即便在失敗的情況下也可以保證兩者的同步。如果您關心檢測重複或跳過的偏移範圍,回滾事務可以防止重複或丟失消息影響結果。這等價於僅僅一次性語義。 甚至對於由聚合(通常很難實現等冪)產生的輸出也可以使用這種策略

// The details depend on your data store, but the general idea looks like this

// begin from the the offsets committed to the database
val fromOffsets = selectOffsetsFromYourDatabase.map { resultSet =>
  new TopicPartition(resultSet.string("topic"), resultSet.int("partition")) -> resultSet.long("offset")
}.toMap

val stream = KafkaUtils.createDirectStream[String, String](
  streamingContext,
  PreferConsistent,
  Assign[String, String](fromOffsets.keys.toList, kafkaParams, fromOffsets)
)

stream.foreachRDD { rdd =>
  val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges

  val results = yourCalculation(rdd)

  // begin your transaction

  // update results
  // update offsets where the end of existing offsets matches the beginning of this batch of offsets
  // assert that offsets were updated correctly

  // end your transaction
}

SSL / TLS

新的kafka消費者支持SSL。開啓它,只需要在把值傳遞給createDirectStream / createRDD之前設置kafkaParams。注意,這僅僅應用與Spark和Kafka brokers之間的通訊;你同樣需要保證Spark節點內部之間的安全(Spark安全)通信。

val kafkaParams = Map[String, Object](
  // the usual params, make sure to change the port in bootstrap.servers if 9092 is not TLS
  "security.protocol" -> "SSL",
  "ssl.truststore.location" -> "/some-directory/kafka.client.truststore.jks",
  "ssl.truststore.password" -> "test1234",
  "ssl.keystore.location" -> "/some-directory/kafka.client.keystore.jks",
  "ssl.keystore.password" -> "test1234",
  "ssl.key.password" -> "test1234"
)

Deploying

For Scala and Java applications, if you are using SBT or Maven for project management, then package spark-streaming-kafka-0-10_2.12 and its dependencies into the application JAR. Make sure spark-core_2.12 and spark-streaming_2.12 are marked as provided dependencies as those are already present in a Spark installation. Then use spark-submit to launch your application (see Deploying section in the main programming guide).
同Spark應用程序一樣,spark-submit命令被用來提交你的程序。

對於JAVA或Scala應用程序來說,如果你使用SBT或Maven來做項目管理,需要將spark-streaming-kafka-0-10_2.12包以及它的依賴包添加到你的應用的JAR包中。確保spark-core_2.12包和spark-streaming_2.12包在你的依賴中被標記爲provided,因爲他們在Spark的安裝包中已經提供了。接下來使用spark-submit命令來部署你的應用(詳情請看部署環節)。

參考:
http://blog.sina.com.cn/s/blog_6dd718930102y02o.html
必讀:Spark與kafka010整合
https://www.e-learn.cn/content/qita/613810

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