Flume HDFS Sink使用及源码分析

85 篇文章 3 订阅
26 篇文章 1 订阅

Flume HDFS Sink使用及源码分析

HDFS Sink介绍

Flume导入数据HDFS,目前只支持创建序列化(sequence)文件和文本(text)文件。还支持这两个文件的压缩。文件可以根据运行的时间,数据的大小和时间的数量来进行周期性的滚动(关闭当前文件产生新的文件)。也可以根据数据属性分区,例如根据时间戳或机器分区。HDFS目录路径可以包含格式化的转义字符,生成目录路径可以通过格式化转移字符(escape sequences),HDFS sink通过这些转义字符生成一个目录或者文件去存储Event。当然在Flume中使用HDFS Sink的话,需要添加HDFS相关的Jar,这样Flume就能使用Hadoop的jar和Hadoop集群交互。注:Hadoop必须支持sync()。

以下是HDFS Sink支持的转义字符:

 

名称 描述

%{host}替代Event Header被命名为“host”的值,支持任意的Header name。
%tUnix毫秒时间
%a短的周名称,例如:Mon, Tue, ...
%A周名称全称,例如:Monday, Tuesday, ...
%b短的月名称,例如:(Jan, Feb, ...
%B月名称全称,例如:January, February, ...
%c日期和时间,例如:Thu Mar 3 23:05:25 2005
%d每个月的某一天,例如:01 - 31
%e每个月的某一天(没有填充0)例如:1,2,3,4---31
%D日期;像:%m/%d/%y
%H小时(00..23)
%I小时(01..12)
%j每个年的某一天,例如:001..366
%k小时,例如:0..23
%m月份,例如:01..12
%n月份,例如:1..12
%M分钟,例如:00..59
%pam 或 pm
%s从1970-01-01 00:00:00 UTC到现在的毫秒数
%S秒,例如:00..59
%y两位数的年份,例如:00..99
%Y年份,例如:2010
%z+hhmm 数字时区,例如:-0400

文件在使用的时候以".tmp"为后缀,一旦文件关闭,扩展名将被移除。
注:跟时间相关的转移序列,Key为“timestamp”必须存在在Event的Headers中(除非hdfs.useLocalTimeStamp设置为true)NameDefaultDescription

channel

 

type组件的名称,必须为:HDFS
hdfs.pathHDFS目录路径,例如:hdfs://namenode/flume/webdata/
hdfs.filePrefixFlumeDataHDFS目录中,由Flume创建的文件前缀。
hdfs.fileSuffix追加到文件的后缀,例如:.txt
hdfs.inUsePrefix文件正在写入时的前缀。
hdfs.inUseSuffix.tmp文件正在写入时的后缀。
hdfs.rollInterval30当前写入的文件滚动间隔,默认30秒生成一个新的文件 (0 = 不滚动)
hdfs.rollSize1024以文件大小触发文件滚动,单位字节(0 = 不滚动)
hdfs.rollCount10
 
 
 
 
 
 
 
以写入的事件数触发文件滚动。(0 = 不滚动)
hdfs.idleTimeout0超时多久以后关闭无效的文件。(0 = 禁用自动关闭的空闲文件)但是还是可能因为网络等多种原因导致,正在写的文件始终没有关闭,从而产生tmp文件
hdfs.batchSize100有多少Event后,写到文件才刷新到HDFS。
hdfs.codeC压缩编解码器,可以使用:gzip, bzip2, lzo, lzop, snappy
hdfs.fileTypeSequenceFile文件格式:通常使用SequenceFile(默认),DataStream或者CompressedStream
(1)DataStream不能压缩输出文件,请不用设置hdfs.codeC编码解码器。
(2)CompressedStream要求设置hdfs.codeC来制定一个有效的编码解码器。
hdfs.maxOpenFiles5000HDFS中允许打开文件的数据,如果数量超过了,最老的文件将被关闭。
hdfs.callTimeout10000允许HDFS操作的毫秒数,例如:open,write, flush, close。如果很多HFDS操作超时,这个配置应该增大。
hdfs.threadsPoolSize10

每个HDFS sink的HDFS的IO操作线程数(例如:open,write)

hdfs.rollTimerPoolSize1每个HDFS sink调度定时文件滚动的线程数。
hdfs.kerberosPrincipal安全访问HDFS Kerberos的主用户。
hdfs.kerberosKeytab安全访问HDFSKerberos keytab
hdfs.proxyUser

 

 

hdfs.roundfalse时间戳应该被四舍五入。(如果为true,会影响所有的时间,除了t%)
hdfs.roundValue1四舍五入的最高倍数(单位配置在hdfs.roundUnit),但是要小于当前时间。
hdfs.roundUnitsecond四舍五入的单位,包含:second,minuteorhour.
hdfs.timeZoneLocal Time时区的名称,主要用来解决目录路径。例如:America/Los_Angeles
hdfs.useLocalTimeStampfalse使用本地时间替换转义字符。 (而不是event header的时间戳)
hdfs.closeTries0在发起一个关闭命令后,HDFS sink必须尝试重命名文件的次数。如果设置为1,重命名失败后,HDFS sink不会再次尝试重命名该文件,这个文件处于打开状态,并且用.tmp作为扩展名。如果为0,Sink会一直尝试重命名,直至重命名成功。如果文件 失败,这个文件可能一直保持打开状态,但是这种情况下数据是完整的。文件将会在Flume下次重启时被关闭。
hdfs.retryInterval180在几秒钟之间连续尝试关闭文件。每个关闭请求都会有多个RPC往返Namenode,因此设置的太低可能导致Namenode超负荷,如果设置0或者更小,如果第一次尝试失败的话,该Sink将不会尝试关闭文件。并且把文件打开,或者用“.tmp”作为扩展名。
serializerTEXT可能的选项包括avro_event或继承了EventSerializer.Builder接口的类名。
serializer.*

 

 

 

关于round:

a1.sinks.k1.hdfs.round=true
a1.sinks.k1.hdfs.roundValue=10
a1.sinks.k1.hdfs.roundUnit=minute

上面的配置将四舍五入配置到10分钟,例如:一个事件的时间戳是11:54:34 AM, June 12, 2012 将导致hdfs的路径变为:/flume/events/2012-06-12/1150/00

 

源码分析

configure(Context context):主要用于加载配置文件。

 

Java代码 

 收藏代码

  1. public void configure(Context context) {  
  2.     this.context = context;  
  3.     //HDFS目录路径,例如:hdfs://namenode/flume/webdata/,也可以用/flume/webdata/,这样要把Hadoop的配置文件放到classpath  
  4.     filePath = Preconditions.checkNotNull(  
  5.         context.getString("hdfs.path"), "hdfs.path is required");  
  6.     //HDFS目录中,由Flume创建的文件前缀。  
  7.    fileName = context.getString("hdfs.filePrefix", defaultFileName);  
  8.     //文件后缀  
  9.    this.suffix = context.getString("hdfs.fileSuffix", defaultSuffix);  
  10.     //文件正在写入时的前缀。  
  11.    inUsePrefix = context.getString("hdfs.inUsePrefix", defaultInUsePrefix);//文件正在写入时的后缀。  
  12.     inUseSuffix = context.getString("hdfs.inUseSuffix", defaultInUseSuffix);  
  13.     //时区的名称,主要用来解决目录路径。例如:America/Los_Angeles  
  14.    String tzName = context.getString("hdfs.timeZone");  
  15.     timeZone = tzName == null ? null : TimeZone.getTimeZone(tzName);  
  16.     rollInterval = context.getLong("hdfs.rollInterval", defaultRollInterval);//当前写入的文件滚动间隔,默认30秒生成一个新的文件 (0 = 不滚动)  
  17.     rollSize = context.getLong("hdfs.rollSize", defaultRollSize);//以文件大小触发文件滚动,单位字节(0 = 不滚动)  
  18.     rollCount = context.getLong("hdfs.rollCount", defaultRollCount);  
  19.     //有多少Event后,写到文件才刷新到HDFS。  
  20.     batchSize = context.getLong("hdfs.batchSize", defaultBatchSize);  
  21.     //超时多久以后关闭无效的文件。(0 = 禁用自动关闭的空闲文件)但是还是可能因为网络等多种原因导致,正在写的文件始终没有关闭,从而产生tmp文件  
  22.     idleTimeout = context.getInteger("hdfs.idleTimeout", 0);  
  23.     //压缩编解码器,可以使用:gzip, bzip2, lzo, lzop, snappy  
  24.     String codecName = context.getString("hdfs.codeC");  
  25.    //文件格式:通常使用SequenceFile(默认), DataStream 或者 CompressedStrea  
  26.     //(1)DataStream不能压缩输出文件,请不用设置hdfs.codeC编码解码器。  
  27.     //(2)CompressedStream要求设置hdfs.codeC来制定一个有效的编码解码器。  
  28.     fileType = context.getString("hdfs.fileType", defaultFileType);  
  29.     //HDFS中允许打开文件的数据,如果数量超过了,最老的文件将被关闭。  
  30.     maxOpenFiles = context.getInteger("hdfs.maxOpenFiles", defaultMaxOpenFiles);  
  31.     //允许HDFS操作的毫秒数,例如:open,write, flush, close。如果很多HFDS操作超时,这个配置应该增大。  
  32.     callTimeout = context.getLong("hdfs.callTimeout", defaultCallTimeout);  
  33.     //允许HDFS操作的毫秒数,例如:open,write, flush, close。如果很多HFDS操作超时,这个配置应该增大。  
  34.     //每个HDFS sink的HDFS的IO操作线程数(例如:open,write)   
  35.     threadsPoolSize = context.getInteger("hdfs.threadsPoolSize", defaultThreadPoolSize);  
  36.     //每个HDFS sink调度定时文件滚动的线程数。  
  37.     rollTimerPoolSize = context.getInteger("hdfs.rollTimerPoolSize", defaultRollTimerPoolSize);  
  38.     //每个HDFS sink调度定时文件滚动的线程数。  
  39.     String kerbConfPrincipal = context.getString("hdfs.kerberosPrincipal");  
  40.     //安全认证  
  41.  String kerbKeytab = context.getString("hdfs.kerberosKeytab");  
  42.  String proxyUser = context.getString("hdfs.proxyUser");  
  43.  tryCount = context.getInteger("hdfs.closeTries", defaultTryCount);  
  44.  if(tryCount <= 0) {  
  45.  LOG.warn("Retry count value : " + tryCount + " is not " +  
  46.  "valid. The sink will try to close the file until the file " +  
  47.  "is eventually closed.");  
  48.  tryCount = defaultTryCount;  
  49.  }  
  50.  retryInterval = context.getLong("hdfs.retryInterval",  
  51.  defaultRetryInterval);  
  52.  if(retryInterval <= 0) {  
  53.  LOG.warn("Retry Interval value: " + retryInterval + " is not " +  
  54.  "valid. If the first close of a file fails, " +  
  55.  "it may remain open and will not be renamed.");  
  56.  tryCount = 1;  
  57.  }  
  58.   
  59.  Preconditions.checkArgument(batchSize > 0,  
  60.  "batchSize must be greater than 0");  
  61.  if (codecName == null) {  
  62.  codeC = null;  
  63.  compType = CompressionType.NONE;  
  64.  } else {  
  65.  codeC = getCodec(codecName);  
  66.  // TODO : set proper compression type  
  67.  compType = CompressionType.BLOCK;  
  68.  }  
  69.   
  70.  // Do not allow user to set fileType DataStream with codeC together  
  71.  // To prevent output file with compress extension (like .snappy)  
  72.  if(fileType.equalsIgnoreCase(HDFSWriterFactory.DataStreamType)  
  73.  && codecName != null) {  
  74.  throw new IllegalArgumentException("fileType: " + fileType +  
  75.  " which does NOT support compressed output. Please don't set codeC" +  
  76.  " or change the fileType if compressed output is desired.");  
  77.  }  
  78.   
  79.  if(fileType.equalsIgnoreCase(HDFSWriterFactory.CompStreamType)) {  
  80.  Preconditions.checkNotNull(codeC, "It's essential to set compress codec"  
  81.  + " when fileType is: " + fileType);  
  82.  }  
  83.   
  84.  // get the appropriate executor  
  85.  this.privExecutor = FlumeAuthenticationUtil.getAuthenticator(  
  86.  kerbConfPrincipal, kerbKeytab).proxyAs(proxyUser);  
  87.   
  88.   
  89.   
  90.     //时间戳应该被四舍五入。(如果为true,会影响所有的时间,除了t%)  
  91.  needRounding = context.getBoolean("hdfs.round", false);  
  92.   
  93.  if(needRounding) {  
  94.       //四舍五入的单位  
  95.  String unit = context.getString("hdfs.roundUnit", "second");  
  96.  if (unit.equalsIgnoreCase("hour")) {  
  97.  this.roundUnit = Calendar.HOUR_OF_DAY;  
  98.  } else if (unit.equalsIgnoreCase("minute")) {  
  99.  this.roundUnit = Calendar.MINUTE;  
  100.  } else if (unit.equalsIgnoreCase("second")){  
  101.  this.roundUnit = Calendar.SECOND;  
  102.  } else {  
  103.  LOG.warn("Rounding unit is not valid, please set one of" +  
  104.  "minute, hour, or second. Rounding will be disabled");  
  105.  needRounding = false;  
  106.  }  
  107.       //四舍五入的最高倍数  
  108.  this.roundValue = context.getInteger("hdfs.roundValue", 1);  
  109.  if(roundUnit == Calendar.SECOND || roundUnit == Calendar.MINUTE){  
  110.  Preconditions.checkArgument(roundValue > 0 && roundValue <= 60,  
  111.  "Round value" +  
  112.  "must be > 0 and <= 60");  
  113.  } else if (roundUnit == Calendar.HOUR_OF_DAY){  
  114.  Preconditions.checkArgument(roundValue > 0 && roundValue <= 24,  
  115.  "Round value" +  
  116.  "must be > 0 and <= 24");  
  117.  }  
  118.  }  
  119.   
  120.  this.useLocalTime = context.getBoolean("hdfs.useLocalTimeStamp", false);  
  121.  if(useLocalTime) {  
  122.  clock = new SystemClock();  
  123.  }  
  124.   
  125.  if (sinkCounter == null) {  
  126.       //<span style="color:#000000;">计数器</span>  
  127.  sinkCounter = new SinkCounter(getName());  
  128.  }  
  129.  }  
  130.       
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

按照Flume的生命周期,先启动start方法:

 

 

Java代码 

 收藏代码

  1. @Override  
  2. public void start() {  
  3.   String timeoutName = "hdfs-" + getName() + "-call-runner-%d";  
  4.   //线程池用于event写入HDFS文件  
  5.   callTimeoutPool = Executors.newFixedThreadPool(threadsPoolSize,  
  6.           new ThreadFactoryBuilder().setNameFormat(timeoutName).build());  
  7.   
  8.   String rollerName = "hdfs-" + getName() + "-roll-timer-%d";  
  9.   //该线程池用来滚动文件  
  10.   timedRollerPool = Executors.newScheduledThreadPool(rollTimerPoolSize,  
  11.           new ThreadFactoryBuilder().setNameFormat(rollerName).build());  
  12.   //该LinkedHashMap用来存储文件的绝对路径以及对应的BucketWriter  
  13.   this.sfWriters = new WriterLinkedHashMap(maxOpenFiles);  
  14.   sinkCounter.start();  
  15.   super.start();  
  16. }  

所有的Event,经Source后发送的Channel,再由Channel传入到Sink,主要调用Sink的process方法实现事务:

 

 

Java代码 

 收藏代码

  1. public Status process() throws EventDeliveryException {  
  2.     Channel channel = getChannel();//获取Channel  
  3.     Transaction transaction = channel.getTransaction();//获取事务  
  4.     List<BucketWriter> writers = Lists.newArrayList();//初始化BucketWriter列表,BucketWriter是操作HDFS主类。  
  5.     transaction.begin();  
  6.     try {  
  7.       int txnEventCount = 0;  
  8.       for (txnEventCount = 0; txnEventCount < batchSize; txnEventCount++) {//批量处理  
  9.         Event event = channel.take();//获取Event  
  10.         if (event == null) {  
  11.           break;  
  12.         }  
  13.   
  14.         // reconstruct the path name by substituting place holders  
  15.         String realPath = BucketPath.escapeString(filePath, event.getHeaders(),  
  16.             timeZone, needRounding, roundUnit, roundValue, useLocalTime);//格式化HDFS路径,根据转义字符  
  17.         String realName = BucketPath.escapeString(fileName, event.getHeaders(),  
  18.           timeZone, needRounding, roundUnit, roundValue, useLocalTime);//格式化文件名称,根据转义字符  
  19.   
  20.         //写入HDFS的绝对路径  
  21.         String lookupPath = realPath + DIRECTORY_DELIMITER + realName;  
  22.         BucketWriter bucketWriter;  
  23.         HDFSWriter hdfsWriter = null;  
  24.         // Callback to remove the reference to the bucket writer from the  
  25.         // sfWriters map so that all buffers used by the HDFS file  
  26.         // handles are garbage collected.  
  27.         WriterCallback closeCallback = new WriterCallback() {  
  28.           @Override  
  29.           public void run(String bucketPath) {  
  30.             LOG.info("Writer callback called.");  
  31.             synchronized (sfWritersLock) {  
  32.               sfWriters.remove(bucketPath);  
  33.             }  
  34.           }  
  35.         };  
  36.         synchronized (sfWritersLock) {  
  37.           //根据HDFS的绝对路径获取对应的BucketWriter对象  
  38.           bucketWriter = sfWriters.get(lookupPath);  
  39.           // we haven't seen this file yet, so open it and cache the handle  
  40.           if (bucketWriter == null) {  
  41.             //初始化BuchetWriter对象  
  42.             hdfsWriter = writerFactory.getWriter(fileType);  
  43.             bucketWriter = initializeBucketWriter(realPath, realName,  
  44.               lookupPath, hdfsWriter, closeCallback);  
  45.             //放入Map  
  46.             sfWriters.put(lookupPath, bucketWriter);  
  47.           }  
  48.         }  
  49.   
  50.         // track the buckets getting written in this transaction  
  51.         if (!writers.contains(bucketWriter)) {  
  52.           //如果BucketWriter列表没有正在写的文件——bucketWriter,则加入  
  53.           writers.add(bucketWriter);  
  54.         }  
  55.   
  56.         // Write the data to HDFS  
  57.         try {  
  58.           //将event写入bucketWriter对应的文件中  
  59.           bucketWriter.append(event);  
  60.         } catch (BucketClosedException ex) {  
  61.           LOG.info("Bucket was closed while trying to append, " +  
  62.             "reinitializing bucket and writing event.");  
  63.           hdfsWriter = writerFactory.getWriter(fileType);  
  64.           bucketWriter = initializeBucketWriter(realPath, realName,  
  65.             lookupPath, hdfsWriter, closeCallback);  
  66.           synchronized (sfWritersLock) {  
  67.             sfWriters.put(lookupPath, bucketWriter);  
  68.           }  
  69.           bucketWriter.append(event);  
  70.         }  
  71.       }  
  72.   
  73.       if (txnEventCount == 0) {  
  74.         //这次事务没有处理任何event  
  75.         sinkCounter.incrementBatchEmptyCount();  
  76.       } else if (txnEventCount == batchSize) {  
  77.         //一次处理batchSize个event  
  78.         sinkCounter.incrementBatchCompleteCount();  
  79.       } else {  
  80.         //channel中剩余的events不足batchSize  
  81.         sinkCounter.incrementBatchUnderflowCount();  
  82.       }  
  83.   
  84.       // flush all pending buckets before committing the transaction  
  85.       //获取List里面的BucketWriter的所有数据都刷新到HDFS  
  86.       for (BucketWriter bucketWriter : writers) {  
  87.         //如果使用转义字符生成文件名或路径,可能还没有满足其他滚动生成新文件的条件,就有新文件产生,  
  88.         //在这种情况下,例如为hdfs.idleTimeout=0,那么就可能会在HDFS中出现很多.tmp后缀的文件。因为调用flush没有关闭该文件。  
  89.         bucketWriter.flush();  
  90.       }  
  91.       //提交事务  
  92.       transaction.commit();  
  93.   
  94.       if (txnEventCount < 1) {  
  95.         return Status.BACKOFF;  
  96.       } else {  
  97.         sinkCounter.addToEventDrainSuccessCount(txnEventCount);  
  98.         return Status.READY;  
  99.       }  
  100.     } catch (IOException eIO) {  
  101.       transaction.rollback();//事务回滚  
  102.       LOG.warn("HDFS IO error", eIO);  
  103.       return Status.BACKOFF;  
  104.     } catch (Throwable th) {  
  105.       transaction.rollback();  
  106.       LOG.error("process failed", th);  
  107.       if (th instanceof Error) {  
  108.         throw (Error) th;  
  109.       } else {  
  110.         throw new EventDeliveryException(th);  
  111.       }  
  112.     } finally {  
  113.       transaction.close();//关闭事务  
  114.     }  
  115.   }  


HDFS Sink流程分析:

1,通过configure(Context context)和start()方法初始化Sink

2,SinkRunner的线程调用process()方法,循环处理批量的Event,如果Event为null,就跳出循环。

3,有Event数据,先格式化HDFS的文件路径和文件名,即:realPath和realName。realPath+realName就是完整HDFS路径:lookupPath,然后根据lookupPath获取BucketWriter对象。

4,BucketWriter对象不存在,则先构建根据fileType构建一个HDFSWriter 对象。然后初始化BucketWriter对象。最后将对象放到sfWriters中,表示正在写的文件。

 

Java代码 

 收藏代码

  1. public HDFSWriter getWriter(String fileType) throws IOException {  
  2.   if (fileType.equalsIgnoreCase(SequenceFileType)) {  
  3.     //通过SequenceFile.Writer写入文件  
  4.     return new HDFSSequenceFile();  
  5.   } else if (fileType.equalsIgnoreCase(DataStreamType)) {  
  6.     //通过FSDataOutputStream  
  7.     return new HDFSDataStream();  
  8.   } else if (fileType.equalsIgnoreCase(CompStreamType)) {  
  9.     return new HDFSCompressedDataStream();  
  10.   } else {  
  11.     throw new IOException("File type " + fileType + " not supported");  
  12.   }  
  13. }  

HDFSSequenceFile:configure(context)方法会首先获取写入格式writeFormat即参数"hdfs.writeFormat",org.apache.flume.sink.hdfs.SequenceFileSerializerType定义了一下三个:

 

Java代码 

 收藏代码

  1. Writable(HDFSWritableSerializer.Builder.class),//默认的  
  2. Text(HDFSTextSerializer.Builder.class),  
  3. Other(null);  


再获取是否使用HDFS本地文件系统"hdfs.useRawLocalFileSystem",默认是flase不使用;然后获取writeFormat的所有配置信息serializerContext;然后根据writeFormat和serializerContext构造SequenceFileSerializer的对象serializer。

 

  HDFSDataStream:configure(context)方法先获取serializerType类型,默认是TEXT(BodyTextEventSerializer.Builder.class),其他的还包含:

 

Java代码 

 收藏代码

  1. public enum EventSerializerType {  
  2.   TEXT(BodyTextEventSerializer.Builder.class),  
  3.   HEADER_AND_TEXT(HeaderAndBodyTextEventSerializer.Builder.class),  
  4.   AVRO_EVENT(FlumeEventAvroEventSerializer.Builder.class),  
  5.   OTHER(null);  


再获取是否使用HDFS本地文件系统"hdfs.useRawLocalFileSystem",默认是flase不使用;最后获取serializer的所有配置信息serializerContext。serializer的实例化在HDFSDataStream.doOpen(Configuration conf, Path dstPath, FileSystem hdfs)方法中实现的。

 

HDFSCompressedDataStream:configure和HDFSDataStream.configure(context)类似,serializerType的类型也一样。serializer的实例化是在HDFSCompressedDataStream.open(String filePath, CompressionCodec codec, CompressionType cType)方法中实现。

5,bucketWriter实例化后存放到sfWriters中,并且判断是否在writers变量的List中,如果不存在,就放入List,这样后面就可以对bucketWriter统一flush了。

6,bucketWriter.append(event);

 

Java代码 

 收藏代码

  1. public synchronized void append(final Event event)  
  2.         throws IOException, InterruptedException {  
  3.   checkAndThrowInterruptedException();//检查当前线程是否被中断  
  4.   // If idleFuture is not null, cancel it before we move forward to avoid a  
  5.   // close call in the middle of the append.  
  6.   if(idleFuture != null) {  
  7.     idleFuture.cancel(false);  
  8.     // There is still a small race condition - if the idleFuture is already  
  9.     // running, interrupting it can cause HDFS close operation to throw -  
  10.     // so we cannot interrupt it while running. If the future could not be  
  11.     // cancelled, it is already running - wait for it to finish before  
  12.     // attempting to write.  
  13.     if(!idleFuture.isDone()) {  
  14.       try {  
  15.         idleFuture.get(callTimeout, TimeUnit.MILLISECONDS);  
  16.       } catch (TimeoutException ex) {  
  17.         LOG.warn("Timeout while trying to cancel closing of idle file. Idle" +  
  18.           " file close may have failed", ex);  
  19.       } catch (Exception ex) {  
  20.         LOG.warn("Error while trying to cancel closing of idle file. ", ex);  
  21.       }  
  22.     }  
  23.     idleFuture = null;  
  24.   }  
  25.   
  26.   // If the bucket writer was closed due to roll timeout or idle timeout,  
  27.   // force a new bucket writer to be created. Roll count and roll size will  
  28.   // just reuse this one  
  29.   if (!isOpen) {  
  30.     if (closed) {  
  31.       throw new BucketClosedException("This bucket writer was closed and " +  
  32.         "this handle is thus no longer valid");  
  33.     }  
  34.     open();//一个文件已经完成将isOpen设置为false,则新建一个文件  
  35.   }  
  36.   
  37.   // check if it's time to rotate the file  
  38.   if (shouldRotate()) {//检查文件的行数及大小,判断是否要关闭文件后重新生成文件。  
  39.     boolean doRotate = true;  
  40.   
  41.     if (isUnderReplicated) {  
  42.       if (maxConsecUnderReplRotations > 0 &&  
  43.           consecutiveUnderReplRotateCount >= maxConsecUnderReplRotations) {  
  44.         doRotate = false;  
  45.         if (consecutiveUnderReplRotateCount == maxConsecUnderReplRotations) {  
  46.           LOG.error("Hit max consecutive under-replication rotations ({}); " +  
  47.               "will not continue rolling files under this path due to " +  
  48.               "under-replication", maxConsecUnderReplRotations);  
  49.         }  
  50.       } else {  
  51.         LOG.warn("Block Under-replication detected. Rotating file.");  
  52.       }  
  53.       consecutiveUnderReplRotateCount++;  
  54.     } else {  
  55.       consecutiveUnderReplRotateCount = 0;  
  56.     }  
  57.   
  58.     if (doRotate) {  
  59.       close();  
  60.       open();//新建一个文件  
  61.     }  
  62.   }  
  63.   
  64.   // write the event  
  65.   try {  
  66.     sinkCounter.incrementEventDrainAttemptCount();  
  67.     callWithTimeout(new CallRunner<Void>() {  
  68.       @Override  
  69.       public Void call() throws Exception {  
  70.         writer.append(event); // could block 往HDFS写入数据。  
  71.         return null;  
  72.       }  
  73.     });  
  74.   } catch (IOException e) {  
  75.     LOG.warn("Caught IOException writing to HDFSWriter ({}). Closing file (" +  
  76.         bucketPath + ") and rethrowing exception.",  
  77.         e.getMessage());  
  78.     try {  
  79.       close(true);  
  80.     } catch (IOException e2) {  
  81.       LOG.warn("Caught IOException while closing file (" +  
  82.            bucketPath + "). Exception follows.", e2);  
  83.     }  
  84.     throw e;  
  85.   }  
  86.   
  87.   // update statistics  
  88.   processSize += event.getBody().length;  
  89.   eventCounter++;  
  90.   batchCounter++;  
  91.   
  92.   if (batchCounter == batchSize) {  
  93.     flush();  
  94.   }  
  95. }  


打开新文件分为两类:

 

第一类不需要压缩

 

Java代码 

 收藏代码

  1. public void open(String filePath) throws IOException {  
  2.   open(filePath, null, CompressionType.NONE);  
  3. }  


第二类要压缩

 

 

Java代码 

 收藏代码

  1. public void open(String filePath, CompressionCodec codeC,  
  2.     CompressionType compType) throws IOException {  
  3.   Configuration conf = new Configuration();  
  4.   Path dstPath = new Path(filePath);  
  5.   FileSystem hdfs = dstPath.getFileSystem(conf);  
  6.   open(dstPath, codeC, compType, conf, hdfs);  
  7. }  

注:HDFSDataStream是不支持压缩的,所以直接调用第一类的open方法。

 

在open方法中,如果按时间滚动的rollInterval不为0,则创建Callable,放入timedRollFuture中rollInterval秒之后关闭文件,默认是30s写一个文件。

最后writer.append(event)是真正写数据到HDFS,writer分如下三种情况:

HDFSSequenceFile:append(event)方法,会先通过serializer.serialize(e)把event处理成一个Key和一个Value。

serializer为HDFSWritableSerializer:

Key:

 

Java代码 

 收藏代码

  1. private Object getKey(Event e) {  
  2.     String timestamp = e.getHeaders().get("timestamp");//获取header的timesteamp  
  3.     long eventStamp;  
  4.   
  5.     if (timestamp == null) {//timestamp不存在就拿系统的当前时间  
  6.       eventStamp = System.currentTimeMillis();  
  7.     } else {  
  8.       eventStamp = Long.valueOf(timestamp);  
  9.     }  
  10.     return new LongWritable(eventStamp);//将时间封装成LongWritable  
  11.   }  

Value:

 

 

Java代码 

 收藏代码

  1. private BytesWritable makeByteWritable(Event e) {  
  2.   BytesWritable bytesObject = new BytesWritable();  
  3.   bytesObject.set(e.getBody(), 0, e.getBody().length);  
  4.   return bytesObject;  
  5. }  

serializer为HDFSTextSerializer:

 

Key同上,Value:

 

Java代码 

 收藏代码

  1. private Text makeText(Event e) {  
  2.   Text textObject = new Text();  
  3.   textObject.set(e.getBody(), 0, e.getBody().length);  
  4.   return textObject;  
  5. }  


writer为HDFSDataStream:

 

直接调用serializer.write(e),serializer分三种:

org.apache.flume.serialization.BodyTextEventSerializer直接读取body写入OutputStream流中,然后在最后加"\n"。

org.apache.flume.serialization.HeaderAndBodyTextEventSerializer将e.getHeaders() + " " +e.getBody()写入数据流,然后根据配置看是否要加"\n"

org.apache.flume.serialization.AvroEventSerializer将event整体写入dataFileWriter。

 

然后appned方法更新统计,processSize统计文件大小;eventCounter统计文件行数;batchCounter是统计最近一次flush之后的处理的event数;

如果处理的event数量达到batchSize的大小,则刷新到HDFS,flush()方法会首先执行writer.sync()即写入HDFS,然后将batchCounter置为0,根据fileType的不同writer也会有很多写入类型:

HDFSSequenceFile:sync()方法执行SequenceFile.Writer.syncFs()将数据写入HDFS中;
HDFSDataStream:sync()方法执行
HDFSCompressedDataStream:sync()方法先执行serializer.flush():只有FlumeEventAvroEventSerializer的flush()方法也有实现dataFileWriter.flush(),其他俩BodyTextEventSerializer和HeaderAndBodyTextEventSerializer均未实现flush()方法。然后执行outStream.flush()和outStream.sync()将数据刷新至HDFS中。

 

7,回到HDFSEventSink.process()方法中,会根据这次事务处理的event数量更新相应的统计;

8,遍历writers,挨个刷新BucketWriter至HDFS;

9,最后提交事务,异常回滚,关闭事务。

最后停止:

 

Java代码 

 收藏代码

  1. @Override  
  2. public void stop() {  
  3.   // do not constrain close() calls with a timeout  
  4.   synchronized (sfWritersLock) {//获取对象锁  
  5.     //遍历对象锁  
  6.     for (Entry<String, BucketWriter> entry : sfWriters.entrySet()) {  
  7.       LOG.info("Closing {}", entry.getKey());  
  8.       //关闭BucketWriter,flush到HDFS  
  9.       try {  
  10.         entry.getValue().close();  
  11.       } catch (Exception ex) {  
  12.         LOG.warn("Exception while closing " + entry.getKey() + ". " +  
  13.                 "Exception follows.", ex);  
  14.         if (ex instanceof InterruptedException) {  
  15.           Thread.currentThread().interrupt();  
  16.         }  
  17.       }  
  18.     }  
  19.   }  
  20.   
  21.   // shut down all our thread pools  
  22.   ExecutorService toShutdown[] = {callTimeoutPool, timedRollerPool};  
  23.   for (ExecutorService execService : toShutdown) {  
  24.     execService.shutdown();  
  25.     try {  
  26.       while (execService.isTerminated() == false) {  
  27.         execService.awaitTermination(  
  28.                 Math.max(defaultCallTimeout, callTimeout), TimeUnit.MILLISECONDS);  
  29.       }  
  30.     } catch (InterruptedException ex) {  
  31.       LOG.warn("shutdown interrupted on " + execService, ex);  
  32.     }  
  33.   }  
  34.   
  35.   callTimeoutPool = null;  
  36.   timedRollerPool = null;  
  37.   
  38.   synchronized (sfWritersLock) {  
  39.     sfWriters.clear();  
  40.     sfWriters = null;  
  41.   }  
  42.   sinkCounter.stop();  
  43.   super.stop();  
  44. }  


 

 

30W年薪的人工智能工程师只是“白菜价”?

人工智能技术向前发展,也必然会出现一些岗位被人工智能取代,但我们相信,随着人工智能的发展,会有更多的新的、属于未来的工作岗位出现,是社会发展的必然产物,我们能做的也许只能是与时俱进了

分享到:  

SpoolDirectorySource使用及源码分析 | Flume几种监控方式

评论

发表评论

 您还没有登录,请您登录后再发表评论

相关资源推荐

上滑加载更多

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值