Nginx Inside Memo (カーネル/VM Advent Calendar)

この記事は http://atnd.org/events/21910 のために書かれたものです。
一つ前の5日目は @sora_h さんの钱柜娱乐_钱柜娱乐平台_钱柜娱乐777(唯一)官方网站でした。明日は、 @master_q さんの予定です。

nginx の内部の仕組みやモジュールの作り方について簡単に紹介してみます。

最近 nginx のコードを読んでハックを試みました。 nginx のコードについてはほとんどドキュメントがなく、日本語でも見かけなかったため(もし書いている人がいたらすみません)、後から続く人にとって多少でも参考になれるようメモをまとめました。(2011/12/06 デバッグについて少し追記)

全部読みきったわけではないので、コードを読む上でのとりかかりぐらいの情報です。もし、間違いがあれば教えてください。
このメモは stable version である Nginx-1.0.10 のコードを対象にしています。

参考情報

  • Joshua Zhu's Blog, Nginx Internals (Slides & Video)
    • 英語と中国語ですが、大変参考になりました。結構読んだ後に見つけたのですが、プレゼンのスライドなので読まないと分からないことが多かったです。

全体アーキテクチャ

各地で説明されているように、クライアントのリクエストをさばくWorkerプロセスと、Workerプロセスを管理して、全体の起動や終了などを管理するMasterプロセスからなる。




WorkerとMaster

WorkerプロセスとMasterプロセスは、UNIXシグナルや共有メモリ、ドメインソケットなど(環境によって違う)で通信する。

各Workerはシングルプロセス*1のシンプルなイベントドリブンな設計になっている。




イベントドリブン動作イメージ (ngx_process_events_and_timers関数)

nginxが受け取るイベントには、ネットワークのソケットの接続開始・終了や、読み出し・書き込み可能状態の変更、シグナルなどの通知などがある。ひとつのプロセスでIOを多重化し、可能な限り処理を非同期に行うことで、各地で評判の高いスループットが実現している。OS(FreeBSDとか)によってはファイルのIOも非同期に行うことができる。

各OSや実行環境でのイベント操作の抽象化のために ngx_event_actions_t という構造体が定義されている。この構造体のメンバにイベント操作の関数ポインタ入っている。




ngx_event_actions_t

個別のイベントについては ngx_event_t でイベントの情報と操作がセットで定義されていて、各地の実装で実際のイベント情報が生成される。

コードレイアウトと起動

コードのレイアウトはシンプルで直感的。見たまま。

  • src
    • core
    • event
    • http
    • mail
    • misc
    • os/unix

エントリポイントは /src/core/nginx.c の main 関数。内容はごく普通のデーモンサーバー。

  • 処理流れ
    • 引数処理
    • デーモン化
    • 環境変数設定して worker フォーク
    • 各モジュール初期化
    • メインループ突入

この中で cycle という単語がよく出てくる。プロセスサイクルの略だと思われる。大体 worker/master プロセスのメインの処理やデータを表す感じ。




ngx_cycle_t の主なメンバ

高速化の努力

全部読んでないけど、なるほどと思った。

イベントドリブン

全体の設計が徹底的にイベントドリブン。非同期に出来るものであれば、何でもこのアーキテクチャに乗って回すことが出来る。後述のモジュール実装でも出てくる。

独自メモリアロケーション

イベントオブジェクトやバッファなど、各地で細かくメモリの割り当て・開放が行われるので、多分かなり効果ありそう。

Rope的バッファ

バッファをリンクリストで連結。イベントドリブンな仕組みと相性が良さそう。

その他自前ライブラリ
  • 文字列
  • キュー、ハッシュ、木(赤黒木、Radix木)
  • 時間計算

やっぱり自前だと速いのかもしれない。

HTTP処理

処理フェーズ状態遷移

HTTPの細かい処理を状態遷移マシンで抽象化。

  • 状態一覧 (ngx_http_init_phase_handlers 周辺で定義)

各状態でやるべき処理が明らかになって見通しが良い。ただし、状態遷移マシンの実装はハック的。

フィルター

よくある出力の抽象化。リンクリストで実装。
設定で柔軟に組み合わせられる。

モジュール

各パーツをモジュールという入れ物に入れて、統一的に扱う仕組み。(ここは抽象度が高くて範囲が広く、いたる所 void* だらけで完全に把握できてない。)

ngx_xxx_module_t が各モジュールごとにあり、そこから各モジュールの設定ファイルの解釈、状態(コンテキスト)にアクセスできる。




主なモジュール構造体 ngx_module_t のメンバ(未完成。。。)

設定ファイルの解釈、起動時の初期化、終了時の処理などが統一的に定義されていて、大体どのモジュールも同じような書き方になっている。

モジュールには core, event, http, mail の4種類がある。各モジュールの依存関係の図が欲しい。

モジュール開発について

nginx にはたくさんモジュールがある。が、本家サイトにはモジュール開発の情報がない。

デバッガ

本体やモジュールが落ちる場合はデバッガを使って原因を調査する。Workerを調査する場合は、追跡しやすくするために worker プロセスは1つにしておくほうが良い。

デバッガを使って本体を起動するか、起動中のPIDを調べてアタッチする。Linuxだとgdbやnemiverを使ってやる感じ。
設定ファイルのパースの状況やモジュールの状態がどうなっているかなどがよく分かる。

gdbで起動させる場合は、フォークしたWorkerプロセスをフォローしてもらうために、以下のコマンドを実行しておく。

set follow-fork-mode child

結局やりたかったこと

これまで業務で DRBD などを用いて大容量冗長化ファイルシステムを用いてきたが、ファイル容量のスケールアップが辛く、また運用のコストも高いので、初期コスト低い・簡単運用・スケールアップ可能な分散ファイルシステムを探している。

最近、 MongoDB の分散FSである GridFS に注目している。

nginx-gridfs というモジュールがあり、これを使うと GridFS に直接つなげることが出来る。単純なローカルファイルへのリクエストとのパフォーマンス比較ではもちろん圧倒的に負ける。

しかしながら、他の分散ファイルシステムと比較した場合のパフォーマンスについては自明ではない。そこで、手元で修正したり、ハックしながら調査中。

GridFS との接続が同期通信なので、ここを非同期にするとスループットが上がるのではないかと考えている。もしくはキャッシュをうまくやる。でも C で実装するのは辛いので mighttpd など、他のプランも考えたい。

GlusterFS も調査したい。

その他高速Webサーバー

まとめ

つかれました。

*1:マルチスレッドで動かすことも出来るみたい