一.首先看看Elasticsearch中插件是如何安裝的c++
咱們安裝好es後,若是要安裝插件須要在 /usr/local/elasticsearch/bin的目錄下使用plugin這個shell腳本,仔細看了下這個shell腳本,發現裏面主要是運行了org.elasticsearch.plugins.PluginManager這個類 exec "$JAVA" $JAVA_OPTS $ES_JAVA_OPTS -Xmx64m -Xms16m -Delasticsearch -Des.path.home="$ES_HOME" $properties -cp "$ES_HOME/lib/*" org.elasticsearch.plugins.PluginManager $argsshell
下面咱們進入PluginManager這個類中看看是如何下載安裝插件的,如何執行這個類,確定應該有個main方法吧,果然如此,下面是pluginManager的main方法less
public static void main(String[] args) { .......... String url = null; OutputMode outputMode = OutputMode.DEFAULT; String pluginName = null; TimeValue timeout = DEFAULT_TIMEOUT; int action = ACTION.NONE; if (args.length < 1) { displayHelp(null); } try { for (int c = 0; c < args.length; c++) { String command = args[c]; switch (command) { case "-u": case "--url": // deprecated versions: case "url": case "-url": url = getCommandValue(args, ++c, "--url"); // Until update is supported, then supplying a URL implies installing // By specifying this action, we also avoid silently failing without // dubious checks. action = ACTION.INSTALL; break; case "-v": case "--verbose": // deprecated versions: case "verbose": case "-verbose": outputMode = OutputMode.VERBOSE; break; case "-s": case "--silent": // deprecated versions: case "silent": case "-silent": outputMode = OutputMode.SILENT; break; case "-i": case "--install": // deprecated versions: case "install": case "-install": pluginName = getCommandValue(args, ++c, "--install"); action = ACTION.INSTALL; break; case "-r": case "--remove": // deprecated versions: case "remove": case "-remove": pluginName = getCommandValue(args, ++c, "--remove"); action = ACTION.REMOVE; break; case "-t": case "--timeout": // deprecated versions: case "timeout": case "-timeout": String timeoutValue = getCommandValue(args, ++c, "--timeout"); timeout = TimeValue.parseTimeValue(timeoutValue, DEFAULT_TIMEOUT); break; case "-l": case "--list": action = ACTION.LIST; break; case "-h": case "--help": displayHelp(null); break; default: displayHelp("Command [" + command + "] unknown."); // Unknown command. We break... System.exit(EXIT_CODE_CMD_USAGE); } } } catch (Throwable e) { displayHelp("Error while parsing options: " + e.getClass().getSimpleName() + ": " + e.getMessage()); System.exit(EXIT_CODE_CMD_USAGE); }
上面代碼中,就是當你輸入各類命令的時候,pluginManager都會根據響應的命令觸發不一樣的動做,例如輸入./plugin list 就會顯示當前es安裝了那些插件elasticsearch
當輸入 plugin -install mobz/elasticsearch-head 命令的時候,pluginManager就會去獲取當前插件名稱, pluginName = getCommandValue(args, ++c, "--install"); 而且將 action賦值 action = ACTION.INSTALL; 當完成賦值後會繼續執行以下代碼,這部分代碼主要構造PluginManger對象,而且根據action的動做去執行相應的操做,pluginManager.downloadAndExtract(pluginName);會根據插件名稱去下載相應的插件模塊化
................ if (action > ACTION.NONE) { int exitCode = EXIT_CODE_ERROR; // we fail unless it's reset //構造pluginManger PluginManager pluginManager = new PluginManager(initialSettings.v2(), url, outputMode, timeout); switch (action) { case ACTION.INSTALL: try { pluginManager.log("-> Installing " + Strings.nullToEmpty(pluginName) + "..."); pluginManager.downloadAndExtract(pluginName);//執行下載解壓任務 exitCode = EXIT_CODE_OK; } catch (IOException e) { exitCode = EXIT_CODE_IO_ERROR; pluginManager.log("Failed to install " + pluginName + ", reason: " + e.getMessage()); } catch (Throwable e) { exitCode = EXIT_CODE_ERROR; displayHelp("Error while installing plugin, reason: " + e.getClass().getSimpleName() + ": " + e.getMessage()); } break; .........................
PluginManager的構造函數主要是傳入下載地址,延時時長等,下面看下downloadAndExtract下載安裝是如何工做的函數
public void downloadAndExtract(String name) throws IOException { ..... HttpDownloadHelper downloadHelper = new HttpDownloadHelper(); boolean downloaded = false; HttpDownloadHelper.DownloadProgress progress; if (outputMode == OutputMode.SILENT) { progress = new HttpDownloadHelper.NullProgress(); } else { progress = new HttpDownloadHelper.VerboseProgress(System.out); } ......... if (url != null) { URL pluginUrl = new URL(url); log("Trying " + pluginUrl.toExternalForm() + "..."); try { downloaded = true; downloadHelper.download(pluginUrl, pluginFile, progress, this.timeout); } catch (ElasticsearchTimeoutException e) { throw e; } catch (Exception e) { // ignore log("Failed: " + ExceptionsHelper.detailedMessage(e)); } }
主要是經過構建HttpDownloadHelper 實現http下載,而且實現了一個下載進度器,這就是咱們安裝插件的時候會有一些輸出的緣由,經過downloadHelper.download(pluginUrl, pluginFile, progress, this.timeout);能夠真正的實現下載,這裏就不在贅述了,當下載完畢後,會去執行解壓和安裝的過程,在解壓文件時候越過父文件夾,將文件解壓到指定目錄,好比head文件夾中,至此下載安裝基本完畢ui
ZipFile zipFile = null; try { zipFile = new ZipFile(pluginFile); boolean removeTopLevelDir = topLevelDirInExcess(zipFile); Enumeration<? extends ZipEntry> zipEntries = zipFile.entries(); while (zipEntries.hasMoreElements()) { ZipEntry zipEntry = zipEntries.nextElement(); if (zipEntry.isDirectory()) { continue; } String zipEntryName = zipEntry.getName().replace('\\', '/'); if (removeTopLevelDir) { zipEntryName = zipEntryName.substring(zipEntryName.indexOf('/')); } File target = new File(extractLocation, zipEntryName); FileSystemUtils.mkdirs(target.getParentFile()); Streams.copy(zipFile.getInputStream(zipEntry), new FileOutputStream(target)); } log("Installed " + name + " into " + extractLocation.getAbsolutePath()); } catch (Exception e) { log("failed to extract plugin [" + pluginFile + "]: " + ExceptionsHelper.detailedMessage(e)); return; } finally { if (zipFile != null) { try { zipFile.close(); } catch (IOException e) { // ignore } } pluginFile.delete(); }
二. elasticSearch是如何加載插件的this
elasticsearch裏面的組件基本都是用上面的方式進行模塊化管理,elasticsearch對guice進行了簡單的封裝,經過ModulesBuilder類構建es的模塊,一個es節點確定夠包括PluginsModule插件模塊,下面咱們看下插件模塊是如何工做,加載咱們的插件的url
public class PluginsModule extends AbstractModule implements SpawnModules, PreProcessModule { private final Settings settings; private final PluginsService pluginsService; public PluginsModule(Settings settings, PluginsService pluginsService) { this.settings = settings; this.pluginsService = pluginsService; } //預處理組件 public Iterable<? extends Module> spawnModules() { List<Module> modules = Lists.newArrayList(); Collection<Class<? extends Module>> modulesClasses = pluginsService.modules(); for (Class<? extends Module> moduleClass : modulesClasses) { modules.add(createModule(moduleClass, settings)); } modules.addAll(pluginsService.modules(settings)); return modules; } ..............
es中全部的組件都是繼承自AbstractModule 插件調用層次爲Modules->PluginsModule->PluginsService->XXPlugin; 都是調用void processModule(Module module)方法。 區別在於 1:PluginsModule實現的是PreProcessModule接口。 2:PluginsService是本身原本的方法。 3:XXPlugin實現的是Plugin接口。 雖然他們都有processModule方法,但不是實現的同一個類。最重要是的仍是pluginService類,它真正負責加載的插件類spa
public PluginsService(Settings settings, Environment environment) { super(settings); this.environment = environment; this.checkLucene = componentSettings.getAsBoolean("check_lucene", true); .................................忽律部分代碼 loadPluginsIntoClassLoader(); if (loadClasspathPlugins) { tupleBuilder.addAll(loadPluginsFromClasspath(settings)); } this.plugins = tupleBuilder.build(); // we load site plugins ImmutableList<Tuple<PluginInfo, Plugin>> tuples = loadSitePlugins(); for (Tuple<PluginInfo, Plugin> tuple : tuples) { sitePlugins.add(tuple.v1().getName()); } .................................忽律部分代碼
其中loadPluginsIntoClassLoader();負責classloader加載class,加載不在當前classpath中的class,使用當前setting中的classloader,這個構造函數負責加載plugin下面的全部的插件,而且使用當前的類加載器加載。
至此,插件的下載和加載所有完成。