背景
系統採用Spring Cloud Stream框架集成Kafka來實現異步消息。
問題
客戶端消費某個topic消息出錯時,會連續輸出這個消息內容3次,同時沒有提交offset。當有大量消息出錯時,topic出現消息積壓。
分析
首先,我們知道實現異步消息的系統架構一般包含3個部分:生產者Producer、消息中間件Broker、消費者Consumer。其中,
-
消息中間件Broker,是指二進制安裝包安裝的單機Kafka或利用zookeeper組成的Kafka集羣或者阿里雲Kafka集羣。
-
生產者Produer和消費者Consumer,是指基於kafka-clients包,實現我們業務邏輯的消息生產者及消費者。
而spring-cloud-stream,是一個高度可擴展的基於事件驅動的框架,在系統中屬於更高層級。系統層級:業務代碼 -> spring-cloud-stream -> spring-integration -> spring-messaging -> spring-kafka -> kafka-clients -> kafka broker。
其次,我們需要了解幾個關鍵點:
-
Kafka Broker支持Consumer提交offset,僅此而已。offset自動提交及手動提交,是由Kafka Consumer實現的。
-
不要誤以爲offset自動提交,是指Kafka Broker在Consumer拉取消息後自己自動提交offset。
-
kafka-clients的自動提交offset與spring-cloud-stream的自動提交offset不是一回事兒。
-
kafka-clients通過設置KafkaConsumer的enable.auto.commit=true來實現自動提交offset。
-
spring-cloud-stream有spring.cloud.stream.kafka.bindings.channelName.consumer.autoCommitOffset配置項,默認true;但spring-kafka有自己定義的一套AckMode(org.springframework.kafka.listener.AbstractMessageListenerContainer.AckMode定義),設置autoCommitOffset=true,spring-kafka會負責提交offset,不會利用kafka-clients的KafkaConsumer的自動提交機制(即KafkaConsumer屬性enable.auto.commit=false)。
-
那麼,解答上面的問題:1、爲什麼會連續消費3次?2、爲什麼之後不再消費,消息出現積壓?
1、沒有消費3次。客戶端只從Broker拉取了1次,spring-kafka使用RetryingAcknowledgingMessageListenerAdapter處理消息,在出現異常時,重新觸發我們業務邏輯再處理2次(spring-retry的RetryTemplate負責重試),一共3次。3次日誌是我們處理邏輯輸出的。
2、大量消息拉取後處理失敗,沒有向Broker提交offset,肯定會導致消息出現積壓。KafkaConsumer有存儲上次消費的offset,每次拉取都會把這個offset傳遞給Broker,從這個offset之後拉取消息,拉取不到,所以不再消費。重啓KafkaConsumer可以重新拉取消費。