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;
}
}