flume的事务实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lilyjoke/article/details/81363906

在flume的内部实现中事务是一个重要的概念,事务保证了数据的可用性(有别于数据库中的事务)。

下图的数据流是spooling directory source-> memory channel-> kafka sink,其中memory channel维护了两个事务,分别是put事务和take事务。

 

下面简要介绍一下事务的代码实现:

1. doPut(放入event),该操作由source触发。

1.1 一般的source会在process方法中,将event提交到channel。但是spoolingdirectorysource比较特殊,它针对一个本地目录创建了ReliableSpoolingFileEventReader对象, 并起一个定时线程去读取其中的文件。在线程中,取到一批event后(文件中一行即为一个event),直接调用ChannelProcessor的processEventBatch方法,将这批数据提交到channel中。

1.2 在ChannelProcessor对象中,调用栈为:reqChannel.put(event) -> BasicChannelSemantics.put -> BasicTransactionSemantics.put -> BasicTransactionSemantics.doPut。其中doPut是一个抽象方法,其具体实现放在各个channel的Transaction中。这里使用的memoryChannel。

//spoolingdirectorysource.java
public void run() {
  try {
    while (!Thread.interrupted()) {
       List<Event> events = reader.readEvents(batchSize);
       ...
       try {
         getChannelProcessor().processEventBatch(events);
       ...}
//ChannelProcessor.java  
public void processEventBatch(List<Event> events) {
    ...
    for (Event event : events) {
      //获取该event绑定的channel
      List<Channel> reqChannels = selector.getRequiredChannels(event);
      for (Channel ch : reqChannels) {
        List<Event> eventQueue = reqChannelQueue.get(ch);
        if (eventQueue == null) {
          eventQueue = new ArrayList<Event>();
          reqChannelQueue.put(ch, eventQueue);
        }
        eventQueue.add(event);
      }
    }
    for (Channel reqChannel : reqChannelQueue.keySet()) {
      Transaction tx = reqChannel.getTransaction(); //获取对应的事务
      try {
        tx.begin(); //事务开始      
        List<Event> batch = reqChannelQueue.get(reqChannel);
        for (Event event : batch) {
          reqChannel.put(event);//处理事务,先将event写入内存,然后由commit批量写入
        } 
        tx.commit(); //事务确认
      } catch (Throwable t) {
        tx.rollback(); //发生意外,事务回滚
        ...
      }
    }
  }
MemoryChannel.java

  protected void doPut(Event event) throws InterruptedException {
    int eventByteSize = (int) Math.ceil(estimateEventSize(event) / byteCapacitySlotSize);
    if (!putList.offer(event)) { //将event放到putList中
      throw new ChannelException("Put queue for MemoryTransaction of capacity " +
              putList.size() + " full, consider committing more frequently, " +
              "increasing capacity or increasing thread count");
    }
    putByteCounter += eventByteSize;
  }

2. getTransaction(获取事务)

该操作最终调用到绑定的channel对象,这里指MemoryChannel.java中的createTransaction方法,在该方法中创建了一个对应的MemoryTransaction对象,设置了channel的容量大小等属性。

 

3. doCommit

实现在channel对象的doCommit()方法中,当一批event正确的放到channel内存中后或者正确的从channel拿出后,调用该方法。

 

4. doTake(拿出event)

该操作由sink触发,最终调用到channel对象的doTake()方法中,依次从channel对象的queue中获取event,放到takeList中。

 

5. doRollback(回滚)

sink写出失败的event,还保存channel对象的在takeList中。当调用这个方法时,说明没有进行前发生异常了,所以takeList并未clear。方法中会把takeList中的最后一个元素,循环取出放回queue的第一个,下次继续操作这些event。

 

总结来说,事务是保证flume可靠性的一个机制,下面是flume可靠性的总结,这个总结大部分是从网上摘抄的,加上了一点自己的理解

事务机制

Flume使用两个独立的事务分别负责soucrce到channel,以及channel到sink的事件传递。例如kafka source,它为消费到的一条记录创建一个事件,一旦事务中所有的事件全部传递到channel且提交成功,那么source就将该文件标记为完成。同理,事务以类似的方式处理从channel到sink的传递过程,如果因为某种原因使得事件无法记录,那么事务将会回滚。且所有的事件都会保持到channel中,等待重新传递。

At-least-once提交方式

Flume的事务机制保证了source产生的每个事件都会传送到sink中。但实际上Flume作为高容量并行采集系统采用的是At-least-once提交方式,会造成每个source产生的事件至少到达sink一次,也就是同一事件有可能重复到达。这样虽然看上去是一个缺陷,但是相比为了保证Flume能够可靠地将事件从source, channel传递到sink,这也是一个可以接受的权衡。

批处理机制

     为了提高效率,Flume尽可能的以事务为单位来处理事件,而不是逐一基于事件进行处理。比如kafka source默认以消费100个event作为一个批次(BatchSize属性来配置,类似数据库的批处理模式)。

没有更多推荐了,返回首页