spring-kafka的線程模型與spring.kafka.listener.concurrency參數

在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。特此記錄

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