搜索引擎ElasticSearch的啓動過程

上一篇文章說了ES的源碼編譯以及如何在本地編譯。這一篇文章主要說明ES的啓動過程。html

環境準備

參考ElasticSearch源碼編譯和Debugjava

說明:本文章使用的ES版本是:6.7.0node

啓動函數:org.elasticsearch.bootstrap.ElasticSearchgit

設置以下斷點:github

clipboard.png

啓動在上一篇文章中介紹的Debug模式中的一種,這裏我用的遠程Debug模式。算法

ElasticSearch的啓動過程

跟着Debug流程走一遍,能夠看出ES啓動流程大概分爲如下幾個階段:bootstrap

  1. org.elasticsearch.bootstrap.Elasticsearch#main(java.lang.String[]) 解析命令參數,加載配置,權限驗證
  2. org.elasticsearch.bootstrap.Bootstrap 初始化,資源檢查
  3. org.elasticsearch.node.Node 啓動單機節點,建立keepAlive線程segmentfault

    1. 爲建立Node對象作準備,並最終建立Node對象api

      1. 建立Node對象安全

        1. 如何加載模塊和插件
        2. 建立模塊和插件的線程池
    2. 啓動Node實例

1、org.elasticsearch.bootstrap.Elasticsearch#main(java.lang.String[])解析命令參數,加載配置,權限驗證

程序入口代碼以下:

-w531

  1. 若是經過啓動命令傳入了DNS Cache時間,則重寫DNS Cache時間
  2. 建立 SecurityManager 安全管理器

    SecurityManager:安全管理器在Java語言中的做用就是檢查操做是否有權限執行,經過則順序進行,不然拋出一個異常
  3. LogConfigurator.registerErrorListener(); 註冊錯誤日誌監聽器
  4. new Elasticsearch(); 建立 Elasticsearch 對象

    Elasticsearch類繼承了EnvironmentAwareCommandCommand,其完整的繼承關係以下

    因此Elasticsearch也能夠解析命令行參數。

  5. elasticsearch.main(args, terminal); 這裏的main方法是其父類中的main方法,這裏由於繼承關係,方法執行的順序以下:

    1. org.elasticsearch.cli.Command#main 註冊shutdownHook,當程序異常關閉時打印異常信息
    2. org.elasticsearch.cli.Command#mainWithoutErrorHandling 解析命令行參數
    3. org.elasticsearch.cli.EnvironmentAwareCommand#execute 加載配置路徑:home、data、logs
    4. org.elasticsearch.cli.EnvironmentAwareCommand#createEnv 加載elasticsearch.yaml配置文件,建立command運行的環境
    5. org.elasticsearch.bootstrap.Elasticsearch#execute 配置驗證,進入Bootstrap.init階段

2、org.elasticsearch.bootstrap.Bootstrap 初始化,資源檢查

Bootstrap階段作的事情比較多,主要方法以下:

/**
     * This method is invoked by {@link Elasticsearch#main(String[])} to startup elasticsearch.
     */
    static void init(
            final boolean foreground,
            final Path pidFile,
            final boolean 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();

        final SecureSettings keystore = loadSecureSettings(initialEnv);
        final Environment environment = createEnvironment(foreground, pidFile, keystore, initialEnv.settings(), initialEnv.configFile());

        if (Node.NODE_NAME_SETTING.exists(environment.settings())) {
            LogConfigurator.setNodeName(Node.NODE_NAME_SETTING.get(environment.settings()));
        }
        try {
            LogConfigurator.configure(environment);
        } catch (IOException e) {
            throw new BootstrapException(e);
        }
        if (environment.pidFile() != null) {
            try {
                PidFile.create(environment.pidFile(), true);
            } catch (IOException e) {
                throw new BootstrapException(e);
            }
        }

        final boolean closeStandardStreams = (foreground == false) || quiet;
        try {
            if (closeStandardStreams) {
                final Logger rootLogger = LogManager.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();

            // 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());

            INSTANCE.setup(true, environment);

            try {
                // any secure settings must be read during node construction
                IOUtils.close(keystore);
            } catch (IOException e) {
                throw new BootstrapException(e);
            }

            INSTANCE.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 = LogManager.getRootLogger();
            final Appender maybeConsoleAppender = Loggers.findAppender(rootLogger, ConsoleAppender.class);
            if (foreground && maybeConsoleAppender != null) {
                Loggers.removeAppender(rootLogger, maybeConsoleAppender);
            }
            Logger logger = LogManager.getLogger(Bootstrap.class);
            // 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) {
                    assert false;
                    e.addSuppressed(uee);
                }
                new StartupException(e).printStackTrace(ps);
                ps.flush();
                try {
                    logger.error("Guice Exception: {}", os.toString("UTF-8"));
                } catch (UnsupportedEncodingException uee) {
                    assert false;
                    e.addSuppressed(uee);
                }
            } else if (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;
        }
    }

詳細流程以下:

  1. INSTANCE = new Bootstrap();, 建立Bootstrap對象實例,該類構造函數會建立一個用戶線程,添加到Runtime Hook中,進行 countDown 操做

    CountDownLatch是一個同步工具類,它容許一個或多個線程一直等待,直到其餘線程執行完後再執行。例如,應用程序的主線程但願在負責啓動框架服務的線程已經啓動全部框架服務以後執行。
  2. loadSecureSettings(initialEnv);:加載 keystore 安全配置,keystore文件不存在則建立,保存;存在則解密,更新keystore
  3. createEnvironment:根據配置,建立Elasticsearch 運行的必須環境
  4. setNodeName:設置節點名稱,這個在日誌中打印的時候會使用
  5. LogConfigurator.configure(environment);:根據log4j2.properties配置文件加載日誌相關配置
  6. PidFile.create(environment.pidFile(), true);:檢查PID文件是否存在,不存在則建立,同時寫入程序進程ID
  7. checkLucene 檢查Lucene jar包版本
  8. setDefaultUncaughtExceptionHandler:設置程序中產生的某些未捕獲的異常的處理方式

    UncaughtExceptionHandler:在多線程中,有時沒法捕獲其餘線程產生的異常,這時候須要某種機制捕獲並處理異常,UncaughtExceptionHandler就是來作這件事情的
  9. INSTANCE.setup(true, environment); 爲建立Node對象作準備,並最終建立Node對象
  10. INSTANCE.start(); 啓動Node實例

3、org.elasticsearch.node.Node 啓動單機節點,建立keepAlive線程

在第二個階段中的最後兩步都就是和建立節點相關的。

一、 INSTANCE.setup(true, environment); 爲建立Node對象作準備,並最終建立Node對象

Bootstrap.init中調用該方法。

setup方法以下:

private void setup(boolean addShutdownHook, Environment environment) throws BootstrapException {
        Settings settings = environment.settings();

        try {
            spawner.spawnNativeControllers(environment);
        } catch (IOException e) {
            throw new BootstrapException(e);
        }

        initializeNatives(
                environment.tmpFile(),
                BootstrapSettings.MEMORY_LOCK_SETTING.get(settings),
                BootstrapSettings.SYSTEM_CALL_FILTER_SETTING.get(settings),
                BootstrapSettings.CTRLHANDLER_SETTING.get(settings));

        // initialize probes before the security manager is installed
        initializeProbes();

        if (addShutdownHook) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    try {
                        IOUtils.close(node, spawner);
                        LoggerContext context = (LoggerContext) LogManager.getContext(false);
                        Configurator.shutdown(context);
                    } catch (IOException ex) {
                        throw new ElasticsearchException("failed to stop node", ex);
                    }
                }
            });
        }

        try {
            // look for jar hell
            final Logger logger = LogManager.getLogger(JarHell.class);
            JarHell.checkJarHell(logger::debug);
        } catch (IOException | URISyntaxException e) {
            throw new BootstrapException(e);
        }

        // Log ifconfig output before SecurityManager is installed
        IfConfig.logIfNecessary();

        // install SM after natives, shutdown hooks, etc.
        try {
            Security.configure(environment, BootstrapSettings.SECURITY_FILTER_BAD_DEFAULTS_SETTING.get(settings));
        } catch (IOException | NoSuchAlgorithmException e) {
            throw new BootstrapException(e);
        }

        node = new Node(environment) {
            @Override
            protected void validateNodeBeforeAcceptingRequests(
                final BootstrapContext context,
                final BoundTransportAddress boundTransportAddress, List<BootstrapCheck> checks) throws NodeValidationException {
                BootstrapChecks.check(context, boundTransportAddress, checks);
            }

            @Override
            protected void registerDerivedNodeNameWithLogger(String nodeName) {
                LogConfigurator.setNodeName(nodeName);
            }
        };
    }
  1. spawner.spawnNativeControllers(environment);:讀取modules文件夾下的全部模塊,遍歷全部模塊,爲每一個模塊生成native Controller。
  2. initializeNatives(Path tmpFile, boolean mlockAll, boolean systemCallFilter, boolean ctrlHandler):初始化本地資源

    1. 若是是root用戶,拋出異常
    2. 嘗試啓動 系統調用過濾器 system call filter
    3. 嘗試調用mlockall
    4. 若是是Windows關閉事件監聽器
  3. initializeProbes(); 初始化進程和系統探針。//TODO:這裏探針的做用是?權限驗證?
  4. 添加一個ShutdownHook,當ES退出時用於關閉必要的IO流,日誌器上下文和配置器等
  5. JarHell.checkJarHell(logger::debug);:Checks the current classpath for duplicate classes
  6. IfConfig.logIfNecessary();:Debug模式,打印IfConfig信息
  7. Security.configure():加載SecurityManager,權限驗證
  8. new Node(environment):根據運行環境,建立Node對象


1.一、new Node(environment) 建立Node對象

Node的建立過程很複雜,這裏只大概說一下里面作了哪些事情,詳細的過程還需讀者細度源碼。其部分代碼以下:

/**
     * Constructs a node
     *
     * @param environment                the environment for this node
     * @param classpathPlugins           the plugins to be loaded from the classpath
     * @param forbidPrivateIndexSettings whether or not private index settings are forbidden when creating an index; this is used in the
     *                                   test framework for tests that rely on being able to set private settings
     */
    protected Node(
            final Environment environment, Collection<Class<? extends Plugin>> classpathPlugins, boolean forbidPrivateIndexSettings) {
        logger = LogManager.getLogger(Node.class);
        final List<Closeable> resourcesToClose = new ArrayList<>(); // register everything we need to release in the case of an error
        boolean success = false;
        try {
            Settings tmpSettings = Settings.builder().put(environment.settings())
                .put(Client.CLIENT_TYPE_SETTING_S.getKey(), CLIENT_TYPE).build();

            /*
             * Create the node environment as soon as possible so we can
             * recover the node id which we might have to use to derive the
             * node name. And it is important to get *that* as soon as possible
             * so that log lines can contain it.
             */
            boolean nodeNameExplicitlyDefined = NODE_NAME_SETTING.exists(tmpSettings);
            try {
                Consumer<String> nodeIdConsumer = nodeNameExplicitlyDefined ?
                        nodeId -> {} : nodeId -> registerDerivedNodeNameWithLogger(nodeIdToNodeName(nodeId));
                nodeEnvironment = new NodeEnvironment(tmpSettings, environment, nodeIdConsumer);
                resourcesToClose.add(nodeEnvironment);
            } catch (IOException ex) {
                throw new IllegalStateException("Failed to create node environment", ex);
            }
            if (nodeNameExplicitlyDefined) {
                logger.info("node name [{}], node ID [{}]",
                        NODE_NAME_SETTING.get(tmpSettings), nodeEnvironment.nodeId());
            } else {
                tmpSettings = Settings.builder()
                        .put(tmpSettings)
                        .put(NODE_NAME_SETTING.getKey(), nodeIdToNodeName(nodeEnvironment.nodeId()))
                        .build();
                logger.info("node name derived from node ID [{}]; set [{}] to override",
                        nodeEnvironment.nodeId(), NODE_NAME_SETTING.getKey());
            }


            final JvmInfo jvmInfo = JvmInfo.jvmInfo();
            logger.info(
                "version[{}], pid[{}], build[{}/{}/{}/{}], OS[{}/{}/{}], JVM[{}/{}/{}/{}]",
                Version.displayVersion(Version.CURRENT, Build.CURRENT.isSnapshot()),
                jvmInfo.pid(),
                Build.CURRENT.flavor().displayName(),
                Build.CURRENT.type().displayName(),
                Build.CURRENT.shortHash(),
                Build.CURRENT.date(),
                Constants.OS_NAME,
                Constants.OS_VERSION,
                Constants.OS_ARCH,
                Constants.JVM_VENDOR,
                Constants.JVM_NAME,
                Constants.JAVA_VERSION,
                Constants.JVM_VERSION);
            logger.info("JVM arguments {}", Arrays.toString(jvmInfo.getInputArguments()));
            warnIfPreRelease(Version.CURRENT, Build.CURRENT.isSnapshot(), logger);

            if (logger.isDebugEnabled()) {
                logger.debug("using config [{}], data [{}], logs [{}], plugins [{}]",
                    environment.configFile(), Arrays.toString(environment.dataFiles()), environment.logsFile(), environment.pluginsFile());
            }
            ...
        }

Node 實例化對象過程以下:

  1. new Lifecycle();:生命週期Lifecycle設置爲 初始化狀態 INITIALIZED
  2. new NodeEnvironment(tmpSettings, environment, nodeIdConsumer);:建立node運行環境
  3. JvmInfo.jvmInfo();:讀取JVM信息,Debug模式打印該信息
  4. new PluginsService(tmpSettings,environment.configFile(),environment.modulesFile(),environment.pluginsFile(),classpathPlugins);:加載擴展服務PluginService,預加載模塊、插件
  5. new LocalNodeFactory(settings, nodeEnvironment.nodeId());:建立本地Node工廠
  6. Environment.assertEquivalent(environment, this.environment);:保證啓動過程當中,配置沒有被更改
  7. new ThreadPool():建立模塊和插件的線程池
  8. new NodeClient:建立Node客戶端
  9. 加載模塊:集羣管理,Indices(什麼用處?)、搜索模塊
  10. new MetaDataCreateIndexService():建立索引服務
  11. modules.createInjector();:加載其餘全部剩餘模塊並注入模塊管理器中
  12. clusterModule.getAllocationService().setGatewayAllocator(injector.getInstance(GatewayAllocator.class));:加載網關模塊
  13. 若是啓動了http配置,則加載rest中的全部ActionHandler,用於處理各類http請求,代碼以下:

    if (NetworkModule.HTTP_ENABLED.get(settings)) {
        logger.debug("initializing HTTP handlers ...");
        actionModule.initRestHandlers(() -> clusterService.state().nodes());
    }


1.1.一、new PluginsService 如何加載模塊和插件

new PluginsService中有代碼:Set<Bundle> modules = getModuleBundles(modulesDirectory);,用來加載模塊和插件,跟進代碼來到org.elasticsearch.plugins.PluginsService#readPluginBundle方法以下:

// get a bundle for a single plugin dir
    private static Bundle readPluginBundle(final Set<Bundle> bundles, final Path plugin, String type) throws IOException {
        LogManager.getLogger(PluginsService.class).trace("--- adding [{}] [{}]", type, plugin.toAbsolutePath());
        final PluginInfo info;
        try {
            info = PluginInfo.readFromProperties(plugin);
        } catch (final IOException e) {
            throw new IllegalStateException("Could not load plugin descriptor for " + type +
                                            " directory [" + plugin.getFileName() + "]", e);
        }
        final Bundle bundle = new Bundle(info, plugin);
        if (bundles.add(bundle) == false) {
            throw new IllegalStateException("duplicate " + type + ": " + info);
        }
        return bundle;
    }

其中的info = PluginInfo.readFromProperties(plugin);就是從指定目錄加載模塊或者插件,代碼以下:

/**
     * Reads the plugin descriptor file.
     *
     * @param path           the path to the root directory for the plugin
     * @return the plugin info
     * @throws IOException if an I/O exception occurred reading the plugin descriptor
     */
    public static PluginInfo readFromProperties(final Path path) throws IOException {
        final Path descriptor = path.resolve(ES_PLUGIN_PROPERTIES);

        final Map<String, String> propsMap;
        {
            final Properties props = new Properties();
            try (InputStream stream = Files.newInputStream(descriptor)) {
                props.load(stream);
            }
            propsMap = props.stringPropertyNames().stream().collect(Collectors.toMap(Function.identity(), props::getProperty));
        }

        final String name = propsMap.remove("name");
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException(
                    "property [name] is missing in [" + descriptor + "]");
        }
        final String description = propsMap.remove("description");
        if (description == null) {
            throw new IllegalArgumentException(
                    "property [description] is missing for plugin [" + name + "]");
        }
        final String version = propsMap.remove("version");
        if (version == null) {
            throw new IllegalArgumentException(
                    "property [version] is missing for plugin [" + name + "]");
        }

        final String esVersionString = propsMap.remove("elasticsearch.version");
        if (esVersionString == null) {
            throw new IllegalArgumentException(
                    "property [elasticsearch.version] is missing for plugin [" + name + "]");
        }
        final Version esVersion = Version.fromString(esVersionString);
        final String javaVersionString = propsMap.remove("java.version");
        if (javaVersionString == null) {
            throw new IllegalArgumentException(
                    "property [java.version] is missing for plugin [" + name + "]");
        }
        JarHell.checkVersionFormat(javaVersionString);
        final String classname = propsMap.remove("classname");
        if (classname == null) {
            throw new IllegalArgumentException(
                    "property [classname] is missing for plugin [" + name + "]");
        }

        final String extendedString = propsMap.remove("extended.plugins");
        final List<String> extendedPlugins;
        if (extendedString == null) {
            extendedPlugins = Collections.emptyList();
        } else {
            extendedPlugins = Arrays.asList(Strings.delimitedListToStringArray(extendedString, ","));
        }

        final String hasNativeControllerValue = propsMap.remove("has.native.controller");
        final boolean hasNativeController;
        if (hasNativeControllerValue == null) {
            hasNativeController = false;
        } else {
            switch (hasNativeControllerValue) {
                case "true":
                    hasNativeController = true;
                    break;
                case "false":
                    hasNativeController = false;
                    break;
                default:
                    final String message = String.format(
                            Locale.ROOT,
                            "property [%s] must be [%s], [%s], or unspecified but was [%s]",
                            "has_native_controller",
                            "true",
                            "false",
                            hasNativeControllerValue);
                    throw new IllegalArgumentException(message);
            }
        }

        if (esVersion.before(Version.V_6_3_0) && esVersion.onOrAfter(Version.V_6_0_0_beta2)) {
            propsMap.remove("requires.keystore");
        }

        if (propsMap.isEmpty() == false) {
            throw new IllegalArgumentException("Unknown properties in plugin descriptor: " + propsMap.keySet());
        }

        return new PluginInfo(name, description, version, esVersion, javaVersionString,
                              classname, extendedPlugins, hasNativeController);
    }

PluginInfo類有兩個全局常量:

public static final String ES_PLUGIN_PROPERTIES = "plugin-descriptor.properties";
    public static final String ES_PLUGIN_POLICY = "plugin-security.policy";

這是兩個配置模板,每一個插件和模塊都會按照plugin-descriptor.properties中的模板讀取響應的配置:name、description、version、elasticsearch.version、java.version、classname、has.native.controller、require.keystore。用這些配置,最終封裝成一個PluginInfo對象。最終返回給PluginsService的數據結構以下:Set<Bundle(PluginInfo, path)>

1.1.二、new ThreadPool():建立模塊和插件的線程池

ES的線程池類型:

public enum ThreadPoolType {
        DIRECT("direct"),
        FIXED("fixed"),
        FIXED_AUTO_QUEUE_SIZE("fixed_auto_queue_size"),
        SCALING("scaling");

        private final String type;

        public String getType() {
            return type;
        }

        ThreadPoolType(String type) {
            this.type = type;
        }

        private static final Map<String, ThreadPoolType> TYPE_MAP;

        static {
            Map<String, ThreadPoolType> typeMap = new HashMap<>();
            for (ThreadPoolType threadPoolType : ThreadPoolType.values()) {
                typeMap.put(threadPoolType.getType(), threadPoolType);
            }
            TYPE_MAP = Collections.unmodifiableMap(typeMap);
        }

        public static ThreadPoolType fromType(String type) {
            ThreadPoolType threadPoolType = TYPE_MAP.get(type);
            if (threadPoolType == null) {
                throw new IllegalArgumentException("no ThreadPoolType for " + type);
            }
            return threadPoolType;
        }
    }

如上,四種類型分別爲:

  • fixed(固定):fixed線程池擁有固定數量的線程來處理請求,在沒有空閒線程時請求將被掛在隊列中。queue_size參數能夠控制在沒有空閒線程時,能排隊掛起的請求數
  • fixed_auto_queue_size:此類型爲實驗性的,將被更改或刪除,不關注
  • scaling(彈性):scaling線程池擁有的線程數量是動態的,這個數字介於core和max參數的配置之間變化。keep_alive參數用來控制線程在線程池中空閒的最長時間
  • direct:此類線程是一種不支持關閉的線程,就意味着一旦使用,則會一直存活下去.

這一步當中,ThreadPool()建立了不少線程池,線程池的名稱以下:

public static class Names {
        public static final String SAME = "same";
        public static final String GENERIC = "generic";
        public static final String LISTENER = "listener";
        public static final String GET = "get";
        public static final String ANALYZE = "analyze";
        public static final String INDEX = "index";
        public static final String WRITE = "write";
        public static final String SEARCH = "search";
        public static final String SEARCH_THROTTLED = "search_throttled";
        public static final String MANAGEMENT = "management";
        public static final String FLUSH = "flush";
        public static final String REFRESH = "refresh";
        public static final String WARMER = "warmer";
        public static final String SNAPSHOT = "snapshot";
        public static final String FORCE_MERGE = "force_merge";
        public static final String FETCH_SHARD_STARTED = "fetch_shard_started";
        public static final String FETCH_SHARD_STORE = "fetch_shard_store";
    }

參考官方文檔能夠查看各個線程池的做用,線程池類型,線程數量,等待隊列數量等。

二、 INSTANCE.start(); 啓動Node實例

Bootstrap.init中調用該方法。

完成上面的步驟以後,若是是控制檯啓動服務,能夠再控制檯看到輸出以下:

clipboard.png

若是看到日誌:

[elasticsearch] [2019-04-09T20:01:12,428][INFO ][o.e.n.Node               ] [node-0] starting ...

就說明Node已經開始啓動了。

Node 的啓動步驟,大概作了這些事情:

  1. 啓動各類服務:

    服務名 簡介
    IndicesService 索引管理
    IndicesClusterStateService 跨集羣同步
    SnapshotsService 負責建立快照
    SnapshotShardsService 此服務在數據和主節點上運行,並控制這些節點上當前快照的分片。 它負責啓動和中止分片級別快照
    RoutingService 偵聽集羣狀態,當它收到ClusterChangedEvent(集羣改變事件)將驗證集羣狀態,路由表可能會更新
    SearchService 搜索服務
    ClusterService 集羣管理
    NodeConnectionsService 此組件負責在節點添加到羣集狀態後鏈接到節點,並在刪除它們時斷開鏈接。 此外,它會按期檢查全部鏈接是否仍處於打開狀態,並在須要時還原它們。 請注意,若是節點斷開/不響應ping,則此組件不負責從羣集中刪除節點。 這是由NodesFaultDetection完成的。 主故障檢測由連接MasterFaultDetection完成。
    ResourceWatcherService 通用資源觀察器服務
    GatewayService 網關服務
    Discovery 節點發現?
    TransportService 節點間數據同步網絡服務
    TaskResultsService
    HttpServerTransport 外部網絡服務
  2. 將node鏈接服務(NodeConnectionsService)綁定到集羣服務上(ClusterService)
  3. TransportService啓動後,驗證節點,驗證經過後,改服務用於node間的數據同步提供網絡支持
  4. 開啓線程,去探測發現是否有集羣,有則加入集羣,這裏也會啓動一個CountDownLatch進行等待,直到集羣選舉出master
  5. 開啓HttpServerTransport,接受外部網絡請求

當看到控制檯以下輸出則說明該節點啓動成功:

[elasticsearch] [2019-04-09T20:04:16,388][INFO ][o.e.n.Node               ] [node-0] started

總結

從上面的步驟能夠看出Elasticsearch的單節點啓動過程仍是很複雜的,並且文章只是列出了大概的啓動步驟,還有不少細節沒有深挖,好比節點和集羣的相互發現與加入,節點間的數據同步,集羣master是如何選舉的等。細節還需各位讀者深讀源碼。

參考:http://laijianfeng.org/2018/09/Elasticsearch-6-3-2-%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B/

系列文章

  1. 搜索引擎ElasticSearch源碼編譯和Debug環境搭建
  2. 搜索引擎ElasticSearch的啓動過程
  3. Elasticsearch建立索引流程
  4. Elasticsearch搜索過程詳解
  5. Elasticsearch搜索相關性排序算法詳解
  6. Elasticsearch中的倒排索引
相關文章
相關標籤/搜索