Flume 在有贊大資料的實踐

有贊技術發表於2019-04-02

一、前言

Flume 在有贊大資料的實踐
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 原則只是說的是 SourceChannelSink 三者之間上下投遞訊息的保證。而當你選擇 MemoryChannel 時,例項如果異常掛了再重啟,在 channel 中的未被 sink 所消費的殘留資料也就丟失了,從而沒辦法保證整條鏈路的 At-least-once。

Flume 的 At-least-once 保證的實現基礎是建立了自身的 Transaction 機制。Flume 的 Transaction 有4個生命週期函式,分別是 startcommitrollbackclose。 當 SourceChannel 批量投遞事件時首先呼叫 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 進行提交,不然就需要接受重複的訊息。

於是我們第一個版本上線了,看上去很有保障了,即使程式異常掛了重啟也不會丟資料。

Flume 在有贊大資料的實踐

可能有同學想到一個關鍵性的問題:如果某一天磁碟壞了而程式異常退出,而 FileChannel 剛好又有未被消費的事件資料,這個時候不就丟資料了嗎?雖然磁碟壞了是一個極低的概率,但這確實是一個需要考慮的問題。

在 Flume 現有元件中比 FlieChannel 更可靠的,可能想到的是 KafkaChannel ,kafka 可以對訊息保留多個副本,從而增強了資料的可靠性。但是我們第二版本的方案沒有選擇它,而是直接擴充套件出 NsqChannel 。於是第二個版本就有了。

Flume 在有贊大資料的實踐
初次使用 Flume 的使用者往往陷入到必須搭配 Source + Channel + Sink 三個元件的固有模式,事實上我們不一定要三個元件都使用上。另外直接 NsqChannelHDFSEventSink 的有幾個好處:

  • 每個訊息的傳遞只需要一次事務,而非兩次,效能上更佳。
  • 避免引入了新的 kafka 服務,減少了資源成本的同時保持架構上更簡單從而更穩定。

四、定製化擴充套件

Flume 在各個元件的擴充套件性支援具有非常好的設計考慮。

當無法滿足我們的自定義需求,我們可以選擇合適的元件上進行擴充套件。下面就講講我們擴充套件的一些內容。

  • NsqSource

在 Flume 定製化一個 Source 比較簡單,繼承一個已有通用實現的抽象類,實現相對幾個生命週期方法即可。這裡說明注意的是 Flume 元件的生命週期在可能被會呼叫多次,比如 Flume 具有自動發現例項配置發生變化並 restart 各個元件,這種情況需要考慮資源的正確釋放。

  • HdfsEventSink 擴充套件配置

它本身就具有 role file 功能,比如當 Sink 是按小時生成檔案,有這一個小時的第一個事件建立新的檔案,然後經過固定的 role 配置時間(比如一小時)關閉檔案。這裡存在的問題就是如果源平時的資料量不大,比如8點這個小時的第一個事件是在8點25分來臨,那就是說需要9點25才能關閉這個檔案。由於沒有關閉的tmp檔案會被離線資料任務的計算引擎所忽略,在小時級的資料離線任務就沒辦法得到實時的資料。而我們做的改造就是 roll file 基於整點時間,而不是第一次事件的時間,比如固定的05分關閉上一次小時的檔案,而離線任務排程時間設定在每小時的05分之後就能解決這個問題。最終的效果給下圖:

Flume 在有贊大資料的實踐

  • 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 例項進行效能調優最常見的配置是事務 batchChannel Capacity

  • 事務 batch 指的是合理設定 batch 配置,可以明顯的改善例項的吞吐量。上面已經講到 SourceChannel 進行 put 或者 SinkChannel 進行 take 都是通過開啟事務來操作,所以調大兩個元件的 batch 配置可以降低 cpu 消耗,減少網路 IO 等待等。
  • Channel 的 capacity 大小直接影響著 source 和 sink 兩端的事件生產和消費。capacity 越大,吞吐量越好,但是其他因素制約著不能設定的很大。比如 MemoryChannel ,直接表現著對記憶體的消耗,以及程式異常退出所丟失的事件數量。不同的 Channel 需要不同的考慮,最終 trade-off 是難免的。

六、總結和展望

Flume 是一個非常穩定的服務,這一點在我們生產環境中得到充分驗證。 同時它的模型設計也非常清晰易懂,每一種元件型別都有很多現成的實現,同時特考慮到各個擴充套件點,所以我們很容易找到或者定製化我們所需要的資料管道的解決方案。

隨著使用者越來越多,需要有一個統一的平臺來集中管理所有的 Flume 例項。 有以下幾點好處:

  • 降低使用者對 Flume 的成本。儘可能在不太瞭解 Flume 的情況下就可以享受到它帶來的價值。
  • 有效對機器的資源進行合理協調使用。
  • 完善的監控讓 FLume 執行的更加穩定可靠。

當然這一步我們也才剛啟動,希望它未來的價值變得越來越大。

相關文章