flume日志收集系统

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组成,其定义接口如下:

  1. //所在文件:flume-ng-sdk\src\main\java\org\apache\flume\Event.java
  2. public interface Event {
  3. /**
  4. * Returns a map of name-value pairs describing the data stored in the body.
  5. */
  6. public Map<String, String> getHeaders();
  7. /**
  8. * Set the event headers
  9. * @param headers Map of headers to replace the current headers.
  10. */
  11. public void setHeaders(Map<String, String> headers);
  12. /**
  13. * Returns the raw byte array of the data contained in this event.
  14. */
  15. public byte[] getBody();
  16. /**
  17. * Sets the raw byte array of the data contained in this event.
  18. * @param body The data.
  19. */
  20. public void setBody(byte[] body);
  21. }

可以看出header类型为key-value的Map集合,body为byte字节数组类型。消息头header并不是用来传输数据的,只是为了路由和跟踪发送事件的优先级和严重性,还可以给事件添加事件ID等,接下来看下event的基本实现,主要就是实现了header、body两部分的设置,以及toString方法的重写:

  1. public class SimpleEvent implements Event {
  2. private Map<String, String> headers;
  3. private byte[] body;
  4. public SimpleEvent() {
  5. headers = new HashMap<String, String>();
  6. body = new byte[0];
  7. }
  8. @Override
  9. public Map<String, String> getHeaders() {
  10. return headers;
  11. }
  12. @Override
  13. public void setHeaders(Map<String, String> headers) {
  14. this.headers = headers;
  15. }
  16. @Override
  17. public byte[] getBody() {
  18. return body;
  19. }
  20. @Override
  21. public void setBody(byte[] body) {
  22. if (body == null) {
  23. body = new byte[0];
  24. }
  25. this.body = body;
  26. }
  27. @Override
  28. public String toString() {
  29. Integer bodyLen = null;
  30. if (body != null) bodyLen = body.length;
  31. return "[Event headers = " + headers + ", body.length = " + bodyLen + " ]";
  32. }
  33. }

看完了event的具体实现,接下来就是event对象的创建了:

  1. /**dir:flume-ng-sdk\src\main\java\org\apache\flume\event\EventBuilder.java
  2. * Instantiate an Event instance based on the provided body and headers.
  3. * If <code>headers</code> is <code>null</code>, then it is ignored.
  4. * @param body
  5. * @param headers
  6. * @return
  7. */
  8. public static Event withBody(byte[] body, Map<String, String> headers) {
  9. Event event = new SimpleEvent(); //创建一个SimpleEvent对象
  10. if (body == null) { //若消息体为空,则创建字节数组,设置消息体
  11. body = new byte[0];
  12. }
  13. event.setBody(body);
  14. if (headers != null) { //消息头不为空,设置header
  15. event.setHeaders(new HashMap<String, String>(headers));
  16. }
  17. return event; //返回event事件
  18. }

以上就是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将回滚该事务并关闭它。代码逻辑可以从下看出:

  1. //flume-ng-core\src\main\java\org\apache\flume\channel\ChannelProcessor.java
  2. // Process required channels
  3. for (Channel reqChannel : reqChannelQueue.keySet()) {
  4. Transaction tx = reqChannel.getTransaction();
  5. Preconditions.checkNotNull(tx, "Transaction object must not be null");
  6. try {
  7. tx.begin();
  8. List<Event> batch = reqChannelQueue.get(reqChannel);
  9. for (Event event : batch) {
  10. reqChannel.put(event);
  11. }
  12. tx.commit();
  13. } catch (Throwable t) {
  14. tx.rollback();
  15. if (t instanceof Error) {
  16. LOG.error("Error while writing to required channel: " + reqChannel, t);
  17. throw (Error) t;
  18. } else if (t instanceof ChannelException) {
  19. throw (ChannelException) t;
  20. } else {
  21. throw new ChannelException("Unable to put batch on required " +
  22. "channel: " + reqChannel, t);
  23. }
  24. } finally {
  25. if (tx != null) {
  26. tx.close();
  27. }
  28. }

从代码中可以发现涉及了事务Transaction的begin、commit、rollback、close四个主要方法.此处使用了回调函数机制。

  1. public interface Transaction {
  2. enum TransactionState { Started, Committed, RolledBack, Closed }
  3. void begin();
  4. void commit();
  5. void rollback();
  6. void close();
  7. }

对于读事件(take event),只有当数据被安全写出到存储系统或者下一个agent中的Source,事务才被提交,一旦数据在最终目的地安全,就提交事务,否则写操作失败,Flume必须回滚事务,以确保事件不会丢失。对于确保数据安全到达是使用了RPC机制完成。 
读事件event

  1. public Status process() throws EventDeliveryException {
  2. Status status = Status.READY;
  3. Channel channel = getChannel();
  4. Transaction transaction = channel.getTransaction();
  5. if (resetConnectionFlag.get()) {
  6. resetConnection();
  7. // if the time to reset is long and the timeout is short
  8. // this may cancel the next reset request
  9. // this should however not be an issue
  10. resetConnectionFlag.set(false);
  11. }
  12. try {
  13. transaction.begin();
  14. verifyConnection();
  15. List<Event> batch = Lists.newLinkedList();
  16. for (int i = 0; i < client.getBatchSize(); i++) {
  17. Event event = channel.take();
  18. if (event == null) {
  19. break;
  20. }
  21. batch.add(event);
  22. }
  23. int size = batch.size();
  24. int batchSize = client.getBatchSize();
  25. if (size == 0) {
  26. sinkCounter.incrementBatchEmptyCount();
  27. status = Status.BACKOFF;
  28. } else {
  29. if (size < batchSize) {
  30. sinkCounter.incrementBatchUnderflowCount();
  31. } else {
  32. sinkCounter.incrementBatchCompleteCount();
  33. }
  34. sinkCounter.addToEventDrainAttemptCount(size);
  35. client.appendBatch(batch);
  36. }
  37. transaction.commit();
  38. sinkCounter.addToEventDrainSuccessCount(size);
  39. } catch (Throwable t) {
  40. transaction.rollback();
  41. if (t instanceof Error) {
  42. throw (Error) t;
  43. } else if (t instanceof ChannelException) {
  44. logger.error("Rpc Sink " + getName() + ": Unable to get event from" +
  45. " channel " + channel.getName() + ". Exception follows.", t);
  46. status = Status.BACKOFF;
  47. } else {
  48. destroyConnection();
  49. throw new EventDeliveryException("Failed to send events", t);
  50. }
  51. } finally {
  52. transaction.close();
  53. }
  54. return status;
  55. }
  56. @VisibleForTesting
  57. RpcClient getUnderlyingClient() {
  58. return client;
  59. }
  60. }

以上就是有关flume的一些基本介绍,后边会继续分析介绍,深入理解学习。


发布了45 篇原创文章 · 获赞 17 · 访问量 7万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章