elasticsearch 원본에서 노드 시작 프로세스 보기

87718 단어 elasticsearch
본고는 원본 코드를 읽는 경로를 통해 elasticsearch 노드가 시작되는 대체적인 절차를 이해하고자 한다.

1. 구성 읽기 실행 환경 만들기


운영 환경은 Environment 대상을 가리킨다. 이 대상은 Settings 대상(es 설정), data 경로, plugins 경로, modules 경로, bin 경로, libs 경로, log 경로, es.path.conf 경로 등을 봉인했다.
관련 소스:
    /** Create an {@link Environment} for the command to use. Overrideable for tests. */
    protected Environment createEnv(final Terminal terminal, final Map<String, String> settings) throws UserException {
        final String esPathConf = System.getProperty("es.path.conf");
        if (esPathConf == null) {
            throw new UserException(ExitCodes.CONFIG, "the system property [es.path.conf] must be set");
        }
        return InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings, getConfigPath(esPathConf));
    }

여기에서 es.path.conf의 설정, 즉es의 각종 설정 파일이 있는 경로를 읽을 수 있습니다.
개인적으로 여기는 es.home의 설정을 읽을 필요가 없다고 생각합니다. 이 설정은 실제로 필요 없습니다. 이미 Environment의 설정이 있고 home류의 구조 함수에 이런 코드가 있기 때문입니다.
Environment(final Settings settings, final Path configPath, final Path tmpPath)
    ...
    if (configPath != null) {
        configFile = configPath.normalize();
    } else {
        configFile = homeFile.resolve("config");
    }
    ...
}

기본 프로필 위치는 config 경로의 elasticsearch.yml 안입니다.Settings 구성 파일의 로드는 다음과 같습니다.
public static Environment  prepareEnvironment(Settings input, Terminal terminal, Map<String, String> properties, Path configPath) {
    ...
    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) {
            throw new SettingsException("Failed to load settings from " + path.toString(), e);
        }
    }
    ...
    return new Environment(output.build(), configPath);
}

불러오는 설정은 Environment 대상으로 봉하여 Node에 설정합니다.

2. 노드 초기화


이 부호는 모두 try류의 아래 이 구조 함수 안에 있다
protected Node(final Environment environment, Collection<Class<? extends Plugin>> classpathPlugins) {
    ...
    logger.info("initializing ...");
    ...
    logger.info("initialized");
    ...
}

일부 세부 사항을 생략하면 우리가 es를 시작할 때 자주 보는 두 개의 로그를 뚜렷하게 볼 수 있다.
다음은 안의 관건적인 세부 사항을 살펴보고 이 구조 함수를 전체적으로 살펴보자.
    protected Node(final Environment environment, Collection<Class<? extends Plugin>> classpathPlugins) {
        final List<Closeable> resourcesToClose = new ArrayList<>(); // register everything we need to release in the case of an error
        boolean success = false;
        {
            // use temp logger just to say we are starting. we can't use it later on because the node name might not be set
            Logger logger = Loggers.getLogger(Node.class, NODE_NAME_SETTING.get(environment.settings()));
            logger.info("initializing ...");
        }
        try {
            ...
        } catch (IOException ex) {
            throw new ElasticsearchException("failed to bind service", ex);
        } finally {
            if (!success) {
                IOUtils.closeWhileHandlingException(resourcesToClose);
            }
        }
    }

구체적인 세부 사항은 이 List에서 많은 자원을 열 수 있습니다. 이 자원들은 모두 finally에 추가됩니다. 초기화에 실패하면 try에서 이 자원을 닫습니다.
그리고 Environment에 들어가서 노드를 구체적으로 초기화하는 절차를 살펴본다.

노드 구성

        originalSettings = environment.settings();
        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, to recover the node id and enable logging
        try {
            nodeEnvironment = new NodeEnvironment(tmpSettings, environment);
            resourcesToClose.add(nodeEnvironment);
        } catch (IOException ex) {
            throw new IllegalStateException("Failed to create node environment", ex);
        }
NodeEnvironment 객체의 구성을 읽고 노드 자체 구성을 생성합니다. 이 구성에는 다음과 같은 정보가 포함되지만 이에 국한되지 않습니다.
  • node.max_local_storage_nodes, 로컬 최대 저장 노드 수량, 기본 1, 즉 로컬 최대 한 개의 데이터를 저장할 수 있는 노드만 동시에 시작할 수 있음;
  • node.id.seed, 노드 id를 생성하는 무작위 피드, 기본값은 0;
  • 노드 데이터 저장 경로 nodePath, 기본값은 {esHomePath}/data/nodes/0, {esHomePath}/data/nodes/1 등;
  • indice 데이터 저장 경로, 기본값은 {nodePath}/indices;

  • 또한 이 안에는 노드 id를 무작위로 생성하여 디스크, jvm 정보를 읽고 출력했습니다. 이곳의 heap size은 기본값인 1/4의 물리적 메모리입니다.
    [2018-10-19T17:52:35,480][INFO ][o.e.e.NodeEnvironment    ] [0aIXj0y] using [1] data paths, mounts [[(F:)]], net usable_space [215gb], net total_space [231gb], types [NTFS]
    [2018-10-19T17:53:12,209][INFO ][o.e.e.NodeEnvironment    ] [0aIXj0y] heap size [7.9gb], compressed ordinary object pointers [true]
    

    노드 이름 생성

            final boolean hadPredefinedNodeName = NODE_NAME_SETTING.exists(tmpSettings);
            final String nodeId = nodeEnvironment.nodeId();
            tmpSettings = addNodeNameIfNeeded(tmpSettings, nodeId);
    
    node.name에 설정이 있는지 확인하고 사용자가 설정하지 않으면 nodeId의 7위를 노드 이름으로 한다.

    plugins 및 modules 로드

    this.pluginsService = new PluginsService(tmpSettings, environment.configFile(), environment.modulesFile(), environment.pluginsFile(), classpathPlugins);
    
    PluginService의 구조 함수에 모든 pluginsmodules을 불러오면 다음과 같은 로그가 출력됩니다.
    [2018-10-22T15:42:43,978][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [aggs-matrix-stats]
    [2018-10-22T15:42:43,980][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [analysis-common]
    [2018-10-22T15:42:43,981][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [ingest-common]
    [2018-10-22T15:42:43,982][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [lang-expression]
    [2018-10-22T15:42:43,983][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [lang-mustache]
    [2018-10-22T15:42:43,983][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [lang-painless]
    [2018-10-22T15:42:43,984][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [mapper-extras]
    [2018-10-22T15:42:43,985][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [parent-join]
    [2018-10-22T15:42:43,986][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [percolator]
    [2018-10-22T15:42:43,987][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [rank-eval]
    [2018-10-22T15:42:43,988][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [reindex]
    [2018-10-22T15:42:43,989][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [repository-url]
    [2018-10-22T15:42:43,990][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [transport-netty4]
    [2018-10-22T15:42:43,991][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [tribe]
    [2018-10-22T15:42:43,992][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [x-pack-core]
    [2018-10-22T15:42:43,993][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [x-pack-deprecation]
    [2018-10-22T15:42:43,994][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [x-pack-graph]
    [2018-10-22T15:42:43,994][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [x-pack-logstash]
    [2018-10-22T15:42:43,995][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [x-pack-ml]
    [2018-10-22T15:42:43,996][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [x-pack-monitoring]
    [2018-10-22T15:42:43,998][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [x-pack-rollup]
    [2018-10-22T15:42:43,999][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [x-pack-security]
    [2018-10-22T15:42:44,000][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [x-pack-sql]
    [2018-10-22T15:42:44,001][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [x-pack-upgrade]
    [2018-10-22T15:42:44,002][INFO ][o.e.p.PluginsService     ] [localhost-debug] loaded module [x-pack-watcher]
    [2018-10-22T15:42:45,130][INFO ][o.e.p.PluginsService     ] [localhost-debug] no plugins loaded
    

    모듈별 초기화


    뒤에 수백 줄에 달하는 코드는 모두 각 모듈과 구성 요소(AbstractComponent류)를 구성하고 있다. 예를 들어 ScriptModule, AnalysisModule, SearchModule, IndicesService, ClusterService, TransportService 등이다.
    인쇄 로그는 다음과 같습니다.
    [2018-10-24T16:10:08,595][DEBUG][o.e.a.ActionModule       ] Using REST wrapper from plugin org.elasticsearch.xpack.security.Security
    [2018-10-24T16:10:08,856][INFO ][o.e.d.DiscoveryModule    ] [localhost-debug] using discovery type [zen]
    

    각 모듈과 부품이 각각 어떤 기능을 담당하는지는 여기서 잠시 깊이 연구하지 않고 실제로는 유명에 따라 대체적으로 그 작용을 추측할 수 있다.

    3. 부팅 노드


    이 코드는 Node류의 start() 방법 안에 있다.

    구성 요소 시작

        /**
         * Start the node. If the node is already started, this method is no-op.
         */
        public Node start() throws NodeValidationException {
            if (!lifecycle.moveToStarted()) {
                return this;
            }
    
            Logger logger = Loggers.getLogger(Node.class, NODE_NAME_SETTING.get(settings));
            logger.info("starting ...");
            pluginLifecycleComponents.forEach(LifecycleComponent::start);
    
            injector.getInstance(MappingUpdatedAction.class).setClient(client);
            injector.getInstance(IndicesService.class).start();
            injector.getInstance(IndicesClusterStateService.class).start();
            injector.getInstance(SnapshotsService.class).start();
            injector.getInstance(SnapshotShardsService.class).start();
            injector.getInstance(RoutingService.class).start();
            injector.getInstance(SearchService.class).start();
            nodeService.getMonitorService().start();
                    final ClusterService clusterService = injector.getInstance(ClusterService.class);
    
            final NodeConnectionsService nodeConnectionsService = injector.getInstance(NodeConnectionsService.class);
            nodeConnectionsService.start();
            clusterService.setNodeConnectionsService(nodeConnectionsService);
    
            injector.getInstance(ResourceWatcherService.class).start();
            injector.getInstance(GatewayService.class).start();
            Discovery discovery = injector.getInstance(Discovery.class);
            clusterService.getMasterService().setClusterStatePublisher(discovery::publish);
    
            // Start the transport service now so the publish address will be added to the local disco node in ClusterService
            TransportService transportService = injector.getInstance(TransportService.class);
            transportService.getTaskManager().setTaskResultsService(injector.getInstance(TaskResultsService.class));
            transportService.start();
            assert localNodeFactory.getNode() != null;
            assert transportService.getLocalNode().equals(localNodeFactory.getNode())
                : "transportService has a different local node than the factory provided";
            final MetaData onDiskMetadata;
            try {
                // we load the global state here (the persistent part of the cluster state stored on disk) to
                // pass it to the bootstrap checks to allow plugins to enforce certain preconditions based on the recovered state.
                if (DiscoveryNode.isMasterNode(settings) || DiscoveryNode.isDataNode(settings)) {
                    onDiskMetadata = injector.getInstance(GatewayMetaState.class).loadMetaState();
                } else {
                    onDiskMetadata = MetaData.EMPTY_META_DATA;
                }
                assert onDiskMetadata != null : "metadata is null but shouldn't"; // this is never null
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
            ...
        }
    

    start은 많은 구성 요소(Service, 모두 AbstractComponent의 하위 클래스)인데 구체적인 논리는 잠시 따지지 않고 로그를 인쇄하면 다음과 같다.
    [2018-10-24T16:10:16,609][INFO ][o.e.n.Node               ] [localhost-debug] starting ...
    [2018-10-24T16:11:14,864][INFO ][o.e.t.TransportService   ] [localhost-debug] publish_address {xx.xx.xx.xx:9300}, bound_addresses {xx.xx.xx.xx:9300}
    

    node 정보 확인

        validateNodeBeforeAcceptingRequests(new BootstrapContext(settings, onDiskMetadata), transportService.boundAddress(), pluginsService
                .filterPlugins(Plugin
                .class)
                .stream()
                .flatMap(p -> p.getBootstrapChecks().stream()).collect(Collectors.toList()));
    

    이 코드는 node에 대해 시작 검증을 하고 로그는 다음과 같다.
    [2018-10-24T16:33:31,983][INFO ][o.e.b.BootstrapChecks    ] [localhost-debug] bound or publishing to a non-loopback address, enforcing bootstrap checks
    
    elasticsearch.yml에서 network.host이나 다른 방식(예를 들어 http.host, transport.host)의 host의 설정을 수정했다면 기본적인 127.0.0.1이 아니라 bootstrap check에 문제가 발견되면 warning이 아니라 error이다.
    여기에 약간의 문제가 보고될 수 있다.예를 들어 설정-Xms와 -Xmx의 값이 같지 않으면 문제가 생길 수 있고 file descriptors과 같은 다른 문제도 인터넷에서 해결 방법을 찾을 수 있다.

    join cluster

    discovery.startInitialJoin();
    

    이 코드는 현재 nodecluster에 추가하려고 시도하였으며, 서로 다른 Discovery은 서로 다른 실현을 하였으며, 기본 실현은 ZenDiscovery이다.
            if (initialStateTimeout.millis() > 0) {
                final ThreadPool thread = injector.getInstance(ThreadPool.class);
                ClusterState clusterState = clusterService.state();
                ClusterStateObserver observer = new ClusterStateObserver(clusterState, clusterService, null, logger, thread.getThreadContext());
                if (clusterState.nodes().getMasterNodeId() == null) {
                    logger.debug("waiting to join the cluster. timeout [{}]", initialStateTimeout);
                    final CountDownLatch latch = new CountDownLatch(1);
                    observer.waitForNextChange(new ClusterStateObserver.Listener() {
                        @Override
                        public void onNewClusterState(ClusterState state) { latch.countDown(); }
    
                        @Override
                        public void onClusterServiceClose() {
                            latch.countDown();
                        }
    
                        @Override
                        public void onTimeout(TimeValue timeout) {
                            logger.warn("timed out while waiting for initial discovery state - timeout: {}",
                                initialStateTimeout);
                            latch.countDown();
                        }
                    }, state -> state.nodes().getMasterNodeId() != null, initialStateTimeout);
    
                    try {
                        latch.await();
                    } catch (InterruptedException e) {
                        throw new ElasticsearchTimeoutException("Interrupted while waiting for initial discovery state");
                    }
                }
            }
    
    cluster에 가입하려면 먼저 master 노드를 찾아야 하고 master 노드를 찾는 데 시간이 걸립니다. 여기는 master 노드가 나타나거나 시간 초과(기본 30초)가 끝날 때까지 기다립니다.

    http 사용

            if (NetworkModule.HTTP_ENABLED.get(settings)) {
                injector.getInstance(HttpServerTransport.class).start();
            }
    

    여기에는 기본적으로 http이 설정되어 있으며 인쇄 로그는 다음과 같습니다.
    [2018-10-24T19:13:07,544][INFO ][o.e.x.s.t.n.SecurityNetty4HttpServerTransport] [localhost-debug] publish_address {xx.xx.xx.xx:9200}, bound_addresses {xx.xx.xx.xx:9200}
    

    여기까지 현재 노드가 성공적으로 시작되었습니다. 이때 master 노드를 선택하지 않았을 수도 있습니다. cluster에 가입하지 않았지만 괜찮습니다. 집단 서비스를 제공할 수 없지만 이 노드를 통해 일부 서비스를 제공할 수 있습니다(http 요청 테스트를 보낼 수 있습니다).

    좋은 웹페이지 즐겨찾기