Flume - FileChannel源码详解

FileChannel在Flume是一个非常重要的Channel,FileChannel可以很好的保证数据的完整性和一致性,提供了类似mysql binlog的机制,保证机器down机,JVM异常退出时数据不丢失,在采集数据量很大的情况下,建议FileChannel设置的目录和程序日志文件保存的目录设成不同的磁盘,以便提高效率。

FileChannel的简易类结构:



FileChannel的内部事务类,FileBackedTransaction:


文件操作类:LogFile(LogFileV2在1.7已经被舍弃):


还有其他几个比较重要的类:

FlumeEventQueue,LogFile,Log,LogUtils。


一,初始化过程:public void configure(Context context)

1,useDualCheckpoints(是否需要备份检查点)

2,compressBackupCheckpoint(是否压缩备份节点)

3,checkpointDir(检查点目录,默认在${user.home}目录下)

4,dataDirs(数据节点目录)

5,capacity(获取配置的容量)

6,keepAlive(超时时间,就是如果channel中没有数据最长等待时间)

7,transactionCapacity(事务的最大容量)

注意:capacity的值一定要大于transactionCapacity,不然会报错,看源码:

  1. Preconditions.checkState(transactionCapacity <= capacity,  
  2.       "File Channel transaction capacity cannot be greater than the " +  
  3.         "capacity of the channel.");  
8, checkpointInterval(log的检查间隔

9,maxFileSize(最大文件的大小,默认是1.5G)

10,minimumRequiredSpace(最少需要多少空间,默认是500M)

11,useLogReplayV1(使用旧重放逻辑)

12,useFastReplay(不使用队列重放)

13,keyProvider(KEY供应商的类型,支持的类型:JCEKSFILE)

14,activeKey(用于加密新数据的密钥名称)

15,cipherProvider(加密提供程序类型,支持的类型:AESCTRNOPADDING)


二,start()方法:

  1. @Override  
  2.   public synchronized void start() {  
  3.     LOG.info("Starting {}..."this);  
  4.     try {  
  5.       Builder builder = new Log.Builder();  
  6.       builder.setCheckpointInterval(checkpointInterval);  
  7.       builder.setMaxFileSize(maxFileSize);  
  8.       builder.setMinimumRequiredSpace(minimumRequiredSpace);  
  9.       builder.setQueueSize(capacity);  
  10.       builder.setCheckpointDir(checkpointDir);  
  11.       builder.setLogDirs(dataDirs);  
  12.       builder.setChannelName(getName());  
  13.       builder.setUseLogReplayV1(useLogReplayV1);  
  14.       builder.setUseFastReplay(useFastReplay);  
  15.       builder.setEncryptionKeyProvider(encryptionKeyProvider);  
  16.       builder.setEncryptionKeyAlias(encryptionActiveKey);  
  17.       builder.setEncryptionCipherProvider(encryptionCipherProvider);  
  18.       builder.setUseDualCheckpoints(useDualCheckpoints);  
  19.       builder.setCompressBackupCheckpoint(compressBackupCheckpoint);  
  20.       builder.setBackupCheckpointDir(backupCheckpointDir);  
  21.       builder.setFsyncPerTransaction(fsyncPerTransaction);  
  22.       builder.setFsyncInterval(fsyncInterval);  
  23.       builder.setCheckpointOnClose(checkpointOnClose);//以上是将configure方法获取到的参数,set到Builder对象  
  24.       log = builder.build();  
  25.       //builder.build();方法通过Builder创建Log对象  
  26.       //并且尝试获取checkpointDir和dataDir文件锁,Log类中的private void lock(File dir) throws IOException方法就是用来尝试过去锁的  
  27.       log.replay();  
  28.       //1,首先获取到checkpointDir的写锁  
  29.       //2,获取最大的fileID  
  30.       //3,读取log文件根据record的类型进行相应的操作,进行恢复;遍历所有的data目录  
  31.       //4,将queue刷新到相关文件  
  32.       open = true;//表示打开channel   
  33.       int depth = getDepth();  
  34.         
  35.       Preconditions.checkState(queueRemaining.tryAcquire(depth),  
  36.           "Unable to acquire " + depth + " permits " + channelNameDescriptor);  
  37.       LOG.info("Queue Size after replay: " + depth + " "  
  38.            + channelNameDescriptor);  
  39.     } catch (Throwable t) {  
  40.       open = false;  
  41.       startupError = t;  
  42.       LOG.error("Failed to start the file channel " + channelNameDescriptor, t);  
  43.       if (t instanceof Error) {  
  44.         throw (Error) t;  
  45.       }  
  46.     }  
  47.     if (open) {  
  48.     //计数器开始统计  
  49.       channelCounter.start();  
  50.       channelCounter.setChannelSize(getDepth());  
  51.       channelCounter.setChannelCapacity(capacity);  
  52.     }  
  53.     super.start();  
  54.   }  
org.apache.flume.channel.file.Log类用来将Event写入磁盘并将指向这些event的指针存入一个内存队列FlumeEventQueue中。并且启动一个线程,每过checkpointInterval毫秒写一次检查点log.writeCheckpoint()。
  1. workerExecutor.scheduleWithFixedDelay(new BackgroundWorker(this),  
  2.         this.checkpointInterval, this.checkpointInterval,  
  3.         TimeUnit.MILLISECONDS);  

  1. static class BackgroundWorker implements Runnable {  
  2.   private static final Logger LOG = LoggerFactory  
  3.       .getLogger(BackgroundWorker.class);  
  4.   private final Log log;  
  5.   
  6.   public BackgroundWorker(Log log) {  
  7.     this.log = log;  
  8.   }  
  9.   
  10.   @Override  
  11.   public void run() {  
  12.     try {  
  13.       if (log.open) {  
  14.         log.writeCheckpoint();  
  15.         //将checpoint、inflightTakes、inflightPuts都刷新至磁盘,先后将inflightPuts、inflightTakes、checkpoint.meta重建,  
  16.         //更新checkpoint文件并刷新至磁盘,这些文件都在checkpointDir目录下;更新log-ID.meta文件;同时肩负起删除log文件及其对应的meta文件的责任。  
  17.       }  
  18.     } catch (IOException e) {  
  19.       LOG.error("Error doing checkpoint", e);  
  20.     } catch (Throwable e) {  
  21.       LOG.error("General error in checkpoint worker", e);  
  22.     }  
  23.   }  
  24. }  

三,事务

很多方法和Memory的事务类相似。如:doTake(),doCommit(),doRollback(),doPut()

下面详细的介绍这几个方法。

1,doPut():source会调用put方法

  1. @Override  
  2.     protected void doPut(Event event) throws InterruptedException {  
  3.       channelCounter.incrementEventPutAttemptCount();  
  4.       if(putList.remainingCapacity() == 0) {//是否有剩余空间  
  5.         throw new ChannelException("Put queue for FileBackedTransaction " +  
  6.             "of capacity " + putList.size() + " full, consider " +  
  7.             "committing more frequently, increasing capacity or " +  
  8.             "increasing thread count. " + channelNameDescriptor);  
  9.       }  
  10.       // this does not need to be in the critical section as it does not  
  11.       // modify the structure of the log or queue.  
  12.       if(!queueRemaining.tryAcquire(keepAlive, TimeUnit.SECONDS)) {//尝试等待  
  13.         throw new ChannelFullException("The channel has reached it's capacity. "  
  14.             + "This might be the result of a sink on the channel having too "  
  15.             + "low of batch size, a downstream system running slower than "  
  16.             + "normal, or that the channel capacity is just too low. "  
  17.             + channelNameDescriptor);  
  18.       }  
  19.       boolean success = false;  
  20.       log.lockShared();//获取checkpoint的读锁,doTake()方法也会获取读锁,所以doTake和doPut只能操作一个,无法同时操作。  
  21.       try {  
  22.        //transactionID是在TransactionIDOracle类中递增的  
  23.         FlumeEventPointer ptr = log.put(transactionID, event);//将Event写入数据文件,使用RandomAccessFile。数据会缓存到inflightputs文件中  
  24.         Preconditions.checkState(putList.offer(ptr), "putList offer failed "  
  25.           + channelNameDescriptor);  
  26.         queue.addWithoutCommit(ptr, transactionID);//指针和事务ID加入到queue队列中。  
  27.         success = true;  
  28.       } catch (IOException e) {  
  29.         throw new ChannelException("Put failed due to IO error "  
  30.                 + channelNameDescriptor, e);  
  31.       } finally {  
  32.         log.unlockShared();//释放读锁  
  33.         if(!success) {  
  34.           // release slot obtained in the case  
  35.           // the put fails for any reason  
  36.           queueRemaining.release();//释放信号量  
  37.         }  
  38.       }  
  39.     }  

2,doTake():sink会调用put方法
  1. <pre name="code" class="java">    protected Event doTake() throws InterruptedException {  
  2.       channelCounter.incrementEventTakeAttemptCount();  
  3.       if(takeList.remainingCapacity() == 0) {  
  4.         throw new ChannelException("Take list for FileBackedTransaction, capacity " +  
  5.             takeList.size() + " full, consider committing more frequently, " +  
  6.             "increasing capacity, or increasing thread count. "  
  7.                + channelNameDescriptor);  
  8.       }  
  9.       log.lockShared();//获取锁  
  10.       /* 
  11.        * 1. Take an event which is in the queue. 
  12.        * 2. If getting that event does not throw NoopRecordException, 
  13.        *    then return it. 
  14.        * 3. Else try to retrieve the next event from the queue 
  15.        * 4. Repeat 2 and 3 until queue is empty or an event is returned. 
  16.        */  
  17.   
  18.       try {  
  19.         while (true) {  
  20.           FlumeEventPointer ptr = queue.removeHead(transactionID);//获取文件指针,ptr的数据结构是fileID和offset  
  21.           if (ptr == null) {  
  22.             return null;  
  23.           } else {  
  24.             try {  
  25.               // first add to takeList so that if write to disk  
  26.               // fails rollback actually does it's work  
  27.               Preconditions.checkState(takeList.offer(ptr),  
  28.                 "takeList offer failed "  
  29.                   + channelNameDescriptor);  
  30.               log.take(transactionID, ptr); // write take to disk  
  31.               Event event = log.get(ptr);//根据文件指针,使用log对象在磁盘中获取到Event。数据会缓存到inflighttakes文件中  
  32.               return event;  
  33.             } catch (IOException e) {  
  34.               throw new ChannelException("Take failed due to IO error "  
  35.                 + channelNameDescriptor, e);  
  36.             } catch (NoopRecordException e) {  
  37.               LOG.warn("Corrupt record replaced by File Channel Integrity " +  
  38.                 "tool found. Will retrieve next event", e);  
  39.               takeList.remove(ptr);  
  40.             } catch (CorruptEventException ex) {  
  41.               if (fsyncPerTransaction) {  
  42.                 throw new ChannelException(ex);  
  43.               }  
  44.               LOG.warn("Corrupt record found. Event will be " +  
  45.                 "skipped, and next event will be read.", ex);  
  46.               takeList.remove(ptr);  
  47.             }  
  48.           }  
  49.         }  
  50.       } finally {  
  51.         log.unlockShared();//释放锁  
  52.       }  
  53.     }  
3,doCommit(): source和sink都会调用该方法提交事务
  1. @Override  
  2. protected void doCommit() throws InterruptedException {  
  3.   int puts = putList.size();  
  4.   int takes = takeList.size();  
  5.   if(puts > 0) {//puts和takes不能同时都>0,其中有一个得是等于零  
  6.     Preconditions.checkState(takes == 0"nonzero puts and takes "  
  7.             + channelNameDescriptor);  
  8.     log.lockShared();//获取锁  
  9.     try {  
  10.       log.commitPut(transactionID);//该操作会封装成一个ByteBuffer类型写入到文件,  
  11.       channelCounter.addToEventPutSuccessCount(puts);  
  12.       synchronized (queue) {  
  13.         while(!putList.isEmpty()) {  
  14.           if(!queue.addTail(putList.removeFirst())) {  
  15.             StringBuilder msg = new StringBuilder();  
  16.             msg.append("Queue add failed, this shouldn't be able to ");  
  17.             msg.append("happen. A portion of the transaction has been ");  
  18.             msg.append("added to the queue but the remaining portion ");  
  19.             msg.append("cannot be added. Those messages will be consumed ");  
  20.             msg.append("despite this transaction failing. Please report.");  
  21.             msg.append(channelNameDescriptor);  
  22.             LOG.error(msg.toString());  
  23.             Preconditions.checkState(false, msg.toString());  
  24.           }  
  25.         }  
  26.         queue.completeTransaction(transactionID);//清空checkpoint文件夹中inflightputs和inflighttakes文件的内容  
  27.       }  
  28.     } catch (IOException e) {  
  29.       throw new ChannelException("Commit failed due to IO error "  
  30.               + channelNameDescriptor, e);  
  31.     } finally {  
  32.       log.unlockShared();//释放锁  
  33.     }  
  34.   
  35.   } else if (takes > 0) {  
  36.     log.lockShared();//释放锁  
  37.     try {  
  38.       log.commitTake(transactionID);//写入data文件  
  39.       queue.completeTransaction(transactionID);//和上面操作一样  
  40.       channelCounter.addToEventTakeSuccessCount(takes);  
  41.     } catch (IOException e) {  
  42.       throw new ChannelException("Commit failed due to IO error "  
  43.           + channelNameDescriptor, e);  
  44.     } finally {  
  45.       log.unlockShared();  
  46.     }  
  47.     queueRemaining.release(takes);  
  48.   }  
  49.   putList.clear();  
  50.   takeList.clear();//清空两个队列  
  51.   channelCounter.setChannelSize(queue.getSize());  
  52. }  

4,doRollback():source和sink都会调用该方法回滚数据

  1. @Override  
  2. protected void doRollback() throws InterruptedException {  
  3.   int puts = putList.size();  
  4.   int takes = takeList.size();  
  5.   log.lockShared();  
  6.   try {  
  7.     if(takes > 0) {  
  8.       Preconditions.checkState(puts == 0"nonzero puts and takes "  
  9.           + channelNameDescriptor);  
  10.       synchronized (queue) {  
  11.         while (!takeList.isEmpty()) {  
  12.           Preconditions.checkState(queue.addHead(takeList.removeLast()),  
  13.               "Queue add failed, this shouldn't be able to happen "  
  14.                   + channelNameDescriptor);  
  15.         }  
  16.       }  
  17.     }  
  18.     putList.clear();  
  19.     takeList.clear();  
  20.     queue.completeTransaction(transactionID);  
  21.     channelCounter.setChannelSize(queue.getSize());  
  22.     log.rollback(transactionID);//也是封装成ByteBuffer,写入到缓存文件中。  
  23.   } catch (IOException e) {  
  24.     throw new ChannelException("Commit failed due to IO error "  
  25.         + channelNameDescriptor, e);  
  26.   } finally {  
  27.     log.unlockShared();  
  28.     // since rollback is being called, puts will never make it on  
  29.     // to the queue and we need to be sure to release the resources  
  30.     queueRemaining.release(puts);  
  31.   }  
  32. }  

Flame的FileChannel在系统崩溃的时候保证数据的完整性和一致性,其实是通过JDK的字节通道实现的(java.nio.channels.FileChannel),字节通道为了保证数据在系统崩溃之后不丢失数据,文件的修改模式会被强制到底层存储设备。


最后看下Flume FileChannel的文件结构:

checkpoint目录:

checkpoint:存放Event在那个data文件logFileID的什么位置offset等信息。

inflighttakes:存放的是事务take的缓存数据,每隔段时间就重建文件。

内容:

1、16字节是校验码;

2、transactionID1+eventsCount1+eventPointer11+eventPointer12+...;

3、transactionID2+eventsCount2+eventPointer21+eventPointer22+...

inflightputs:存放的是事务对应的put缓存数据,每隔段时间就重建文件。

内容:

1、16字节是校验码;

2、transactionID1+eventsCount1+eventPointer11+eventPointer12+...;

3、transactionID2+eventsCount2+eventPointer21+eventPointer22+...

checkpoint.meta:主要存储的是logfileID及对应event的数量等信息。

data目录:

log-ID.meta:主要记录log-ID下一个写入位置以及logWriteOrderID等信息。

log-ID:数据文件,目录里数据文件保持不超过2个。


FileChannel实现比较复杂,先写这么多,以后有需要细细了解。


http://blog.csdn.net/qianshangding0708/article/details/48133033

猜你喜欢

转载自blog.csdn.net/a040600145/article/details/76080887