[9]elasticsearch源码深入分析——Plugin组件加载

本篇为elasticsearch源码分析系列文章的第九篇,又到了我们深扒ElasticSearch源码的时候了:)

本篇开始将会详细解释Node实例化的过程中PluginsService的相关内容,PluginService算是Node实例化的重要内容,了解PluginService的加载过程有助于我们理解Node实例化和ElasticSearch启动时工作流程,此外PluginsService还涉及到ElasticSearch中线程池的使用,关于ElasticSearch中线程池的封装使用,我们会在下一篇叙述。

插件的安装

ElasticSearch中的插件是一个允许插入自定义功能的扩展。插件大致分为三类:

  • java插件:这些插件只包含Jar文件,并且必须安装在集群中的每个节点上。安装后,每个节点必须重新启动,该插件才变得可见。比如分词器插件。
  • 站点插件:这些插件包含了静态的Web内容,如JavaScript,HTML和CSS文件,可直接从Elasticsearch访问。站点插件可能只需要在一个节点上安装,并且不需要重新启动就能变得可见。站点插件的内容是通过一个类似的网址访问:HTTP://IP:9200/_plugin/[插件名称]
  • 混合插件:混合插件同时包含jar文件和静态的Web内容

在ElasticSearch的bin目录下,可以执行命令

bin/plugin list:查看已经安装了的ElasticSearch插件。
bin/plugin install [plugin_name]:安装一个ElasticSearch插件。

插件的安装过程如图所示:

插件安装

在安装好之后,在ElasticSearch的plugins目录下就能看到插件包了:

安装好的icu插件

可以看出icu分词插件是个纯java插件。icu
是Elasticsearch的分析器插件,使用国际化组件Unicode(ICU)提供丰富的处理 Unicode编码的工具。该查件对处理亚洲语言特别有用,还有大量对除英语外其他语言进行正确匹配和排序所必须的分词过滤器。

插件何时进行加载

落地到编码层次的话,plugins文件夹中的插件在Node实例化的时候被加载,构建代码如下:

this.pluginsService = new PluginsService(tmpSettings, environment.configFile(), environment.modulesFile(), environment.pluginsFile(), classpathPlugins);

其中environment.pluginsFile()的路径的内容就是xxx\elasticsearch\elasticsearch-6.0.0-rc2\plugins\analysis-icu

ElasticSearch插件扩展机制

在ElasticSearch的org.elasticsearch.plugins的包中提供了若干种插件的扩展类,完全覆盖了所有插件的扩展需求。除了能实现以下接口:

  • ActionPlugin
  • AnalysisPlugin
  • ClusterPlugin
  • DiscoveryPlugin
  • IngestPlugin
  • MapperPlugin
  • NetworkPlugin
  • RepositoryPlugin
  • ScriptPlugin
  • SearchPlugin

进一步定制ElasticSearch外,除了这个类扩展点还声明一些@DeprecatedonModule方法。使用这些方法应该特别注意,使用5.x以前风格扩展语法不能成功构建。这也是成功的开源软件在考虑平滑升级时的设计思路时值得学习的地方。定义了插件的实现方法,这些插件从低版本升级到5.x之后的版本就不会太过艰难。

比如上面提到的icu分词插件,就实现了AnalysisPlugin, MapperPlugin,这两个接口。

public class AnalysisICUPlugin extends Plugin implements AnalysisPlugin, MapperPlugin

关于这些插件的详细解析,我们会在以后的文章中讲解。

PluginsService类的构造函数解析

一般查看组件源码都是先从构造函数开始,PluginsService的构造函数为PluginsService(Settings settings, Path configPath, Path modulesDirectory, Path pluginsDirectory, Collection<Class<? extends Plugin>> classpathPlugins)

有如下五个参数,大都是路径设定类的:

  • settings:ElasticSearch启动时的系统设定
  • Path:ElasticSearch的config配置文件的目录,类型为Path
  • modulesDirectory:ElasticSearch的modules目录,类型为Path
  • pluginsDirectory:ElasticSearch的plugins目录,类型为Path
  • classpathPlugins:在classpath路径中的插件,是Plugin类的子类集合,有这种情况的出现主要是为了在测试和使用transport clients的情况下加载插件。
加载classpathPlugins中的插件

PluginsService先构造了一个List,List的元素为PluginsInfo类型和Plugin类型的元组(Tuple)。

接着再遍历classpathPlugins的值中的Plugin实现类对象,通过反射Plugin的实现类,调用方法getConstructors()得到Plugin的子类的构造函数,得到构造函数后,对构造函数进行一系列的检查:

  • 自定义实现的Plugin子类必须有public的构造函数
  • 有且只能有一个构造函数
  • 接受参数不能大于两个,且第一个为Settings类型,第二个为Path类型。看过我以前文章的同学对Setting肯定不会陌生。

由此可以知道,想自己实现ElasticSearch的插件,就必须继承Plugin类,定义一个构造函数,根据要实现插件的类型实现不同的接口,比如SearchPlugin,ScriptPlugin,RepositoryPlugin

构造Plugins之modulesDirectory

接下来遍历modules路径中各个module的plugin-descriptor.properties文件,取出文件中的如下属性:

  • name
  • description
  • version
  • elasticsearch.version
  • java.version
  • has.native.controller:该插件是否需要本地控制器
  • requires.keystore:该插件是否需要ElasticSearch创建秘钥库

构造出各个modulePluginInfo对象,然后遍历出各个module下面的jar包的路径,这样每个module的jar包和信息就都有了,我们可以看到ElasticSearch是封装了一个内部类Bundle,如下图:

内部封装类Bundle

至此找到了所有需要加载的module,下面是加载所有的plugins

构造Plugins之pluginsDirectory

首先调用checkForFailedPluginRemovals()方法,遍历pluginsDirectory路径中所有的包含.removing-字符的文件,抛出应该remove该插件的异常IllegalStateException。

检查完成后,遍历pluginsDirectory路径下的文件,遍历过程中,很细心的检查了当前查询路径是否是MacOS或者可能的桌面系统,而不是服务器。如果符合上述条件就跳过该次遍历,不符合的话依然按照加载module的方法那样,取得plugins文件夹下的各个plugin的plugin-descriptor.properties文件,依次加载name,description,version,elasticsearch.version,java.version,has.native.controller,requires.keystore属性,封装成PluginInfo,查找出jar包,最后封装成Bundle对象。

这样就得到了两个Bundle对象,一个modules,一个plugins。构造一个整体集合后,检查其中的jar包,避免jar-hell

至此PluginsService对象就已经构造完毕,通过赋值语句this.pluginsService = new PluginsService(tmpSettings, environment.configFile(), environment.modulesFile(), environment.pluginsFile(), classpathPlugins),Node就对象获得了PluginsService的对象。

PluginsService构造函数总结

我们整理一下PluginsService的构造函数做了哪些工作,

  • this.configPath = configPath:设置了configPath的路径值
  • this.info = new PluginsAndModules(pluginsList, modulesList):加载了Plugins和Modules中的基本信息和jar路径
  • his.plugins = Collections.unmodifiableList(pluginsLoaded):加载了bundle对象,bundle中包含了基础信息和所有jar的URL

PluginsService类的作用

在通篇过完了PluginsService类的构造参数后,我们继续来看PluginsService对象在Node中起到的作用。

在Node对象构建完了PluginsService对象后,紧接着在Node中,通过PluginsService对象的updatedSettings()方法,将PluginsService类在构造时从modulesplugins路径中加载的plugin对象取出遍历,依次调用各个plugin的additionalSettings()方法。然后put更新Settings对象,达到了更新Settings对象的目的。

additionalSettings()这个方法在不同的plugin实现类中有不同实现,具体作用是构建插件运行过程中需要的Settings对象。如下图是Netty4plugin的实现:

Netty4Plugin的实现

由此可见,PluginsService组件中保存的Plugin元组是ElasticSearch发挥功能的重要内容,其中加载的Modules路径下的Plugin组件一起构成了ElasticSearch的核心主干功能。

ElasticSearch中线程池的加载参数直接来源于PluginsService,下一篇文章我们会讲解在Node实例化过程中线程池的封装过程,希望大家持续关注哦^ _ ^。

推荐阅读更多精彩内容