本文简单的讨论一下Apache NIFI项目结构的类资源隔离机制,适合接触过源码的同学阅读。
以nifi-flume-bundle为例
nifi-flume-bundle
-- nifi-flume-processors
-- nifi-flume-nar
nifi-flume-bundle 有两个子项目,nifi-flume-processors里是Processor的具体实现,打成jar包。nifi-flume-nar里没有代码实现负责将nifi-flume-processors.jar及其依赖打成nar包。
NAR是NiFi Archive的缩写,创建nar的原因是为了实现Java类加载器隔离资源。NIFI的组件实现都来自不同的公司和贡献者,代码里往往会引入不同版本的第三方库(比如apache-commons等)。NAR文件避免了NoClassDefFoundError异常的出现(这些异常是由于在不同处理器的类加载器中已经加载了错误版本的依赖而引发的)。
META-INF
bundled-dependencies
async-1.4.0.jar
...
flume-dataset-sink-1.6.0.jar
...
nifi-flume-processors-1.11.4.jar
nifi-utils-1.11.4.jar
...
DEPENDENCIES
docs
...
LICENSE
MANIFEST.MF
maven
org.apache.nifi
nifi-flume-nar
pom.properties
pom.xml
NOTICE
NAR文件实际上跟WAR和NAR差不多,但有一些区别。NAR的根目录是META-INF(与JAR中一样,而WAR是WEB-INF)。META-INF根目录下是描述性文件,例如LICENSE,DEPENDENCIES(列出捆绑的依赖项的许可证信息)和NOTICE(包含处理器本身的许可证)。
此外,META-INF下还有3个关键文件和目录。首先是MANIFEST.MF文件,它跟jar里的文件基本相同,但是,其中多包含一些NAR信息,比如Nar-Id用来识别nar,Nar-Version是NAR里处理器的版本,Nar-Dependency-Id是当前NAR所依赖的NAR的ID(nar不能依赖多个其他nar)等等,还包括有关用于构建NAR的Java和Maven版本以及其来源的有用元数据。
Manifest-Version: 1.0
Build-Branch: develop
Build-Timestamp: 2020-07-09T11:08:58Z
Archiver-Version: Plexus Archiver
Nar-Dependency-Group: org.apache.nifi
Built-By: zhangcheng
Nar-Id: nifi-flume-nar
Clone-During-Instance-Class-Loading: false
Nar-Dependency-Version: 1.11.4
Nar-Version: 1.11.4
Build-Tag: nifi-1.11.4-RC1
Build-Revision: 7c28976
Nar-Group: org.apache.nifi
Nar-Dependency-Id: nifi-hadoop-libraries-nar
Created-By: Apache Maven 3.6.1
Build-Jdk: 1.8.0_181
maven目录包含用于构建NAR的POM文件(Maven构建描述文件),以及一个pom.properties文件,其中包含NAR的maven兼容详细信息(maven用于依赖性识别的3个关键元素,groupId,artifiactId,version)
#Generated by Maven
#Thu Jul 09 11:08:58 CST 2020
version=1.11.4
groupId=org.apache.nifi
artifactId=nifi-flume-nar
bundled-dependencies目录下是组件的jar包及其依赖的其他jar包,这些jar会被特定的类加载器加载。
在NIFI启动时,会把lib目录下的nar文件都解压到work/nar目录下。其中nifi-framework-nar-1.11.4.nar
会单独解压到work/nar/framework/
下,其他的nar文件都解压到work/nar/extensions/
下,对应的目录类似于nifi-flume-nar-1.11.4.ar-unpacked
。
在NIFI启动源码解读的NiFi.java 源码解读和NIFI Nar包加载机制源码解读中我们说过每一个nar包对应创建一个类加载器,使用不同的类加载器去加载这个nar资源。
遵循JAVA的类加载器委托加载机制,由NarClassLoaders的createNarClassLoader
创建
private static ClassLoader createNarClassLoader(final File narDirectory, final ClassLoader parentClassLoader) throws IOException, ClassNotFoundException {
logger.debug("Loading NAR file: " + narDirectory.getAbsolutePath());
final ClassLoader narClassLoader = new NarClassLoader(narDirectory, parentClassLoader);
logger.info("Loaded NAR file: " + narDirectory.getAbsolutePath() + " as class loader " + narClassLoader);
return narClassLoader;
}
每一个nar包对应创建一个类加载器,源码里JettyServer.start()里启动jetty前调用extensionManager.discoverExtensions(systemBundle, bundles);
(bundles里记录了nar信息和对应的类加载器),
@Override
public void discoverExtensions(final Set narBundles) {
// get the current context class loader
ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
// consider each nar class loader
for (final Bundle bundle : narBundles) {
// Must set the context class loader to the nar classloader itself
// so that static initialization techniques that depend on the context class loader will work properly
final ClassLoader ncl = bundle.getClassLoader();
Thread.currentThread().setContextClassLoader(ncl);
loadExtensions(bundle);
// Create a look-up from coordinate to bundle
bundleCoordinateBundleLookup.put(bundle.getBundleDetails().getCoordinate(), bundle);
}
// restore the current context class loader if appropriate
if (currentContextClassLoader != null) {
Thread.currentThread().setContextClassLoader(currentContextClassLoader);
}
}
在loadExtensions(bundle)里使用SPI机制ServiceLoader
去加载各个组件的class信息,而组件代码所涉及的其他类的class也会隐式的由当前组件的Class对象中引用的类加载器去加载,这样就完成了整个项目架构以nar为基础的类资源隔离。
private void loadExtensions(final Bundle bundle) {
for (final Map.Entry> entry : definitionMap.entrySet()) {
final boolean isControllerService = ControllerService.class.equals(entry.getKey());
final boolean isProcessor = Processor.class.equals(entry.getKey());
final boolean isReportingTask = ReportingTask.class.equals(entry.getKey());
//SPI机制
final ServiceLoader> serviceLoader = ServiceLoader.load(entry.getKey(), bundle.getClassLoader());
for (final Object o : serviceLoader) {
try {
loadExtension(o, entry.getKey(), bundle);
} catch (Exception e) {
logger.warn("Failed to register extension {} due to: {}" , new Object[]{o.getClass().getCanonicalName(), e.getMessage()});
if (logger.isDebugEnabled()) {
logger.debug("", e);
}
}
}
classLoaderBundleLookup.put(bundle.getClassLoader(), bundle);
}
}
文章有帮助的话,小手一抖点击在看,并转发吧。
谢谢支持哟 (*^__^*)