上篇文章写了 ElasticSearch 源码解析 —— 环境搭建 ,其中里面说了启动 打开 server 模块下的 Elasticsearch 类:org.elasticsearch.bootstrap.Elasticsearch,运行里面的 main 函数就可以启动 ElasticSearch 了,这篇文章讲讲启动流程,因为篇幅会很多,所以分了两篇来写。
启动流程
main 方法入口
可以看到入口其实是一个 main 方法,方法里面先是检查权限,然后是一个错误日志监听器(确保在日志配置之前状态日志没有出现 error),然后是 Elasticsearch 对象的创建,然后调用了静态方法 main 方法(18 行),并把创建的对象和参数以及 Terminal 默认值传进去。静态的 main 方法里面调用 elasticsearch.main 方法。
publicstaticvoidmain(final String[] args)throws Exception { //1、入口 // we want the JVM to think there is a security manager installed so that if internal policy decisions that would be based on the // presence of a security manager or lack thereof act as if there is a security manager present (e.g., DNS cache policy) System.setSecurityManager(new SecurityManager() { @Override publicvoidcheckPermission(Permission perm){ // grant all permissions so that we can later set the security manager to the one that we want } }); LogConfigurator.registerErrorListener(); // final Elasticsearch elasticsearch = new Elasticsearch(); int status = main(args, elasticsearch, Terminal.DEFAULT); //2、调用Elasticsearch.main方法 if (status != ExitCodes.OK) { exit(status); } }
staticintmain(final String[] args, final Elasticsearch elasticsearch, final Terminal terminal)throws Exception { return elasticsearch.main(args, terminal); //3、command main }
因为 Elasticsearch 类是继承了 EnvironmentAwareCommand 类,EnvironmentAwareCommand 类继承了 Command 类,但是 Elasticsearch 类并没有重写 main 方法,所以上面调用的 elasticsearch.main 其实是调用了 Command 的 main 方法,代码如下:
/** Parses options for this command from args and executes it. */ publicfinalintmain(String[] args, Terminal terminal)throws Exception { if (addShutdownHook()) { //利用Runtime.getRuntime().addShutdownHook方法加入一个Hook,在程序退出时触发该Hook shutdownHookThread = new Thread(() -> { try { this.close(); } catch (final IOException e) { try ( StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) { e.printStackTrace(pw); terminal.println(sw.toString()); } catch (final IOException impossible) { // StringWriter#close declares a checked IOException from the Closeable interface but the Javadocs for StringWriter // say that an exception here is impossible thrownew AssertionError(impossible); } } }); Runtime.getRuntime().addShutdownHook(shutdownHookThread); }
方法前面是根据传参去判断配置的,如果配置为空,就会直接跳到执行 putSystemPropertyIfSettingIsMissing 方法,这里会配置三个属性:path.data、path.home、path.logs 设置 es 的 data、home、logs 目录,它这里是根据我们 ide 配置的 vm options 进行设置的,这也是为什么我们上篇文章说的配置信息,如果不配置的话就会直接报错。下面看看 putSystemPropertyIfSettingIsMissing 方法代码里面怎么做到的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/** Ensure the given setting exists, reading it from system properties if not already set. */ privatestaticvoidputSystemPropertyIfSettingIsMissing(final Map<String, String> settings, final String setting, final String key){ final String value = System.getProperty(key);//获取key(es.path.data)找系统设置 if (value != null) { if (settings.containsKey(setting)) { final String message = String.format( Locale.ROOT, "duplicate setting [%s] found via command-line [%s] and system property [%s]", setting, settings.get(setting), value); thrownew IllegalArgumentException(message); } else { settings.put(setting, value); } } }
publicstatic Environment prepareEnvironment(Settings input, Terminal terminal, Map<String, String> properties, Path configPath){ // just create enough settings to build the environment, to get the config dir Settings.Builder output = Settings.builder(); initializeSettings(output, input, properties); Environment environment = new Environment(output.build(), configPath);
//查看 es.path.conf 目录下的配置文件是不是 yml 格式的,如果不是则抛出一个异常 if (Files.exists(environment.configFile().resolve("elasticsearch.yaml"))) { thrownew SettingsException("elasticsearch.yaml was deprecated in 5.5.0 and must be renamed to elasticsearch.yml"); }
if (Files.exists(environment.configFile().resolve("elasticsearch.json"))) { thrownew SettingsException("elasticsearch.json was deprecated in 5.5.0 and must be converted to elasticsearch.yml"); }
output = Settings.builder(); // start with a fresh output Path path = environment.configFile().resolve("elasticsearch.yml"); if (Files.exists(path)) { try { output.loadFromPath(path); //加载文件并读取配置文件内容 } catch (IOException e) { thrownew SettingsException("Failed to load settings from " + path.toString(), e); } }
// re-initialize settings now that the config file has been loaded initializeSettings(output, input, properties); //再一次初始化设置 finalizeSettings(output, terminal);
environment = new Environment(output.build(), configPath);
// we put back the path.logs so we can use it in the logging configuration file output.put(Environment.PATH_LOGS_SETTING.getKey(), environment.logsFile().toAbsolutePath().normalize().toString()); returnnew Environment(output.build(), configPath); }
voidinit(finalboolean daemonize, final Path pidFile, finalboolean quiet, Environment initialEnv) throws NodeValidationException, UserException { try { Bootstrap.init(!daemonize, pidFile, quiet, initialEnv); //11、执行 Bootstrap 中的 init 方法 } catch (BootstrapException | RuntimeException e) { // format exceptions to the console in a special way // to avoid 2MB stacktraces from guice, etc. thrownew StartupException(e); } }
staticvoidinit( finalboolean foreground, final Path pidFile, finalboolean quiet, final Environment initialEnv)throws BootstrapException, NodeValidationException, UserException { // force the class initializer for BootstrapInfo to run before // the security manager is installed BootstrapInfo.init();
INSTANCE = new Bootstrap(); //12、创建一个 Bootstrap 实例
finalboolean closeStandardStreams = (foreground == false) || quiet; try { if (closeStandardStreams) { final Logger rootLogger = ESLoggerFactory.getRootLogger(); final Appender maybeConsoleAppender = Loggers.findAppender(rootLogger, ConsoleAppender.class); if (maybeConsoleAppender != null) { Loggers.removeAppender(rootLogger, maybeConsoleAppender); } closeSystOut(); }
// fail if somebody replaced the lucene jars checkLucene(); //14、检查Lucene版本
// install the default uncaught exception handler; must be done before security is initialized as we do not want to grant the runtime permission setDefaultUncaughtExceptionHandler Thread.setDefaultUncaughtExceptionHandler( new ElasticsearchUncaughtExceptionHandler(() -> Node.NODE_NAME_SETTING.get(environment.settings())));
try { // any secure settings must be read during node construction IOUtils.close(keystore); } catch (IOException e) { thrownew BootstrapException(e); }
INSTANCE.start(); //26、调用 start 方法
if (closeStandardStreams) { closeSysError(); } } catch (NodeValidationException | RuntimeException e) { // disable console logging, so user does not see the exception twice (jvm will show it already) final Logger rootLogger = ESLoggerFactory.getRootLogger(); final Appender maybeConsoleAppender = Loggers.findAppender(rootLogger, ConsoleAppender.class); if (foreground && maybeConsoleAppender != null) { Loggers.removeAppender(rootLogger, maybeConsoleAppender); } Logger logger = Loggers.getLogger(Bootstrap.class); if (INSTANCE.node != null) { logger = Loggers.getLogger(Bootstrap.class, Node.NODE_NAME_SETTING.get(INSTANCE.node.settings())); } // HACK, it sucks to do this, but we will run users out of disk space otherwise if (e instanceof CreationException) { // guice: log the shortened exc to the log file ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintStream ps = null; try { ps = new PrintStream(os, false, "UTF-8"); } catch (UnsupportedEncodingException uee) { assertfalse; e.addSuppressed(uee); } new StartupException(e).printStackTrace(ps); ps.flush(); try { logger.error("Guice Exception: {}", os.toString("UTF-8")); } catch (UnsupportedEncodingException uee) { assertfalse; e.addSuppressed(uee); } } elseif (e instanceof NodeValidationException) { logger.error("node validation exception\n{}", e.getMessage()); } else { // full exception logger.error("Exception", e); } // re-enable it if appropriate, so they can see any logging during the shutdown process if (foreground && maybeConsoleAppender != null) { Loggers.addAppender(rootLogger, maybeConsoleAppender); } throw e; } }