Flume架构与源码分析-核心组件分析-1

开发 开发工具
start方法在整个Flume启动时或者初始化组件时都会调用start方法进行组件初始化,Flume组件出现异常停止时会调用stop,getLifecycleState返回组件的生命周期状态,有IDLE, START, STOP, ERROR四个状态。

[[177130]]

首先所有核心组件都会实现org.apache.flume.lifecycle.LifecycleAware接口:

Java代码

  1. public interface LifecycleAware {   
  2.   public void start();   
  3.   public void stop();   
  4.   public LifecycleState getLifecycleState();   
  5. }   

start方法在整个Flume启动时或者初始化组件时都会调用start方法进行组件初始化,Flume组件出现异常停止时会调用stop,getLifecycleState返回组件的生命周期状态,有IDLE, START, STOP, ERROR四个状态。

如果开发的组件需要配置,如设置一些属性;可以实现org.apache.flume.conf.Configurable接口:

Java代码

  1. public interface Configurable {   
  2.    public void configure(Context context);   
  3. }   

Flume在启动组件之前会调用configure来初始化组件一些配置。

1、Source

Source用于采集日志数据,有两种实现方式:轮训拉取和事件驱动机制;Source接口如下:

Java代码

  1. public interface Source extends LifecycleAware, NamedComponent {   
  2.   public void setChannelProcessor(ChannelProcessor channelProcessor);   
  3.   public ChannelProcessor getChannelProcessor();   
  4. }    

Source接口首先继承了LifecycleAware接口,然后只提供了ChannelProcessor的setter和getter接口,也就是说它的的所有逻辑的实现应该在LifecycleAware接口的start和stop中实现;ChannelProcessor之前介绍过用来进行日志流的过滤和Channel的选择及调度。

而Source是通过SourceFactory工厂创建,默认提供了DefaultSourceFactory,其首先通过Enum类型org.apache.flume.conf.source.SourceType查找默认实现,如exec,则找到org.apache.flume.source.ExecSource实现,如果找不到直接Class.forName(className)创建。

Source提供了两种机制: PollableSource(轮训拉取)和EventDrivenSource(事件驱动):

PollableSource默认提供了如下实现:

比如JMSSource实现使用javax.jms.MessageConsumer.receive(pollTimeout)主动去拉取消息。

EventDrivenSource默认提供了如下实现:

比如NetcatSource、HttpSource就是事件驱动,即被动等待;比如HttpSource就是内部启动了一个内嵌的Jetty启动了一个Servlet容器,通过FlumeHTTPServlet去接收消息。

Flume提供了SourceRunner用来启动Source的流转:

Java代码

  1. public class EventDrivenSourceRunner extends SourceRunner {   
  2.   private LifecycleState lifecycleState;   
  3.   public EventDrivenSourceRunner() {   
  4.       lifecycleState = LifecycleState.IDLE; //启动之前是空闲状态   
  5.   }   
  6.    
  7.   @Override   
  8.   public void start() {   
  9.     Source source = getSource(); //获取Source   
  10.     ChannelProcessor cp = source.getChannelProcessor(); //Channel处理器   
  11.     cp.initialize(); //初始化Channel处理器   
  12.     source.start();  //启动Source   
  13.     lifecycleState = LifecycleState.START; //本组件状态改成启动状态   
  14.   }   
  15.   @Override   
  16.   public void stop() {   
  17.     Source source = getSource(); //先停Source   
  18.     source.stop();   
  19.     ChannelProcessor cp = source.getChannelProcessor();   
  20.     cp.close();//再停Channel处理器   
  21.     lifecycleState = LifecycleState.STOP; //本组件状态改成停止状态   
  22.   }   
  23. }    

从本组件也可以看出:1、首先要初始化ChannelProcessor,其实现时初始化过滤器链;2、接着启动Source并更改本组件的状态。

Java代码

  1. public class PollableSourceRunner extends SourceRunner {   
  2.  @Override   
  3.  public void start() {   
  4.   PollableSource source = (PollableSource) getSource();   
  5.   ChannelProcessor cp = source.getChannelProcessor();   
  6.   cp.initialize();   
  7.   source.start();   
  8.    
  9.   runner = new PollingRunner();   
  10.   runner.source = source;   
  11.   runner.counterGroup = counterGroup;   
  12.   runner.shouldStop = shouldStop;   
  13.    
  14.   runnerThread = new Thread(runner);   
  15.   runnerThread.setName(getClass().getSimpleName() + "-" +    
  16.       source.getClass().getSimpleName() + "-" + source.getName());   
  17.   runnerThread.start();    
  18.    
  19.   lifecycleState = LifecycleState.START;   
  20.  }   
  21. }    

而PollingRunner首先初始化组件,但是又启动了一个线程PollingRunner,其作用就是轮训拉取数据:

Java代码

  1. @Override   
  2.   public void run() {   
  3.     while (!shouldStop.get()) { //如果没有停止,则一直在死循环运行   
  4.       counterGroup.incrementAndGet("runner.polls");   
  5.    
  6.       try {   
  7.         //调用PollableSource的process方法进行轮训拉取,然后判断是否遇到了失败补偿   
  8.         if (source.process().equals(PollableSource.Status.BACKOFF)) {/   
  9.           counterGroup.incrementAndGet("runner.backoffs");   
  10.    
  11.           //失败补偿时暂停线程处理,等待超时时间之后重试   
  12.           Thread.sleep(Math.min(   
  13.               counterGroup.incrementAndGet("runner.backoffs.consecutive")   
  14.               * source.getBackOffSleepIncrement(), source.getMaxBackOffSleepInterval()));   
  15.         } else {   
  16.           counterGroup.set("runner.backoffs.consecutive", 0L);   
  17.         }   
  18.       } catch (InterruptedException e) {   
  19.                 }   
  20.       }   
  21.     }   
  22.   }   
  23. }    

Flume在启动时会判断Source是PollableSource还是EventDrivenSource来选择使用PollableSourceRunner还是EventDrivenSourceRunner。

比如HttpSource实现,其通过FlumeHTTPServlet接收消息然后:

Java代码

  1. List<Event> events = Collections.emptyList(); //create empty list   
  2. //首先从请求中获取Event   
  3. events = handler.getEvents(request);   
  4. //然后交给ChannelProcessor进行处理   
  5. getChannelProcessor().processEventBatch(events);    

到此基本的Source流程就介绍完了,其作用就是监听日志,采集,然后交给ChannelProcessor进行处理。

2、Channel

Channel用于连接Source和Sink,Source生产日志发送到Channel,Sink从Channel消费日志;也就是说通过Channel实现了Source和Sink的解耦,可以实现多对多的关联,和Source、Sink的异步化。

之前Source采集到日志后会交给ChannelProcessor处理,那么接下来我们先从ChannelProcessor入手,其依赖三个组件:

Java代码

  1. private final ChannelSelector selector;  //Channel选择器   
  2. private final InterceptorChain interceptorChain; //拦截器链   
  3. private ExecutorService execService; //用于实现可选Channel的ExecutorService,默认是单线程实现    

接下来看下其是如何处理Event的:

Java代码

  1. public void processEvent(Event event) {   
  2.   event = interceptorChain.intercept(event); //首先进行拦截器链过滤   
  3.   if (event == null) {   
  4.     return;   
  5.   }   
  6.   List<Event> events = new ArrayList<Event>(1);   
  7.   events.add(event);   
  8.    
  9.   //通过Channel选择器获取必须成功处理的Channel,然后事务中执行   
  10.   List<Channel> requiredChannels = selector.getRequiredChannels(event);   
  11.   for (Channel reqChannel : requiredChannels) {    
  12.     executeChannelTransaction(reqChannel, events, false);   
  13.   }   
  14.    
  15.   //通过Channel选择器获取可选的Channel,这些Channel失败是可以忽略,不影响其他Channel的处理   
  16.   List<Channel> optionalChannels = selector.getOptionalChannels(event);   
  17.   for (Channel optChannel : optionalChannels) {   
  18.     execService.submit(new OptionalChannelTransactionRunnable(optChannel, events));   
  19.   }   
  20. }    

另外内部还提供了批处理实现方法processEventBatch;对于内部事务实现的话可以参考executeChannelTransaction方法,整体事务机制类似于JDBC:

Java代码

  1. private static void executeChannelTransaction(Channel channel, List<Event> batch, boolean isOptional) {   
  2.   //1、获取Channel上的事务   
  3.   Transaction tx = channel.getTransaction();   
  4.   Preconditions.checkNotNull(tx, "Transaction object must not be null");   
  5.   try {   
  6.     //2、开启事务   
  7.     tx.begin();   
  8.     //3、在Channel上执行批量put操作   
  9.     for (Event event : batch) {   
  10.       channel.put(event);   
  11.     }   
  12.     //4、成功后提交事务   
  13.     tx.commit();   
  14.   } catch (Throwable t) {   
  15.     //5、异常后回滚事务   
  16.     tx.rollback();   
  17.     if (t instanceof Error) {   
  18.        LOG.error("Error while writing to channel: " +   
  19.            channel, t);   
  20.        throw (Error) t;   
  21.     } else if(!isOptional) {//如果是可选的Channel,异常忽略   
  22.        throw new ChannelException("Unable to put batch on required " +   
  23.              "channel: " + channel, t);   
  24.     }   
  25.   } finally {   
  26.     //***关闭事务   
  27.     tx.close();   
  28.   }   
  29. }   

Interceptor用于过滤Event,即传入一个Event然后进行过滤加工,然后返回一个新的Event,接口如下:

Java代码

  1. public interface Interceptor {   
  2.     public void initialize();   
  3.     public Event intercept(Event event);   
  4.     public List<Event> intercept(List<Event> events);   
  5.     public void close();   
  6. }    

可以看到其提供了initialize和close方法用于启动和关闭;intercept方法用于过滤或加工Event。比如HostInterceptor拦截器用于获取本机IP然后默认添加到Event的字段为host的Header中。

接下来就是ChannelSelector选择器了,其通过如下方式创建:

Java代码

  1. //获取ChannelSelector配置,比如agent.sources.s1.selector.type = replicating   
  2. ChannelSelectorConfiguration selectorConfig = config.getSelectorConfiguration();   
  3. //使用Source关联的Channel创建,比如agent.sources.s1.channels = c1 c2   
  4. ChannelSelector selector = ChannelSelectorFactory.create(sourceChannels, selectorConfig);    

ChannelSelector默认提供了两种实现:复制和多路复用:

默认实现是复制选择器ReplicatingChannelSelector,即把接收到的消息复制到每一个Channel;多路复用选择器MultiplexingChannelSelector会根据Event Header中的参数进行选择,以此来选择使用哪个Channel。

而Channel是Event中转的地方,Source发布Event到Channel,Sink消费Channel的Event;Channel接口提供了如下接口用来实现Event流转:

Java代码

  1. public interface Channel extends LifecycleAware, NamedComponent {   
  2.   public void put(Event event) throws ChannelException;   
  3.   public Event take() throws ChannelException;   
  4.   public Transaction getTransaction();   
  5. }    

put用于发布Event,take用于消费Event,getTransaction用于事务支持。默认提供了如下Channel的实现:

对于Channel的实现我们后续单独章节介绍。

3、Sink

Sink从Channel消费Event,然后进行转移到收集/聚合层或存储层。Sink接口如下所示:

Java代码

  1. public interface Sink extends LifecycleAware, NamedComponent {   
  2.   public void setChannel(Channel channel);   
  3.   public Channel getChannel();   
  4.   public Status process() throws EventDeliveryException;   
  5.   public static enum Status {   
  6.     READY, BACKOFF   
  7.   }   
  8. }    

类似于Source,其首先继承了LifecycleAware,然后提供了Channel的getter/setter方法,并提供了process方法进行消费,此方法会返回消费的状态,READY或BACKOFF。

Sink也是通过SinkFactory工厂来创建,其也提供了DefaultSinkFactory默认工厂,比如传入hdfs,会先查找Enum org.apache.flume.conf.sink.SinkType,然后找到相应的默认处理类org.apache.flume.sink.hdfs.HDFSEventSink,如果没找到默认处理类,直接通过Class.forName(className)进行反射创建。

我们知道Sink还提供了分组功能,用于把多个Sink聚合为一组进行使用,内部提供了SinkGroup用来完成这个事情。此时问题来了,如何去调度多个Sink,其内部使用了SinkProcessor来完成这个事情,默认提供了故障转移和负载均衡两个策略。

首先SinkGroup就是聚合多个Sink为一组,然后将多个Sink传给SinkProcessorFactory进行创建SinkProcessor,而策略是根据配置文件中配置的如agent.sinkgroups.g1.processor.type = load_balance来选择的。

SinkProcessor提供了如下实现:

DefaultSinkProcessor:默认实现,用于单个Sink的场景使用。

FailoverSinkProcessor:故障转移实现:

Java代码

  1. public Status process() throws EventDeliveryException {   
  2.   Long now = System.currentTimeMillis();   
  3.     //1、首先检查失败队列的头部的Sink是否已经过了失败补偿等待时间了   
  4.   while(!failedSinks.isEmpty() && failedSinks.peek().getRefresh() < now) {   
  5.     //2、如果可以使用了,则从失败Sink队列获取队列***个Sink   
  6.     FailedSink cur = failedSinks.poll();   
  7.     Status s;   
  8.     try {   
  9.       s = cur.getSink().process(); //3、使用此Sink进行处理   
  10.       if (s  == Status.READY) { //4、如果处理成功   
  11.         liveSinks.put(cur.getPriority(), cur.getSink()); //4.1、放回存活Sink队列   
  12.         activeSink = liveSinks.get(liveSinks.lastKey());   
  13.       } else {   
  14.         failedSinks.add(cur); //4.2、如果此时不是READY,即BACKOFF期间,再次放回失败队列   
  15.       }   
  16.       return s;   
  17.     } catch (Exception e) {   
  18.       cur.incFails(); //5、如果遇到异常了,则增加失败次数,并放回失败队列   
  19.       failedSinks.add(cur);   
  20.     }   
  21.   }   
  22.    
  23.   Status ret = null;   
  24.   while(activeSink != null) { //6、此时失败队列中没有Sink能处理了,那么需要使用存活Sink队列进行处理   
  25.     try {   
  26.       ret = activeSink.process();   
  27.       return ret;   
  28.     } catch (Exception e) { //7、处理失败进行转移到失败队列   
  29.       activeSink = moveActiveToDeadAndGetNext();   
  30.     }   
  31.   }   
  32.    
  33.   throw new EventDeliveryException("All sinks failed to process, " +   
  34.       "nothing left to failover to");   
  35. }   

失败队列是一个优先级队列,使用refresh属性排序,而refresh是通过如下机制计算的:

Java代码

  1. refresh = System.currentTimeMillis() 
  2. + Math.min(maxPenalty, (1 << sequentialFailures) * FAILURE_PENALTY); 

其中maxPenalty是***等待时间,默认30s,而(1 << sequentialFailures) * FAILURE_PENALTY)用于实现指数级等待时间递增, FAILURE_PENALTY是1s。

LoadBalanceSinkProcessor:用于实现Sink的负载均衡,其通过SinkSelector进行实现,类似于ChannelSelector。LoadBalanceSinkProcessor在启动时会根据配置,如agent.sinkgroups.g1.processor.selector = random进行选择,默认提供了两种选择器:

LoadBalanceSinkProcessor使用如下机制进行负载均衡:

Java代码

  1. public Status process() throws EventDeliveryException {   
  2.   Status status = null;   
  3.   //1、使用选择器创建相应的迭代器,也就是用来选择Sink的迭代器   
  4.   Iterator<Sink> sinkIterator = selector.createSinkIterator();   
  5.   while (sinkIterator.hasNext()) {   
  6.     Sink sink = sinkIterator.next();   
  7.     try {   
  8.       //2、选择器迭代Sink进行处理,如果成功直接break掉这次处理,此次负载均衡就算完成了   
  9.       status = sink.process();   
  10.       break;   
  11.     } catch (Exception ex) {   
  12.       //3、失败后会通知选择器,采取相应的失败退避补偿算法进行处理   
  13.       selector.informSinkFailed(sink);   
  14.       LOGGER.warn("Sink failed to consume event. "   
  15.           + "Attempting next sink if available.", ex);   
  16.     }   
  17.   }   
  18.   if (status == null) {   
  19.     throw new EventDeliveryException("All configured sinks have failed");   
  20.   }   
  21.   return status;   
  22. }    

如上的核心就是怎么创建迭代器,如何进行失败退避补偿处理,首先我们看下RoundRobinSinkSelector实现,其内部是通过通用的RoundRobinOrderSelector选择器实现:

Java代码

  1. public Iterator<T> createIterator() {   
  2.   //1、获取存活的Sink索引,   
  3.   List<Integer> activeIndices = getIndexList();   
  4.   int size = activeIndices.size();   
  5.   //2、如果上次记录的下一个存活Sink的位置超过了size,那么从队列头重新开始计数   
  6.   if (nextHead >= size) {   
  7.     nextHead = 0;   
  8.   }   
  9.   //3、获取本次使用的起始位置   
  10.   int begin = nextHead++;   
  11.   if (nextHead == activeIndices.size()) {   
  12.     nextHead = 0;   
  13.   }   
  14.   //4、从该位置开始迭代,其实现类似于环形队列,比如整个队列是5,起始位置是3,则按照 3、4、0、1、2的顺序进行轮训,实现了轮训算法    
  15.   int[] indexOrder = new int[size];   
  16.   for (int i = 0; i < size; i++) {   
  17.     indexOrder[i] = activeIndices.get((begin + i) % size);   
  18.   }   
  19.   //indexOrder是迭代顺序,getObjects返回相关的Sinks;   
  20.   return new SpecificOrderIterator<T>(indexOrder, getObjects());   
  21. }    

getIndexList实现如下:

Java代码

  1. protected List<Integer> getIndexList() {   
  2.   long now = System.currentTimeMillis();   
  3.   List<Integer> indexList = new ArrayList<Integer>();   
  4.   int i = 0;   
  5.   for (T obj : stateMap.keySet()) {   
  6.     if (!isShouldBackOff() || stateMap.get(obj).restoreTime < now) {   
  7.       indexList.add(i);   
  8.     }   
  9.     i++;   
  10.   }   
  11.   return indexList;   
  12. }   

isShouldBackOff()表示是否开启退避算法支持,如果不开启,则认为每个Sink都是存活的,每次都会重试,通过agent.sinkgroups.g1.processor.backoff = true配置开启,默认false;restoreTime和之前介绍的refresh一样,是退避补偿等待时间,算法类似,就不多介绍了。

那么什么时候调用Sink进行消费呢?其类似于SourceRunner,Sink提供了SinkRunner进行轮训拉取处理,SinkRunner会轮训调度SinkProcessor消费Channel的消息,然后调用Sink进行转移。SinkProcessor之前介绍过,其负责消息复制/路由。

SinkRunner实现如下:

Java代码

  1. public void start() {   
  2.   SinkProcessor policy = getPolicy();   
  3.   policy.start();   
  4.   runner = new PollingRunner();   
  5.   runner.policy = policy;   
  6.   runner.counterGroup = counterGroup;   
  7.   runner.shouldStop = new AtomicBoolean();   
  8.   runnerThread = new Thread(runner);   
  9.   runnerThread.setName("SinkRunner-PollingRunner-" +   
  10.       policy.getClass().getSimpleName());   
  11.   runnerThread.start();   
  12.   lifecycleState = LifecycleState.START;   
  13. }    

即获取SinkProcessor然后启动它,接着启动轮训线程去处理。PollingRunner线程负责轮训消息,核心实现如下:

Java代码

  1. public void run() {   
  2.   while (!shouldStop.get()) { //如果没有停止   
  3.     try {   
  4.       if (policy.process().equals(Sink.Status.BACKOFF)) {//如果处理失败了,进行退避补偿处理   
  5.         counterGroup.incrementAndGet("runner.backoffs");   
  6.         Thread.sleep(Math.min(   
  7.             counterGroup.incrementAndGet("runner.backoffs.consecutive")   
  8.             * backoffSleepIncrement, maxBackoffSleep)); //暂停退避补偿设定的超时时间   
  9.       } else {   
  10.         counterGroup.set("runner.backoffs.consecutive", 0L);   
  11.       }   
  12.     } catch (Exception e) {   
  13.       try {   
  14.         Thread.sleep(maxBackoffSleep); //如果遇到异常则等待***退避时间   
  15.       } catch (InterruptedException ex) {   
  16.         Thread.currentThread().interrupt();   
  17.       }   
  18.     }   
  19.   }   
  20. }    

整体实现类似于PollableSourceRunner实现,整体处理都是交给SinkProcessor完成的。SinkProcessor会轮训Sink的process方法进行处理;此处以LoggerSink为例:

Java代码

  1. @Override   
  2. public Status process() throws EventDeliveryException {   
  3.   Status result = Status.READY;   
  4.   Channel channel = getChannel();   
  5.   //1、获取事务   
  6.   Transaction transaction = channel.getTransaction();   
  7.   Event event = null;   
  8.    
  9.   try {   
  10.     //2、开启事务   
  11.     transaction.begin();   
  12.     //3、从Channel获取Event   
  13.     event = channel.take();   
  14.     if (event != null) {   
  15.       if (logger.isInfoEnabled()) {   
  16.         logger.info("Event: " + EventHelper.dumpEvent(event, maxBytesToLog));   
  17.       }   
  18.     } else {//4、如果Channel中没有Event,则默认进入故障补偿机制,即防止死循环造成CPU负载高   
  19.       result = Status.BACKOFF;   
  20.     }   
  21.     //5、成功后提交事务   
  22.     transaction.commit();   
  23.   } catch (Exception ex) {   
  24.     //6、失败后回滚事务   
  25.     transaction.rollback();   
  26.     throw new EventDeliveryException("Failed to log event: " + event, ex);   
  27.   } finally {   
  28.     //7、关闭事务   
  29.     transaction.close();   
  30.   }   
  31.   return result;   
  32. }    

Sink中一些实现是支持批处理的,比如RollingFileSink:

Java代码

  1. //1、开启事务   
  2. //2、批处理   
  3. for (int i = 0; i < batchSize; i++) {   
  4.   event = channel.take();   
  5.   if (event != null) {   
  6.     sinkCounter.incrementEventDrainAttemptCount();   
  7.     eventAttemptCounter++;   
  8.     serializer.write(event);   
  9.   }   
  10. }   
  11. //3、提交/回滚事务、关闭事务   

定义一个批处理大小然后在事务中执行批处理。

【本文是51CTO专栏作者张开涛的原创文章,作者微信公众号:开涛的博客,id:kaitao-1234567】

责任编辑:武晓燕 来源: 开涛的博客
相关推荐

2016-11-29 09:38:06

Flume架构核心组件

2016-11-25 13:14:50

Flume架构源码

2016-11-29 16:59:46

Flume架构源码

2022-06-07 10:33:29

Camera组件鸿蒙

2015-04-24 09:33:11

Cloud Found组件分析PaaS

2021-09-05 07:35:58

lifecycleAndroid组件原理

2011-04-29 13:40:37

MongoDBCommand

2009-12-31 15:55:06

ADO.NET结构

2022-01-05 08:53:13

Spring原理分析MVC

2016-10-21 13:03:18

androidhandlerlooper

2021-09-08 10:47:33

Flink执行流程

2015-08-11 15:52:52

大数据数据分析

2019-10-08 10:01:22

Kafka应用场景架构

2022-07-17 06:51:22

Vite 3.0前端

2017-05-04 22:30:17

Zuul过滤器微服务

2011-05-26 10:05:48

MongoDB

2019-10-16 16:33:41

Docker架构语言

2022-03-18 15:55:15

鸿蒙操作系统架构

2011-03-15 11:33:18

iptables

2020-08-06 08:26:22

Kubernetes架构开发
点赞
收藏

51CTO技术栈公众号