flume之hdfsSink分析

概述

前边分析了flume的 Source 和 MemoryChannel 两个组件,接下来分析下第三个大组件 Sink。Sink组件主要用于从Channel 中拉取数据至下一个flume agent 或者目的存储对象(如HDFS)。

要分析Sink,就来先看下Sink接口的定义:

 
  
  1. public interface Sink extends LifecycleAware, NamedComponent {
  2. /**
  3. * 设置Channel
  4. */
  5. public void setChannel(Channel channel);
  6. /**
  7. * 返回具体sink的channel
  8. */
  9. public Channel getChannel();
  10. /**
  11. * 请求的Sink尝试从连接的Channel消费数据data。这个方法应该在一个事务范围内从Channel消费。
  12. * 成功分发事务应该被提交。失败应该回退。
  13. * 如果有 1个或多个Event被成功分发,则为READY;
  14. * 如果没有数据从Channel取回放至sink,则为BACKOFF
  15. * 在任何类型的故障传递数据到下一跳目的地的情况下,抛出异常EventDeliveryException
  16. */
  17. public Status process() throws EventDeliveryException;
  18. public static enum Status {
  19. READY, BACKOFF
  20. }
  21. }

从入口Application类出发

从前边的分析可知,flume系统的入口为 Application 类,在该类中会依次启动 Channel、Sink、Source三个组件。从启动代码分析可以发现Sink组件启动调用对象 Sink运行器SinkRunner的start方法,该操作发生在MonitorRunnable线程中调用lifecycleAware.start()方法启动一个Sink组件,eclipse查看该方法具体Sink相关实现如下: 

flume之hdfsSink分析_第1张图片 
进入SinkRunner类中的 start 方法如下:

 
  
  1. @Override
  2. public void start() {
  3. SinkProcessor policy = getPolicy(); //获取
  4. policy.start();
  5. runner = new PollingRunner();
  6. runner.policy = policy;
  7. runner.counterGroup = counterGroup;
  8. runner.shouldStop = new AtomicBoolean(); //以原子方式创建 Boolean值,默认为false
  9. runnerThread = new Thread(runner);
  10. runnerThread.setName("SinkRunner-PollingRunner-" +
  11. policy.getClass().getSimpleName());
  12. runnerThread.start(); //启动线程
  13. lifecycleState = LifecycleState.START;
  14. }

在该start方法中首先会获取一个SinkProcessor,指定线程的属性(policy、counterGroup 、shouldStop )并且启动它。然后会创建一个线程PollingRunner,调用线程的run方法:

 
  
  1. @Override
  2. public void run() {
  3. logger.debug("Polling sink runner starting");
  4. while (!shouldStop.get()) {
  5. try {
  6. if (policy.process().equals(Sink.Status.BACKOFF)) {
  7. counterGroup.incrementAndGet("runner.backoffs");
  8. Thread.sleep(Math.min(
  9. counterGroup.incrementAndGet("runner.backoffs.consecutive")
  10. * backoffSleepIncrement, maxBackoffSleep));
  11. } else {
  12. counterGroup.set("runner.backoffs.consecutive", 0L);
  13. }
  14. } catch (InterruptedException e) {
  15. ......
  16. }
  17. }
  18. logger.debug("Polling runner exiting. Metrics:{}", counterGroup);
  19. }
  20. }

在run方法中可以发现使用while循环(直到设置shouldStop为true结束循环)调用SinkProcessor中的process方法进行下一步的处理。

Sink处理器

SinkProcessor就是Sink处理器,那么SinkRunner运行器和SinlkProcessor处理器有什么不同呢?其实SinkRunner实际上主要就是运行Sink的(Sink启动入口首先就是调用该对象,相比于Source也有其SourceRunner),而 SinkProcessor 决定究竟哪个 Sink 应该从自己对应的 Channel 中拉取事件。 
为什么需要SinkProcessor呢? 
Flume可以聚合线程到Sink组,每个Sink组可以包含一个或多个Sink,如果一个Sink没有定义Sink组,那么该Sink可以被认为是在一个组内,且该Sink是组内的唯一成员。Flume会为每一个Sink组实例化一个SinkRunner运行器,来运行该 Sink 组。如下Sink组件框架图: 

  flume之hdfsSink分析_第2张图片
SinkRunner运行器运行一个Sink Group(如图组内有Sink1、Sink2、Sink3),Sink运行器仅仅是一个询问 Sink组来处理下一批事件的线程。每个Sink组有一个SinkProcessor处理器,该处理器将调用组中某一个Sink的process方法处理事件。 
了解了Sink处理器,接下来查看下都有哪些SinkProcessor,如下图有两种实现, 

flume之hdfsSink分析_第3张图片

    1. 基于抽象类AbstractSinkProcessor实现: 
      实现的子类有FailoverSinkProcessor和LoadBalancingSinkProcessor,适用于配置有Sink组的情况;FailoverSinkProcessor是故障转移处理器,从Sink组中以优先级的顺序选择Sink,直至失败再选择组中第二优先级高的Sink处理;LoadBalancingSinkProcessor是负载均衡处理器,Sink选择顺序支持Random(随机)或者Round-robin(轮询)。
  • 2.是flume系统默认的Sink处理器类DefaultSinkProcessor,只接受一个单一的Sink,没有任何额外的处理(相比于第一种)传递process的处理结果。

若没有配置Sink组,采用的默认就是DefaultSinkProcessor类中的process,该方法中因为不需要做任何的额外处理,代码也是十分的简单,直接调用Sink 的process方法(也就是配置中具体定义的sink,比如写入HDFS中,那就是调用hdfsSink):

 
  
  1. @Override
  2. public Status process() throws EventDeliveryException {
  3. return sink.process();
  4. }

HDFSEventSink process方法

HDFSEventSink 的process方法是Sink组件的核心代码,其中实现了Sink的event事务处理。每一种具体的sink都必须实现process方法,目前1.7版本自带如下: 

flume之hdfsSink分析_第4张图片

在HDFSEventSink.java中

 
  
  1. /**
  2. * 从channel拉数据发送到HDFS。每个事务可以取出batchSize个events.
  3. * 找到event对应的存储桶bucket。确保文件打开。序列化数据写入到HDFS上的文件中。
  4. * 这个方法不是线程安全的。
  5. */
  6. public Status process() throws EventDeliveryException {
  7. // 获取管道channel
  8. Channel channel = getChannel();
  9. Transaction transaction = channel.getTransaction(); //getTransaction获取或创建事务Transaction
  10. List<BucketWriter> writers = Lists.newArrayList();
  11. transaction.begin(); //事务开始
  12. try {
  13. int txnEventCount = 0;
  14. //从channel中取出batchSize个event
  15. for (txnEventCount = 0; txnEventCount < batchSize; txnEventCount++) {
  16. Event event = channel.take();
  17. if (event == null) {
  18. break;
  19. }
  20. // reconstruct the path name by substituting place holders
  21. // 通过替换占位符重建路径名
  22. String realPath = BucketPath.escapeString(filePath, event.getHeaders(),
  23. timeZone, needRounding, roundUnit, roundValue, useLocalTime);
  24. String realName = BucketPath.escapeString(fileName, event.getHeaders(),
  25. timeZone, needRounding, roundUnit, roundValue, useLocalTime);
  26. String lookupPath = realPath + DIRECTORY_DELIMITER + realName;
  27. LOG.debug("realPath:"+realPath+" ; realName: "+realName);
  28. LOG.debug("lookupPath: "+lookupPath);
  29. /* filePath配置: hdfs.path = /user/portal/tmp/syx/flume-events/%y-%m-%d/%H%M
  30. * filePrefix配置: hdfs.filePrefix = events
  31. *
  32. * 添加调试输出如下:
  33. * realPath: /user/portal/tmp/syx/flume-events/16-12-17/2110 ; realName: events
  34. * lookupPath: /user/portal/tmp/syx/flume-events/16-12-17/2110/events
  35. */
  36. BucketWriter bucketWriter;
  37. HDFSWriter hdfsWriter = null;
  38. // Callback to remove the reference to the bucket writer from the
  39. // sfWriters map so that all buffers used by the HDFS file
  40. // handles are garbage collected.
  41. /*
  42. * 构造一个回调函数,回调函数从sfWriters map中移除对bucket写入器的引用,
  43. * 以便HDFS文件句柄使用的所有缓冲区被垃圾回收gc
  44. */
  45. WriterCallback closeCallback = new WriterCallback() {
  46. @Override
  47. public void run(String bucketPath) {
  48. LOG.info("Writer callback called.");
  49. synchronized (sfWritersLock) {
  50. sfWriters.remove(bucketPath); //从sfWriters映射中移除指定键bucketPath的映射关系
  51. }
  52. }
  53. };
  54. synchronized (sfWritersLock) {
  55. bucketWriter = sfWriters.get(lookupPath);
  56. // we haven't seen this file yet, so open it and cache the handle
  57. // 我们还没有看到这个文件,所以打开它并缓存句柄
  58. if (bucketWriter == null) {
  59. //根据fileType选择对应的HDFSWriter,3种:SequenceFile, DataStream or CompressedStream
  60. hdfsWriter = writerFactory.getWriter(fileType);
  61. //initializeBucketWriter方法如其名,初始化BucketWriter
  62. bucketWriter = initializeBucketWriter(realPath, realName,
  63. lookupPath, hdfsWriter, closeCallback);
  64. sfWriters.put(lookupPath, bucketWriter);
  65. }
  66. }
  67. // track the buckets getting written in this transaction 跟踪在事务中写入的buckets
  68. if (!writers.contains(bucketWriter)) {
  69. writers.add(bucketWriter);
  70. }
  71. //写数据到HDFS
  72. try {
  73. bucketWriter.append(event);
  74. } catch (BucketClosedException ex) {
  75. LOG.info("Bucket was closed while trying to append, " +
  76. "reinitializing bucket and writing event.");
  77. hdfsWriter = writerFactory.getWriter(fileType);
  78. bucketWriter = initializeBucketWriter(realPath, realName,
  79. lookupPath, hdfsWriter, closeCallback); //根据传入参数创建BucketWriter对象
  80. synchronized (sfWritersLock) {
  81. sfWriters.put(lookupPath, bucketWriter);
  82. }
  83. bucketWriter.append(event);
  84. }
  85. }
  86. if (txnEventCount == 0) {
  87. sinkCounter.incrementBatchEmptyCount();
  88. } else if (txnEventCount == batchSize) {
  89. sinkCounter.incrementBatchCompleteCount();
  90. } else {
  91. sinkCounter.incrementBatchUnderflowCount();
  92. }
  93. // flush all pending buckets before committing the transaction
  94. //在提交事务前flush所有的文件
  95. for (BucketWriter bucketWriter : writers) {
  96. bucketWriter.flush();
  97. }
  98. transaction.commit(); //提交事务
  99. if (txnEventCount < 1) {
  100. return Status.BACKOFF;
  101. } else {
  102. sinkCounter.addToEventDrainSuccessCount(txnEventCount);
  103. return Status.READY;
  104. }
  105. } catch (IOException eIO) {
  106. transaction.rollback(); //发生异常,回滚事务
  107. LOG.warn("HDFS IO error", eIO);
  108. return Status.BACKOFF;
  109. } catch (Throwable th) {
  110. transaction.rollback();
  111. LOG.error("process failed", th);
  112. if (th instanceof Error) {
  113. throw (Error) th;
  114. } else {
  115. throw new EventDeliveryException(th);
  116. }
  117. } finally {
  118. transaction.close(); //关闭事务
  119. }
  120. }

这个process方法中主要实现功能就是event的事务处理,开始一个事务transaction,然后从对应的Channel中take出event,写入HDFS中,写入的过程是open根据配置文件指定的路径名称在对应目录中生成对应的临时文件(默认以.tmp为后缀)打开文件,将event写入,在到达某个时间点或者文件达到指定大小,对文件进行重新命名rename操作,然后提交commit事务,若发生异常则回滚事务,否则正常关闭文件即可。这一过程中对文件的操作均是调用HDFS的文件操作方法(open、mkdir、rename等)。在写入hdfs时还有文件压缩、hadoop中副本处理等一些点,这里就讲了。

至此Sink写入hdfs的大致过程结束。

你可能感兴趣的:(big,data)