Kafka技術專題之「性能調優篇」消息隊列服務端出現內存溢出OOM以及相關性能調優實戰分析

內存問題

本篇文章介紹Kafka處理大文件出現內存溢出 java.lang.OutOfMemoryError: Direct buffer memory,主要內容包括基礎應用、實用技巧、原理機制等方面,希望對大家有所幫助。

bin目錄下的kafka-run-class.sh中須要配置的參數

kafka是由scala和java編寫的。因此須要調一些jvm的參數。java的內存分爲堆內內存和堆外內存。

JVM參數系列
  • -Xms2048m, -Xmx2048m,設置的是堆內內存。

  • -Xms是初始可用的最大堆內內存。-Xmx設置的是最大可用的堆內內存。兩者設置成同樣是由於效率問題,可讓jvm少作一些運算。若是這兩個參數設置的過小,kafka會出現java.lang.OutOfMemoryError: Java heap space的錯誤。

  • -XX:MaxDirectMemorySize=8192m。這個參數配置的過小,kafka會出現java.lang.OutOfMemoryError: Direct buffer memory的錯誤。 由於kafka的網絡IO使用了java的nio中的DirectMemory的方式,而這個申請的是堆外內存。

producer端

Kafka設計的初衷是迅速處理短小的消息,一般10K大小的消息吞吐性能最好。但有時候,我們需要處理更大的消息,比如XML文檔或JSON內容,一個消息差不多有10-100M,這種情況下,Kakfa應該如何處理?

配置建議:

  1. (傳輸資源文件的位置-再消費的時候進行獲取真正的資源)首選的方法是不直接傳送這些大的數據。如果有共享存儲,如NAS, HDFS, S3等,可以把這些大的文件存放到共享存儲,然後使用Kafka來傳送文件的位置信息。
  2. (傳輸資源文件的進行數據流的拆分,分批次發送)將大的消息數據切片或切塊,在生產端將數據切片爲10K大小,使用分區主鍵確保一個大消息的所有部分會被髮送到同一個kafka分區(這樣每一部分的拆分順序得以保留),如此以來,當消費端使用時會將這些部分重新還原爲原始的消息。
  3. (傳輸資源文件的進行數據流壓縮,降低傳輸量)Kafka的生產端可以壓縮消息,如果原始消息是XML,當通過壓縮之後,消息可能會變得不那麼大。在生產端的配置參數中使用compression.codec和commpressed.topics可以開啓壓縮功能,壓縮算法可以使用GZip或Snappy。

不過如果上述方法都不是你需要的,而你最終還是希望傳送大的消息,那麼,則可以在kafka中設置下面一些參數:

broker端

配置文件須要配置的參數
  • message.max.bytes (默認:1000000) : kafka會接收單個消息size的最大限制, 默認爲1M左右。若是producer發送比這個大的消息,kafka默認會丟掉。producer能夠從callback函數中得到錯誤碼:10。設置了之後控制了broker能接收消息的最大字節數,這個值應該比消費端的fetch.message.max.bytes更小纔對,否則broker就會因爲消費端無法使用這個消息而掛起。
  • log.segment.bytes(默認: 1GB) : kafka數據文件的大小。默認爲1G, 須要確保此值大於一個消息的最大大小。一般說來使用默認值即可(一般一個消息很難大於1G,因爲這是一個消息系統,而不是文件系統)。
  • replica.fetch.max.bytes (默認: 1MB) : broker可複製的消息的最大字節數, 默認爲1M。這個值比message.max.bytes大,不然broker會接收此消息,但沒法將此消息複製出去,從而形成數據丟失。

注意:message.max.bytes,要設置大於發送最大數據的大小,不然會produce失敗

consumer端

broker爲什麼會返回總量爲1000大小的數據呢?

librdkafka有這樣一個參數:fetch.max.bytes, 它有這樣的描述:

Maximum amount of data the broker shall return for a Fetch request. Messages are fetched in batches by the consumer and if the first message batch in the first non-empty partition of the Fetch request is larger than this value, then the message batch will still be returned to ensure the consumer can make progress. The maximum message batch size accepted by the broker is defined via message.max.bytes (broker config) or max.message.bytes (broker topic config). fetch.max.bytes is automatically adjusted upwards to be at least message.max.bytes (consumer config).

配置的參數

receive.message.max.bytes(默認 1MB) : kafka協議response的最大長度,也是消費者能讀取的最大消息。這個值應該大於或等於message.max.bytes,不然消費會失敗。

  • 較低版本的librdkafka的receive.message.max.bytes只支持1000到1000000000。
  • 最新版本的能夠支持到2147483647。

否則會出現 “Receive failed: Invalid message size 1047207987 (0..1000000000): increase receive.message.max.bytes”這樣的錯誤。

  • 使用此參數的時候還須要注意一個問題,在broker端設置的message.max.bytes爲1000,consumer端設置的receive.message.max.bytes也爲1000,可是除了數據,response還有協議相關字段,這時候整個response的大小就會超過1000。

注意:一定要選擇kafka來傳送大的消息,還有些事項需要考慮。要傳送大的消息,不是當出現問題之後再來考慮如何解決,而是在一開始設計的時候,就要考慮到大消息對集羣和主題的影響。

性能影響
  • 根據前面提到的性能測試,kafka在消息爲10K時吞吐量達到最大,更大的消息會降低吞吐量,在設計集羣的容量時,尤其要考慮這點
可用的內存和分區數造成OOM的場景
  • Brokers會爲每個分區分配replica.fetch.max.bytes參數指定的內存空間,假設replica.fetch.max.bytes=1M,且有1000個分區,則需要差不多1G的內存,確保分區數 * 最大的消息不會超過服務器的內存,否則會報OOM錯誤。

  • 消費端的fetch.message.max.bytes指定了最大消息需要的內存空間,同樣,分區數 * 最大需要內存空間不能超過服務器的內存。所以,如果你有大的消息要傳送,則在內存一定的情況下,只能使用較少的分區數或者使用更大內存的服務器。

垃圾回收

到現在爲止,我在kafka的使用中還沒發現過此問題,但這應該是一個需要考慮的潛在問題。更大的消息會讓GC的時間更長(因爲broker需要分配更大的塊),隨時關注GC的日誌和服務器的日誌信息。如果長時間的GC導致kafka丟失了zookeeper的會話,則需要配置zookeeper.session.timeout.ms參數爲更大的超時時間。

推薦使用1.7出來的G1垃圾回收機制代替CMS。
  • G1>CMS的優勢

    • G1在壓縮空間方面有優勢
    • G1通過將內存空間分成區域(Region)的方式避免內存碎片問題
    • Eden, Survivor, Old區不再固定、在內存使用效率上來說更靈活
    • G1可以通過設置預期停頓時間(Pause Time)來控制垃圾收集時間避免應用雪崩現象
    • G1在回收內存後會馬上同時做合併空閒內存的工作、而CMS默認是在STW(stop the world)的時候做
    • G1會在Young GC中使用、而CMS只能在O區使用
  • G1適合的場景:

    • 服務端多核CPU、JVM內存佔用較大的應用(至少大於4G)
    • 應用在運行過程中會產生大量內存碎片、需要經常壓縮空間
    • 想要更可控、可預期的GC停頓週期;防止高併發下應用雪崩現象
    • 我們的kafka的kafka-run-class.sh 中已經包含了
KAFKA_JVM_PERFORMANCE_OPTS="-server -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+DisableExplicitGC -Djava.awt.headless=true"

所以只需要修改kafka-server-start.sh。這裏面將內存設置爲4G,因爲當前kafka的堆內存使用了800多M,1個G的內存不夠用。但是分配太多,也沒什麼用,還容易影響到pagecache,降低效率:

export KAFKA_HEAP_OPTS="-Xms4g -Xmx4g"

內存方面須要考慮的問題

Brokers allocate a buffer the size of replica.fetch.max.bytes for each partition they replicate. If replica.fetch.max.bytes is set to 1 MiB, and you have 1000 partitions, about 1 GiB of RAM is required. Ensure that the number of partitions multiplied by the size of the largest message does not exceed available memory.

The same consideration applies for the consumer fetch.message.max.bytes setting. Ensure that you have enough memory for the largest message for each partition the consumer replicates. With larger messages, you might need to use fewer partitions or provide more RAM.

若是一個消息須要的處理時間很長,broker會認爲consumer已經掛了,把partition分配給其餘的consumer,而後循環往復, 這條record永遠無法消費。方法是增長max.poll.interval.ms 參數。

提高性能調優能力

Don't fear the filesystem!中提到kafka使用page cache進行文件存儲。計算機的內存分爲虛擬內存和物理內存。物理內存是真實的內存,虛擬內存是用磁盤來代替內存。並通過swap機制實現磁盤到物理內存的加載和替換,這裏面用到的磁盤我們稱爲swap磁盤。

pageCache機制

在寫文件的時候,Linux首先將數據寫入沒有被使用的內存中,這些內存被叫做內存頁(page cache)。然後讀的時候,Linux會優先從page cache中查找,如果找不到就會從硬盤中查找。

當物理內存使用達到一定的比例後,Linux就會使用進行swap,使用磁盤作爲虛擬內存。通過cat /proc/sys/vm/swappiness可以看到swap參數。這個參數表示虛擬內存中swap磁盤佔了多少百分比。0表示最大限度的使用內存,100表示儘量使用swap磁盤。

系統默認的參數是60,當物理內存使用率達到40%,就會頻繁進行swap,影響系統性能,推薦將vm.swappiness 設置爲較低的值1。最終我設置爲10,因爲我們的機器的內存還是比較小的,只有40G,設置的太小,可能會影響到虛擬內存的使用吧。

髒文件

當大量的持續不斷的數據寫入cache內存中後,這些數據就被稱爲髒數據。需要儘快將這些髒數據flush到磁盤中,釋放內存。

這裏需要關注兩個參數:
  • vm.dirty_background_ratio:這個參數指定了當文件系統緩存髒頁數量達到系統內存百分之多少時(如5%)就會觸發pdflush/flush/kdmflush等後臺回寫進程運行,將一定緩存的髒頁異步地刷入外存;

  • vm.dirty_ratio:這個參數則指定了當文件系統緩存髒頁數量達到系統內存百分之多少時(如10%),系統不得不開始處理緩存髒頁(因爲此時髒頁數量已經比較多,爲了避免數據丟失需要將一定髒頁刷入外存);在此過程中很多應用進程可能會因爲系統轉而處理文件IO而阻塞。

這裏推薦將vm.dirty_background_ratio設置爲5, vm.dirty_ratio有的人設置爲10,但是我覺得太小了,還是默認的就可以了。

網絡

kafka集羣對網絡的要求比較高,可以將socket的緩衝設置爲原來的兩倍。

  • net.core.wmem_default 設置爲128K
  • net.core.rmem_default 設置爲128K

學習資料

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