一、前言
Flume 是一個分散式的高可靠,可擴充套件的資料採集服務。Flume 在有讚的大資料業務中一直扮演著一個穩定可靠的日誌資料“搬運工” 的角色。本文主要講一下有贊大資料部門在 Flume 的應用實踐,同時也穿插著我們對 Flume 的一些理解。
二、Delivery 保證
認識 Flume 對事件投遞的可靠性保證是非常重要的,它往往是我們是否使用 Flume 來解決問題的決定因素之一。
訊息投遞的可靠保證有三種:
- At-least-once
- At-most-once
- Exactly-once
基本上所有工具的使用使用者都希望工具框架能保證訊息 Exactly-once ,這樣就不必在設計實現上考慮訊息的丟失或者重複的處理場景。但是事實上很少有工具和框架能做到這一點,真正能做到這一點所付出的成本往往很大,或者帶來的額外影響反而讓你覺得不值得。假設 Flume 真的做到了 Exactly-once ,那勢必降低了穩定性和吞吐量,所以 Flume 選擇的策略是 At-least-once 。
當然這裡的 At-least-once 需要加上引號,並不是說用上 Flume 的隨便哪個元件組成一個例項,執行過程中就能儲存訊息不會丟失。事實上 At-least-once 原則只是說的是 Source
、 Channel
和 Sink
三者之間上下投遞訊息的保證。而當你選擇 MemoryChannel
時,例項如果異常掛了再重啟,在 channel 中的未被 sink 所消費的殘留資料也就丟失了,從而沒辦法保證整條鏈路的 At-least-once。
Flume 的 At-least-once 保證的實現基礎是建立了自身的 Transaction
機制。Flume 的 Transaction
有4個生命週期函式,分別是 start
、 commit
、rollback
和 close
。
當 Source
往 Channel
批量投遞事件時首先呼叫 start
開啟事務,批量
put 完事件後通過 commit 來提交事務,如果 commit
異常則 rollback
,然後 close
事務,最後 Source
將剛才提交的一批訊息事件向源服務 ack(比如 kafka 提交新的 offset )。Sink
消費 Channel
也是相同的模式,唯一的區別就是 Sink 需要在向目標源完成寫入之後才對事務進行 commit
。兩個元件的相同做法都是隻有向下遊成功投遞了訊息才會向上遊 ack
,從而保證了資料能 At-least-once 向下投遞。
三、datay應用場景
基於 mysql binlog
的數倉增量同步(datay
業務)是大資料這邊使用 Flume 中一個比較經典的應用場景,datay 具體業務不詳細說明,需要強調的是它對Flume的一個要求是必須保證在 nsq
(訊息佇列)的 binlog
訊息能可靠的落地到 hdfs
,不允許一條訊息的丟失,需要絕對的 At-least-once。
Flume 模型本身是基於 At-least-once 原則來傳遞事件,所以需要需要考慮是在各種異常情況(比如程式異常掛了)下的 At-least-once 保證。顯然 MemoryChannel
無法滿足,所以我們用 FlieChannel
做代替。由於公司目前是使用 nsq 作為 binlog 的訊息中轉服務,故我們沒有辦法使用現有的 KafkaSource
,所以基於公司的 nsq sdk 擴充套件了 NsqSource
。這裡需要說明的是為了保證 At-least-once,Source
源必須支援訊息接收的 ack 機制,比如 kafka 客戶端只有認為消費了訊息後,才對 offset 進行提交,不然就需要接受重複的訊息。
於是我們第一個版本上線了,看上去很有保障了,即使程式異常掛了重啟也不會丟資料。
可能有同學想到一個關鍵性的問題:如果某一天磁碟壞了而程式異常退出,而 FileChannel
剛好又有未被消費的事件資料,這個時候不就丟資料了嗎?雖然磁碟壞了是一個極低的概率,但這確實是一個需要考慮的問題。
在 Flume 現有元件中比 FlieChannel
更可靠的,可能想到的是 KafkaChannel
,kafka 可以對訊息保留多個副本,從而增強了資料的可靠性。但是我們第二版本的方案沒有選擇它,而是直接擴充套件出 NsqChannel 。於是第二個版本就有了。
Source
+ Channel
+ Sink
三個元件的固有模式,事實上我們不一定要三個元件都使用上。另外直接 NsqChannel
到 HDFSEventSink
的有幾個好處:
- 每個訊息的傳遞只需要一次事務,而非兩次,效能上更佳。
- 避免引入了新的 kafka 服務,減少了資源成本的同時保持架構上更簡單從而更穩定。
四、定製化擴充套件
Flume 在各個元件的擴充套件性支援具有非常好的設計考慮。
當無法滿足我們的自定義需求,我們可以選擇合適的元件上進行擴充套件。下面就講講我們擴充套件的一些內容。
NsqSource
。
在 Flume 定製化一個 Source
比較簡單,繼承一個已有通用實現的抽象類,實現相對幾個生命週期方法即可。這裡說明注意的是 Flume 元件的生命週期在可能被會呼叫多次,比如 Flume 具有自動發現例項配置發生變化並 restart
各個元件,這種情況需要考慮資源的正確釋放。
HdfsEventSink
擴充套件配置。
它本身就具有 role file 功能,比如當 Sink 是按小時生成檔案,有這一個小時的第一個事件建立新的檔案,然後經過固定的 role 配置時間(比如一小時)關閉檔案。這裡存在的問題就是如果源平時的資料量不大,比如8點這個小時的第一個事件是在8點25分來臨,那就是說需要9點25才能關閉這個檔案。由於沒有關閉的tmp檔案會被離線資料任務的計算引擎所忽略,在小時級的資料離線任務就沒辦法得到實時的資料。而我們做的改造就是 roll file 基於整點時間,而不是第一次事件的時間,比如固定的05分關閉上一次小時的檔案,而離線任務排程時間設定在每小時的05分之後就能解決這個問題。最終的效果給下圖:
MetricsReportServer
。
當我們需要收集 Flume 例項執行時的各個元件 counter metric ,就需要開啟 MonitorService
服務。自定義了一個定期發生 http 請求彙報 metric 到一個集中的 web 服務。原生的 HTTPMetricsServer
也是基於 http 服務,區別在於它將 Flume 作為 http 服務端,而我們將很多例項部署在一臺機器上,埠分配成了比較頭疼的問題。
當我們收集到以下的 counter metric 時,就可以利用它來實現一些監控報警。
{
"identity":"olap_offline_daily_olap_druid_test_timezone_4@49",
"startTime":1544287799839,
"reportCount":4933,
"metrics":{
"SINK.olap_offline_daily_olap_druid_test_timezone_4_snk":{
"ConnectionCreatedCount":"9",
"ConnectionClosedCount":"8",
"Type":"SINK",
"BatchCompleteCount":"6335",
"BatchEmptyCount":"2",
"EventDrainAttemptCount":"686278",
"StartTime":"1544287799837",
"EventDrainSuccessCount":"686267",
"BatchUnderflowCount":"5269",
"StopTime":"0",
"ConnectionFailedCount":"48460"
},
"SOURCE.olap_offline_daily_olap_druid_test_timezone_4_src":{
"KafkaEventGetTimer":"26344146",
"AppendBatchAcceptedCount":"0",
"EventAcceptedCount":"686278",
"AppendReceivedCount":"0",
"StartTime":"1544287800219",
"AppendBatchReceivedCount":"0",
"KafkaCommitTimer":"14295",
"EventReceivedCount":"15882278",
"Type":"SOURCE",
"OpenConnectionCount":"0",
"AppendAcceptedCount":"0",
"KafkaEmptyCount":"0",
"StopTime":"0"
},
"CHANNEL.olap_offline_daily_olap_druid_test_timezone_4_cha":{
"ChannelCapacity":"10000",
"ChannelFillPercentage":"0.11",
"Type":"CHANNEL",
"ChannelSize":"11",
"EventTakeSuccessCount":"686267",
"StartTime":"1544287799332",
"EventTakeAttemptCount":"715780",
"EventPutAttemptCount":"15882278",
"EventPutSuccessCount":"686278",
"StopTime":"0"
}
}
}
複製程式碼
- 事件時間戳攔截。有一些 hdfs sink 業務對訊息事件的時間比較敏感,同一小時的資料必須放在同一個目錄裡,這就要求使用
HdfsEventSink
的時候不能使用系統時間來計算檔案目錄,而是應該基於訊息內容中的某個時間戳欄位。這個可以通過擴充套件Interceptor
來解決。Interceptor
用於在Source
投遞事件給Channel
前的一個攔截處理,一般都是用來對事件豐富header
資訊。強烈不建議在Source
中直接處理,實現一個Interceptor
可以滿足其他Source
類似需求的複用性。
五、效能調優
Flume 例項進行效能調優最常見的配置是事務 batch
和 Channel Capacity
。
- 事務 batch 指的是合理設定 batch 配置,可以明顯的改善例項的吞吐量。上面已經講到
Source
對Channel
進行 put 或者Sink
對Channel
進行 take 都是通過開啟事務來操作,所以調大兩個元件的 batch 配置可以降低 cpu 消耗,減少網路 IO 等待等。 Channel
的 capacity 大小直接影響著 source 和 sink 兩端的事件生產和消費。capacity 越大,吞吐量越好,但是其他因素制約著不能設定的很大。比如MemoryChannel
,直接表現著對記憶體的消耗,以及程式異常退出所丟失的事件數量。不同的Channel
需要不同的考慮,最終 trade-off 是難免的。
六、總結和展望
Flume 是一個非常穩定的服務,這一點在我們生產環境中得到充分驗證。 同時它的模型設計也非常清晰易懂,每一種元件型別都有很多現成的實現,同時特考慮到各個擴充套件點,所以我們很容易找到或者定製化我們所需要的資料管道的解決方案。
隨著使用者越來越多,需要有一個統一的平臺來集中管理所有的 Flume 例項。 有以下幾點好處:
- 降低使用者對 Flume 的成本。儘可能在不太瞭解 Flume 的情況下就可以享受到它帶來的價值。
- 有效對機器的資源進行合理協調使用。
- 完善的監控讓 FLume 執行的更加穩定可靠。
當然這一步我們也才剛啟動,希望它未來的價值變得越來越大。