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プロセスは、UNIXシグナルや共有メモリ、ドメインソケットなど(環境によって違う)で通信する。
各Workerはシングルプロセス*1のシンプルなイベントドリブンな設計になっている。
nginxが受け取るイベントには、ネットワークのソケットの接続開始・終了や、読み出し・書き込み可能状態の変更、シグナルなどの通知などがある。ひとつのプロセスでIOを多重化し、可能な限り処理を非同期に行うことで、各地で評判の高いスループットが実現している。OS(FreeBSDとか)によってはファイルのIOも非同期に行うことができる。
各OSや実行環境でのイベント操作の抽象化のために ngx_event_actions_t という構造体が定義されている。この構造体のメンバにイベント操作の関数ポインタ入っている。
個別のイベントについては ngx_event_t でイベントの情報と操作がセットで定義されていて、各地の実装で実際のイベント情報が生成される。
コードレイアウトと起動
コードのレイアウトはシンプルで直感的。見たまま。
- src
- core
- event
- http
- misc
- os/unix
エントリポイントは /src/core/nginx.c の main 関数。内容はごく普通のデーモンサーバー。
- 処理流れ
- 引数処理
- デーモン化
- 環境変数設定して worker フォーク
- 各モジュール初期化
- メインループ突入
この中で cycle という単語がよく出てくる。プロセスサイクルの略だと思われる。大体 worker/master プロセスのメインの処理やデータを表す感じ。
高速化の努力
全部読んでないけど、なるほどと思った。
イベントドリブン
全体の設計が徹底的にイベントドリブン。非同期に出来るものであれば、何でもこのアーキテクチャに乗って回すことが出来る。後述のモジュール実装でも出てくる。
独自メモリアロケーション
イベントオブジェクトやバッファなど、各地で細かくメモリの割り当て・開放が行われるので、多分かなり効果ありそう。
Rope的バッファ
バッファをリンクリストで連結。イベントドリブンな仕組みと相性が良さそう。
HTTP処理
処理フェーズ状態遷移
HTTPの細かい処理を状態遷移マシンで抽象化。
- 状態一覧 (ngx_http_init_phase_handlers 周辺で定義)
各状態でやるべき処理が明らかになって見通しが良い。ただし、状態遷移マシンの実装はハック的。
フィルター
よくある出力の抽象化。リンクリストで実装。
設定で柔軟に組み合わせられる。
モジュール
各パーツをモジュールという入れ物に入れて、統一的に扱う仕組み。(ここは抽象度が高くて範囲が広く、いたる所 void* だらけで完全に把握できてない。)
ngx_xxx_module_t が各モジュールごとにあり、そこから各モジュールの設定ファイルの解釈、状態(コンテキスト)にアクセスできる。
設定ファイルの解釈、起動時の初期化、終了時の処理などが統一的に定義されていて、大体どのモジュールも同じような書き方になっている。
モジュールには core, event, http, mail の4種類がある。各モジュールの依存関係の図が欲しい。
モジュール開発について
nginx にはたくさんモジュールがある。が、本家サイトにはモジュール開発の情報がない。
- Joshua Zhu s Blog, Creating a Hello World! Nginx Module
- 上の解説プレゼンの人による最小限のコードによる Hello Module 。なんとなく雰囲気が分かる。
- ただ、これだけでは全然分からないので、下のドキュメントを読む必要がある。
- Emiller's Guide to Nginx Module Development
- Emiller's Advanced Topics In Nginx Module Development
- 上の続き。もう少し細かい話題など。まだ続くらしい。
- NginxでのModuleの作り方 - よねのはてな
- 米林さんの記事。この記事を書いた大分後に見つけました。とても参考になります。(2012/08/28追記)
デバッガ
本体やモジュールが落ちる場合はデバッガを使って原因を調査する。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サーバー
- Cherokee Web Server | Home
- かわいい。GUI。速いらしい。
- Mighttpd
- Haskell。速いらしい。モジュール作り易いなら乗り換えたい。
まとめ
つかれました。
*1:マルチスレッドで動かすことも出来るみたい