總結kafka的consumer消費能力很低的情況下的處理方案
簡介
由於專案中需要使用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
會由於沒有接受到心跳而掛掉,並且出現一些日誌
[rhllor] Tue Oct 18 21:39:16 CST 2016 INFO [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.AbstractCoordinator coordinatorDead 529: kafka-example|NTI|Marking the coordinator 2147483646 dead. [rhllor] Tue Oct 18 21:39:16 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.AbstractCoordinator sendGroupMetadataRequest 465: kafka-example|NTI|Issuing group metadata request to broker 1[rhllor] Tue Oct 18 21:39:16 CST 2016 ERROR [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.ConsumerCoordinator handle 550: kafka-example|NTI|Error ILLEGAL_GENERATION occurred while committing offsets for group new-message-1[rhllor] Tue Oct 18 21:39:16 CST 2016 WARN [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.ConsumerCoordinator onComplete 424: kafka-example|NTI|Auto offset commit failed: Commit cannot be completed due to group rebalance [rhllor] Tue Oct 18 21:39:16 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.ConsumerCoordinator run 408: kafka-example|NTI|Cannot auto-commit offsets now since the coordinator is unknown, will retry after backoff [rhllor] Tue Oct 18 21:39:17 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.AbstractCoordinator handleGroupMetadataResponse 478: kafka-example|NTI|Group metadata response ClientResponse(receivedTimeMs=1476797957072, disconnected=false, request=ClientRequest(expectResponse=true, callback=org.apache.kafka.clients.consumer.internals.ConsumerNetworkClient$RequestFutureCompletionHandler@1d3d7e6, request=RequestSend(header={api_key=10,api_version=0,correlation_id=20,client_id=consumer-1}, body={group_id=new-message-1}), createdTimeMs=1476797956485, sendTimeMs=1476797956485), responseBody={error_code=0,coordinator={node_id=1,host=10.10.44.124,port=9092}}) [rhllor] Tue Oct 18 21:39:17 CST 2016 ERROR [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.ConsumerCoordinator handle 550: kafka-example|NTI|Error ILLEGAL_GENERATION occurred while committing offsets for group new-message-1[rhllor] Tue Oct 18 21:39:17 CST 2016 WARN [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.ConsumerCoordinator maybeAutoCommitOffsetsSync 445: kafka-example|NTI|Auto offset commit failed: [rhllor] Tue Oct 18 21:39:17 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.ConsumerCoordinator onJoinPrepare 247: kafka-example|NTI|Revoking previously assigned partitions [rhllor-log-0, rhllor-log-1, rhllor-log-2] [rhllor] Tue Oct 18 21:39:17 CST 2016 INFO [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.springframework.kafka.listener.KafkaMessageListenerContainer onPartitionsRevoked 244: kafka-example|NTI|partitions revoked:[rhllor-log-0, rhllor-log-1, rhllor-log-2] [rhllor] Tue Oct 18 21:39:17 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.AbstractCoordinator performGroupJoin 309: kafka-example|NTI|(Re-)joining group new-message-1[rhllor] Tue Oct 18 21:39:17 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.AbstractCoordinator performGroupJoin 318: kafka-example|NTI|Issuing request (JOIN_GROUP: {group_id=new-message-1,session_timeout=15000,member_id=consumer-1-64063d04-9d4e-45af-a927-17ccf31c6ec1,protocol_type=consumer,group_protocols=[{protocol_name=range,protocol_metadata=java.nio.HeapByteBuffer[pos=0 lim=22 cap=22]}]}) to coordinator 2147483646[rhllor] Tue Oct 18 21:39:17 CST 2016 INFO [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.AbstractCoordinator handle 354: kafka-example|NTI|Attempt to join group new-message-1 failed due to unknown member id, resetting and retrying. [rhllor] Tue Oct 18 21:39:17 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.AbstractCoordinator performGroupJoin 309: kafka-example|NTI|(Re-)joining group new-message-1[rhllor] Tue Oct 18 21:39:17 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.AbstractCoordinator performGroupJoin 318: kafka-example|NTI|Issuing request (JOIN_GROUP: {group_id=new-message-1,session_timeout=15000,member_id=,protocol_type=consumer,group_protocols=[{protocol_name=range,protocol_metadata=java.nio.HeapByteBuffer[pos=0 lim=22 cap=22]}]}) to coordinator 2147483646[rhllor] Tue Oct 18 21:39:17 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.AbstractCoordinator handle 336: kafka-example|NTI|Joined group: {error_code=0,generation_id=1,group_protocol=range,leader_id=consumer-1-d3f30611-5788-4b81-bf0d-e779a11093d2,member_id=consumer-1-d3f30611-5788-4b81-bf0d-e779a11093d2,members=[{member_id=consumer-1-d3f30611-5788-4b81-bf0d-e779a11093d2,member_metadata=java.nio.HeapByteBuffer[pos=0 lim=22 cap=22]}]} [rhllor] Tue Oct 18 21:39:17 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.ConsumerCoordinator performAssignment 225: kafka-example|NTI|Performing range assignment for subscriptions {consumer-1-d3f30611-5788-4b81-bf0d-e779a11093d2=org.apache.kafka.clients.consumer.internals.PartitionAssignor$Subscription@1dbca7d4} [rhllor] Tue Oct 18 21:39:17 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.ConsumerCoordinator performAssignment 229: kafka-example|NTI|Finished assignment: {consumer-1-d3f30611-5788-4b81-bf0d-e779a11093d2=org.apache.kafka.clients.consumer.internals.PartitionAssignor$Assignment@4826f394} [rhllor] Tue Oct 18 21:39:17 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.AbstractCoordinator onJoinLeader 397: kafka-example|NTI|Issuing leader SyncGroup (SYNC_GROUP: {group_id=new-message-1,generation_id=1,member_id=consumer-1-d3f30611-5788-4b81-bf0d-e779a11093d2,group_assignment=[{member_id=consumer-1-d3f30611-5788-4b81-bf0d-e779a11093d2,member_assignment=java.nio.HeapByteBuffer[pos=0 lim=38 cap=38]}]}) to coordinator 2147483646[rhllor] Tue Oct 18 21:39:17 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.AbstractCoordinator handle 423: kafka-example|NTI|Received successful sync group response for group new-message-1: {error_code=0,member_assignment=java.nio.HeapByteBuffer[pos=0 lim=38 cap=38]} [rhllor] Tue Oct 18 21:39:17 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.ConsumerCoordinator onJoinComplete 191: kafka-example|NTI|Setting newly assigned partitions [rhllor-log-0, rhllor-log-1, rhllor-log-2] [rhllor] Tue Oct 18 21:39:17 CST 2016 INFO [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.springframework.kafka.listener.KafkaMessageListenerContainer onPartitionsAssigned 249: kafka-example|NTI|partitions assigned:[rhllor-log-0, rhllor-log-1, rhllor-log-2] [rhllor] Tue Oct 18 21:39:17 CST 2016 DEBUG [org.springframework.kafka.KafkaListenerEndpointContainer#0-0-kafka-consumer-1] org.apache.kafka.clients.consumer.internals.ConsumerCoordinator sendOffsetFetchRequest 581: kafka-example|NTI|Fetching committed offsets for partitions: [rhllor-log-0, rhllor-log-1, rhllor-log-2]
日誌的意思大概是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提供了多種提交策略:
/** * The ack mode to use when auto ack (in the configuration properties) is false. * <ul> * <li>RECORD: Ack after each record has been passed to the listener.</li> * <li>BATCH: Ack after each batch of records received from the consumer has been * passed to the listener</li> * <li>TIME: Ack after this number of milliseconds; (should be greater than * {@code #setPollTimeout(long) pollTimeout}.</li> * <li>COUNT: Ack after at least this number of records have been received</li> * <li>MANUAL: Listener is responsible for acking - use a * {@link AcknowledgingMessageListener}. * </ul> */ private AbstractMessageListenerContainer.AckMode ackMode = AckMode.BATCH;
這些策略保證了在一批訊息沒有完成消費的情況下,也能提交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這樣的邏輯。
最後
spring-kafka是一個很好的用來操作kafka的庫,並且可以和spring進行完美結合。
spring-kafka提供了一些kafka使用上功能的擴充套件。
相比於使用原生的kafka-client的api的話,使用更加簡單,需要編寫的碼量更少。
最好能夠使用最新的kafka(0.10.0)和spring-kafka(1.1.1.RELEASE)的版本
作者:LOC_Thomas
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4289/viewspace-2819183/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- xcodebuild -workspace 情況下,部分坑的處理XCodeUI
- 處理表鎖定的情況
- Kafka中消費者延遲處理訊息Kafka
- 沒有備份的情況下處理undo損壞
- 動態sql查詢結果多行的處理情況SQL
- kafka消費者消費訊息的流程Kafka
- 探索Kafka消費者的內部結構Kafka
- 實際業務處理 Kafka 訊息丟失、重複消費和順序消費的問題Kafka
- 使用多執行緒增加kafka消費能力執行緒Kafka
- Java程式異常處理的特殊情況Java
- java的kafka生產消費JavaKafka
- Redo 丟失的4種情況的處理方法
- 如何處理瀏覽器的斷網情況?瀏覽器
- kafka消費Kafka
- session儲存資料庫中以及禁用cookie情況下的處理Session資料庫Cookie
- 第十八篇:批量處理情況下的回射客戶端客戶端
- 沒有備份的情況下如何處理logical & physical corrupt blockBloC
- Redo丟失的4種情況及處理方法
- jQuery的動畫處理總結jQuery動畫
- kafka不停止服務的情況下修改日誌保留時間Kafka
- Kafka 消費組消費者分配策略Kafka
- SYSAUX表空間佔用過大情況下的處理(AWR資訊過多)UX
- Kafka Consumer 的 Rebalance 機制Kafka
- Kafka 消費者解析Kafka
- Kafka - 消費介面分析Kafka
- Laravel-admin 處理 select 有 data 鍵的情況Laravel
- MySQL主從不同步的幾種情況總結MySql
- oradebug處理DB hang情況
- 在MDX中處理邊界情況
- 有return的情況下try catch finally的執行順序(最有說服力的總結)
- Oracle 無備份情況下undo檔案損壞處理Oracle
- asp.net ashx處理程式中switch case的替代方案總結ASP.NET
- mysql的處理能力問題MySql
- Spark streaming消費Kafka的正確姿勢SparkKafka
- 可貴的總結能力
- Kafka ConsumerKafka
- 如何在不使用POJO程式碼的情況下建立Java批處理插入 -DZone JavaPOJOJava
- 我的 iOS 音訊處理總結iOS音訊