平臺搭建---Kafka使用---Kafka客戶端是如何找到 leader 分區的

來源:2017-07-28 21:07:05
在正常情況下,Kafka中的每個Topic都會有很多個分區,每個分區又會存在多個副本。在這些副本中,存在一個leader分區,而剩下的分區叫做 follower,所有對分區的讀寫操作都是對leader分區進行的。所以當我們向Kafka寫消息或者從Kafka讀取消息的時候,必須先找到對應分區的Leader及其所在的Broker地址,這樣纔可以進行後續的操作。本文將要介紹的就是 Kafka 是如何找到 leader 分區的。

我們知道, Kafka 是使用 Scala 語言編寫的,但是其支持很多語言的客戶端,包括:C/C++、PHP、Go以及Ruby等等(參見https://cwiki.apache.org/confluence/display/KAFKA/Clients)。這是爲什麼呢?這是因爲 Kafka 內部實現了一套基於TCP層的協議,只要使用這種協議與Kafka進行通信,就可以使用很多語言來操作Kafka。

目前 Kafka 內部支持多達30多種協議,本文介紹的 Kafka 客戶端是如何找到 leader 分區就涉及到 Kafka 內部的 Metadata 協議。Metadata 協議主要解決以下四種問題:

  • Kafka中存在哪些主題?
  • 每個主題有幾個分區?
  • Leader分區所在的broker地址及端口?
  • 每個broker的地址及端口是多少?

客戶端只需要構造相應的請求,併發送到Broker端,即可獲取到上面四個問題的答案。整個過程如下:

  • 客戶端構造相應的請求
  • 客戶端將請求發送到Broker端
  • Broker端接收到請求處理,並將結果發送到客戶端。

Metadata 請求協議(v0-v3版本)如下:

TopicMetadataRequest => [TopicNames]
TopicNames => string

客戶端只需要構造一個 TopicMetadataRequest ,裏面包括我們需要查詢主題的名字(TopicNames);當然,我們可以一次查詢多個主題,只需要將這些主題放進List裏面即可。同時,我們還可以不傳入任何主題的名字,這時候 Kafka 將會把內部所有的主題相關的信息發送給客戶端。

目前 Metadata 請求協議存在五個版本,v0-v3版本格式一致。但是這些協議存在一個問題:當 Kafka 服務器端將auto.create.topics.enable參數設置爲 ture 時,如果我們查詢的主題不存在,Kafka 將會自動創建這個主題,這很可能不是我們想要的結果。所以,基於這個問題,到了 Metadata 請求協議第五版,格式已經變化了,如下:

Metadata Request (Version: 4) => [TopicNames] allow_auto_topic_creation
TopicNames => STRING
allow_auto_topic_creation => BOOLEAN

我們可以指定allow_auto_topic_creation 參數來告訴 Kafka 是否需要在主題不存在的時候創建,這時候控制權就在我們了。

Kafka 的 Broker 收到客戶端的請求處理完之後,會構造一個 TopicMetadataResponse,併發送給客戶端。TopicMetadataResponse 協議的格式如下:

MetadataResponse => [Broker][TopicMetadata]
Broker => NodeId Host Port  (any number of brokers may be returned)
NodeId => int32
Host => string
Port => int32
TopicMetadata => TopicErrorCode TopicName [PartitionMetadata]
TopicErrorCode => int16
PartitionMetadata => PartitionErrorCode PartitionId Leader Replicas Isr
PartitionErrorCode => int16
PartitionId => int32
Leader => int32
Replicas => [int32]
Isr => [int32]

可以看到,相應協議裏面包含了每個分區的 Leader、Replicas 以及 Isr 信息,同時還包括了Kafka 集羣所有Broker的信息。如果處理出現了問題,會出現相應的錯誤信息碼,主要包括下面幾個:

UnknownTopic (3)
LeaderNotAvailable (5)
InvalidTopic (17)
TopicAuthorizationFailed (29)

而且,Metadata 協議是目前唯一一個可以向任何 Broker 發送的協議。因爲任何一個 Broker 在啓動之後會存儲這些Metadata信息的。而且,Kafka 提供的客戶端在獲取到 Metadata 信息之後也會將它存儲到內存中的。並且在以下幾種情況會更新已經緩存下來的 Metadata 信息:

  • meta‐data.max.age.ms 參數配置的時間過期之後;
  • 在往Kafka發送請求是收到 Not a Leader 異常。

以上兩種情況 Kafka提供的客戶端會自動再發送一次 Metadata 請求,這樣就可以獲取到更新的信息。整個過程如下:
這裏寫圖片描述
我們來看看程序裏面如何構造 TopicMetadataRequest 以及處理 TopicMetadataResponse。

package com.iteblog.kafka

import kafka.api.TopicMetadataRequest._
import kafka.api.{TopicMetadataRequest, TopicMetadataResponse}
import kafka.consumer.SimpleConsumer

object MetaDataDemo {
  def main(args: Array[String]): Unit = {
    val consumer = new SimpleConsumer("1.iteblog.com", 9092, 50, 1024 * 4, DefaultClientId)

    val req: TopicMetadataRequest = new TopicMetadataRequest(CurrentVersion, 0, DefaultClientId, List("iteblog_hadoop"))
    val resp: TopicMetadataResponse = consumer.send(req)

    println("Broker Infos:")
    println(resp.brokers.mkString("\n\t"))
    val metadata = resp.topicsMetadata
    metadata.foreach { topicMetadata =>
      val partitionsMetadata = topicMetadata.partitionsMetadata
      partitionsMetadata.foreach { partitionMetadata =>
        println(s"partitionId=${partitionMetadata.partitionId}\n\tleader=${partitionMetadata.leader}" +
          s"\n\tisr=${partitionMetadata.isr}\n\treplicas=${partitionMetadata.replicas}")
      }
    }
  }
}

TopicMetadataRequest 是通過 SimpleConsumer 的 send 方法發送的,其返回的是 TopicMetadataResponse ,其中就包含了我們需要的信息。 運行上面的程序輸出如下:

Broker Infos:
    id:5,host:5.iteblog.com,port:9092
    id:1,host:1.iteblog.com,port:9092
    id:6,host:6.iteblog.com,port:9092
    id:2,host:2.iteblog.com,port:9092
    id:7,host:7.iteblog.com,port:9092
    id:3,host:3.iteblog.com,port:9092
    id:8,host:8.iteblog.com,port:9092
    id:4,host:4.iteblog.com,port:9092
partitionId=0
    leader=Some(id:1,host:1.iteblog.com,port:9092)
    isr=Vector(id:1,host:1.iteblog.com,port:9092)
    replicas=Vector(id:1,host:1.iteblog.com,port:9092, id:8,host:8.iteblog.com,port:9092)
partitionId=1
    leader=Some(id:2,host:2.iteblog.com,port:9092)
    isr=Vector(id:2,host:2.iteblog.com,port:9092, id:1,host:1.iteblog.com,port:9092)
    replicas=Vector(id:2,host:2.iteblog.com,port:9092, id:1,host:1.iteblog.com,port:9092)
partitionId=2
    leader=Some(id:3,host:3.iteblog.com,port:9092)
    isr=Vector(id:3,host:3.iteblog.com,port:9092, id:2,host:2.iteblog.com,port:9092)
    replicas=Vector(id:3,host:3.iteblog.com,port:9092, id:2,host:2.iteblog.com,port:9092)
partitionId=3
    leader=Some(id:4,host:4.iteblog.com,port:9092)
    isr=Vector(id:4,host:4.iteblog.com,port:9092, id:3,host:3.iteblog.com,port:9092)
    replicas=Vector(id:4,host:4.iteblog.com,port:9092, id:3,host:3.iteblog.com,port:9092)
partitionId=4
    leader=Some(id:5,host:5.iteblog.com,port:9092)
    isr=Vector(id:5,host:5.iteblog.com,port:9092, id:4,host:4.iteblog.com,port:9092)
    replicas=Vector(id:5,host:5.iteblog.com,port:9092, id:4,host:4.iteblog.com,port:9092)
partitionId=5
    leader=Some(id:6,host:6.iteblog.com,port:9092)
    isr=Vector(id:6,host:6.iteblog.com,port:9092, id:5,host:5.iteblog.com,port:9092)
    replicas=Vector(id:6,host:6.iteblog.com,port:9092, id:5,host:5.iteblog.com,port:9092)
partitionId=6
    leader=Some(id:7,host:7.iteblog.com,port:9092)
    isr=Vector(id:6,host:6.iteblog.com,port:9092, id:7,host:7.iteblog.com,port:9092)
    replicas=Vector(id:7,host:7.iteblog.com,port:9092, id:6,host:6.iteblog.com,port:9092)
partitionId=7
    leader=Some(id:8,host:8.iteblog.com,port:9092)
    isr=Vector(id:8,host:8.iteblog.com,port:9092)
    replicas=Vector(id:8,host:8.iteblog.com,port:9092, id:7,host:7.iteblog.com,port:9092)
partitionId=8
    leader=Some(id:1,host:1.iteblog.com,port:9092)
    isr=Vector(id:2,host:2.iteblog.com,port:9092, id:1,host:1.iteblog.com,port:9092)
    replicas=Vector(id:1,host:1.iteblog.com,port:9092, id:2,host:2.iteblog.com,port:9092)
partitionId=9
    leader=Some(id:2,host:2.iteblog.com,port:9092)
    isr=Vector(id:3,host:3.iteblog.com,port:9092, id:2,host:2.iteblog.com,port:9092)
    replicas=Vector(id:2,host:2.iteblog.com,port:9092, id:3,host:3.iteblog.com,port:9092)
partitionId=10
    leader=Some(id:3,host:3.iteblog.com,port:9092)
    isr=Vector(id:4,host:4.iteblog.com,port:9092, id:3,host:3.iteblog.com,port:9092)
    replicas=Vector(id:3,host:3.iteblog.com,port:9092, id:4,host:4.iteblog.com,port:9092)
partitionId=11
    leader=Some(id:6,host:6.iteblog.com,port:9092)
    isr=Vector(id:6,host:6.iteblog.com,port:9092, id:1,host:1.iteblog.com,port:9092)
    replicas=Vector(id:6,host:6.iteblog.com,port:9092, id:1,host:1.iteblog.com,port:9092)
partitionId=12
    leader=Some(id:7,host:7.iteblog.com,port:9092)
    isr=Vector(id:7,host:7.iteblog.com,port:9092, id:2,host:2.iteblog.com,port:9092)
    replicas=Vector(id:7,host:7.iteblog.com,port:9092, id:2,host:2.iteblog.com,port:9092)
partitionId=13
    leader=Some(id:8,host:8.iteblog.com,port:9092)
    isr=Vector(id:8,host:8.iteblog.com,port:9092, id:3,host:3.iteblog.com,port:9092)
    replicas=Vector(id:8,host:8.iteblog.com,port:9092, id:3,host:3.iteblog.com,port:9092)
partitionId=14
    leader=Some(id:1,host:1.iteblog.com,port:9092)
    isr=Vector(id:1,host:1.iteblog.com,port:9092, id:4,host:4.iteblog.com,port:9092)
    replicas=Vector(id:1,host:1.iteblog.com,port:9092, id:4,host:4.iteblog.com,port:9092)
partitionId=15
    leader=Some(id:2,host:2.iteblog.com,port:9092)
    isr=Vector(id:2,host:2.iteblog.com,port:9092, id:5,host:5.iteblog.com,port:9092)
    replicas=Vector(id:2,host:2.iteblog.com,port:9092, id:5,host:5.iteblog.com,port:9092)
partitionId=16
    leader=Some(id:3,host:3.iteblog.com,port:9092)
    isr=Vector(id:3,host:3.iteblog.com,port:9092, id:7,host:7.iteblog.com,port:9092)
    replicas=Vector(id:3,host:3.iteblog.com,port:9092, id:7,host:7.iteblog.com,port:9092)
partitionId=17
    leader=Some(id:4,host:4.iteblog.com,port:9092)
    isr=Vector(id:4,host:4.iteblog.com,port:9092, id:8,host:8.iteblog.com,port:9092)
    replicas=Vector(id:4,host:4.iteblog.com,port:9092, id:8,host:8.iteblog.com,port:9092)
partitionId=18
    leader=Some(id:5,host:5.iteblog.com,port:9092)
    isr=Vector(id:5,host:5.iteblog.com,port:9092, id:1,host:1.iteblog.com,port:9092)
    replicas=Vector(id:5,host:5.iteblog.com,port:9092, id:1,host:1.iteblog.com,port:9092)
partitionId=19
    leader=Some(id:6,host:6.iteblog.com,port:9092)
    isr=Vector(id:6,host:6.iteblog.com,port:9092, id:2,host:2.iteblog.com,port:9092)
    replicas=Vector(id:6,host:6.iteblog.com,port:9092, id:2,host:2.iteblog.com,port:9092)
partitionId=20
    leader=Some(id:7,host:7.iteblog.com,port:9092)
    isr=Vector(id:7,host:7.iteblog.com,port:9092, id:3,host:3.iteblog.com,port:9092)
    replicas=Vector(id:7,host:7.iteblog.com,port:9092, id:3,host:3.iteblog.com,port:9092)
partitionId=21
    leader=Some(id:8,host:8.iteblog.com,port:9092)
    isr=Vector(id:8,host:8.iteblog.com,port:9092, id:4,host:4.iteblog.com,port:9092)
    replicas=Vector(id:8,host:8.iteblog.com,port:9092, id:4,host:4.iteblog.com,port:9092)
partitionId=22
    leader=Some(id:1,host:1.iteblog.com,port:9092)
    isr=Vector(id:1,host:1.iteblog.com,port:9092, id:5,host:5.iteblog.com,port:9092)
    replicas=Vector(id:1,host:1.iteblog.com,port:9092, id:5,host:5.iteblog.com,port:9092)

上面的輸出就可以看到各個分區的leader所在機器、isr以及所有replicas等信息。有一點我們需要注意,因爲目前存在多個版本的 Metadata 請求協議,我們可以使用低版本的協議與高版本的Kafka集羣進行通信,因爲高版本的 Kafka 能夠支持低版本的 Metadata 請求協議;但是我們不能使用高版本的 Metadata 請求協議與低版本的 Kafka 通信。

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