高吞吐量Flume Agent調優小結

前言

所有電商企業在一年一度的雙11都要迎來大促與大考,我司也不例外(所以最近真是前所未有的忙亂)。前段時間在配合執行全鏈路壓測的過程中,發現平時不太關注的Flume配置可能存在瓶頸。Flume在筆者負責的實時計算平臺裏用於收集所有後端訪問日誌和埋點日誌,其效率和穩定性比較重要。除了及時擴容之外,也有必要對Flume進行調優。今天在百忙之中擠出一點時間來寫寫。

Flume系統以一個或多個Flume-NG Agent的形式部署,一個Agent對應一個JVM進程,並且由三個部分組成:Source、Channel和Sink,示意圖如下。

Source

Flume有3種能夠監聽文件的Source,分別是Exec Source(配合tail -f命令)、Spooling Directory Source和Taildir Source。Taildir Source顯然是最好用的,在我們的實踐中,需要注意的參數列舉如下。

  • filegroups
    如果需要監聽的日誌文件較多,應該將它們分散在不同的目錄下,並配置多個filegroup來並行讀取。注意日誌文件的正則表達式要寫好,防止日誌滾動重命名時仍然符合正則表達式造成重複。示例:
a1.sources.r1.type = TAILDIR
a1.sources.r1.filegroups = f1 f2
a1.sources.r1.filegroups.f1 = /data/logs/ng1/access.log
a1.sources.r1.headers.f1.headerKey1 = ng1
a1.sources.r1.filegroups.f2 = /data/logs/ng2/.*log
a1.sources.r1.headers.f2.headerKey1 = ng2
  • batchSize
    該參數控制向Channel發送數據的批次大小,默認值爲100。如果日誌流量很大,適當增加此值可以增大Source的吞吐量,但是不能超過Channel的capacity和transactionCapacity的限制(後文再說)。示例:
a1.sources.r1.batchSize = 1000
  • maxBatchCount
    該參數控制從同一個文件中連續讀取的最大批次數量,默認不限制。如果Flume同時監聽多個文件,並且其中某個文件的寫入速度遠快於其他文件,那麼其他文件有可能幾乎無法被讀取,所以強烈建議設定此參數。示例:
a1.sources.r1.maxBatchCount = 100
  • writePosInterval
    該參數控制向記錄讀取位置的JSON文件(由positionFile參數指定)寫入inode和偏移量的頻率,默認爲3000ms。當Agent重新啓動時,會從JSON文件中獲取最近記錄的偏移量開始讀取。也就是說,適當降低writePosInterval可以減少Agent重啓導致的重複讀取的數據量。
a1.sources.r1.writePosInterval = 1000

Channel

Flume內置了多種Channel的實現,比較常用的有Memory Channel、File Channel、JDBC Channel、Kafka Channel等。我們的選擇主要針對Memory Channel和File Channel兩種,對比一下:

  • Memory Channel將staging事件數據存儲在Agent堆內存中,File Channel則將它們存儲在指定的文件中;
  • 如果Agent失敗,Memory Channel會丟失所有緩存的staging事件,File Channel則可以通過額外記錄的checkpoint信息恢復,保證斷點續傳;
  • Memory Channel能夠容納的數據量受堆內存的影響,而File Channel不受此限制。

鑑於我們下游業務的主要痛點在吞吐量與實時性,且可以容忍數據少量丟失,日誌服務器的磁盤壓力也已經比較大了,故Memory Channel更加合適。需要注意的參數如下。

  • capacity、transactionCapacity
    這兩個參數分別代表Channel最多能容納的事件數目,以及每個事務(即Source的一次put或者Sink的一次take)能夠包含的最多事件數目。顯然,必須滿足batchSize <= transactionCapacity <= capacity的關係。適當調大capacity和transactionCapacity可以使得Channel的吞吐量增高,且能夠保證不會出現The channel is full or unexpected failure的異常。示例:
a1.channels.c1.type = memory
a1.channels.c1.transactionCapacity = 5000
a1.channels.c1.capacity = 10000
  • byteCapacity
    該參數代表Memory Channel中緩存的事件消息的最大總大小,以字節爲單位,默認是Flume Agent最大堆內存的80%。此值不建議更改爲固定的,而是建議通過改變Agent的JVM參數來影響,後面再提。

  • byteCapacityBufferPercentage
    Memory Channel中緩存的事件消息頭佔byteCapacity的比例,默認是20%。如果事件的header信息很少,可以適當減小(我們沒有更改)。

  • keep-alive
    向Channel中put或take一條事件的超時時間,默認爲3秒,對於Memory Channel一般不用更改。如果業務數據是由很多突發流量組成(也就是說Channel經常處於時滿時空的狀態),那麼建議適當調大。示例:

a1.channels.c1.keep-alive = 15

當然File Channel也很常用,其參數就不再贅述,看官可參考官方文檔。

Sink

我們實時數倉接入層的起點是Kafka,自然要利用Kafka Sink。需要注意的參數列舉如下。

  • kafka.flumeBatchSize
    從Channel取出數據併發送到Kafka的批次大小,與Source的batchSize同理。

  • kafka.producer.acks
    該參數的含義就留給看官去回想(很基礎的),一般設爲折衷的1即可。設爲-1的可靠性最高,但是相應地會影響吞吐量。

  • kafka.producer.linger.ms
    Kafka Producer檢查批次是否ready的超時時間,超時即發送(與producer.batch.size共同作用)。一般設爲數十到100毫秒,可以在時效性和吞吐量之間取得比較好的平衡。

  • kafka.producer.compression.type
    Producer消息壓縮算法,支持gzip/snappy/lz4,如果希望降低消息的體積可以配置。

示例:

a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.kafka.flumeBatchSize = 1000
a1.sinks.k1.kafka.producer.acks = 1
a1.sinks.k1.kafka.producer.linger.ms = 50
a1.sinks.k1.kafka.producer.compression.type = snappy

Kafka Sink也支持其他Producer參數,可以按需配置。

Interceptor

攔截器方面就比較簡單粗暴,在注重吞吐量的場合一定不要使用或者自定義規則複雜的攔截器(比如自帶的Regex Interceptor、Search and Replace Interceptor),最好是不使用任何攔截器,把數據清洗的任務交給下游去處理(Flink它不香嘛

Agent Process

在flume-env.sh中添加JVM參數,避免默認堆內存太小導致OOM。

export JAVA_OPTS="-Xms8192m -Xmx8192m -Xmn3072m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+HeapDumpOnOutOfMemoryError"

另外,Taildir Source會積極地使用堆外內存,如果發現Flume消耗的總內存量過大,可以適當限制直接內存的用量,如:-XX:MaxDirectMemorySize=4096m

Flume原生並沒有傳統意義上的“高可用”配置(Sink Group Failover不算)。爲了防止Agent進程因爲各種原因靜默地掛掉,需要用一個“保姆腳本”(nanny script)定期檢測Agent進程的狀態,並及時拉起來。當然也可以在下游採用兩級Collector的架構增強魯棒性,本文不表。Cloudera Community上有一個關於Flume HA的提問,參見這裏

The End

經過上述適當的調優過程,我們的單個Flume-NG Agent能夠輕鬆承受高達5W+ RPS的持續流量高峯,比較令人滿意了。

民那晚安晚安。

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