Kafka參數詳解及調優--消費者

引言

在實際的kafka開發中,我們會發現,無論是生產者還是消費者,都需要構建一個Properties對象,里面設置了很多參數。對于很多初學者來說,會看不懂這些參數分別代表什么含義。
在本篇文章我們就來詳細地了解一下這些參數的作用,并探討下如何使用合理的配置去優化提高生產/消費效率。

正文

1.kafka消費者參數

我們先來看一段消費者的構建代碼。

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("auto.offset.reset", "earliest");
props.put("session.timeout.ms", "30000");
props.put("fetch.min.bytes", "1048576");
props.put("fetch.max.wait.ms", "2000");
props.put("max.partition.fetch.bytes", "2097152");
props.put("max.poll.records", "10000");
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<String, String>(props);

在這段代碼中有很多常用的參數配置,在線上使用時,我們要根據實際的數據量和數據大小來決定這些配置的具體值。下面來挑出其中比較重要的幾個參數來詳細解析一下。

1.1 enable.auto.commit

指定了消費者是否自動提交偏移量,默認值是true,為了盡量避免重復數據和數據丟失,可以把它設置為false,有自己控制合適提交偏移量,如果設置為true, 可以通過設置 auto.commit.interval.ms屬性來控制提交的頻率。
詳細地來說:
當一個consumer因某種原因退出Group時,進行重新分配partition后,同一group中的另一個consumer在讀取該partition時,怎么能夠知道上一個consumer該從哪個offset的message讀取呢?也是是如何保證同一個group內的consumer不重復消費消息呢?上面說了一次走網絡的fetch請求會拉取到一定量的數據,但是這些數據還沒有被消息完畢,Consumer就掛掉了,下一次進行數據fetch時,是否會從上次讀到的數據開始讀取,而導致Consumer消費的數據丟失嗎?
為了做到這一點,當使用完poll從本地緩存拉取到數據之后,需要client調用commitSync方法(或者commitAsync方法)去commit 下一次該去讀取 哪一個offset的message。
而這個commit方法會通過走網絡的commit請求將offset在coordinator中保留,這樣就能夠保證下一次讀取(不論進行了rebalance)時,既不會重復消費消息,也不會遺漏消息。
對于offset的commit,Kafka Consumer Java Client支持兩種模式:由KafkaConsumer自動提交,或者是用戶通過調用commitSync、commitAsync方法的方式完成offset的提交。

自動提交的例子:

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());

     }

手動提交的栗子:

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();
         }
     }

在手動提交單個partition的offset時,需要注意的一點是:要提交的是下一次要讀取的offset,例如:

try {
         while(running) {
            // 取得消息
             ConsumerRecords<String, String> records = consumer.poll(Long.MAX_VALUE);
            // 根據分區來遍歷數據:
             for (TopicPartition partition : records.partitions()) {
                 List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);
                 // 數據處理
                 for (ConsumerRecord<String, String> record : partitionRecords) {
                     System.out.println(record.offset() + ": " + record.value());
                 }
                 // 取得當前讀取到的最后一條記錄的offset
                 long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset();
                // 提交offset,記得要 + 1
                 consumer.commitSync(Collections.singletonMap(partition, new OffsetAndMetadata(lastOffset + 1)));
             }
         }
     } finally {
       consumer.close();
     }

1.2 auto.offset.reset

該屬性指定了消費者在讀取一個沒有偏移量后者偏移量無效(消費者長時間失效當前的偏移量已經過時并且被刪除了)的分區的情況下,應該作何處理,默認值是latest,也就是從最新記錄讀取數據(消費者啟動之后生成的記錄),另一個值是earliest,意思是在偏移量無效的情況下,消費者從起始位置開始讀取數據。

1.3 session.timeout.ms

該屬性指定了當消費者被認為已經掛掉之前可以與服務器斷開連接的時間。默認是3s,消費者在3s之內沒有再次向服務器發送心跳,那么將會被認為已經死亡。此時,協調器將會出發再均衡,把它的分區分配給其他的消費者,該屬性與heartbeat.interval.ms緊密相關,該參數定義了消費者發送心跳的時間間隔,也就是心跳頻率,一般要同時修改這兩個參數,heartbeat.interval.ms參數值必須要小于session.timeout.ms,一般是session.timeout.ms的三分之一,比如,session.timeout.ms設置成3min,那么heartbeat.interval.ms一般設置成1min,這樣,可以更快的檢測以及恢復崩潰的節點,不過長時間的輪詢或垃圾收集可能導致非預期的再均衡(有一種情況就是網絡延遲,本身消費者是沒有掛掉的,但是網絡延遲造成了心跳超時,這樣本不該發生再均衡,但是因為網絡原因造成了非預期的再均衡),把該屬性的值設置得大一些,可以減少意外的再均衡,不過檢測節點崩憤-需要更長的時間。

1.4 max.partition.fetch.bytes

該屬性指定了服務器從每個分區里返回給消費者的最大字節數。它的默認值是lMB , 也
就是說,kafkaConsumer.poll() 方法從每個分區里返回的記錄最多不超max.partitions.fetch.bytes 指定的字節。如果一個主題有20 個分區和5 個消費者,那么每個消費者需要至少4MB 的可用內存來接收記錄。在為消費者分配內存時,可以給它們多分配一些,因為如果群組里有消費者發生奔潰,剩下的消費者需要處理更多的分區。max.partition.fetch.bytes 的值必須比broker 能夠接收的最大消息的字節數(通過max.message.size 屬性配置)大, 否則消費者可能無法讀取這些消息,導致消費者一直掛起重試,例如,max.message.size設置為2MB,而該屬性設置為1MB,那么當一個生產者可能就會生產一條大小為2MB的消息,那么就會出現問題,消費者能從分區取回的最大消息大小就只有1MB,但是數據量是2MB,所以就會導致消費者一直掛起重試。
在設置該屬性時,另一個需要考慮的因素是消費者處理數據的時間。消費者需要頻繁調用poll()方法
來避免會話過期和發生分區再均衡,如果單次調用poll()返回的數據太多,消費者需要更多的時間來處理,可能無怯及時進行下一個輪詢來避免會話過期。如果出現這種情況, 可以把max.partitioin.fetch.bytes 值改小,或者延長會話過期時間。

1.5 fetch.min.bytes

消費者從服務器獲取記錄的最小字節數,broker收到消費者拉取數據的請求的時候,如果可用數據量小于設置的值,那么broker將會等待有足夠可用的數據的時候才返回給消費者,這樣可以降低消費者和broker的工作負載。
因為當主題不是很活躍的情況下,就不需要來來回回的處理消息,如果沒有很多可用數據,但消費者的CPU 使用率卻很高,那么就需要把該屬性的值設得比默認值大。如果消費者的數量比較多,把該屬性的值設置得大一點可以降低broker 的工作負載。

1.6 fetch.max.wait.ms

fetch.min.bytes設置了broker返回給消費者最小的數據量,而fetch.max.wait.ms設置的則是broker的等待時間,兩個屬性只要滿足了任何一條,broker都會將數據返回給消費者,也就是說舉個例子,fetch.min.bytes設置成1MB,fetch.max.wait.ms設置成1000ms,那么如果在1000ms時間內,如果數據量達到了1MB,broker將會把數據返回給消費者;如果已經過了1000ms,但是數據量還沒有達到1MB,那么broker仍然會把當前積累的所有數據返回給消費者。

1.7 max.poll.records

控制單次調用call方法能夠返回的記錄數量,幫助控制在輪詢里需要處理的數據量。

1.8 receive.buffer.bytes + send.buffer.bytes

socket 在讀寫數據時用到的TCP 緩沖區也可以設置大小。如果它們被設為-1 ,就使用操作系統的默認值。如果生產者或消費者與broker 處于不同的數據中心內,可以適當增大這些值,因為跨數據中心的網絡一般都有比較高的延遲和比較低的帶寬。

1.9 partition.assignment.strategy

分區分配策略,kafka有兩個默認策略:

  • Range:該策略會把主題的若干個連續的分區分配給消費者
  • Robin:該策略把主題的所有分區逐個分配給消費者

分區策略默認是:org.apache.kafka.clients.consumer.RangeAssignor=>Range策略
org.apache.kafka.clients.consumer.RoundRobinAssignor=>Robin策略

1.10 client.id

Consumer進程的標識。如果設置一個人為可讀的值,跟蹤問題會比較方便。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容