在spring應用中如果需要訂閱kafka消息,通常情況下我們不會直接使用kafka-client, 而是使用更方便的一層封裝spring-kafka
。不過,它可不是簡單的封裝了kafka-client, 這裏面有很多需要注意的問題,比如下面這個參數:
spring.kafka.listener.concurrency=3
它並不像參數名那樣簡單,背後挺複雜的。如果你用jstack把線程dump出來,會發現spring-kafka啓動了好多線程,然後一臉懵逼。下面就主要介紹一下這些線程都是幹什麼的。
在spring-kafka在運行時會啓動兩類線程,一類是Consumer線程,另一類是Listener線程。前者用來直接調用kafka-client的poll()方法獲取消息,後者纔是調用我們代碼中標有@KafkaListener
註解方法的線程。如果直接使用kafka-client的話,那麼正常的寫法是一個while循環,在循環裏面調用poll(),然後處理消息,這在kafka broker看來就是一個Consumer。如果你想用多個Consumer, 除了多啓動幾個進程以外,也可以在一個進程使用多個線程執行此while()循環。spring-kafka就是這麼幹的。
對於spring.kafka.listener.concurrency=3
這個參數來說,它設置的是每個@KafkaListener
的併發個數。每添加一個@KafkaListener
, spring-kafka都會啓動一條Consumer線程來監聽這些topic(註解可以指定監聽多個topic), 然後再啓動spring.kafka.listener.concurrency
條線程來真正執行你的Listener。這裏有一點要注意的是隻有enable-auto-commit
設爲false
時纔會啓動Listener線程,有源碼爲證:
// if the container is set to auto-commit, then execute in the
// same thread
// otherwise send to the buffering queue
if (this.autoCommit) {
invokeListener(records);
}
else {
if (sendToListener(records)) {
if (this.assignedPartitions != null) {
// avoid group management rebalance due to a slow
// consumer
this.consumer.pause(this.assignedPartitions);
this.paused = true;
this.unsent = records;
}
}
}
所以,當concurrency=3時,如果你程序裏有兩個方法標記了@KafkaListener
,那麼此時會啓動 2 個Consumer線程,2 * 3 = 6個Listener線程。
這個信息在排查錯誤的時候非常重要,但官方文檔居然沒怎麼提線程的事(不夠詳細),只是在介紹KafkaContainerListener
。特此記錄