flume源码是从github上面拉下来的,我拉的是master分支的代码,pom.xml文件里面显示是1.10版本。
一、源码主要模块说明
-
flume-ng-channels 里面包含了filechannel,jdbcchannel,kafkachannel,memorychannel通道的实现。
-
flume-ng-source 里面包含了jms, kafka, scribe, taildir, twitter 等数据源的实现。
-
flume-ng-sink 里面包含了dataset, hdfs, hive, http, irc, elasticsearch, hbase, kafka, solr等实现。
-
flume-ng-clients 实现了log4j相关的几个Appender,使得log4j的日志输出可以直接发送给flume-agent;其中有一个LoadBalancingLog4jAppender的实现,提供了多个flume-agent的load balance和ha功能,采用flume作为日志收集的可以考虑将这个appender引入内部的log4j中。
-
flume-ng-configuration 这个主要就是Flume配置信息相关的类,包括载入flume-config.properties配置文件并解析。其中包括了Source的配置,Sink的配置,Channel的配置,在阅读源码前推荐先梳理这部分关系再看其他部分的。
-
flume-ng-core flume整个核心框架,包括了各个模块的接口以及逻辑关系实现。其中instrumentation是flume内部实现的一套metric机制,metric的变化和维护,其核心也就是在MonitoredCounterGroup中通过一个Map<key, AtomicLong>来实现metric的计量。ng-core下几乎大部分代码任然几种在channel、sink、source几个子目录下,其他目录基本完成一个util和辅助的功能。
-
flume-ng-node 实现启动flume的一些基本类,包括main函数的入口(Application.java中)。在理解configuration之后,从application的main函数入手,可以较快的了解整个flume的代码。
-
flume-shared 里面包含了kafka ssl 工具类的实现。
-
flume-tools 里面包含了FlumeToolsMain
二、flume启动文件
位置:bin/flume-ng
################################
# constants
################################
FLUME_AGENT_CLASS="org.apache.flume.node.Application"
FLUME_AVRO_CLIENT_CLASS="org.apache.flume.client.avro.AvroCLIClient"
FLUME_VERSION_CLASS="org.apache.flume.tools.VersionInfo"
FLUME_TOOLS_CLASS="org.apache.flume.tools.FlumeToolsMain"
run_flume() {
local FLUME_APPLICATION_CLASS
if [ "$#" -gt 0 ]; then
FLUME_APPLICATION_CLASS=$1
shift
else
error "Must specify flume application class" 1
fi
if [ ${CLEAN_FLAG} -ne 0 ]; then
set -x
fi
$EXEC $JAVA_HOME/bin/java $JAVA_OPTS $FLUME_JAVA_OPTS "${arr_java_props[@]}" -cp "$FLUME_CLASSPATH" \
-Djava.library.path=$FLUME_JAVA_LIBRARY_PATH "$FLUME_APPLICATION_CLASS" $*
}
################################
# main
################################
# set default params
#启动flume所需的依赖,包括主目录lib下的jar包、conf目录、自定义的插件(放到plugins.d)等
FLUME_CLASSPATH=""
#启动flume的入口(一般是:org.apache.flume.node.Application)
FLUME_JAVA_LIBRARY_PATH=""
#java虚拟机参数"(-Xmx20m)
JAVA_OPTS="-Xmx20m"
LD_LIBRARY_PATH=""
opt_conf=""
opt_classpath=""
opt_plugins_dirs=""
arr_java_props=()
arr_java_props_ct=0
opt_dryrun=""
mode=$1
shift
# 根据不同的参数,确定启动模式
case "$mode" in
help)
display_help
exit 0
;;
agent)
opt_agent=1
;;
node)
opt_agent=1
warn "The \"node\" command is deprecated. Please use \"agent\" instead."
;;
avro-client)
opt_avro_client=1
;;
tool)
opt_tool=1
;;
version)
opt_version=1
CLEAN_FLAG=0
;;
*)
error "Unknown or unspecified command '$mode'"
echo
display_help
exit 1
;;
esac
# finally, invoke the appropriate command
# 根据不同的参数,执行不同的启动类,每个常量所对应的类路径看上面变量。
if [ -n "$opt_agent" ] ; then
run_flume $FLUME_AGENT_CLASS $args
elif [ -n "$opt_avro_client" ] ; then
run_flume $FLUME_AVRO_CLIENT_CLASS $args
elif [ -n "${opt_version}" ] ; then
run_flume $FLUME_VERSION_CLASS $args
elif [ -n "${opt_tool}" ] ; then
run_flume $FLUME_TOOLS_CLASS $args
else
error "This message should never appear" 1
fi
从启动脚本看,有四种启动模式,分别对应的四种启动类:
FLUME_AGENT_CLASS="org.apache.flume.node.Application"
FLUME_AVRO_CLIENT_CLASS="org.apache.flume.client.avro.AvroCLIClient"
FLUME_VERSION_CLASS="org.apache.flume.tools.VersionInfo"
FLUME_TOOLS_CLASS="org.apache.flume.tools.FlumeToolsMain"
命令: ./flume-ng version
调用的是org.apache.flume.tools.VersionInfo类
命令: ./flume-ng tool FCINTEGRITYTOOL --dataDirs <arg>
调用的是org.apache.flume.tools.FlumeToolsMain类
从上面代码中可以看出FlumeToolType枚举类中列出了Tool命令可以调用的策略
从FlumeToolType枚举类中看到只有一种策略,调用的是org.apache.flume.tools.FileChannelIntegrityTool。
FileChannelIntegrityTool.java
FileChannelIntegrityTool内部有一个重要属性:eventValidator 事件验证器,默认不做任何验证工作,用户可以实现EventValidator接口自定义事件验证器,事件验证器主要对读取出来的事件做拦截验证工作。启动是时候 --eventValidator <arg>参数配置自定义事件验证器,-D <property=value> 参数为自定义事件验证器的参数。
@Override
public void run(String[] args) throws IOException, ParseException {
....解析命令行参数
for (File dataDir : dataDirs) {
// 拿到目录下的文件列表 拦截部分文件
File[] dataFiles = dataDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
// METADATA_FILENAME = ".meta";
// METADATA_TMP_FILENAME = ".tmp";
// OLD_METADATA_FILENAME = METADATA_FILENAME + ".old";
// FILE_LOCK = "in_use.lock";
if (!name.endsWith(Serialization.METADATA_FILENAME)
&& !name.endsWith(Serialization.METADATA_TMP_FILENAME)
&& !name.endsWith(Serialization.OLD_METADATA_FILENAME)
&& !name.equals(Log.FILE_LOCK)) {
return true;
}
return false;
}
});
if (dataFiles != null && dataFiles.length > 0) {
for (File dataFile : dataFiles) {
// TODO: 对每个文件进行操作
LOG.info("Checking for corruption in " + dataFile.toString());
// TODO: 这里主要是测试LogFileV3.SequentialReader 和 LogFile.OperationRecordUpdater
LogFile.SequentialReader reader = new LogFileV3.SequentialReader(dataFile, null,
true);
LogFile.OperationRecordUpdater updater = new LogFile.OperationRecordUpdater(dataFile);
boolean fileDone = false;
boolean fileBackedup = false;
while (!fileDone) {
long eventPosition = 0;
try {
// This depends on the fact that events are of the form: Type, length, data.
// 这取决于事件的形式:类型,长度,数据。
eventPosition = reader.getPosition();
// Try to get the record, if the checksums don't match,
// this will throw a CorruptEventException - so the real logic is in the catch block below.
// 尝试获取记录,如果校验和不匹配,
// 这将引发CorruptEventException 因此,真正的逻辑在下面的catch块中。
LogRecord record = reader.next();
totalChannelEvents++;
if (record != null) {
// 取出事务事件
TransactionEventRecord recordEvent = record.getEvent();
Event event = EventUtils.getEventFromTransactionEvent(recordEvent);
if (event != null) {
totalPutEvents++;
try {
// 这里对每个事务事件进行特定逻辑的验证
if (!eventValidator.validateEvent(event)) {
if (!fileBackedup) {
Serialization.copyFile(dataFile, new File(dataFile.getParent(),
dataFile.getName() + ".bak"));
fileBackedup = true;
}
// 无效事件
invalidEvents++;
updater.markRecordAsNoop(eventPosition);
} else {
validEvents++;
}
} catch (Exception e) {
// OOPS, didn't expected an exception
// considering as failure case
// marking as noop
System.err.println("Encountered Exception while validating event, " +
"marking as invalid");
updater.markRecordAsNoop(eventPosition);
eventsWithException++;
}
}
} else {
// 没有事件了
fileDone = true;
}
} catch (CorruptEventException e) {
// 获取下一个事件发生错误
corruptEvents++;
totalChannelEvents++;
LOG.warn("Corruption found in " + dataFile.toString() + " at " + eventPosition);
if (!fileBackedup) {
Serialization.copyFile(dataFile, new File(dataFile.getParent(),
dataFile.getName() + ".bak"));
fileBackedup = true;
}
updater.markRecordAsNoop(eventPosition);
}
}
updater.close();
reader.close();
}
}
}
printSummary();
}
FileChannelIntegrityTool的主要逻辑就是读取文件中的事件,然后每个事件通过事件验证器执行验证逻辑。最后输出统计结果。
实验: 准备文件
输出:
---------- Summary --------------------
Number of Events in the Channel = 37
Number of Put Events Processed = 25
Number of Valid Put Events = 25
Number of Invalid Put Events = 0
Number of Put Events that threw Exception during validation = 0
Number of Corrupt Events = 0
---------------------------------------
总结:这次主要是了解flume源码结构,并分析了两个flume无关痛痒的功能。这两个功能一般都是平时测试排错中用到,生产环境下主要还是用"org.apache.flume.node.Application"、"org.apache.flume.client.avro.AvroCLIClient"这两个类启动flume。