簡介
由于項目中需要使用kafka作為消息隊列,并且項目是基于spring-boot來進行構建的,所以項目采用了spring-kafka作為原生kafka的一個擴展庫進行使用。先說明一下版本:
- spring-boot 的版本是1.4.0.RELEASE
- kafka 的版本是0.9.0.x 版本
- spring-kafka 的版本是1.0.3.RELEASE
用過kafka的人都知道,對于使用kafka來說,producer的使用相對簡單一些,只需要把數據按照指定的格式發送給kafka中某一個topic就可以了。本文主要是針對spring-kafka的consumer端上的使用進行簡單一些分析和總結。
kafka的速度是很快,所以一般來說producer的生產消息的邏輯速度都會比consumer的消費消息的邏輯速度快。
具體案例
之前在項目中遇到了一個案例是,consumer消費一條數據平均需要200ms的時間,并且在某個時刻,producer會在短時間內產生大量的數據丟進kafka的broker里面(假設平均1s中內丟入了5w條需要消費的消息,這個情況會持續幾分鐘)。
對于這種情況,kafka的consumer的行為會是:
- kafka的consumer會從broker里面取出一批數據,?給消費線程進行消費。
- 由于取出的一批消息數量太大,consumer在session.timeout.ms時間之內沒有消費完成
- consumer coordinator 會由于沒有接受到心跳而掛掉,并且出現一些日志
日志的意思大概是coordinator掛掉了,然后自動提交offset失敗,然后重新分配partition給客戶端 - 由于自動提交offset失敗,導致重新分配了partition的客戶端又重新消費之前的一批數據
- 接著consumer重新消費,又出現了消費超時,無限循環下去。
解決方案
遇到了這個問題之后, 我們做了一些步驟:
- 提高了partition的數量,從而提高了consumer的并行能力,從而提高數據的消費能力
- 對于單partition的消費線程,增加了一個固定長度的阻塞隊列和工作線程池進一步提高并行消費的能力
- 由于使用了spring-kafka,則把kafka-client的enable.auto.commit設置成了false,表示禁止kafka-client自動提交offset,因為就是之前的自動提交失敗,導致offset永遠沒更新,從而轉向使用spring-kafka的offset提交機制。并且spring-kafka提供了多種提交策略:
這些策略保證了在一批消息沒有完成消費的情況下,也能提交offset,從而避免了完全提交不上而導致永遠重復消費的問題。
分析
那么問題來了,為什么spring-kafka的提交offset的策略能夠解決spring-kafka的auto-commit的帶來的重復消費的問題呢?下面通過分析spring-kafka的關鍵源碼來解析這個問題。
首先來看看spring-kafka的消費線程邏輯
if (isRunning() && this.definedPartitions != null) {
initPartitionsIfNeeded();
// we start the invoker here as there will be no rebalance calls to
// trigger it, but only if the container is not set to autocommit
// otherwise we will process records on a separate thread
if (!this.autoCommit) {
startInvoker();
}
}
上面可以看到,如果auto.commit關掉的話,spring-kafka會啟動一個invoker,這個invoker的目的就是啟動一個線程去消費數據,他消費的數據不是直接從kafka里面直接取的,那么他消費的數據從哪里來呢?他是從一個spring-kafka自己創建的阻塞隊列里面取的。
然后會進入一個循環,從源代碼中可以看到如果auto.commit被關掉的話, 他會先把之前處理過的數據先進行提交offset,然后再去從kafka里面取數據。
然后把取到的數據丟給上面提到的阻塞列隊,由上面創建的線程去消費,并且如果阻塞隊列滿了導致取到的數據塞不進去的話,spring-kafka會調用kafka的pause方法,則consumer會停止從kafka里面繼續再拿數據。
接著spring-kafka還會處理一些異常的情況,比如失敗之后是不是需要commit offset這樣的邏輯。
方法二
- 可以根據消費者的消費速度對session.timeout.ms的時間進行設置,適當延長
- 或者減少每次從partition里面撈取的數據分片的大小,提高消費者的消費速度。