<Flume><Source Code><Flume源码阅读笔记>
Overview
source采集的日志首先会传入ChannelProcessor, 在其内首先会通过Interceptors进行过滤加工,然后通过ChannelSelector选择channel。
Source和Sink之间是异步的,sink只需要监听自己关系的Channel的变化即可。
sink存在写失败的情况,flume提供了如下策略:
默认是一个sink,若写入失败,则该事务失败,稍后重试。
故障转移策略:给多个sink定义优先级,失败时会路由到下一个优先级的sink。sink只要抛出一次异常就会被认为是失败了,则从存活Sink中移除,然后指数级时间等待重试,默认是等待1s开始重试,最大等待重试时间是30s.
flume还提供了负载均衡策略:默认提供轮训和随机两种算法。通过抽象一个类似ChannelSelector的SinkSelector进行选择。
以上,对于Source和sink如何异步、channel如何实现事务机制,详见后面的具体源码分析。
The whole process
首先是flume的启动, 提供了两种启动方式:使用EmbeddedAgent内嵌在Java应用中或使用Application单独启动一个进程。 一般使用Application起一个进程比较多,我们这里也主要分析这种方式。
程序入口:org.apache.flume.node.Application的main方法。
注:因为暂时还没有了解到Zookeeper原理,所以这里关于ZK的部分就跳过了。
flume启动流程大致如下:
设置默认值启动参数,参数是否是必须的
Options options = new Options();
Option option = new Option("n", "name", true, "the name of this agent");
option.setRequired(true);
options.addOption(option);
option = new Option("f", "conf-file", true,
"specify a config file (required if -z missing)");
option.setRequired(false);
options.addOption(option);
......解析命令行参数
if (commandLine.hasOption('h')) {
new HelpFormatter().printHelp("flume-ng agent", options, true);
return;
}
String agentName = commandLine.getOptionValue('n');
boolean reload = !commandLine.hasOption("no-reload-conf"); // 是否reload配置文件
if (commandLine.hasOption('z') || commandLine.hasOption("zkConnString")) {
isZkConfigured = true;
}Zookepper相关:暂时略
打开配置文件
if (isZkConfigured) {
... // 若配置了zk,则使用zk参数启动
} else {
// 打开配置文件,如果不存在则快速失败
File configurationFile = new File(commandLine.getOptionValue('f'));
// 确保没有配置文件的时候agent会启动失败
if (!configurationFile.exists()) {
...// If command line invocation, then need to fail fast
}
List<LifecycleAware> components = Lists.newArrayList();
// 若需要定期reload配置文件
if (reload) {
// 使用EventBus事件总线, to allow publish-subscribe-style communication
EventBus eventBus = new EventBus(agentName + "-event-bus");
// 读取配置文件,使用定期轮训拉起策略,默认30s拉取一次
PollingPropertiesFileConfigurationProvider configurationProvider =
new PollingPropertiesFileConfigurationProvider(
agentName, configurationFile, eventBus, 30);
components.add(configurationProvider);
// 向Application注册组件
application = new Application(components);
// 向EventBus注册本应用,EB会自动注册Application中使用@Subscribe声明的方法
// TODO: EventBus, and why reload configuration
eventBus.register(application);
} else {
// 若配置文件不支持定期reload
PropertiesFileConfigurationProvider configurationProvider =
new PropertiesFileConfigurationProvider(agentName, configurationFile);
application = new Application();
// 直接使用配置文件初始化Flume组件
application.handleConfigurationEvent(configurationProvider.getConfiguration());
}
}reload conf:若需要reload,则使用事件总线EventBus实现,Application的handleConfigurationEvent是事件订阅者,PollingPropertiesFileConfigurationProvider是事件发布者,其会定期轮训检查文件是否变更,如果变更则重新读取配置文件,发布配置文件事件变更,而handleConfigurationEvent会收到该配置变更重新进行初始化。
handleConfigurationEvent:
@Subscribe
public synchronized void handleConfigurationEvent(MaterializedConfiguration conf) {
// MaterializedConfiguration存储Flume运行时需要的组件:Source、Channel、Sink、SourceRunner、SinkRunner等。其是通过ConfigurationProvider进行初始化获取,比如PollingPropertiesFileConfigurationProvider会读取配置文件然后进行组件的初始化。
stopAllComponents(); // 停止所有组件
startAllComponents(conf);// 使用配置文件初始化所有组件
}startAllComponents
要首先启动channels,等待所有channels启动才能继续。然后启动SinkRunner,准备好消费者。最后启动SourceRunner开始进行采集日志。
LifecycleSupervisor是组件守护哨兵,对这些组件进行守护,出问题时默认策略是自动重启。
这里的启动都是supervisor.supervise(entry.getValue(),new SupervisorPolicy.AlwaysRestartPolicy(), LifecycleState.START); 这是如何启动的,我们后面再介绍。
private void startAllComponents(MaterializedConfiguration materializedConfiguration) {
logger.info("Starting new configuration:{}", materializedConfiguration);
this.materializedConfiguration = materializedConfiguration;
// 启动channels。
for (Entry<String, Channel> entry :
materializedConfiguration.getChannels().entrySet()) {
try {
logger.info("Starting Channel " + entry.getKey());
// TODO: LifecycleSupervisor启动
// new SupervisorPolicy.AlwaysRestartPolicy():使用失败时总是重启的策略
// LifecycleState.START: 初始化组件默认状态为START
supervisor.supervise(entry.getValue(),
new SupervisorPolicy.AlwaysRestartPolicy(), LifecycleState.START);
} catch (Exception e) {
logger.error("Error while starting {}", entry.getValue(), e);
}
}
/*
* Wait for all channels to start.
*/
for (Channel ch : materializedConfiguration.getChannels().values()) {
while (ch.getLifecycleState() != LifecycleState.START
&& !supervisor.isComponentInErrorState(ch)) {
try {
logger.info("Waiting for channel: " + ch.getName() +
" to start. Sleeping for 500 ms");
Thread.sleep(500);
} catch (InterruptedException e) {
logger.error("Interrupted while waiting for channel to start.", e);
Throwables.propagate(e);
}
}
}
// 启动sinkRunner
for (Entry<String, SinkRunner> entry : materializedConfiguration.getSinkRunners().entrySet()) {
try {
logger.info("Starting Sink " + entry.getKey());
supervisor.supervise(entry.getValue(),
new SupervisorPolicy.AlwaysRestartPolicy(), LifecycleState.START);
} catch (Exception e) {
logger.error("Error while starting {}", entry.getValue(), e);
}
}
// 启动SourceRunner TODO: SourceRunner & SinkRunner
for (Entry<String, SourceRunner> entry :
materializedConfiguration.getSourceRunners().entrySet()) {
try {
logger.info("Starting Source " + entry.getKey());
supervisor.supervise(entry.getValue(),
new SupervisorPolicy.AlwaysRestartPolicy(), LifecycleState.START);
} catch (Exception e) {
logger.error("Error while starting {}", entry.getValue(), e);
}
}
this.loadMonitoring();
}之后main函数调用了
application.start();
/**
其循环Application注册的组件,然后守护哨兵对它进行守护,默认策略是出现问题会自动重启组件,假设我们支持reload配置文件,则之前启动Application时注册过PollingPropertiesFileConfigurationProvider组件,即该组件会被守护哨兵守护着,出现问题默认策略自动重启
**/
public synchronized void start() {
// private final List<LifecycleAware> components;
for (LifecycleAware component : components) {
// private final LifecycleSupervisor supervisor;
supervisor.supervise(component,
new SupervisorPolicy.AlwaysRestartPolicy(), LifecycleState.START);
}
}相应的stop函数。首先是main函数中:
final Application appReference = application;
// Runtinme.getRuntime(): Returns the runtime object associated with the current Java application.
/**
addShutdownHook: 注册一个新的虚拟机关闭钩子。
虚拟机shutdown有两种情况:1)当最后一个非守护进行户外那个退出或调用system.exit时,程序正常退出;2)JVM通过ctrl-c等被用户中断。
**/
Runtime.getRuntime().addShutdownHook(new Thread("agent-shutdown-hook") {
@Override
public void run() {
appReference.stop();
}
});
public synchronized void stop() {
// 关闭守护哨兵和监控服务。
supervisor.stop();
if (monitorServer != null) {
monitorServer.stop();
}
}至此,Application整个流程就分析完了。
整体流程可以总结为:
首先初始化命令行配置;
接着读取配置文件;
根据是否需要reload初始化配置文件中的组件;如果需要reload会使用EventBus进行发布订阅变化;
接着创建Application,创建守护哨兵LifecycleSupervisor,并先停止所有组件,接着启动所有组件;启动顺序:Channel、SinkRunner、SourceRunner,并把这些组件注册给守护哨兵、初始化监控服务MonitorService;停止顺序:SourceRunner、SinkRunner、Channel;
如果配置文件需要定期reload,则需要注册PollingPropertiesFileConfigurationProvider到守护哨兵;
最后注册虚拟机关闭钩子,停止守护哨兵和监控服务。
LifecycleSupervisor
守护哨兵,负责监控和重启组件
My: 所有需要被监控和重启的组件都应implements LifecycleAware
public class LifecycleSupervisor implements LifecycleAware {
public LifecycleSupervisor() {
lifecycleState = LifecycleState.IDLE;
// 存放被监控的组件
supervisedProcesses = new HashMap<LifecycleAware, Supervisoree>();
// 存放正在被监控的组件
monitorFutures = new HashMap<LifecycleAware, ScheduledFuture<?>>();
// 创建监控服务线程池
monitorService = new ScheduledThreadPoolExecutor(10,
new ThreadFactoryBuilder().setNameFormat(
"lifecycleSupervisor-" + Thread.currentThread().getId() + "-%d")
.build());
monitorService.setMaximumPoolSize(20);
monitorService.setKeepAliveTime(30, TimeUnit.SECONDS);
// 定期清理被取消的组件
purger = new Purger();
// 默认不进行清理
needToPurge = false;
}
... // start() & stop()... // 进行组件守护
public synchronized void supervise(LifecycleAware lifecycleAware,
SupervisorPolicy policy, LifecycleState desiredState) {
if (this.monitorService.isShutdown()
|| this.monitorService.isTerminated()
|| this.monitorService.isTerminating()) {
...// 如果哨兵已停止则抛出异常
}
// 初始化守护组件
Supervisoree process = new Supervisoree();
process.status = new Status();
// 默认策略是失败重启
process.policy = policy;
process.status.desiredState = desiredState; // 初始化组件默认状态,一般为START
process.status.error = false;
// 组件监控器,用于定时获取组件的最新状态,或重启组件。后面会介绍MonitorRunnable具体做什么。
MonitorRunnable monitorRunnable = new MonitorRunnable();
monitorRunnable.lifecycleAware = lifecycleAware;
monitorRunnable.supervisoree = process;
monitorRunnable.monitorService = monitorService;
supervisedProcesses.put(lifecycleAware, process);
// 以固定时间间隔执行monitorRunnable线程
// scheduleWithFixedDelay: Creates and executes a periodic action. If any execution of the task encounters an exception, subsequent executions are suppressed. Otherwise, the task will only terminate via cancellation or termination of the executor.
// 所以需要把所有异常捕获,才能保证定时任务继续执行。
ScheduledFuture<?> future = monitorService.scheduleWithFixedDelay(
monitorRunnable, 0, 3, TimeUnit.SECONDS);
monitorFutures.put(lifecycleAware, future);
}MonitorRunnable:负责进行组件状态迁移或组件故障恢复
public static class MonitorRunnable implements Runnable {
public ScheduledExecutorService monitorService;
public LifecycleAware lifecycleAware;
public Supervisoree supervisoree;
@Override
public void run() {
long now = System.currentTimeMillis();
try {
if (supervisoree.status.firstSeen == null) {
logger.debug("first time seeing {}", lifecycleAware);
supervisoree.status.firstSeen = now; // 记录第一次状态查看时间
}
supervisoree.status.lastSeen = now; // 记录最后一次状态查看时间
synchronized (lifecycleAware) {
// 如果守护组件被丢弃或出错了,则直接返回
if (supervisoree.status.discard) {
// 也就是此时已经调用了unsupervise
logger.info("Component has already been stopped {}", lifecycleAware);
return;
} else if (supervisoree.status.error) {
logger.info("Component {} is in error state, and Flume will not"
+ "attempt to change its state", lifecycleAware);
return;
}
// 更新最后一次查看到的状态
supervisoree.status.lastSeenState = lifecycleAware.getLifecycleState();
// 如果组件的状态和守护组件看到的状态不一致,则以守护组件的状态为准,然后进行初始化
if (!lifecycleAware.getLifecycleState().equals(
supervisoree.status.desiredState)) {
switch (supervisoree.status.desiredState) {
// 如果是启动状态,则启动组件。# 最开始的时候组件应该就是这么启动的
case START:
try {
lifecycleAware.start();
} catch (Throwable e) {
logger.error("Unable to start " + lifecycleAware
+ " - Exception follows.", e);
if (e instanceof Error) {
// This component can never recover, shut it down.
supervisoree.status.desiredState = LifecycleState.STOP;
try {
lifecycleAware.stop();
logger.warn("Component {} stopped, since it could not be"
+ "successfully started due to missing dependencies",
lifecycleAware);
} catch (Throwable e1) {
logger.error("Unsuccessful attempt to "
+ "shutdown component: {} due to missing dependencies."
+ " Please shutdown the agent"
+ "or disable this component, or the agent will be"
+ "in an undefined state.", e1);
supervisoree.status.error = true;
if (e1 instanceof Error) {
throw (Error) e1;
}
// Set the state to stop, so that the conf poller can
// proceed.
}
}
supervisoree.status.failures++;
}
break;
case STOP:
try {
lifecycleAware.stop();
} catch (Throwable e) {
logger.error("Unable to stop " + lifecycleAware
+ " - Exception follows.", e);
if (e instanceof Error) {
throw (Error) e;
}
supervisoree.status.failures++;
}
break;
default:
logger.warn("I refuse to acknowledge {} as a desired state",
supervisoree.status.desiredState);
}
if (!supervisoree.policy.isValid(lifecycleAware, supervisoree.status)) {
logger.error(
"Policy {} of {} has been violated - supervisor should exit!",
supervisoree.policy, lifecycleAware);
}
}
}
} catch (Throwable t) {
logger.error("Unexpected error", t);
}
logger.debug("Status check complete");
}
}
Source
SourceRunner
首先是SourceRunner,它控制how a source is driven。
它是一个用来实例化derived classes(派生类)的抽象类。 根据指定的source,来通过其内的static factory method 来实例化runner。
// 根据指定source的类型来实例化一个source runner的静态工厂方法
// 输入是要运行的source,返回可以运行指定source的runner
public static SourceRunner forSource(Source source) {
SourceRunner runner = null;
if (source instanceof PollableSource) {
runner = new PollableSourceRunner();
((PollableSourceRunner) runner).setSource((PollableSource) source);
} else if (source instanceof EventDrivenSource) {
runner = new EventDrivenSourceRunner();
((EventDrivenSourceRunner) runner).setSource((EventDrivenSource) source);
} else {
throw new IllegalArgumentException("No known runner type for source "
+ source);
}
return runner;
}
EventDrivenSourceRunner
starts、stops and manages EventDrivenSource event-driven sources
其内有如下几个方法:
构造方法
public EventDrivenSourceRunner() {
lifecycleState = LifecycleState.IDLE;
}start()
@Override
public void start() {
Source source = getSource(); //获取Source
ChannelProcessor cp = source.getChannelProcessor(); //Channel处理器
cp.initialize(); //初始化Channel处理器
source.start(); //启动Source
lifecycleState = LifecycleState.START; //本组件状态改成启动状态
}stop()、toString()、getLifecycleState()
PollableSourceRunner
public class PollableSourceRunner extends SourceRunner {
@Override
public void start() {
PollableSource source = (PollableSource) getSource(); //获取Source
ChannelProcessor cp = source.getChannelProcessor(); //Channel处理器
cp.initialize(); //初始化channel处理器
source.start(); //启动source
runner = new PollingRunner(); //新建一个PollingRunner线程来拉取数据
runner.source = source;
runner.counterGroup = counterGroup;
runner.shouldStop = shouldStop;
runnerThread = new Thread(runner);
runnerThread.setName(getClass().getSimpleName() + "-" +
source.getClass().getSimpleName() + "-" + source.getName());
runnerThread.start();
lifecycleState = LifecycleState.START;
}
}
PollingRunner线程
@Override
public void run() {
while (!shouldStop.get()) { //如果没有停止,则一直在死循环运行
counterGroup.incrementAndGet("runner.polls"); //原子操作
try {
//调用PollableSource的process方法进行轮训拉取,然后判断是否遇到了失败补偿
if (source.process().equals(PollableSource.Status.BACKOFF)) {/
counterGroup.incrementAndGet("runner.backoffs");
//失败补偿时暂停线程处理,等待超时时间之后重试
Thread.sleep(Math.min(
counterGroup.incrementAndGet("runner.backoffs.consecutive")
* source.getBackOffSleepIncrement(), source.getMaxBackOffSleepInterval()));
} else {
counterGroup.set("runner.backoffs.consecutive", 0L);
}
} catch (InterruptedException e) {
}
}
}
}
}
TODO
Source
public interface Source extends LifecycleAware, NamedComponent {
public void setChannelProcessor(ChannelProcessor channelProcessor);
public ChannelProcessor getChannelProcessor();
}
继承了LifecycleAware接口,然后只提供了ChannelProcessor的setter和getter接口。其中:
它的的所有逻辑的实现应该在LifecycleAware接口的start和stop中实现;
ChannelProcessor用来进行日志流的过滤和Channel的选择及调度。
由上述的Runner我们知道,Source 提供了两种机制: PollableSource (轮训拉取)和 EventDrivenSource (事件驱动)
Source作用就是监听日志,采集,然后交给ChannelProcessor处理。
EventDrivenSource
事件驱动型source不需要外部driver来获取event,EventDriven是一个implement Source的空接口。
从这里开始~~~‘
Channel
通过 Channel 实现了 Source 和 Sink 的解耦,可以实现多对多的关联,和 Source 、 Sink 的异步化
Channel exposes a transaction interface that can be used by its clients to ensure automic put(Event) and take() semantics.
ChannelProcesoor
前面我们了解到Source采集日志后会交给ChannelProcessor处理,so接下来我们从ChannelProcessor入手,其依赖如下组件:
private final ChannelSelector selector; //Channel选择器,.flume.ChannelSelector
private final InterceptorChain interceptorChain; //拦截器链,.flume.interceptor.InterceptorChain
private ExecutorService execService; //用于实现可选Channel的ExecutorService,默认是单线程实现 [注:这个我在某个博客上看到的,但这个组件我在ChannelProcessor中没有搜到]我们来看ChannelProcessor是如何处理Event的:
// Attempts to put the given event into each configured channel
public void processEvent(Event event) {
event = interceptorChain.intercept(event); //首先进行拦截器链过滤,TODO:intercep...
// InterceptorChain实现了Interceptor接口,调用a list of other Interceptors. 实现event的过滤和加工。具体见后面
if (event == null) {
return;
}
// Process required channels
//通过Channel选择器获取必须成功处理的Channel,然后事务中执行.
List<Channel> requiredChannels = selector.getRequiredChannels(event);
for (Channel reqChannel : requiredChannels) {
Transaction tx = reqChannel.getTransaction(); // 继承自Channel接口的类要实现getTransaction()方法,TODO:getTransaction
Preconditions.checkNotNull(tx, "Transaction object must not be null");
try {
tx.begin(); //开始事务
reqChannel.put(event); // 将event放到reqChannel
tx.commit(); //提交事务
} catch (Throwable t) {
tx.rollback(); // 如果捕捉到throwable(including Error & Exception),则回滚事务
if (t instanceof Error) {
LOG.error("Error while writing to required channel: " + reqChannel, t);
throw (Error) t;
} else if (t instanceof ChannelException) {
throw (ChannelException) t;
} else {
throw new ChannelException("Unable to put event on required " +
"channel: " + reqChannel, t); //TODO: Channelexception可能会被handle,不然如何保证RequiredChannel的成功处理?
}
} finally {
if (tx != null) {
tx.close(); // 最后如果事务非空,还得关闭该事务
}
}
}
// Process optional channels
//通过Channel选择器获取可选的Channel,这些Channel失败是可以忽略,不影响其他Channel的处理
List<Channel> optionalChannels = selector.getOptionalChannels(event);
for (Channel optChannel : optionalChannels) {
Transaction tx = null;
try {
tx = optChannel.getTransaction();
tx.begin();
optChannel.put(event);
tx.commit();
} catch (Throwable t) {
tx.rollback();
LOG.error("Unable to put event on optional channel: " + optChannel, t);
if (t instanceof Error) {
throw (Error) t;
}
} finally {
if (tx != null) {
tx.close();
}
}
}
}看下flume内实现的channel类
Channel接口
public interface Channel extends LifecycleAware, NamedComponent {
// put() and get() must be invoked within an active Transaction boundary
public void put(Event event) throws ChannelException;
public Event take() throws ChannelException;
// @return: the transaction instance associated with this channel
public Transaction getTransaction();
}
AbstractChannel
abstract class AbstractChannel implements Channel, LifecycleAware, Configurable
实现了lifecycleStatus的改变(在构造、start()和stop()方法中),实现了空configure()方法。没有做什么具体的channel相关的处理。
BasicChannelSemantics
基本Channel语义的实现,包括Transaction类的thread-local语义的实现。
public abstract class BasicChannelSemantics extends AbstractChannel {
// 1. 事务使用ThreadLocal存储,保证事务线程安全
private ThreadLocal<BasicTransactionSemantics> currentTransaction
= new ThreadLocal<BasicTransactionSemantics>();
private boolean initialized = false;
protected void initialize() {} // 2. 进行一些初始化工作
// 3.提供给实现类(子类)的创建事务的回调
// 用于new Transaction对象,该对象必须继承自BasicTransactionSemantics
// 比如MemoryChannel覆盖了该方法,方法体内new了一个实例,该实例为其内私有类MemoryTransaction,该私有类继承了BasicTransactionSemantics。
// MemoryTransaction内部用两条双向并发阻塞队列LinkedBlockingDeque实现putList和takeList。具体的稍后看,会介绍到MemoryChannel TODO
protected abstract BasicTransactionSemantics createTransaction();
// 4. 往Channel中放Event,其直接委托给事务的put方法
// 确保该thread存在一个事务,然后将put方法委托给该线程的BasicTransactionSemantics实例
@Override
public void put(Event event) throws ChannelException {
// ThreadLocal<BasicTransactionSemantics>的实例currentTransaction
// 即取得当前线程的事务实例
BasicTransactionSemantics transaction = currentTransaction.get();
Preconditions.checkState(transaction != null,
"No transaction exists for this thread");
transaction.put(event);
}
// 5.从Channel获取Event,也是直接委托给事务的take方法实现
@Override
public Event take() throws ChannelException {
BasicTransactionSemantics transaction = currentTransaction.get();
Preconditions.checkState(transaction != null,
"No transaction exists for this thread");
return transaction.take();
}
@Override
public Transaction getTransaction() {
// 1. 如果channel is not ready, then 初始化该channel
if (!initialized) {
synchronized (this) {
if (!initialized) {
initialize();
initialized = true;
}
}
}
// 2. 如果当前线程没有open的事务(无事务或已关闭),则创建一个,并绑定到currentTransaction中
BasicTransactionSemantics transaction = currentTransaction.get();
if (transaction == null || transaction.getState().equals(
BasicTransactionSemantics.State.CLOSED)) {
transaction = createTransaction();
currentTransaction.set(transaction);
}
return transaction;
}
}
MemoryChannel
当写入硬盘不实际或不需要数据持久化时,推荐使用。或在单元测试时使用。
大部分channel会把put和take委托给事务去完成。
纯内存的Channel实现,整个事务操作都是在内存中完成的。
每个事务都有一个TakeList和PutList,分别用于存储事务相关的取数据和放数据,等事务提交时才完全同步到Channel Queue,或者失败把取数据回滚到Channel Queue。 TODO:整体理解何时commit、rollback。
public class MemoryChannel extends BasicChannelSemantics {
// TODO: about factory
private static Logger LOGGER = LoggerFactory.getLogger(MemoryChannel.class);
...//一些常量定义:缺省值defaultCapacity、defaultTransCapacity、byteCapacitySlotSize..
// 内部类,继承自BasicTransactionSemantics。TODO: About BasicTransactionSemantics
private class MemoryTransaction extends BasicTransactionSemantics {
// 每个事务都有两条双向并发阻塞队列,TODO: LinkedBlockingDeque
private LinkedBlockingDeque<Event> takeList;
private LinkedBlockingDeque<Event> putList;
private final ChannelCounter channelCounter;
...//
public MemoryTransaction(int transCapacity, ChannelCounter counter) {
putList = new LinkedBlockingDeque<Event>(transCapacity);
takeList = new LinkedBlockingDeque<Event>(transCapacity);
channelCounter = counter;
}
// 将event放到putList中
// 整个doPut操作相对来说比较简单,就是往事务putList队列放入Event,如果满了则直接抛异常回滚事务;否则放入putList暂存,等事务提交时转移到Channel Queue。另外需要增加放入队列的字节数计数器,以便之后做字节容量限制
@Override
protected void doPut(Event event) throws InterruptedException {
// channelCounter是一个计数器,记录当前队列放入Event数、取出event数、成功数等。
channelCounter.incrementEventPutAttemptCount(); // 增加放入event计数器
// estimateEventSize计算当前Event body大小,ceil():向上取整
int eventByteSize = (int) Math.ceil(estimateEventSize(event) / byteCapacitySlotSize);
// 往事务队列的putList中放入Event,如果满了,则抛异常回滚事务
if (!putList.offer(event)) {
throw new ChannelException(
"Put queue for MemoryTransaction of capacity " +
putList.size() + " full, consider committing more frequently, " +
"increasing capacity or increasing thread count");
}
putByteCounter += eventByteSize; // 增加放入队列字节数计数器
}
// 从Channel Queue中取event放到takeList中
@Override
protected Event doTake() throws InterruptedException {
channelCounter.incrementEventTakeAttemptCount();
// 如果takeList队列没有剩余容量,即当前事务已经消费了最大容量的Event
if (takeList.remainingCapacity() == 0) {
throw new ChannelException("Take list for MemoryTransaction, capacity " +
takeList.size() + " full, consider committing more frequently, " +
"increasing capacity, or increasing thread count");
}
// queueStored试图获取一个信号量,超时直接返回null
if (!queueStored.tryAcquire(keepAlive, TimeUnit.SECONDS)) {
return null;
}
Event event;
// 从Channel Queue获取一个Event, 对Channel Queue的操作必须加queueLock
synchronized (queueLock) {
event = queue.poll();
}
// 因为信号量的保证,Channel Queue不应该返回null,出现了就不正常了
Preconditions.checkNotNull(event, "Queue.poll returned NULL despite semaphore " +
"signalling existence of entry");
// 暂存到事务的takeList队列
takeList.put(event);
// 计算当前Event body大小并增加取出队列字节数计数器
int eventByteSize = (int) Math.ceil(estimateEventSize(event) / byteCapacitySlotSize);
takeByteCounter += eventByteSize;
return event;
}
// 等事务提交时,才将当前事务的put list同步到Channel Queue
@Override
protected void doCommit() throws InterruptedException {
// /1、计算改变的Event数量,即取出数量-放入数
int remainingChange = takeList.size() - putList.size();
if (remainingChange < 0) {
// bytesRemaining是字节容量信号量,超出容量则回滚事务
if (!bytesRemaining.tryAcquire(putByteCounter, keepAlive, TimeUnit.SECONDS)) {
throw new ChannelException("Cannot commit transaction. Byte capacity " +
"allocated to store event body " + byteCapacity * byteCapacitySlotSize +
"reached. Please increase heap space/byte capacity allocated to " +
"the channel as the sinks may not be keeping up with the sources");
}
if (!queueRemaining.tryAcquire(-remainingChange, keepAlive, TimeUnit.SECONDS)) {
bytesRemaining.release(putByteCounter);
throw new ChannelFullException("Space for commit to queue couldn't be acquired." +
" Sinks are likely not keeping up with sources, or the buffer size is too tight");
}
}
int puts = putList.size();
int takes = takeList.size();
synchronized (queueLock) {
if (puts > 0) {
while (!putList.isEmpty()) {
if (!queue.offer(putList.removeFirst())) { // offer:添加一个元素并返回true
throw new RuntimeException("Queue add failed, this shouldn't be able to happen");
}
}
}
putList.clear();
takeList.clear();
}
bytesRemaining.release(takeByteCounter);
takeByteCounter = 0;
putByteCounter = 0;
queueStored.release(puts);
if (remainingChange > 0) {
queueRemaining.release(remainingChange);
}
if (puts > 0) {
channelCounter.addToEventPutSuccessCount(puts);
}
if (takes > 0) {
channelCounter.addToEventTakeSuccessCount(takes);
}
channelCounter.setChannelSize(queue.size());
}
// 事务失败时,将take list数据回滚到Channel Queue
// 在回滚时,需要把takeList中暂存的事件回滚到Channel Queue,并回滚queueStored信号量。
@Override
protected void doRollback() {
int takes = takeList.size();
synchronized (queueLock) {
Preconditions.checkState(queue.remainingCapacity() >= takeList.size(),
"Not enough space in memory channel " +
"queue to rollback takes. This should never happen, please report");
while (!takeList.isEmpty()) {
queue.addFirst(takeList.removeLast());
}
putList.clear();
}
bytesRemaining.release(putByteCounter);
putByteCounter = 0;
takeByteCounter = 0;
queueStored.release(takes);
channelCounter.setChannelSize(queue.size());
}
}
private Object queueLock = new Object();
// 在操作Channel Queue时都需要锁定,因为Channel Queue可能动态扩容(会被重新new)。用法就是synchronized(queueLock){...操作queue}
@GuardedBy(value = "queueLock") // 用@GuardedBy注解告诉维护者这个变量被哪个锁保护着
private LinkedBlockingDeque<Event> queue; // 由一个Channel Queue存储整个Channel的Event数据
// Semaphore可控制某资源可被同时访问的个数,acquire()获取一个许可,若无等待,而release()释放一个许可
// queueRemaining表示可存储事件容量。在提交事务时增加或减少该信号量
// 1. 首先在configure()函数中初始化为一个capacity大小的信号量
// 2. 在resize的时候,如果要缩容则要看是否还能acquire到oldCapacity - capacity个许可,不能则不允许缩容(很合理啊,不然就丢失数据了)。若是扩容,则queueRemaining.release(capacity - oldCapacity)
// 3. 提交事务时,如果takeList.size() < putList.size(),则要检查是否有足够的queueRemaining
private Semaphore queueRemaining;
// 表示ChannelQueue已存储事件容量
// 2. 在configure()中初始化为一个大小为0的信号量
// 3. 在doTake()时tryAcquire是否有许可
// 4. 在commit()时release(puts)增加puts个许可
// 5. 在rollback()时release(takes)个许可
private Semaphore queueStored;
// maximum items in a transaction queue
private volatile Integer transCapacity;
private volatile int keepAlive;
private volatile int byteCapacity;
private volatile int lastByteCapacity;
private volatile int byteCapacityBufferPercentage;
private Semaphore bytesRemaining;
private ChannelCounter channelCounter;
public MemoryChannel() {
super();
}
@Override
public void configure(Context context) {
// Read parameters from context
// capacity、transactionCapacity、byteCapacity、byteCapacityBufferPercentage...
}
// 因为多个事务要操作ChannelQueue,还要考虑ChannelQueue的扩容问题,因此MemoryChannel使用了锁来实现;而容量问题则使用了信号量来实现。
// 改变queue的容量,是通过新建一个LinkedBlockingDeque来实现的,并将原queue的东西加进来。
private void resizeQueue(int capacity) throws InterruptedException {
int oldCapacity;
// 计算原queue的capacity,注意该方法需加锁
synchronized (queueLock) {
oldCapacity = queue.size() + queue.remainingCapacity();
}
if (oldCapacity == capacity) {
return;
} else if (oldCapacity > capacity) {
// tryAcquire():从该信号量中获取指定数量的许可
//首先要预占老容量-新容量的大小,以便缩容容量。如果获取失败,默认是记录日志,然后忽略
if (!queueRemaining.tryAcquire(oldCapacity - capacity, keepAlive, TimeUnit.SECONDS)) {
LOGGER.warn("Couldn't acquire permits to downsize the queue, resizing has been aborted");
} else {
//否则,直接缩容,然后复制老Queue的数据,缩容时需要锁定queueLock,这一系列操作要线程安全
synchronized (queueLock) {
LinkedBlockingDeque<Event> newQueue = new LinkedBlockingDeque<Event>(capacity);
newQueue.addAll(queue);
queue = newQueue;
}
}
}
Interceptor
flume内部实现了很多自定义的Interceptor,如下图:
同时还实现了InterceptorChain用来链式处理event。
InterceptorChain
Implementation of Interceptor that calls a list of other Interceptors
Interptor接口: 用于过滤、加工Event,然后返回一个新的Event。
相比之下,InterceptorChain就是对event逐个(链式)调用其内的Interceptor(接口子类)实例的各个方法。
public class InterceptorChain implements Interceptor {
// list of interceptors that will be traversed, in order
private List<Interceptor> interceptors;
public InterceptorChain() {
interceptors = Lists.newLinkedList(); // 构造方法,type LinkedList
}
public void setInterceptors(List<Interceptor> interceptors) {
this.interceptors = interceptors; // set方法
}
// Interceptor接口的intercept方法: Interception of a single Event.事件拦截
// @return: Original or modified event, or null if the Event is to be dropped.
@Override
public Event intercept(Event event) {
for (Interceptor interceptor : interceptors) {
if (event == null) {
return null;
}
event = interceptor.intercept(event); // 注意:该类的实例会调用上面的set方法初始化intercptors,其中的intercptor是Interceptor接口子类的实例。所以这里的intercept()方法调用的是Interceptor的某个接口所覆盖的方法。[Interceptor有很多子类,下面有一个demo子类的分析,可以往下看HostInterceptor]
}
return event;
}
// Interceptor接口: Interception of a batch of events
// @return: Output list of events
@Override
public List<Event> intercept(List<Event> events) {
... // 基本同上面的方法,不过调用的是interceptor.intercept(events);
}
// Interceptor: Any initialization / startup needed by the Interceptor.
@Override
public void initialize() {
Iterator<Interceptor> iter = interceptors.iterator();
while (iter.hasNext()) {
Interceptor interceptor = iter.next();
interceptor.initialize(); // 挨个对linkedlist中的interceptor实例进行initialize
}
}
@Override
public void close() {
...// 挨个对linkedlist中的interceptor实例进行close
}
HostInterceptor
implements Interceptor
功能:在所有拦截的events的header中上加上本机的host name或IP
public class HostInterceptor implements Interceptor {
... // 一些private变量
/**
* Only {@link HostInterceptor.Builder} can build me
*/
// private的构造方法,so只能通过下面的静态方法Builder实例化
private HostInterceptor(boolean preserveExisting,
boolean useIP, String header) {
// 用xx.conf内的值初始化这些变量
this.preserveExisting = preserveExisting;
this.header = header;
InetAddress addr;
try {
addr = InetAddress.getLocalHost(); //Returns the address of the local host.
if (useIP) {
//Returns the IP address string in textual presentation
host = addr.getHostAddress();
} else {
// Gets the fully qualified domain name for this IP address.
host = addr.getCanonicalHostName();
}
} catch (UnknownHostException e) {
logger.warn("Could not get local host address. Exception follows.", e);
}
}
@Override
public void initialize() {
// no-op
}
/**
* Modifies events in-place.
*/
@Override
public Event intercept(Event event) {
Map<String, String> headers = event.getHeaders();
// 如果要要保存当前的'host‘值并且当前已有头部,那么就不处理直接返回。
if (preserveExisting && headers.containsKey(header)) {
return event;
}
if (host != null) {
headers.put(header, host); //将host添加到头部
}
return event;
}
@Override
public List<Event> intercept(List<Event> events) {
... // 为events中的每一个event调用intercept(Event event)
}
@Override
public void close() {
// no-op
}
/**
* Builder which builds new instances of the HostInterceptor.
*/
public static class Builder implements Interceptor.Builder {
private boolean preserveExisting = PRESERVE_DFLT;
private boolean useIP = USE_IP_DFLT;
private String header = HOST;
@Override
public Interceptor build() {
return new HostInterceptor(preserveExisting, useIP, header);
}
@Override
public void configure(Context context) {
preserveExisting = context.getBoolean(PRESERVE, PRESERVE_DFLT);
useIP = context.getBoolean(USE_IP, USE_IP_DFLT);
header = context.getString(HOST_HEADER, HOST);
}
}
public static class Constants {
public static String HOST = "host";
... // 一些配置的缺省值
}
}demo Usage in xx.conf: more details see User Guide
agent.sources.r1.interceptors = i1
agent.sources.r1.interceptors.i1.type = host
# preserveExisting: 是否保存当前已存在的'host'值,缺省是不保存
agent.sources.r1.interceptors.i1.preserveExisting = true
agent.sources.r1.interceptors.i1.useIP = false
agent.sources.r1.interceptors.i1.hostHeader = hostname
Selector
先上一张所有selector的继承关系图
可见ChannelSelector默认提供了两种实现:复制和多路复用。默认实现是ReplicatingChannelSelector。
ChannelSelector
interface
基于不同实现政策,允许在channels的集合中选取channels子集。
// NamedComponent接口:用于给component附加一个名字,包括setName()和getName()方法
public interface ChannelSelector extends NamedComponent, Configurable {
// @param channels:all channels the selector could select from.
public void setChannels(List<Channel> channels);
/**
* Returns a list of required channels. 这些channels的写入失败会传达回接收事件的source.
* @param: event
* @return: the list of required channels that this selector has selected for
* the given event.
*/
public List<Channel> getRequiredChannels(Event event);
/**
* Returns a list of optional channels. 这些channels的写入失败会被忽略。
* @param: event
* @return: the list of optional channels that this selector has selected for
* the given event.
*/
public List<Channel> getOptionalChannels(Event event);
/**
* @return the list of all channels that this selector is configured to work
* with.
*/
public List<Channel> getAllChannels();
}
## AbstractChannelSelector
* abstract class
```java
public abstract class AbstractChannelSelector implements ChannelSelector {
private List<Channel> channels;
private String name; ...// override ChannelSelctor的getAllChannels()、setChannels(List<Channel> channels)、setName(String name)、getName()方法。 //@return: A map of name to channel instance.
protected Map<String, Channel> getChannelNameMap() {
Map<String, Channel> channelNameMap = new HashMap<String, Channel>();
for (Channel ch : getAllChannels()) {
// 对每一个Channel, 将Channel和其名字放到HashMap中
channelNameMap.put(ch.getName(), ch);
}
return channelNameMap;
}
/**
* Given a list of channel names as space delimited string,
* returns list of channels.
* @return List of {@linkplain Channel}s represented by the names.
*/
// 根据(space分隔的channel名字的)字符串, 返回相应的channel,利用名字-channel的HashMap
protected List<Channel> getChannelListFromNames(String channels,
Map<String, Channel> channelNameMap) {
List<Channel> configuredChannels = new ArrayList<Channel>();
if (channels == null || channels.isEmpty()) { // 判空
return configuredChannels;
}
String[] chNames = channels.split(" ");
for (String name : chNames) {
Channel ch = channelNameMap.get(name);
if (ch != null) {
configuredChannels.add(ch);
} else {
throw new FlumeException("Selector channel not found: "
+ name);
}
}
return configuredChannels;
}
}
ReplicatingChannelSelector
ChannelSelector的一个具体实现,即把接收到的消息复制到每一个Channel。【与之对应的,MultiplexingChannelSelector会根据 Event Header 中的参数进行选择,以此来选择使用哪个 Channel】
Replicating channel selector. 允许event被放置到source所配置的所有channels中。
实际的实现方式是,默认将所有channel加入requiredChannels中,optionalChannels为空。然后根据配置的"optional"将该配置对应的channel加入optionalChannels,并从requiredChannels中移除(添加和移除是在configure方法中实现的)。 TODO:看一下这个配置如何实现
public class ReplicatingChannelSelector extends AbstractChannelSelector {
// Configuration to set a subset of the channels as optional.
public static final String CONFIG_OPTIONAL = "optional";
List<Channel> requiredChannels = null; // 在configure()中被设置为getAllChannels()的返回值,即所有配置的channels
List<Channel> optionalChannels = new ArrayList<Channel>();
@Override
public List<Channel> getRequiredChannels(Event event) {
/*
* Seems like there are lot of components within flume that do not call
* configure method. It is conceiveable that custom component tests too
* do that. So in that case, revert to old behavior.
*/
// 如果component没有调用configure(),那么requiredChannels为null,此时再调用一次。
// TODO: configure()方法是在哪里调用的? 同样的问题在很多class中都存在
if (requiredChannels == null) {
return getAllChannels();
}
return requiredChannels;
}
@Override
public List<Channel> getOptionalChannels(Event event) {
return optionalChannels;
}
@Override
public void configure(Context context) {
String optionalList = context.getString(CONFIG_OPTIONAL);
requiredChannels = new ArrayList<Channel>(getAllChannels());
Map<String, Channel> channelNameMap = getChannelNameMap();
// 根据OptionList(String, 是空格分隔的channel名字),得到相应的Channel,并将channel放到optionalChannel&& 从requiredChannels中移除。
if (optionalList != null && !optionalList.isEmpty()) {
for (String optional : optionalList.split("\\s+")) {
Channel optionalChannel = channelNameMap.get(optional);
requiredChannels.remove(optionalChannel);
if (!optionalChannels.contains(optionalChannel)) {
optionalChannels.add(optionalChannel);
}
}
}
}
}
Sink
Sink Runner
A driver for sinks that polls them, attempting to process events if any are available in the Channel. All sinks are polled.
public class SinkRunner implements LifecycleAware {
private PollingRunner runner; // 内部类,实现了Runnable接口
private SinkProcessor policy; //
}
Sink Processor
分为两类:
DefaultSinkProcessor处理单sink,直接传送不附加任何处理。
public void start() {
Preconditions.checkNotNull(sink, "DefaultSinkProcessor sink not set");
sink.start(); // start()方法直接启动single sink
lifecycleState = LifecycleState.START;
}
// stop()方法类似,configure()方法为空
public Status process() throws EventDeliveryException {
return sink.process(); // 直接调用sink的process()
}
public void setSinks(List<Sink> sinks) {
Preconditions.checkNotNull(sinks);
Preconditions.checkArgument(sinks.size() == 1, "DefaultSinkPolicy can "
+ "only handle one sink, "
+ "try using a policy that supports multiple sinks");
sink = sinks.get(0);
}多sink处理(AbstractSinkProcessor),其中又包括两种:
FailoverSinkProcessor:故障切换—>通过维持一个sinks的优先级list —> 把故障sinks降级放到一个pool中被赋予一个冷冻周期。必须先调用setSinks()再configure()
public void setSinks(List<Sink> sinks) {
// needed to implement the start/stop functionality
super.setSinks(sinks);
this.sinks = new HashMap<String, Sink>();
for (Sink sink : sinks) {
this.sinks.put(sink.getName(), sink);
}
}
private Sink moveActiveToDeadAndGetNext() {
Integer key = liveSinks.lastKey();
failedSinks.add(new FailedSink(key, activeSink, 1)); // 把当前liveSinks里的第一优先级key移除到failedSinks中
liveSinks.remove(key);
if (liveSinks.isEmpty()) return null;
if (liveSinks.lastKey() != null) {
return liveSinks.get(liveSinks.lastKey());
} else {
return null;
}
}
...LoadBalancingSinkProcessor: 提供在多个sinks之间负载均衡的能力—> 维持一个active sinks的索引序列(load需分布在这些sinks上) —> 算法包括ROUND_ROBIN(default)和RANDOM选择机制。
内部通过一个
private interface SinkSelector
实现。该接口下实现了两个私有静态类RoundRobinSinkSelector
和RandomOrderSinkSelector
.
<Flume><Source Code><Flume源码阅读笔记>的更多相关文章
- 简单物联网:外网访问内网路由器下树莓派Flask服务器
最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...
- 利用ssh反向代理以及autossh实现从外网连接内网服务器
前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...
- 外网访问内网Docker容器
外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...
- 外网访问内网SpringBoot
外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...
- 外网访问内网Elasticsearch WEB
外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...
- 怎样从外网访问内网Rails
外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...
- 怎样从外网访问内网Memcached数据库
外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...
- 怎样从外网访问内网CouchDB数据库
外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...
- 怎样从外网访问内网DB2数据库
外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...
- 怎样从外网访问内网OpenLDAP数据库
外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...
随机推荐
- Confluence 6 空间中的常用宏
小组空间(Team Spaces): 介绍小组:User Profile Macro 将会对 Confluence 的用户显示属性的简单摘要,属性照片,联系方式. 在你小组中分享通知和新闻:The B ...
- python记录_day05 字典
字典 字典由花括号表示{ },元素是key:value的键值对,元素之间用逗号隔开 特点:1.字典中key是不能重复的 且是不可变的数据类型,因为字典是使用hash算法来计算key的哈希值,然后用哈希 ...
- hdu-4180-exgcd
RealPhobia Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...
- UI基础二:下拉,F4,OP等
常用的搜索帮助有SE11的SH,域,值列表,组件等...下面介绍一下经常用的: 一:下拉 dropdown是最经常用的,也是最简单的一种. 不管是查询条件,还是结果清单,还是明细界面,下拉都是一样的 ...
- 水题系列二:PhoneNumbers
问题描述: Phonenumbers 企业喜欢用容易被记住的电话号码.让电话号码容易被记住的一个办法是将它写成一 个容易记 住的 单词或 者短语 .例如 ,你 需要给 滑铁卢 大学打 电话时 ,可 以 ...
- 解决iOS第三方SDK之间重复的symbols问题
前言:今天公司项目准备使用高德导航,其中用到了高德3D地图SDK,然后就出现bug了.在真机上可以完美运行,但是在模拟器上,就出现了一大片的bug:提示有82个Duplicate symbols,仔细 ...
- 通过BeanPostProcessor理解Spring中Bean的生命周期
通过BeanPostProcessor理解Spring中Bean的生命周期及AOP原理 Spring源码解析(十一)Spring扩展接口InstantiationAwareBeanPostProces ...
- MSOCache office问题
\MSOCache 链接:https://pan.baidu.com/s/1dVvjYnD0D6DG6oFK_Mww2w密码:qrvp undefied
- Oracle 如何循环查询结果集,进行新增或修改
Oracle的PL/SQL中怎样循环查询的结果集,然后根据查询结果进行判断,是新增或修改操作 loop循环例子 for item in (select a,b,c from table_a where ...
- 浅谈Linux系统运维工程师必备技能
一.什么是运维工程师 相信读者们必定听说过Linux,也听说过运维工程师.那么运维工程师是个什么概念呢? 百度百科上的官方解释如下: 运维工程师(Operations)在国内又称为运维开发工程师(De ...