Flume

安裝FLume

Flume的安裝非常簡單,其核心就是agent。
wget http://apache.fayea.com/flume/1.6.0/apache-flume-1.6.0-bin.tar.gz
tar zxvf apache-flume-1.6.0-bin.tar.gz
mv apache-flume-1.6.0-bin apache-flume-1.6.0
爲了運行方便,我們把bin目錄加到Path中:
vim /etc/profile
export FLUME_HOME=/opt/flume/apache-flume-1.6.0
export PATH=PATH: FLUME_HOME/bin
source /etc/profile
測試一下是否安裝成功:
flume-ng help
這裏寫圖片描述
入門例子
Flume的核心工作都是通過Flume Agent來完成的,Flume agent是一個長期運行的Java進程,其中運行着source和sink,source和sink之間通過channel連接。source作爲生產者,產生數據,輸送到channel,sink從則從channel中讀取數據,並保存到HDFS之類的數據目的地。Flume中自帶了非常豐富的各個組件實現。例如從目錄中拉取新文件數據的spool source,收集運行命令輸出結果的exec source。channel的實現由內存memory,文件file channel等。sink有簡單的logger輸出,HDFS sink,avro sink等。

數據在Flume中用事件event來表示,因此一個運行中的Flume agent就是一個event的流動系統,由source產生,傳送到channel,再傳送到sink用於存儲或者下一步處理。

多個Flume Agent可以相互連接構成一個拓撲圖,從而提供穩定的、高吞吐的數據收集系統。因此Flume系統中,核心是配置。使用Flume提供的豐富組件,構成滿足自己需求的系統。如果原生組件不足以滿足需求,完全可以擴展自己需要的組件,Flume提供了非常好的拓展點。

Agent的示意圖如下:

這裏寫圖片描述

假設我們現在想監控/tmp/spooldir目錄下的文件變動,一旦有新增文件後,讀取每一行,輸出到控制檯。我們首先配置一個名爲test-agent.properties的文件,內容如下:
agent1.sources = source1
agent1.sinks = sink1
agent1.channels = channel1

agent1.sources.source1.channels=channel1
agent1.sinks.sink1.channel = channel1

agent1.sources.source1.type=spooldir
agent1.sources.source1.spoolDir=/tmp/spooldir

agent1.sinks.sink1.type=logger

agent1.channels.channel1.type=file
配置文件的屬性名稱採用級聯的層次結構來設置各種屬性。我們採用Flume自帶的spooldir作爲source,使用具有持久化特性的file channel以及簡單的logger sink。

接下來先創建監控的目錄:
mkdir /tmp/spooldir
使用flume-ng啓動agent:
flume-ng agent –conf-file /opt/flume/conf/spool-to-logger.properties –name agent1 –conf $FLUME_HOME/conf -Dflume.root.logger=INFO,console
這裏寫圖片描述

日誌中主要是一些source sink的啓動信息。

接着我們在在一個終端中,往/tmp/spooldir中新增一個文件,爲了保存新增文件的原子操作,我們先創建一個隱藏文件:
echo “Hello Flume” > /tmp/spooldir/.file1.txt
然後使用原子性操作mv改變爲可見文件:
mv /tmp/spooldir/.file1.txt /tmp/spooldir/file1.txt
此時日誌中可以看到如下輸出:
這裏寫圖片描述
成功。一行會被當做一個事件,我們寫入一行,所以控制檯收到一個事件的日誌。多行文件的輸出類似:
這裏寫圖片描述
body的輸出中是UTF-8編碼格式。成功處理完之後,file channel中的文件名添加了COMPLETE的後綴,表示該文件已經處理過。

事務與可靠性

Flume使用獨立的事務在source-channel和channel-sink之間傳送事件。在上面的例子中,只有當事件被成功提交到file channel之後,原始文件纔會被標記爲完成(COMPLETE後綴)。類似的,從channel到logger輸出控制檯也封裝在事務中,如果某種原因導致無法輸出到控制檯,則事務回滾,事件數據依然保存在file channel中。

file channel雖然提供了持久化,但是其性能較差,吞吐量會受到一定的限制。相反,memory channel則犧牲可靠性換取吞吐量,如果內存中的事件因爲機器故障重啓而丟失,則這些事件無法恢復。

Flume在傳送事件時,保證至少一次到達(at-least-once),也就是說可能出現重複。例如,上述的spool source中,當重啓Agent之後,如果文件在上次還沒有被標識爲完成,可能提交了部分數據到channel,因此重啓後會重新處理這些未完成的文件。如果上次處理過程中,有些數據已經輸出到控制檯,但是事務還沒有提交(在輸出之後以提交之間發生故障),則這些事件會被重試,重現重複。

如果需要精確的一次到達,可以使用exactly onece,要達到精確一次到達,需要使用兩階段提交協議,這樣的協議開銷非常昂貴。因此Flume區別於傳統的消息中間件的一點在於其使用至少一次到達來達到高容量的併發。而傳統的消息中間件一般採用精確的一次到達。很多場合中,完全可以在數據的其他處理環節中對重複的數據進行去重,通常是採用MapReduce或者Hive作業。

批量處理

爲了效率,Flume在一次事務中會嘗試批量讀取事件。這對於file channel的性能提升尤爲明顯,因此一次在一次file channel的事務中,需要產生一次昂貴的fsync調用。例如,在上面提到的spool source中,可以使用batchSize屬性來配置一次讀取多少行。在Avro sink中,在調用RPC發送事件給Avro source之前,也會嘗試讀取100個事件,然後再批量發送。當然,如果沒有達到100個事件,不會阻塞。

HDFS Sink

Flume最初的出發點就是用於收集大量數據到Hadoop存儲中。下面是一個HDFS sink的配置例如:

agent1.sources = source1
agent1.sinks = sink1
agent1.channels = channel1

agent1.sources.source1.channels = channel1
agent1.sinks.sink1.channel = channel1

agent1.sources.source1.type = spooldir
agent1.sources.source1.spoolDir = /tmp/spooldir

agent1.sinks.sink1.type = hdfs
agent1.sinks.sink1.hdfs.path = /tmp/flume
agent1.sinks.sink1.hdfs.filePrefix = events
agent1.sinks.sink1.hdfs.fileSuffix = .log
agent1.sinks.sink1.hdfs.inUsePrefix = _
agent1.sinks.sink1.hdfs.fileType = DataStream

agent1.channels.channel1.type = file
HDFS sink的sink 類型爲hdfs,配置項包括文件路徑,文件前綴後綴,文件格式等。

正在處理但是還未完成的文件會有一個.tmp後綴,正在處理的前綴使用inUsePredix屬性設置,我們這裏設置爲下劃線,在MapReduce作業中,會自動忽略下劃線開頭的文件。一個典型的臨時文件(未完成)名稱如_events.1386734789.log.tmp,其中的數字是HDFS Sink生成的時間戳。

文件會一直處於打開狀態,直到下面的任意條件滿足:

時間達到30秒(通過hdfs.rollInterval屬性配置) 達到hdfs.rollSize配置的大小(默認爲1024字節) 事件數量達到hdfs.rollCount配置的數量(默認爲10)
當上述任意條件滿足之後,文件被關閉,前綴後綴被移除。新到來的時間寫入新的文件,然後繼續上述過程。

HDFS sink使用運行agent的用戶名寫入HDFS,可以通過hdfs.proxyUser配置。

分區與攔截器

大量的數據集經常會進行分區,使用時間來分區是很常見的一種形式,例如每天一個分區,然後MapReduce作業定期處理分區。通過設置HDFS Sink的Path,很容易對數據進行分區:

agent1.sinks.sink1.hdfs.path = /tmp/flume/year=%Y/month=%m/day=%d
上述配置按照天對數據進行分區。如果使用Hive,一樣可以映射到Hive中的分區和Buckets。

事件被寫入哪一個分區,根據事件頭部中的timestamp來判斷。默認情況下,事件頭部中沒有時間,但是可以配置一個時間攔截器來添加。Flume的攔截器(Interceptor)機制用於修改或者刪除事件數據,攔截器被綁定到source中,並在事件到達channel之前運行。下面的配置爲source1添加了一個攔截器:
agent1.sources.source1.interceptors=interceptor1
agent1.sources.source1.interceptors.interceptor1.type=timestamp
這個時間戳是在agent機器上產生事件的時間戳,如果agent運行在大規模的agent拓撲中,數據HDFS的時間可能與時間產生的時間有較大差別。HDFS Sink提供了一個屬性用於配置使用數據到達HDFS的時間作爲時間戳,這個屬性是hdfs.userLocalTimeStamp.

文件格式

通常情況下,使用二進制存儲數據所需要的空間會比存儲爲文本所需空間小。HDFS sink提供了hdfs.fileType用於控制文件類型。
這個屬性默認爲SequenceFile,這種文件格式使用時間的時間戳作爲LongWritable Key,然後沒有時間戳頭部,則使用當前時間。事件的body作爲BytesWritable寫入到value。如果value要保存爲Text,可以將hdfs.writeFormar設置爲Text。

如果要以Avro的文件格式寫入數據,hdfs.fileType設置爲DateStream。另外需要設置一個值爲avro_event的serializer屬性(沒有hdfs.前綴)。serializer.compressionCodec屬性用於設置壓縮。

agent1.sinks.sink1.type=hdfs
agent1.sinks.sink1.hdfs.path=/tmp/flume
agent1.sinks.sink1.hdfs.filePrefix=events
agent1.sinks.sink1.hdfs.fileSuffix=.avro
agent1.sinks.sink1.hdfs.fileType=DataStream
agent1.sinks.sink1.hdfs.serializer=avro_event
agent1.sinks.sink1.hdfs.serializer.compressionCodec=snappy

一個事件將被轉化爲一條Avro記錄,這個記錄中有2個filed:headers和body。headers是一個字符串Avro Map,body則爲Avro Bytes。avro記錄的schema也可以自定義。

如果要將內存中的Avro 對象發送到Flume,可以選擇使用Log4jAppender,這個Appender允許我們將Avro的通用對象或者特定對象(generic,specifix)寫入到日誌中,然後發送到Avro Source(充當Avro RPC Server)。此時serializer的值要設爲:
org.apache.flume.sink.hdfs.AvroEventSerializer$Builder
Avro schema在header中設置。如果要將其他對象轉化爲Avro對象(進而發送給Avro Source),可以通過實現AbstractAvroEventSerializer來完成。

Fan Out

Fan out(扇出)是指一個source的數據被傳送給多個channel。事件發生時,同時發送給多個channel,進而到達多個sink(一個sink只能對應一個channel)。例如我們想把數據傳到HDFS進行存檔,同時想把數據放到Solr或者ElasticSearch進行搜索,此時可以給source配置兩個channel:

agent.sources.source1.channels=filechannel solrchannel
下圖是同時傳送到file channel和memory的示意圖:

加載中…

當一個source配置有多個channel時,Flume針對每個channel使用單獨的事務進行數據傳送。在上面的例子中,Flume將使用一個事務把數據從spool目錄傳送到file channel。另一個memory channel則使用另一個事務。如果兩個事務都失敗(例如因爲空間不足),則事件不會從source中移除,而是繼續保留在source,稍後再重試。<喎�”/kf/ware/vc/” target=”_blank” class=”keylink”>vcD4NCjxwPjxjb2RlPsjnufvO0sPHttTEs7j2Y2hhbm5lbLXEyv2+3c3q1fvQ1LHIvc+yu9TauvWjrMD9yOfJz8r2tcRsb2dnZXIgc2lua6OsztLDx7K7zKu52NDExuTK/b7dv8m/v9DUo6zS8rTLztLDx7/J0tSw0dXiuPZjaGFubmVsxeTWw86qv8nRobXEo7o8L2NvZGU+PC9wPg0KPHByZSBjbGFzcz0=”brush:java;”> agent1.sources.source1.selector.optional = memorychannel

此時,如果memory channel對應的事務失敗了,則事件不會被留在source被在稍後重試,而是忽略掉optional channel的事務失敗,把事件從source中移除。

在正常的fan-out模式中,事件被複制到所有的channel,但是Flume還提供了更靈活的選擇。所以我們可以把某些事件發送到channela,把另一個類型的事件發送到channelb。要達到這種多路複用的效果,可以使用multiplexing selector設置source,使得我們可以精確定義事件到channel的路由或者映射關係。

分佈式Agent拓撲

當我們有多個數據源時,在把數據送到類似HDFS的存儲之前,我們可能想先做一次聚合或者預處理。例如,如果數據要存到HDFS中供MapReduce作業處理,如果數據以無數的小文件存在,對MapReduce的性能是極不利的。因此我們可以在數據原始來源處部署Agent,然後多個來源的數據先做一個去重和聚合,組成比較大的文件,然後再彙總到HDFS。此時Agent之間是分層的:

這裏寫圖片描述

上圖中,tier2對tier1的數據進行聚合,他們之間的連接通過tier1一個特殊的sink和tier2一個特殊的source完成。tier1使用Avro sink通過RPC將事件遠程發送給位於tier2的Avro source。此時tier1的sink充當的是RPC的客戶端調用,tier2Avro source則充當RPC Server。
注意這裏的Avro sink和source並沒有讀寫Avro文件的能力,它們之間只是使用AVRO RPC作爲通信協議,傳輸事件。如果需要使用sink把數據寫入到Avro文件中,則需要使用類似HDFS的sink來完成。

第一層的agent配置如下:
agent1.sources = source1
agent1.sinks = sink1
agent1.channels = channel1
agent1.sources.source1.channels = channel1
agent1.sinks.sink1.channel = channel1
agent1.sources.source1.type = spooldir
agent1.sources.source1.spoolDir = /tmp/spooldir
agent1.sinks.sink1.type = avro
agent1.sinks.sink1.hostname = localhost
agent1.sinks.sink1.port = 10000
agent1.channels.channel1.type = file
agent1.channels.channel1.checkpointDir=/tmp/agent1/file- channel/checkpoint
agent1.channels.channel1.dataDirs=/tmp/agent1/file- channel/data

第二層agent配置如下:
agent2.sources = source2
agent2.sinks = sink2
agent2.channels = channel2
agent2.sources.source2.channels = channel2
agent2.sinks.sink2.channel = channel2
agent2.sources.source2.type = avro
agent2.sources.source2.bind = localhost
agent2.sources.source2.port = 10000
agent2.sinks.sink2.type = hdfs
agent2.sinks.sink2.hdfs.path = /tmp/flume
agent2.sinks.sink2.hdfs.filePrefix = events
agent2.sinks.sink2.hdfs.fileSuffix = .log
agent2.sinks.sink2.hdfs.fileType = DataStream
agent2.channels.channel2.type = file
agent2.channels.channel2.checkpointDir=/tmp/agent2/file- channel/checkpoint
agent2.channels.channel2.dataDirs=/tmp/agent2/file- channel/data

最後的結構圖如下:

這裏寫圖片描述

在Flume Agent中,分別使用不同的事務在source-channel和channel-sink之間傳遞事件,保證事件的可靠傳遞。在Avro Source-Sink的情況下,Flume也使用事務能確保事件可靠地從Avro sink傳輸到Avto source並且寫入channel。

在agent1中,Avro Sink從channel中讀取事件(批次),並且發送給agent1的source,這一整個邏輯被包含在一個事務中,只有當sink收到來自agent2 Avro Source的確認消息之後,纔會提交事務。消息確認採用異步機制。
在agent2中,Avro source讀取來自agent1的事件,並寫入到channel的邏輯也封裝在一個事務中,只有當數據確保寫入channel之後,纔會向agent1發送確認消息。從而確保事件從一個agent的channel可靠傳送到另一個agent的channel。

在這樣的架構中,agent1和agent2只要其中一個出現故障,數據就無法被傳送到HDFS。爲了解決這個問題,可以使用下一節中的Sink group來達到故障轉移。

Sink Group

Sink group在邏輯上封裝一組sink,使用時(與Channel配合時)當做一個sink使用。利用sink group,可以達到負載均衡、故障轉移等目的。例如,下圖中的兩個tier2 agent被封裝成成一個sink group,當其中一個不可用時,數據被髮往另一個,避免全部中斷。

這裏寫圖片描述

看一個sink group的配置:
agent1.sources = source1
agent1.sinks =sink1a sink1b

group

agent1.sinkgroups= sinkgroup1
agent1.channels = channel1

link

agent1.sources.source1.channels= channel1
agent1.sinks.sink1a.channel=channel1
agent1.sinks.sink1b.channel=channel1

sink group

agent1.sinkgroups.sinkgroup1.sinks=sink1a sink1b
agent1.sinkgroups.sinkgroup1.processor.type=load_balance
agent1.sinkgroups.sinkgroup1.processor.backoff=true

source1

agent1.sources.source1.type=spooldir
agent1.sources.source1.spoolDir=/tmp/spooldir

sink1a

agent1.sinks.sink1a.type=avro
agent1.sinks.sink1a.hostname=slave1
agent1.sinks.sink1a.port=10000

sink1b

agent1.sinks.sink1b.type=avro
agent1.sinks.sink1b.hostname=slave1
agent1.sinks.sink1b.port=10001

channel1

agent1.channels.channel1.type=file

agent1.channels.chennel1.checkpointDir=/opt/flume/checkpoint/agent1/file-channel/checkpoint
agent1.channels.channel1.dataDir=/opt/flume/data/agent1/file-channel/data

上述配置中,我們配置了2個sink共享一個file channel,並使用avro將數據傳遞到下一級agent。這些都是常規配置,除此之外,我們爲agent定義了sink group:
agent1.sinkgroups= sinkgroup1
以及相應的sink group屬性。這個group包含sink1a和sink1b,type配置爲load_balance,Flume事件將被負載均衡到這兩個sink,默認採用round-robin決定事件應該發送到哪一個sink,如果sink1a不可用,則發往sink1b,如果兩個都不用,則事件保留在file channel。負載均衡的策略可以通過processor.selector來修改。

默認情況下,sink的不可用是不會被processor記住的,如果這一次sink1a不可用,下一批事件的時候,還會再次嘗試sink1a,這可能導致效率很低。因此,我們配置了processor.backoff=true,當某個sink不可用時,就會被加入黑名單列表中,一定時間之後再從黑名單中移除,繼續被嘗試。黑名單的最長有效期通過processor.selector.maxTimeOut配置。

另一種processor的類型是failover,這種類型的sink group,事件被髮送到一個優先的sink,如果這個優先的sink不可用,則切換到備用的sink。failover sink processor在組內維護一個優先級順序,分發事件時,按照優先級從高到低依次分發直到有可用的sink。不可用的sink將被暫時加入黑名單,時間通過processor.maxpenalty配置,最長30秒。

在下一級agent中,我們再10000和10001端口分配配置兩個avro source。agent2a的配置如下:
agent2a.sources=source2a
agent2a.sinks = sink2a
agent2a.channels = channel2a

agent2a.sources.source2a.channels =channel2a
agent2a.sinks.sink2a.channel=channel2a

agent2a.sources.source2a.type=avro
agent2a.sources.source2a.bind=slave1
agent2a.sources.source2a.port=10000

agent2a.sinks.sink2a.type=hdfs
agent2a.sinks.sink2a.hdfs.path=/tmp/flume

避免衝突

agent2a.sinks.sink2a.hdfs.filePrefix = events-a
agent2a.sinks.sink2a.hdfs.fileSuffix = .log
agent2a.sinks.sink2a.hdfs.fileType = DataStream

agent2a.channels.channel2a.type=file

agent2a.channels.chennel2a.checkpointDir=/opt/flume/checkpoint/agent2a/file-channel/checkpoint
agent2a.channels.channel2a.dataDir=/opt/flume/data/agent2a/file-channel/data
agent2b的配置完全類似:
agent2b.sources=source2b
agent2b.sinks = sink2b
agent2b.channels = channel2b

agent2b.sources.source2b.channels =channel2b
agent2b.sinks.sink2b.channel=channel2b

agent2b.sources.source2b.type=avro
agent2b.sources.source2b.bind=slave1
agent2b.sources.source2b.port=10001

agent2b.sinks.sink2b.type=hdfs
agent2b.sinks.sink2b.hdfs.path=/tmp/flume

避免衝突

agent2b.sinks.sink2b.hdfs.filePrefix = events-b
agent2b.sinks.sink2b.hdfs.fileSuffix = .log
agent2b.sinks.sink2b.hdfs.fileType = DataStream

agent2b.channels.channel2b.type=file

agent2b.channels.chennel2b.checkpointDir=/opt/flume/checkpoint/agent2b/file-channel/checkpoint
agent2b.channels.channel2b.dataDir=/opt/flume/dataagent2b/file-channel/data
配置了hdfs的文件前綴,是爲了避免兩個agent同時寫入的時候出現衝突。如果二級的agent部署在不同的機器上,可以配置一個hostname攔截器,然後使用hostname作爲文件前綴:
agent2a.sinks.sink2a.hdfs.filePrefix=event-%{host}
最終整個示意圖如下:

這裏寫圖片描述

OK,我們在真實環境中運行一下這個系統:
在/opt/fluem/conf目錄下放着我們剛纔的三個屬性文件:

spool-tier1.properties
spool-tier2-a.properties
spool-tier2-b.properties

啓動HDFS:

start-dfs.sh
使用jps確保正常啓動。

啓動tier2的agent:
flume-ng agent –conf-file /opt/flume/conf/spool-tier2-a.properties –name agent2a –conf $FLUME_HOME/conf -Dflume.root.logger=INFO,console

flume-ng agent –conf-file /opt/flume/conf/spool-tier2-a.properties –name agent2a –conf $FLUME_HOME/conf -Dflume.root.logger=INFO,console
這裏寫圖片描述

啓動tier1的agent:

flume-ng agent --conf-file /opt/flume/conf/spool-tier1.properties --name agent1 --conf $FLUME_HOME/conf -Dflume.root.logger=INFO,console
往/tmp/spooldir目錄新增文件。可以在日誌中看到,正常情況下,agent2a和agent2b採用round-robin方式輪流,如果我們退出其中的agent2a,則此時負載全部落到agent2b上。

在應用中基礎Flume

Avro Source作爲一個RPC Server,可以接受RPC請求,所以可以寫客戶端程序往Avro Source發送事件。

Flume SDK提供了Avro和Thrift Source的客戶端接口,通過這個SDK,很容易將事件發送到Avro Source或者Thrift Source。同時支持負載均衡、故障轉移等。

Flume嵌入式的Agent提供了類似的功能,它運行在一個Java引用程序中,可以通過其暴露的EmbeddedAgent對象發送時間,事件的接受端目前只支持Avro。

Flume組件一覽

上文中只提到了Flume的部分組件,除此外還有很多組件可供使用和擴展,下表列出一些常見的組件:

類別 組件 描述
Source Avro 監聽Avro RPC調用(來自Avro sink或者Flume SDK)
Source Exec 運行Unix命令,例如tail -f,並將標準輸出中的每一行轉化成一個事件,注意該source不保證傳送事件到channel
HTTP 在端口上監聽,並通過可插拔的Handler將請求轉化爲事件
JMS 從JMS隊列或者topic讀取消息,轉化爲事件
Netcat 監聽端口,每行文本轉化爲事件
Sequencegenerator 遞增生成序列號,用於測試
Spooling directory 檢測目錄中新增文件,把文件中的每一行轉化爲事件
Syslog 從系統日誌中讀取每一行並轉化爲事件
Thrift 監聽端口,接受Thrift RPC調用(來自Thrift Sink或者FLume SDK)
Twitter 對接Twitter的Straming API
Sink Avro 通過Avro RPC發送事件到Avro Source
Elasticsearch 按照Logstash的格式將事件寫入ES集羣
File roll 將事件寫入本地文件系統
HBase 使用指定的serializer將事件寫入HBase
HDFS 以文本、序列文件、Avro或者其他格式寫入事件到HDFS
IRC 發送事件到IRC channel
Logger 使用SLF4J將事件輸出到日誌,測試用
Morphline(Solr) 常用與加載數據到Solr,運行一系列Marphline命令
Null 忽略事件
Thrift 使用Thrift RPC發送事件到Thrift Source
Channel File 事件保存在本地文件
JDBC 事件寫入到嵌入式的Derby數據庫
Memory 在內存隊列中保存事件
Interceptor Host 添加agent所在的機器的host或者ip到事件頭部
Morphline 基於Morphline配置文件過濾事件或者修改頭部
Regex extractor 從事件body中提取匹配的表達式
Static 設置固定的Header到事件
TimeStamp 設置時間戳頭部
UUID 設置id頭部

發佈了83 篇原創文章 · 獲贊 44 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章