本套系列部落格從真實商業環境抽取案例進行總結和分享,並給出Spark商業應用實戰指導,請持續關注本套部落格。版權宣告:本套Spark商業應用實戰歸作者(秦凱新)所有,禁止轉載,歡迎學習。
- kafka 商業環境實戰-kafka生產環境規劃
- kafka 商業環境實戰-kafka生產者和消費者吞吐量測試
- kafka 商業環境實戰-kafka生產者Producer引數設定及引數調優建議
- kafka 商業環境實戰-kafka叢集管理重要操作指令運維兵書
- kafka 商業環境實戰-kafka叢集Broker端引數設定及調優準則建議
- kafka 商業環境實戰-kafka之Producer同步與非同步訊息傳送及事務冪等性案例應用實戰
- kafka 商業環境實戰-kafka之Consumer多種消費模式案例應用實戰
- kafka 商業環境實戰-kafka消費者Consumer引數設定及引數調優建議
- [kafka 商業環境實戰-kafka位移提交機制與消費者組的重平衡策略]
- [kafka 商業環境實戰-kafka訊息Poll輪詢機制原理深入剖析]
- [kafka 商業環境實戰-kafka副本與ISR同步機制原理深入剖析]
- [kafka 商業環境實戰-kafka精確一次語義EOS的原理深入剖析]
- [kafka 商業環境實戰-kafka訊息的冪等性與事務支援機制深入剖析]
- [kafka 商業環境實戰-kafka叢集Controller競選與責任設計思路架構詳解]
- [kafka 商業環境實戰-kafka叢集訊息格式之V1版本到V2版本的平滑過渡詳解]
- [kafka 商業環境實戰-kafka叢集水印與leader epoch對資料一致性保障的深入研究]
- [kafka 商業環境實戰-kafka調優過程在吞吐量,永續性,低延時,可用性等指標的折中選擇研究]
1 訊息的接收->基於Consumer Group
Consumer Group 主要用於實現高伸縮性,高容錯性的Consumer機制。因此,訊息的接收是基於Consumer Group 的。組內多個Consumer例項可以同時讀取Kafka訊息,同一時刻一條訊息只能被一個消費者消費,而且一旦某一個consumer "掛了", Consumer Group 會立即將已經崩潰的Consumer負責的分割槽轉交給其他Consumer來負責。從而保證 Consumer Group 能夠正常工作。
2 位移儲存->基於Consumer Group
說來奇怪,位移儲存是基於Consumer Group,同時引入檢查點模式,定期實現offset的持久化。
3 位移提交->拋棄ZooKeeper
Consumer會定期向kafka叢集彙報自己消費資料的進度,這一過程叫做位移的提交。這一過程已經拋棄Zookeeper,因為Zookeeper只是一個協調服務元件,不能作為儲存元件,高併發的讀取勢必造成Zk的壓力。
- 新版本位移提交是在kafka內部維護了一個內部Topic(_consumer_offsets)。
- 在kafka內部日誌目錄下面,總共有50個資料夾,每一個資料夾包含日誌檔案和索引檔案。日誌檔案主要是K-V結構,(group.id,topic,分割槽號)。
- 假設線上有很多的consumer和ConsumerGroup,通過對group.id做Hash求模運算,這50個資料夾就可以分散同時位移提交的壓力。
4 官方案例
4.1 自動提交位移
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test");
props.put("enable.auto.commit", "true");
props.put("auto.commit.interval.ms", "1000");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("foo", "bar"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records)
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
複製程式碼
4.2 手動提交位移
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "test");
props.put("enable.auto.commit", "false");
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("foo", "bar"));
final int minBatchSize = 200;
List<ConsumerRecord<String, String>> buffer = new ArrayList<>();
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
buffer.add(record);
}
if (buffer.size() >= minBatchSize) {
insertIntoDb(buffer);
consumer.commitSync();
buffer.clear();
}
}
複製程式碼
5 kafka Consumer引數設定
- consumer.poll(1000) 重要引數
- 新版本的Consumer的Poll方法使用了類似於Select I/O機制,因此所有相關事件(包括reblance,訊息獲取等)都發生在一個事件迴圈之中。
- 1000是一個超時時間,一旦拿到足夠多的資料(引數設定),consumer.poll(1000)會立即返回 ConsumerRecords<String, String> records。
- 如果沒有拿到足夠多的資料,會阻塞1000ms,但不會超過1000ms就會返回。
- session. timeout. ms <= coordinator檢測失敗的時間
- 預設值是10s
- 該引數是 Consumer Group 主動檢測 (組內成員comsummer)崩潰的時間間隔。若設定10min,那麼Consumer Group的管理者(group coordinator)可能需要10分鐘才能感受到。太漫長了是吧。
- max. poll. interval. ms <= 處理邏輯最大時間
- 這個引數是0.10.1.0版本後新增的,可能很多地方看不到喔。這個引數需要根據實際業務處理時間進行設定,一旦Consumer處理不過來,就會被踢出Consumer Group 。
- 注意:如果業務平均處理邏輯為1分鐘,那麼max. poll. interval. ms需要設定稍微大於1分鐘即可,但是session. timeout. ms可以設定小一點(如10s),用於快速檢測Consumer崩潰。
- auto.offset.reset
- 該屬性指定了消費者在讀取一個沒有偏移量後者偏移量無效(消費者長時間失效當前的偏移量已經過時並且被刪除了)的分割槽的情況下,應該作何處理,預設值是latest,也就是從最新記錄讀取資料(消費者啟動之後生成的記錄),另一個值是earliest,意思是在偏移量無效的情況下,消費者從起始位置開始讀取資料。
- enable.auto.commit
- 對於精確到一次的語義,最好手動提交位移
- fetch.max.bytes
- 單次獲取資料的最大訊息數。
- max.poll.records <= 吞吐量
- 單次poll呼叫返回的最大訊息數,如果處理邏輯很輕量,可以適當提高該值。
- 一次從kafka中poll出來的資料條數,max.poll.records條資料需要在在session.timeout.ms這個時間內處理完
- 預設值為500
- heartbeat. interval. ms <= 居然拖家帶口
- heartbeat心跳主要用於溝通交流,及時返回請求響應。這個時間間隔真是越快越好。因為一旦出現reblance,那麼就會將新的分配方案或者通知重新加入group的命令放進心跳響應中。
- connection. max. idle. ms <= socket連線
- kafka會定期的關閉空閒Socket連線。預設是9分鐘。如果不在乎這些資源開銷,推薦把這些引數值為-1,即不關閉這些空閒連線。
- request. timeout. ms
- 這個配置控制一次請求響應的最長等待時間。如果在超時時間內未得到響應,kafka要麼重發這條訊息,要麼超過重試次數的情況下直接置為失敗。
- 訊息傳送的最長等待時間.需大於session.timeout.ms這個時間
- fetch.min.bytes
- server傳送到消費端的最小資料,若是不滿足這個數值則會等待直到滿足指定大小。預設為1表示立即接收。
- fetch.wait.max.ms
- 若是不滿足fetch.min.bytes時,等待消費端請求的最長等待時間
- 0.11 新功能
- 空消費組延時rebalance,主要在server.properties檔案配置
- group.initial.rebalance.delay.ms <=牛逼了,我的kafka,防止成員加入請求後本應立即開啟的rebalance
- 對於使用者來說,這個改進最直接的效果就是新增了一個broker配置:group.initial.rebalance.delay.ms,
- 預設是3秒鐘。
- 主要作用是讓coordinator推遲空消費組接收到成員加入請求後本應立即開啟的rebalance。在實際使用時,假設你預估你的所有consumer組成員加入需要在10s內完成,那麼你就可以設定該引數=10000。
6 線上採坑
org.apache.kafka.clients.consumer.CommitFailedException:
Commit cannot be completed since the group has already rebalanced and assigned the partitions to another member.
This means that the time between subsequent calls to poll() was longer than the configured session.timeout.ms, which typically implies that the poll loop is spending too much time message processing.
You can address this either by increasing the session timeout or by reducing the maximum size of batches returned in poll() with max.poll.records. [com.bonc.framework.server.kafka.consumer.ConsumerLoop]
複製程式碼
基於最新版本10,注意此版本session. timeout. ms 與max.poll.interval.ms進行功能分離了。
- 可以發現頻繁reblance,並伴隨者重複性消費,這是一個很嚴重的問題,就是處理邏輯過重,max.poll. interval.ms 過小導致。發生的原因就是 poll()的迴圈呼叫時間過長,出現了處理超時。此時只用調大max.poll. interval.ms ,調小max.poll.records即可,同時要把request. timeout. ms設定大於max.poll. interval.ms
7 總結
優化會繼續,暫時把核心放在request. timeout. ms, max. poll. interval. ms,max.poll.records 上,避免因為處理邏輯過重,導致Consumer被頻繁的踢出Consumer group。
秦凱新 於深圳