この記事は スタンバイ Advent Calendar 2021の18日目の記事です。
はじめに
最近、Elasticsearchのカスタムプラグインを開発する機会があったのと、Elasticsearchへの理解がまだ足りないことを感じ、今期からチームメンバーと社内Elasticsearchのソースコードリーディング勉強会を行っています。
今回の記事では、これまでの勉強会の内容を大枠にまとめて、Elasticsearchの起動プロセスを整理しました。
今回利用したElasticsearchのバージョンは7.14で、起動前の準備(環境変数の設定など)もあるけど、ここで割愛します。知識不足に関してあらかじめご了承ください。記事の内容についてこんなところをこう理解すればいいよなどアドバイスをいただけると非常に嬉しいです!
Elasticsearchが起動されるまでの概要
Elasticsearchの起動プロセスは大体以下のステップ
- org.elasticsearch.bootstrap.Elasticsearch#main()
 - org.elasticsearch.bootstrap.Bootstrap
 - org.elasticsearch.node.Node
 
まずElasticsearchのエントリーポイントを説明します。elasticsearchの起動スクリプトから(bin/elasticsearch)確認して、エントリーポイントはorg.elasticsearch.bootstrap.Elasticsearchです
  exec \
    "$JAVA" \
    "$XSHARE" \
    $ES_JAVA_OPTS \
    -Des.path.home="$ES_HOME" \
    -Des.path.conf="$ES_PATH_CONF" \
    -Des.distribution.flavor="$ES_DISTRIBUTION_FLAVOR" \
    -Des.distribution.type="$ES_DISTRIBUTION_TYPE" \
    -Des.bundled_jdk="$ES_BUNDLED_JDK" \
    -cp "$ES_CLASSPATH" \
    org.elasticsearch.bootstrap.Elasticsearch \
    "$@" <<<"$KEYSTORE_PASSWORD"
org.elasticsearch.bootstrap.Elasticsearch#main()
entry pointのコードは以下になります
やることは以下になります
- 起動コマンドからDNS Cache Policyを設定するなら、DNS Cache Policyをオーバーライドする
 - SecurityManagerを設定、checkPermissionで全部の権限を許可
 
JavaのSecurityManagerはセキュリティーを損なう恐れのある操作(File、Socketをいじるなど)の実行権限をチェック、権限あれば実行、なければ例外を出す
- 
エラーログリスナーを登録
- 早めにログリスナーを使って、ログが記録できないことを防ぐ
 
 - 
Elasticsearchインスタンスを作って、staticの
elasticsearch.main(args, terminal)を呼び出し、作成されたElasticsearchインスタンス、パラメータとTerminalデフォルト値を渡す- ElasticsearchクラスはEnvironmentAwareCommandクラスを継承され、EnvironmentAwareCommandクラスはCommandクラスを継承され、Elasticsearchクラスはmainメソッドをoverrideしないのでelasticsearch.mainはCommandのmainメソッドを使う
 
以下はCommandのmainメソッドコード
継承があるので以下の順番で実行していく
 - 
Command#main
- RuntimeのaddShutdownHookメソッドでshutdownHookを登録
- スレッド終了時、Elasticsearch#closeを呼び出す
 - 異常でshutdownされる場合にスタックトレースをプリントアウト
 
 - mainWithoutErrorHandlingを呼び出す
 
 - RuntimeのaddShutdownHookメソッドでshutdownHookを登録
 
JavaのShutdownHookはJVMから終了連絡を受けて、ShutdownHook内のメソッドを呼び出し、掃除処理を行い、スムーズにJVM終了できる
- 
Command#mainWithoutErrorHandling
- パラメータをパースしてterminalを設置して、EnvironmentAwareCommandのexecute()を実行(Commandクラスの抽象executeメソッドをオーバーライト)
 
 - 
EnvironmentAwareCommand#execute
- パラメータからSettingを判別、Settingがなければvm optionsからpath.data、path.home、path.logsをSettingに配置
 - execute(terminal, options, createEnv(settings))を呼び出し
 
 - 
EnvironmentAwareCommand#createEnv
- prepareEnvironmentを経由elasticsearch.ymlをロードして、Environmentを作成
 
 - 
Elasticsearch#execute
- daemonizer/pidFile/quietの値を取得、Bootstrapのinitを呼び出し
 
 
org.elasticsearch.bootstrap.Bootstrap
initのコードが多くて、リンクだけ残します
https://github.com/elastic/elasticsearch/blob/fc519e380029c6daecc950802435f7266f08708e/server/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java#L334
最初はBootstrapインスタンスを作って、KeepAliveスレッドを起動
private final CountDownLatch keepAliveLatch = new CountDownLatch(1);
    private final Thread keepAliveThread;
    private final Spawner spawner = new Spawner();
    /** creates a new instance */
    Bootstrap() {
        keepAliveThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //count
                    keepAliveLatch.await();
                } catch (InterruptedException e) {
                    // bail out
                }
            }
        }, "elasticsearch[keepAlive/" + Version.CURRENT + "]");
        keepAliveThread.setDaemon(false);
        // keep this thread alive (non daemon thread) until we shutdown
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                //thread終了時、KeepAliveLatchが0になって、keepAliveThread終了
                keepAliveLatch.countDown();
            }
        });
    }
CountDownLatchは他のスレッドの処理を待機する仕組みです。他のスレッドを処理完了後処理することができます。
Javaはユーザースレッドとデーモンスレッドがあります。
JVMの終了フロー
- ユーザースレッドがない場合にJVM終了を始める
 - JVMが全デーモンスレッドを終了
 - JVM終了
 
keepAliveThreadはCountDownLatchで設定されてかつユーザースレッドとして設定されます。
なので、ここの流れは
- JVMが終了請求を受ける
 - shutdownHook実行
 - keepAliveLatch.countDown()実行
 - KeepAliveLatchが0になって、keepAliveThread終了
 - JVM終了
 
KeepAliveスレッドを起動後、Bootstrapのinit()について、大枠のプロセスは以下のように
- SettingとconfigによってES実行時Environmentを作る
 - セキュリティとログ周りの設定
- keystoreのセキュリティ設定をロードする。keystoreファイルない時に作って保存する。keystoreファイルある時に復号化して、keystoreを更新する
 - setNodeName ログをプリント時Node名を設定、log4j2.propertiesの設定ファイルからログの設定をロード
 
 - PIDファイルがあるかどうかを確認、なければ作る、現在のpidを書き込む
 - Luceneのバージョンを確認
 - setDefaultUncaughtExceptionHandlerでCatchされない例外の処理方法を設置
 
マルチスレッドで他のスレッドから出す例外をcatchできないケースもあります。例外をcatchと処理することが必要で、UncaughtExceptionHandlerはこんな処理です
- INSTANCE.setup(true, environment);INSTANCE.start();
- Nodeインスタンスを作るため準備して、Nodeインスタンスを作って、Nodeインスタンスを起動
 
 
感想など
- Elasticsearchの起動だけでこんなプロセスがあることを理解できて、細かい粒度でElasticsearchの仕組みを把握できるようになりました。
 - CountDownLatchなどのプロセス調査により、Javaのマルチスレッドプログラミングに詳しくなりました。
 
参考

