flume日誌收集系統
概述
Flume是一個分佈式的、可靠的、高可用的海量日誌採集系統。它能夠將不同數據源的海量日誌數據進行高效收集、聚合、移動,最後存儲到一箇中心化數據存儲系統中。能夠做到實時推送事件,並且可以滿足數據量是持續且量級很大的情況。
基本組件
Flume傳輸的數據的基本單位是event,如果是文本文件,通常是一行記錄,這也是事務的基本單位,代表着一個數據流的最小完整單元。flume運行的核心是agent。它是一個完整的數據收集工具,含有三個核心組件,分別是source、channel、sink。Event從Source,流向Channel,再到Sink,本身爲一個byte數組,並可攜帶headers信息。基本流程如下:
-
1.Source
Source主要負責接收數據到Flume Agent組件,可以從其他系統或者應用程序中接收數據,比如web server、log4j等。也可以是從其他Agent的Sink通過RPC發送的數據 -
2.Channel
Channel充當了Source與Sink之間的緩衝區,使得source與sink之間的藕合度降低,source只管向Channel發數據,sink只需從Channel取數據,有點類似於隊列的功能。Agent緩衝已經接收,但尚未寫出到另一個Agent或者存儲系統的數據。多個Source可以同時寫入到一個Channel,多個Sink也可以從相同的Channel讀取,但是一個Sink只能從一個Channel中讀取。它允許Source和Sink可以工作在不同的速率,使得flume可以處理Source 高峯時的負載,即使此時Sink無法讀取Channel。
Channel有多種方式:有MemoryChannel、KafkaChannel、JDBCChannel、FileChannel等。
Memory Channel 實現是一個內存緩衝區,性能比較好,若JVM或者機器重啓,Channel衝的任何數據都將丟失。如果允許數據小量丟失,推薦使用;
KafkaChannel 是用Kafka作爲Channel,即就是將數據保存在kafka中。
File Channel,event 保存在本地磁盤中,可靠性高,只要磁盤上存儲的數據仍然是起作用的和可訪問的,就不會丟失數據,但吞吐量低於Memory Channel;
JDBC Channel,event保存在關係數據中,一般不推薦使用;
- 3.Sink
Sink主要是移除Channel中的數據並寫入到另一個Agent或者數據存儲或者一些其他系統的組件中。Sink會連續的輪詢Channel中的事件,將事件傳送到下一個目的地,一旦成功傳遞後,Sink就會通知Channel將該事件刪除,這些都是依據於flume中的事務機制實現的。
flume event
flume中event是數據傳輸的基本單元,由消息頭header和消息體body組成,其定義接口如下:
//所在文件:flume-ng-sdk\src\main\java\org\apache\flume\Event.java
public interface Event {
/**
* Returns a map of name-value pairs describing the data stored in the body.
*/
public Map<String, String> getHeaders();
/**
* Set the event headers
* @param headers Map of headers to replace the current headers.
*/
public void setHeaders(Map<String, String> headers);
/**
* Returns the raw byte array of the data contained in this event.
*/
public byte[] getBody();
/**
* Sets the raw byte array of the data contained in this event.
* @param body The data.
*/
public void setBody(byte[] body);
}
可以看出header類型爲key-value的Map集合,body爲byte字節數組類型。消息頭header並不是用來傳輸數據的,只是爲了路由和跟蹤發送事件的優先級和嚴重性,還可以給事件添加事件ID等,接下來看下event的基本實現,主要就是實現了header、body兩部分的設置,以及toString方法的重寫:
public class SimpleEvent implements Event {
private Map<String, String> headers;
private byte[] body;
public SimpleEvent() {
headers = new HashMap<String, String>();
body = new byte[0];
}
@Override
public Map<String, String> getHeaders() {
return headers;
}
@Override
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
@Override
public byte[] getBody() {
return body;
}
@Override
public void setBody(byte[] body) {
if (body == null) {
body = new byte[0];
}
this.body = body;
}
@Override
public String toString() {
Integer bodyLen = null;
if (body != null) bodyLen = body.length;
return "[Event headers = " + headers + ", body.length = " + bodyLen + " ]";
}
}
看完了event的具體實現,接下來就是event對象的創建了:
/**dir:flume-ng-sdk\src\main\java\org\apache\flume\event\EventBuilder.java
* Instantiate an Event instance based on the provided body and headers.
* If <code>headers</code> is <code>null</code>, then it is ignored.
* @param body
* @param headers
* @return
*/
public static Event withBody(byte[] body, Map<String, String> headers) {
Event event = new SimpleEvent(); //創建一個SimpleEvent對象
if (body == null) { //若消息體爲空,則創建字節數組,設置消息體
body = new byte[0];
}
event.setBody(body);
if (headers != null) { //消息頭不爲空,設置header
event.setHeaders(new HashMap<String, String>(headers));
}
return event; //返回event事件
}
以上就是flume 事件event的一個基本創建過程。接下來看看Channel中的Transaction機制如何保證數據完整性。
flume事務(Transaction)
事務是Flume的可靠性的基礎,能夠保證傳輸數據的完整性。這裏說的完整性保證主要取決於Agent中使用的Channel的持久性保證,若使用的是內置的Source或Sink以及一個持久的Channel,Agent可以保證不會丟失數據。flume提供了無數據丟失的保證,主要實現就是Channel中的事務機制(熟悉數據庫原理的應該會對事務這個概念有一些瞭解,但此處的事務又不同於數據庫事務)。每個flume事務代表一批自動寫入到Channel或者從Channel刪除的事件,無論是當Source將事件寫入Channel時,或Sink從Channel讀取事件時,它必須在事務的範圍之內進行操作。
下圖就是一個事務的大致結構:
put event是寫事件,take event是讀事件。Source寫事件至Channel時,事務由Channel處理器(ChannelProcessor)處理,只有event被成功寫入Channel,ChannelProcessor才提交事務,否則,ChannelProcessor將回滾該事務並關閉它。代碼邏輯可以從下看出:
//flume-ng-core\src\main\java\org\apache\flume\channel\ChannelProcessor.java
// Process required channels
for (Channel reqChannel : reqChannelQueue.keySet()) {
Transaction tx = reqChannel.getTransaction();
Preconditions.checkNotNull(tx, "Transaction object must not be null");
try {
tx.begin();
List<Event> batch = reqChannelQueue.get(reqChannel);
for (Event event : batch) {
reqChannel.put(event);
}
tx.commit();
} catch (Throwable t) {
tx.rollback();
if (t instanceof Error) {
LOG.error("Error while writing to required channel: " + reqChannel, t);
throw (Error) t;
} else if (t instanceof ChannelException) {
throw (ChannelException) t;
} else {
throw new ChannelException("Unable to put batch on required " +
"channel: " + reqChannel, t);
}
} finally {
if (tx != null) {
tx.close();
}
}
從代碼中可以發現涉及了事務Transaction的begin、commit、rollback、close四個主要方法.此處使用了回調函數機制。
public interface Transaction {
enum TransactionState { Started, Committed, RolledBack, Closed }
void begin();
void commit();
void rollback();
void close();
}
對於讀事件(take event),只有當數據被安全寫出到存儲系統或者下一個agent中的Source,事務才被提交,一旦數據在最終目的地安全,就提交事務,否則寫操作失敗,Flume必須回滾事務,以確保事件不會丟失。對於確保數據安全到達是使用了RPC機制完成。
讀事件event
public Status process() throws EventDeliveryException {
Status status = Status.READY;
Channel channel = getChannel();
Transaction transaction = channel.getTransaction();
if (resetConnectionFlag.get()) {
resetConnection();
// if the time to reset is long and the timeout is short
// this may cancel the next reset request
// this should however not be an issue
resetConnectionFlag.set(false);
}
try {
transaction.begin();
verifyConnection();
List<Event> batch = Lists.newLinkedList();
for (int i = 0; i < client.getBatchSize(); i++) {
Event event = channel.take();
if (event == null) {
break;
}
batch.add(event);
}
int size = batch.size();
int batchSize = client.getBatchSize();
if (size == 0) {
sinkCounter.incrementBatchEmptyCount();
status = Status.BACKOFF;
} else {
if (size < batchSize) {
sinkCounter.incrementBatchUnderflowCount();
} else {
sinkCounter.incrementBatchCompleteCount();
}
sinkCounter.addToEventDrainAttemptCount(size);
client.appendBatch(batch);
}
transaction.commit();
sinkCounter.addToEventDrainSuccessCount(size);
} catch (Throwable t) {
transaction.rollback();
if (t instanceof Error) {
throw (Error) t;
} else if (t instanceof ChannelException) {
logger.error("Rpc Sink " + getName() + ": Unable to get event from" +
" channel " + channel.getName() + ". Exception follows.", t);
status = Status.BACKOFF;
} else {
destroyConnection();
throw new EventDeliveryException("Failed to send events", t);
}
} finally {
transaction.close();
}
return status;
}
@VisibleForTesting
RpcClient getUnderlyingClient() {
return client;
}
}