Apache Kafak如何處理訊息反序列化失敗等毒丸現象?

banq發表於2020-07-02

在Kafka的場景下毒丸是:針對Kafka主題生產推入的記錄,無論嘗試多少次,消費者使用都會失敗。
因此,毒丸可以有不同的形式:
  • 記錄已損壞(我自己從未使用Kafka遇到過此問題)
  • 反序列化失敗

主題的消費者應配置正確的反序列化器,以能夠反序列化生產者的序列化Java物件的位元組。只要生產者和消費使用者都使用相同的相容序列器和解串器,一切就可以正常工作。
當生產者序列化程式和消費者反序列化程式不相容時,您將陷入毒丸場景。在鍵和值反序列化器中都可能發生這種不相容。
在現實生活中的專案中,我在以下情況下遇到了毒藥:
  • 生產者更改了鍵或值序列化器,並繼續將資料生產到同一Kafka主題。這給該主題的所有消費者帶來了反序列化問題。
  • 使用者配置了錯誤的key或值反序列化器。
  • 不同的生產者使用不同的鍵或值序列化程式,開始生產有關Kafka主題的記錄。

好奇如何在您當地的開發環境中造成毒藥?但是更重要的是,您自己學習如何透過應用此部落格文章中介紹的配置來保護您的消費者應用程式。
您可以在GitHub上找到示例專案。

選擇適合您的專案的序列化器。如果需要,您甚至可以實現自己的自定義序列化程式。
卡夫卡叢集負責:
  • 以容錯方式將記錄儲存在主題中
  • 在多個Kafka經紀人之間分配記錄
  • 在各個Kafka經紀人之間複製記錄(一個或多個副本)

Kafka叢集不負責:
  • 型別檢查
  • 模式驗證
  • 使用SQL資料庫時習慣的其他限制

Kafka甚至都不知道資料的結構。Kafka主題中的記錄儲存為位元組陣列。Kafka旨在分發位元組。這就是Kafka快速且可擴充套件的原因之一。
消費者負責:
  • 輪詢Kafka主題
  • 在微批次中使用該主題的記錄
  • 將位元組反序列化為鍵和值

使用者可以開始使用Kafka主題的記錄之前,必須在應用程式中配置相應的鍵和值反序列化器。這是使用Spring Boot和Spring Kafka的鍵和值序列化程式的Kafka使用者配置示例:

spring:
  kafka:
    consumer:
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: io.confluent.kafka.serializers.KafkaAvroDeserializer


在使用者應用程式中無法處理毒藥的影響很大。讓我們來看看發生了什麼:
  • 消費者應用程式正在使用Kafka主題。
  • 在某個時間點,應用程式無法對記錄進行反序列化(遇到毒丸)。
  • 消費者不能處理毒丸。
  • 因為使用者偏移量沒有向前移動,所以阻止了主題分割槽的使用。
  • 消費者將一次又一次地(非常迅速地)嘗試反序列化記錄,但是永遠不會成功。因此,您的消費者應用程式將陷入一個無窮迴圈,嘗試對失敗的記錄進行反序列化。
  • 對於每次失敗,都會在您的日誌檔案中寫入一行...糟糕!

java.lang.IllegalStateException: This error handler cannot process 'SerializationException's directly; please consider configuring an 'ErrorHandlingDeserializer' in the value and/or key deserializer
        at org.springframework.kafka.listener.SeekUtils.seekOrRecover(SeekUtils.java:145) ~[spring-kafka-2.5.0.RELEASE.jar!/:2.5.0.RELEASE]
        at org.springframework.kafka.listener.SeekToCurrentErrorHandler.handle(SeekToCurrentErrorHandler.java:103) ~[spring-kafka-2.5.0.RELEASE.jar!/:2.5.0.RELEASE]
        at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.handleConsumerException(KafkaMessageListenerContainer.java:1241) ~[spring-kafka-2.5.0.RELEASE.jar!/:2.5.0.RELEASE]
        at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:1002) ~[spring-kafka-2.5.0.RELEASE.jar!/:2.5.0.RELEASE]
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) ~[na:na]
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
        at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
Caused by: org.apache.kafka.common.errors.SerializationException: Error deserializing key/value for partition stock-quotes-avro-1 at offset 69. If needed, please seek past the record to continue consumption.
Caused by: org.apache.kafka.common.errors.SerializationException: Unknown magic byte!


壞結果:
如果您沒有及時注意到,您的消費者應用程式可以快速將數GB的日誌檔案寫入磁碟。您還可以將日誌自動傳送到日誌聚合工具,例如ELK堆疊(Elasticsearch,Logstash和Kibana)。

如果您沒有適當的監視,則有時可能會“吃掉”所有伺服器磁碟空間。在最壞的情況下,您可能還會在同一臺計算機上執行其他服務,由於磁碟已滿,它們將開始報告執行狀況不佳!

如何在有毒藥情況下生存?
有兩種方法可以使毒丸藥倖存下來:

  • 等待直到Kafka主題的保留期過去。如果您的Kafka主題配置了保留策略,則可以等待直到該時間過去,以確保毒藥消失了。但是,在相同保留期內,使用毒丸後,您還將丟失針對Kafka主題生成的所有記錄。在現實生活中,這是不行的!
  • 更改消費者組。您可以更改使用者組並從日誌的開頭開始使用(開始使用寫入該主題的下一條記錄)。在這種情況下,您將不會消耗毒藥和主題中最後寫入的記錄之間的記錄。所以這也是不行的!
  • 手動/以程式設計方式更新偏移量。您可以執行此操作,但這並不簡單。您必須知道毒藥的偏移量,並開始消耗毒藥之後的下一條記錄。如果還有其他毒藥該怎麼辦?
  • Spring-Kafka來救援!配置ErrorHandlingDeserializer。這是要走的路。繼續閱讀以瞭解如何配置您的使用應用程式。

使用Spring Kafka解決問題 ErrorHandlingDeserializer:

當反序列化器無法反序列化訊息時,Spring將無法處理該問題,因為它發生在poll()返回之前。為了解決這個問題,ErrorHandlingDeserializer已經引入了。該處理器將委託給實際的反序列化(鍵或值)。如果委託未能反序列化記錄內容,則在包含原因和原始位元組的標頭中ErrorHandlingDeserializer返回一個null值和一個DeserializationException。當您使用記錄級時MessageListener,如果中ConsumerRecord包含DeserializationException鍵或值的標頭,ErrorHandler則會使用failed呼叫容器的ConsumerRecord。記錄不會傳遞給偵聽器。


背後的想法ErrorHandlingDeserializer很簡單,但是當我第一次配置它時,我花了一些時間來解決問題。
對於我們的鍵和值反序列化器,請配置ErrorHandlingDeserializerSpring Kafka提供的。
該ErrorHandlingDeserializer會委託給真正的解串器(key和value)。我們必須“告訴” ErrorHandlingDeserializer:
  • The key deserializer class (spring.deserializer.key.delegate.class)
  • The value deserializer class (spring.deserializer.value.delegate.class)

例如:
  • spring.deserializer.key.delegate.class is the StringDeserializer
  • spring.deserializer.value.delegate.class is the KafkaAvroDeserializer


application.yml中配置ErrorHandlingDeserializer :

spring:
  kafka:
    bootstrap-servers: localhost:9092
    consumer:
      # Configures the Spring Kafka ErrorHandlingDeserializer that delegates to the 'real' deserializers
      key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
      value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
    properties:
      # Delegate deserializers
      spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
      spring.deserializer.value.delegate.class: io.confluent.kafka.serializers.KafkaAvroDeserializer


現在,當鍵或值委託無法對毒藥進行反序列化時,ErrorHandlingDeserializer返回空值並DeserializationException在包含原因和原始位元組的標頭中新增一個。
如果ConsumerRecord包含DeserializationException鍵或值的標頭,ErrorHandler則使用failed呼叫容器的標頭ConsumerRecord,並且不會將記錄傳遞給偵聽器(用註釋的類或方法@KafkaListener)。
預設情況下,容器的錯誤處理程式是SeekToCurrentErrorHandler。透過配置LoggingErrorHandler,我們可以記錄毒丸的內容。
這是配置的示例LoggingErrorHandler:

@Configuration
@EnableKafka
public class KafkaConfiguration {

  /**
   * Boot will autowire this into the container factory.
   */
  @Bean
  public LoggingErrorHandler errorHandler() {
    return new LoggingErrorHandler();
  }
}


長話短說,ErrorHandlingDeserializer確保處理並記錄了毒藥。消費者抵消向前移動,以便消費者可以繼續消費下一個記錄。
萬歲-您在毒丸場景中倖免於難!

釋出死信主題
在許多情況下,記錄反序列化異常足夠好,但是以後使檢查毒藥變得更加困難。從Spring Kafka 2.3開始,您可以配置ErrorHandlingDeserializer與a組合使用DeadLetterPublishingRecoverer,SeekToCurrentErrorHandler以將毒藥的值釋出到死信主題。

@Configuration
@EnableKafka
public class KafkaConfiguration {

  /**
   * Boot will autowire this into the container factory.
   */
  @Bean
  public SeekToCurrentErrorHandler errorHandler(DeadLetterPublishingRecoverer deadLetterPublishingRecoverer) {
    return new SeekToCurrentErrorHandler(deadLetterPublishingRecoverer);
  }

  /**
   * Configure the {@link DeadLetterPublishingRecoverer} to publish poison pill bytes to a dead letter topic:
   * "stock-quotes-avro.DLT".
   */
  @Bean
  public DeadLetterPublishingRecoverer publisher(KafkaTemplate bytesTemplate) {
    return new DeadLetterPublishingRecoverer(bytesTemplate);
  }
}


這使您可以靈活地使用毒藥和檢查資料。Spring Kafka將把死信記錄傳送到一個名為的主題<originalTopicName>.DLT(字尾為的原始主題的名稱.DLT),併傳送到與原始記錄相同的分割槽。
請注意,您的使用者應用程式也將成為生產者,因此您需要在配置(application.yml)中配置鍵和值序列化程式:

spring:
  kafka:
    producer:
      # Important!
      # In case you publish to a 'dead letter topic' you consumer application becomes
      # a producer as well! So you need to specify the producer properties!
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.ByteArraySerializer



 

相關文章