nginx のスムーズな再起動を実装する方法

nginx のスムーズな再起動を実装する方法

1. 背景

サーバーの開発プロセスでは、新しいコードや構成をロードするためにサービスを再起動することが避けられません。サーバーの再起動中にサービスが中断されないことが保証されれば、再起動によるビジネスへの影響をゼロにすることができます。最近、nginx のスムーズな再起動について調べて、とても興味深いと感じました。興味のある学生が読めるように記録しました。

2. プロセスを再起動する

  • 再起動とは、古いサーバーを新しいサーバーに置き換えることを意味します。タスクを引き継ぐプロセスでは、必然的に古いサーバーと新しいサーバーが共存することになります。したがって、再起動のプロセスはおおよそ次のようになります。
    • 新しいサーバーを起動する
    • 古いサーバーと新しいサーバーが共存し、両方がリクエストを処理してサービスを提供します。
    • 古いサーバーはすべてのリクエストを処理した後、正常に終了します。
  • ここでの主な問題は、古いサーバーと新しいサーバーが共存できるようにする方法です。再起動前と再起動後のサーバー ポートが同じである場合、両方が同じポートをリッスンできるようにするにはどうすればよいでしょうか。

3. nginxの実装

nginx のスムーズな再起動を検証するために、まず nginx の起動時に新しいサーバー インスタンスを再度起動してみました。結果は図のとおりです。

当然のことながら、古いサーバーと新しいサーバーは同じポート 80 を使用するため、サーバー インスタンスを再度開いても機能しません。ソケットの再利用ポート オプションが有効になっていない場合、ポートを再利用すると、bind システム コールは失敗します。デフォルトでは、nginx はバインドを 5 回再試行し、失敗するとすぐに終了します。 Nginx は IPV4 アドレス 0.0.0.0 と IPV6 アドレス [::] をリッスンする必要があるため、図には 10 個の emerg ログが出力されています。

次に、次の 2 つのコマンドで構成されるスムーズ再起動コマンドを試します。

kill -USR2 `cat /var/run/nginx.pid`
キル -QUIT `cat /var/run/nginx.pid.oldbin`

最初のコマンドは、古いマスター プロセスに USR2 シグナルを送信します。プロセスの pid は /var/run/nginx.pid ファイルに保存されます。nginx.pid ファイル パスは nginx.conf によって設定されます。

2 番目のコマンドは、古いマスター プロセスに QUIT シグナルを送信します。プロセスの pid は /var/run/nginx.pid.oldbin ファイルに保存され、古いマスター プロセスは終了します。

それで疑問なのは、なぜ古いマスター プロセスの pid が 2 つの pid ファイルに存在するのかということです。実際、古いマスター プロセスに USR2 シグナルを送信した後、古いマスター プロセスは pid の名前を変更し、元の nginx.pid ファイルの名前が nginx.pid.oldbin に変更されました。このようにして、新しいマスターはファイル名 nginx.pid を使用できます。

まず最初のコマンドを実行すると、結果は次のようになります。

はい、古いマスタープロセスと新しいマスタープロセスおよびワーカープロセスが共存します。 2 番目のコマンドを実行してみましょう。結果は次のようになります。

ご覧のとおり、古いマスター プロセス 8527 とそのワーカー プロセスはすべて終了し、新しいマスター プロセス 12740 だけが残っています。

新しいインスタンスを手動で起動しても機能しないのに、シグナルで再起動すると機能するのはなぜなのか、不思議に思わざるを得ません。まず、nginx ログ ファイルを確認します。

前のエラー ログに加えて、ソケットが継承され、fd 値が 6 と 7 であることを示す通知がもう 1 つあります。 ログに従って、nginx ソース コードを調べ、nginx.c/ngx_exec_new_binary 関数を見つけました。

ngx_pid_t
ngx_exec_new_binary(ngx_cycle_t *サイクル、char *const *argv)
{
  ...
  ctx.path = argv[0];
  ctx.name = "新しいバイナリプロセス";
  ctx.argv = argv;
  2;
  env = ngx_set_environment(サイクル、&n);
...
  var = ngx_alloc(sizeof(NGINX_VAR)
          + サイクル->listening.nelts * (NGX_INT32_LEN + 1) + 2、
          サイクル->ログ);
...
  p = ngx_cpymem(var, NGINX_VAR "=", sizeof(NGINX_VAR));
  ls = サイクル->listening.elts;
  (i = 0; i < cycle->listening.nelts; i++) {
    p = ngx_sprintf(p, "%ud;", ls[i].fd);
  }
  *p = '\0';
  env[n++] = var;
...
  env[n] = NULL;
...
  ctx.envp = (char *const *) env;
  ccf は ngx_core_conf_t に格納されます。
  ngx_rename_file(ccf->pid.data, ccf->oldpid.data) == NGX_FILE_ERROR の場合 {
    ...
    NGX_INVALID_PID を返します。
  }
  pid = ngx_execute(サイクル、&ctx);
  (pid == NGX_INVALID_PID)の場合{
    ngx_rename_file(ccf->oldpid.data, ccf->pid.data) の場合
      == NGX_FILE_ERROR)
    {
      ...
    }
  }
...
  pid を返します。
}

機能フローは

  1. 古いマスター プロセスによって監視されているすべての fd を、新しいマスター プロセスの env 環境変数 NGINX_VAR にコピーします。
  2. 名前を変更する pid ファイルの名前を変更する
  3. ngx_execute 関数は子プロセスをフォークし、execve はコマンド ラインを実行して新しいサーバーを起動します。
  4. サーバーの起動プロセスでは、環境変数 NGINX_VAR が解析されます。ngx_connection.c/ngx_add_inherited_sockets の具体的なコードは次のとおりです。
静的 ngx_int_t
ngx_add_inherited_sockets(ngx_cycle_t *cycle)
{
...
  継承 = (u_char *) getenv(NGINX_VAR);
  if (継承 == NULL) {
    NGX_OK を返します。
  }
  if (ngx_array_init(&cycle->listening, cycle->pool, 10,
            サイズ(ngx_listening_t)
    != NGX_OK)
  {
    NGX_ERROR を返します。
  }
  (p = 継承、v = p; *p; p++) {
    (*p == ':' || *p == ';') の場合 {
      s = ngx_atoi(v, p - v);
      ...
      v = p + 1;
      ls = ngx_array_push(&cycle->listening);
      ls == NULLの場合{
        NGX_ERROR を返します。
      }
      ngx_memzero(ls、sizeof(ngx_listening_t));
      ls->fd = (ngx_socket_t) s;
    }
  }
  ...
  ngx_継承 = 1;
  ngx_set_inherited_sockets(cycle) を返します。
}

機能フローは次のとおりです。

環境変数NGINX_VARの値を解析し、配列に格納するfdを取得します。

これらのソケットの情報を保存するために、fd に対応するソケットが ngx_inherited に設定されます。

つまり、新しいサーバーは listen ポートをまったく再バインドしません。これらの fd の状態と値は、新しいマスター プロセスがフォークしたときに引き継がれます。新しいマスター プロセスは、継承されたファイル記述子を listen して処理できます。ここで重要な点は、listen ソケットのファイル記述子が ENV を介して渡されることです。

以上がこの記事の全内容です。皆様の勉強のお役に立てれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • nginx のスムーズな再起動とアップグレードを実装する方法
  • Nginxスムーズアップグレードの詳しい操作方法
  • Nginx 1.8.0 バージョンが新しいバージョン 1.9.7 にスムーズにアップグレードされました
  • 1 分で Nginx のバージョンをスムーズにアップグレードおよびロールバックする方法
  • nginxのスムーズなアップグレードのプロセスを詳しく説明
  • nginx のスムーズな再起動とスムーズなアップグレードのグラフィックチュートリアル

<<:  JSはGMTとUTCのタイムゾーンを完全に理解しています

>>:  MySQL クイックデータ比較テクニック

推薦する

Nginx コンテンツ キャッシュと共通パラメータ設定の詳細

使用シナリオ:プロジェクトのページでは、頻繁に変更されず、個別のカスタマイズも伴わない大量のデータを...

HTML で複数のクラス属性を定義する場合の無効な解決策

HTML を記述する過程で、クラス属性に複数の値を定義することがよくありますが、定義した値が無効であ...

Linuxサーバーのディスク容量を拡張する方法

目次序文ステップ序文今日、es ログが記録されていないことに気付きました。filebeat、elas...

Centos7.3 に mysql5.7.18 をインストールするための詳細なチュートリアル

1 Linuxディストリビューションのバージョンを確認する[root@typecodes ~]# c...

ES6 Promiseの使い方の詳細な説明

目次約束とは何ですか?拒否の使用法キャッチの使い方すべての使用法レースの使用約束とは何ですか? Pr...

MySQL スケジュールタスク (EVENT イベント) を詳細に設定する方法

目次1. イベントとは何ですか? 2. 「イベント」機能を有効にする1. 機能が有効になっているかど...

React.Childrenの詳しい使い方

目次1. React.Children.map 2. React.Children.forEach ...

MySQL 5.7 でブロックポジショニング DDL の問題を解決する

前回の記事「MySQL テーブル構造の変更、メタデータ ロックを知っておく必要があります」では、MD...

MySQLクエリ文の実行プロセスの詳細な説明

目次1. クライアントとサーバー間の通信方法2. クエリキャッシュ3. クエリ最適化処理4. クエリ...

Docker イメージの作成、アップロード、プル、およびデプロイ操作 (Alibaba Cloud を使用)

学習プロセス中にプッシュ イメージが常にタイムアウトすることがわかったため、Alibaba Clou...

MySQL ステートメントに一重引用符またはバックスラッシュを含む値を挿入する方法

序文この記事では主に、シングルクォートやバックスラッシュを含む値を挿入するMySQLステートメントに...

RedisとMySQLの違いを簡単に説明してください

MySQL はディスクに保存される永続的なストレージであり、取得には一定の IO が伴うことはご存じ...

MySQLメモリストレージエンジンに関する知識

メモリストレージエンジンに関する知識ポイントメモリ ストレージ エンジンは日常業務ではほとんど使用さ...

JS のあらゆる場所で絶対等価演算子の使用をやめる

目次概要1. NULL値のテスト2. ユーザー入力を読み取る導入事実の根源はどこにあるのでしょうか?...

Docker-compose を使用して ELK をデプロイするためのサンプル コード

環境ホストIP 192.168.0.9 Docker バージョン 19.03.2 docker-co...