4.Kafka Producer機制優化-提高發送消息可靠性

名稱解釋:

Broker:負責消息的存儲和轉發,也可以叫消息中介節點
Topic:每種消息的分類叫做主題(Topic)。
Partition:每一個Topic被切分爲多個Partitions。

背景

Producer構造Message對象時,傳入key參數,當Producer發送Message,會根據key確定目標Partition,當Kafka集羣中某個Partition所有存活的節點都失效或掛掉。會造成Producer嘗試重新發送message.send.max.retries(默認值爲3)次後拋出Exception,每次嘗試都會休眠一定時間(默認值爲100ms)。用戶捕捉到異常其結果,停止發送會阻塞,繼續發送消息會丟失。

解決思路

Kafka中默認發送消息方式不變,給用戶提供一種可選擇方式,增加一個消息發送失效轉移的開關,當Producer發送到目標Partition的(所有副本本來存活的)節點都失效或掛掉,就轉發到其他Partition上。
M:表示Partition的主分區。
S:表示Partition的從分區。下面圖2所示,消息輪詢發送到Partition的0-3上。
這裏寫圖片描述
圖1
上圖本來一條消息是發送到Partition-0所在Broker的,當Partition-0所在Broker全部失效或掛掉後,如下圖所示。
這裏寫圖片描述
圖2
消息(輪詢均衡)發送到其他Partition上,待失效的Brokers恢復,發送消息恢復到圖1狀態。

Producer代碼修改

代碼修改如下:
ProducerConfig.scala類代碼中增加一行:
val sendSwitchBrokerEnabled = props.getBoolean(“send.switch.broker.enable”, false)
DefaultEventHandler.scala類增加一行
val isSendSwitchBrokerEnabled = config.sendSwitchBrokerEnabled

DefaultEventHandler.scala類中增加如下方法:

private def getPartition(topic: String, key: Any, topicPartitionList: Seq[PartitionAndLeader], isSendSwitchBrokerEnabled: Boolean = false): Int = {
  val numPartitions = topicPartitionList.size
  if(numPartitions <= 0)
    throw new UnknownTopicOrPartitionException("Topic " + topic + " doesn't exist")
  var partition =
    if (key == null) {
      // If the key is null, we don't really need a partitioner
      // So we look up in the send partition cache for the topic to decide the target partition
      val id = sendPartitionPerTopicCache.get(topic)
      id match {
        case Some(partitionId) =>
          // directly return the partitionId without checking availability of the leader,
          // since we want to postpone the failure until the send operation anyways
          partitionId
        case None =>
          val availablePartitions = topicPartitionList.filter(_.leaderBrokerIdOpt.isDefined)
          if (availablePartitions.isEmpty)
            throw new LeaderNotAvailableException("No leader for any partition in topic " + topic)
          val index = Utils.abs(Random.nextInt) % availablePartitions.size
          val partitionId = availablePartitions(index).partitionId
          sendPartitionPerTopicCache.put(topic, partitionId)
          partitionId
      }
    } else
    partitioner.partition(key, numPartitions)

  if(partition < 0 || partition >= numPartitions)
    throw new UnknownTopicOrPartitionException("Invalid partition id: " + partition + " for topic " + topic +
      "; Valid values are in the inclusive range of [0, " + (numPartitions-1) + "]")
  trace("Assigning message of topic %s and key %s to a selected partition %d".format(topic, if (key == null) "[none]" else key.toString, partition))

  if (isSendSwitchBrokerEnabled) {
        if(partitionsLeaderInTopicsCache.isEmpty()) {
          val availablePartitions = topicPartitionList.map { partitionAndLeader =>
              if(partitionAndLeader.leaderBrokerIdOpt.isDefined) {
                partitionsLeaderInTopicsCache.put(TopicAndPartition(topic, partitionAndLeader.partitionId),
                    partitionAndLeader.leaderBrokerIdOpt.get)
              }
          }
        }

        if(!partitionsLeaderInTopicsCache.containsKey(TopicAndPartition(topic, partition))) {
            val availablePartitions = topicPartitionList.filter(_.leaderBrokerIdOpt.isDefined)
            if (availablePartitions.isEmpty)
              throw new LeaderNotAvailableException("No leader for any partition in topic " + topic)
            val index = Utils.abs(Random.nextInt) % availablePartitions.size
            partition = availablePartitions(index).partitionId
        }
  }
  partition
}
發佈了13 篇原創文章 · 獲贊 25 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章