Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Nginx Hacking Guide
Search
Yusuke Nojima
March 15, 2015
Programming
0
590
Nginx Hacking Guide
nginx の実装の説明(@ KMC 春合宿 2015)
Yusuke Nojima
March 15, 2015
Tweet
Share
More Decks by Yusuke Nojima
See All by Yusuke Nojima
インフラ自動化の落とし穴と宣言的アーキテクチャ
nojima
24
12k
SSLセッションキャッシュを共有したいだけの人生だった
nojima
6
2.6k
Other Decks in Programming
See All in Programming
Honoの来た道とこれから
yusukebe
19
3k
What’s New in Compose Multiplatform - A Live Tour (droidcon London 2024)
zsmb
1
350
生成 AI を活用した toitta 切片分類機能の裏側 / Inside toitta's AI-Based Factoid Clustering
pokutuna
0
580
ECS Service Connectのこれまでのアップデートと今後のRoadmapを見てみる
tkikuc
2
210
VR HMDとしてのVision Pro+ゲーム開発について
yasei_no_otoko
0
100
WEBエンジニア向けAI活用入門
sutetotanuki
0
300
RailsのPull requestsのレビューの時に私が考えていること
yahonda
5
1.7k
カラム追加で増えるActiveRecordのメモリサイズ イメージできますか?
asayamakk
4
1.6k
offers_20241022_imakiire.pdf
imakurusu
2
360
macOS でできる リアルタイム動画像処理
biacco42
7
2k
CSC305 Lecture 13
javiergs
PRO
0
130
Generative AI Use Cases JP (略称:GenU)奮闘記
hideg
0
160
Featured
See All Featured
jQuery: Nuts, Bolts and Bling
dougneiner
61
7.5k
Visualization
eitanlees
144
15k
Designing for Performance
lara
604
68k
Git: the NoSQL Database
bkeepers
PRO
425
64k
How To Stay Up To Date on Web Technology
chriscoyier
788
250k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
9
680
Large-scale JavaScript Application Architecture
addyosmani
510
110k
10 Git Anti Patterns You Should be Aware of
lemiorhan
654
59k
Code Reviewing Like a Champion
maltzj
519
39k
Build your cross-platform service in a week with App Engine
jlugia
229
18k
Imperfection Machines: The Place of Print at Facebook
scottboms
264
13k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
231
17k
Transcript
Nginx Hacking Guide KMC 春合宿 2015 野島 裕輔
自己紹介 • nojima (野島 裕輔) • 30代(たぶん) 会長 • サイボウズ株式会社のインフラチームに所属
• ロードバランサとして Apache を使っていた状態から nginx に 完全移行する仕事をした。nginx 改造スキルが身についた。 2015/3/12 Nginx Hacking Guide 2
nginx • 高速で高機能な HTTP サーバ • static なファイルの配信 • リバースプロキシ
• ロシア製 • 同時接続処理にイベント駆動を利用しており、同時接続 が多い場合でも高いパフォーマンスを発揮する。 • 最近は Apache よりも nginx を採用するケースが増えて きている。 2015/3/12 Nginx Hacking Guide 3
demo • configure • make / make install • 起動
• server / location • proxy_pass • if • internal redirect • addon 2015/3/12 Nginx Hacking Guide 4
この講座では… • nginx の実装について説明します。 • 特に、nginx の非同期処理について説明します。 • 質問は逐次お願いします。 2015/3/12
Nginx Hacking Guide 5
同時接続処理 2015/3/12 Nginx Hacking Guide 6
同時接続の処理方法 • HTTP サーバは複数のコネクションを同時に捌けないと いけない。 • よく使われるコネクション処理のアーキテクチャは以下 の3つ • マルチプロセスモデル
• マルチスレッドモデル • イベント駆動モデル 2015/3/12 Nginx Hacking Guide 7
マルチプロセスモデル • コネクション一つにつき一つの プロセスを割り当てるモデル。 • 実装が簡単。 • オーバーヘッドが大きい。 • メモリ
• コンテキストスイッチ • Apache の Prefork MPM は このモデルを使っている。 Master Process Worker Process Worker Process Worker Process Conn 1 Conn 2 Conn 3 2015/3/12 Nginx Hacking Guide 8
マルチスレッドモデル • コネクション一つにつき一つの スレッドを割り当てるモデル。 • 実装が簡単。 • マルチプロセスモデル程ではな いが、オーバーヘッドが大きい。 •
メモリ • コンテキストスイッチ • Apache の Worker MPM はこ のモデルを使っている。 Main Thread Worker Thread Worker Thread Worker Thread Conn 1 Conn 2 Conn 3 2015/3/12 Nginx Hacking Guide 9
イベント駆動モデル • 非同期IOを駆使して、一つの スレッドで全てのコネクションを 捌くモデル。 • 実装が大変。 • オーバーヘッドは小さい。 •
nginx はこのモデルを使っている。 Main Thread Conn 2 Conn 1 Conn 3 2015/3/12 Nginx Hacking Guide 10
比較 モデル サーバ マルチプロセスモデル Apache (perfork) passenger unicorn マルチスレッドモデル Apache
(worker) イベント駆動モデル nginx node.js ※ 待ち受けだけをイベント駆動で行い、その後の処理を マルチスレッドで行うようなものもある (Apache Event MPM) 2015/3/12 Nginx Hacking Guide 11
複数のモデルの組み合わせ nginx unicorn rails rails rails nginx でバッファする 遅い多数のコネクション 高速な少数のコネクション
少数のプロセス数で サーブできる。 2015/3/12 Nginx Hacking Guide 12
nginx のプロセス管理 2015/3/12 Nginx Hacking Guide 13
Master - Worker Master Process Worker Process Worker Process Worker
Process Worker Process • マルチプロセスモデルと違って、普通はプロセッサ数以上には Worker Process を立てない。 • 各 Worker Process はシングルスレッド。 • Worker 間で listening socket を共有しており、全ての Worker が accept を呼び出す。 2015/3/12 Nginx Hacking Guide 14
Master Process の役割 • Master Process はリクエスト処理は行わない。 (Single process mode
の場合を除く) • Master は Worker を産んだり殺したりできる。 • この仕組みを使って Graceful Reload (設定再読み込み) や Graceful Restart が実装されている。 2015/3/12 Nginx Hacking Guide 15
Graceful Reload Master Process Worker Process (old) Worker Process (old)
Worker Process (new) Worker Process (new) SIGHUP SIGQUIT spawn 2015/3/12 Nginx Hacking Guide 16
Graceful Restart Master Process (old) Worker Process (old) Worker Process
(old) Worker Process (new) Worker Process (new) SIGUSR2 SIGQUIT Master Process (new) exec SIGQUIT SIGQUIT 2015/3/12 Nginx Hacking Guide 17
nginx のイベント処理 2015/3/12 Nginx Hacking Guide 18
read(2) char buf[1024]; ssize_t n = read(fd, buf, sizeof(buf)); •
read はファイルやソケットからデータを読む関数。 • 以下では fd が TCP ソケットである場合のみを扱う。 • 意外と OS のレイヤーで色々隠蔽されているので、実際 に何が起こるのかが微妙に分かりにくい。 2015/3/12 Nginx Hacking Guide 19
read(2) char buf[1024]; ssize_t n = read(fd, buf, sizeof(buf)); ソケットに割り当てられたバッファ
buf read カーネル空間 ユーザー空間 2015/3/12 Nginx Hacking Guide 20
非同期IO • 普通、read は fd からデータを1バイト以上読み取るか、 エラーになるまで返ってこない(ブロックする)。 • fd がノンブロッキングモードに設定されている場合、
データが直ちに fd から読めないときは、n = -1, errno = EAGAIN ですぐに返ってくる。 • 正確には EAGAIN or EWOULDBLOCK だけど、いちいち断るのが 面倒なので、以降 EAGAIN で統一します。 char buffer[1024]; ssize_t n = read(fd, buffer, sizeof(buf)); 2015/3/12 Nginx Hacking Guide 21
非同期IO • read によって処理がブロックされてしまうと、 そのスレッドはしばらく何もできない。 • fd をノンブロッキングモードにしておくことで、 fd からデータを
read できるようになるまで別の処理を 行うことができる。 2015/3/12 Nginx Hacking Guide 22
非同期 IO ssize_t n = read(fd, buf, sizeof(buf)); if (n
< 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { このコネクションの処理は一旦中断して、別のコネクションを処理する。 return; } エラー処理を行う。 return; } 通常系の処理を行う。 2015/3/12 Nginx Hacking Guide 23
write(2) char buf[1024]; size_t len = ...; ssize_t n =
write(fd, buf, len); ソケットの送信用バッファ buf write カーネル空間 ユーザー空間 2015/3/12 Nginx Hacking Guide 24
write(2) • write はカーネルのバッファに書き込むだけ。 • バッファ内のデータをいつ送信するのかは OS が決める。 • すぐに送りたい場合はプラットフォーム依存な方法で
フラッシュするしかない。 • 普通はバッファに空きがなければ空きができるまで ブロックする。 • ソケットがノンブロッキングモードな場合、空きが 全くなければ EAGAIN が返ってくる。 2015/3/12 Nginx Hacking Guide 25
ソケットの監視 • ノンブロッキング IO では、ソケットが読み書き可能に なったタイミングを自力で検出して、処理を再開させな いといけない。 • そこで epoll
などの API を使ってソケットの監視を行う。 1. epoll に使っているソケットを登録しておく。 2. epoll_wait を呼び出すと、登録されたソケットの中で、読み書 き可能なものを教えてくれる。 3. それらのソケットに対して、中断していた処理を再開させる。 2015/3/12 Nginx Hacking Guide 26
イベントループ (epoll の場合) for (;;) { struct epoll_event events[MAX_EVENTS]; //
入出力可能になった FD の集合を得る。 int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (n == -1) エラー処理。 for (int i = 0; i < n; ++i) { ここで events[i].data.fd を使って処理を進める。 } } ソケットの FD は予め epoll_fd に登録して あるものとする 2015/3/12 Nginx Hacking Guide 27
イベントループ • epoll は Linux 特有。 • *BSD 系だと kqueue
とかが使えるらしい。 (よく知らない) • select や poll は様々なプラットフォームで使えるが、 遅い。 • したがって、マルチプラットフォームなアプリケーショ ンではこれらを使い分ける必要がある。 2015/3/12 Nginx Hacking Guide 28
event module • nginx はイベント処理を行うための独自のフレームワー クを持っている。 • プラットフォームごとの API の違いを吸収
• これにより、プラットフォームが提供する API がレベルトリ ガーである場合でも上のレベルではエッジトリガーな API が使 える。 2015/3/12 Nginx Hacking Guide 29
ngx_event_t typedef void (*ngx_event_handler_pt)(ngx_event_t *ev); struct ngx_event_s { // コールバックで使用するデータ
void *data; ... // イベントが発生したときに呼び出されるコールバック ngx_event_handler_pt handler; }; 2015/3/12 Nginx Hacking Guide 30
ngx_add_event ngx_event_t *rev, *wev; ... // イベントを登録する。 // rev->data, wev->data
は ngx_connection_t である必要がある。 // 登録したコネクションで読み書きが可能になったらコールバック関数が // 呼ばれる。 ngx_add_event(rev, NGX_READ_EVENT, NGX_CLEAR_EVENT); ngx_add_event(wev, NGX_WRITE_EVENT, NGX_CLEAR_EVENT); 2015/3/12 Nginx Hacking Guide 31
nginx のリクエスト処理 2015/3/12 Nginx Hacking Guide 32
nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む
フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 33
nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む
フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 34
ngx_connection_t typedef struct ngx_connection_s ngx_connection_t; // socket を wrap した構造体
struct ngx_connection_s { void *data; // 何か ngx_event_t *read; // read イベントハンドラ ngx_event_t *write; // write イベントハンドラ ngx_socket_t fd; // socket ngx_log_t *log; // エラーログ用 ngx_pool_t *pool; // メモリプール ... }; 2015/3/12 Nginx Hacking Guide 35
ngx_connection_t • ngx_connection_t はクライアントと nginx の間の接続 や、nginx とバックエンドサーバの間の接続を表す。 • socket
の fd と event handler と memory pool とその他 色々が組み合わさったものだと思えばよい。 • ngx_connection_t のメモリ割り当てを省くために、 未使用の ngx_connection_t はフリーリストにつな がっていて、新しい接続が来たらフリーリストから ngx_connection_t を取ってくる実装になっている。 2015/3/12 Nginx Hacking Guide 36
init_connection (抜粋) void ngx_http_init_connection(ngx_connection_t *c) { hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
c->data = hc; // address:port に対応する server の設定を取ってくる port = c->listening->servers; hc->conf_ctx = hc->addr_conf->default_server->ctx; // read イベントハンドラの設定 rev = c->read; rev->handler = ngx_http_wait_request_handler; // write イベントハンドラの設定 c->write->handler = ngx_http_empty_handler; // タイマーイベントの設定 ngx_add_timer(rev, c->listening->post_accept_timeout); // read イベントを登録 ngx_handle_read_event(rev, 0); } 2015/3/12 Nginx Hacking Guide 37
static void ngx_http_wait_request_handler( ngx_event_t *rev) { c = rev->data; //
タイムアウトしてたらエラー if (rev->timedout) { ngx_http_close_connection(c); return; } // 既に close されていたらエラー if (c->close) { ngx_http_close_connection(c); return; } // バッファが確保されていない場合は確保する b = c->buffer; if (b == NULL) { b = ngx_create_temp_buf(c->pool, size); c->buffer = b; } n = c->recv(c, b->last, size); if (n == NGX_AGAIN) { // タイマがセットされていなければセットする if (!rev->timer_set) ngx_add_timer(rev, c->listening->post_accept_timeout); ngx_handle_read_event(rev, 0); return; } if (n == NGX_ERROR) { ngx_http_close_connection(c); return; } if (n == 0) { // EOF ngx_http_close_connection(c); return; } // 正常に読めたとき b->last += n; // リクエスト構造体の作成 c->data = ngx_http_create_request(c); // ハンドラのセット rev->handler = ngx_http_process_request_line; // 次のハンドラを呼ぶ ngx_http_process_request_line(rev); } 2015/3/12 Nginx Hacking Guide 38
nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む
フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 41
ngx_http_request_t struct ngx_http_request_s { ngx_connection_t *connection; // コネクション void **ctx;
// 設定依存のデータ void **main_conf; // 設定 (main) void **srv_conf; // 設定 (server) void **loc_conf; // 設定 (location) ngx_http_event_handler_pt read_event_handler; ngx_http_event_handler_pt write_event_handler; ngx_pool_t *pool; // メモリプール ngx_http_headers_in_t headers_in; // リクエストヘッダ ngx_http_headers_out_t headers_out; // レスポンスヘッダ ngx_http_request_body_t *request_body; // リクエストボディ ... 2015/3/12 Nginx Hacking Guide 42
ngx_http_request_t • ngx_http_request_t は、一対のリクエストとレスポンス を表す。 • HTTP では一回のコネクションで複数個のリクエストを送れるこ とに注意。 •
リバースプロキシする場合はバックエンドへのリクエス トとかの情報も持つ。 • read/write イベントハンドラを持つ • ngx_event_t よりもちょっと高レベル(HTTP より) 2015/3/12 Nginx Hacking Guide 43
nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む
フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 44
ヘッダの処理 • おおまかな流れ • ソケットから読めるだけ読んでバッファに貯める。 • ブロックされる直前まで読んだら(あるいは EOF まで読んだ ら)パースする。(実際には一行ずつ)
• パースできたら次のステップにすすむ。リクエストが読み切れ てなくてパースできなかったら次に読めた時にもう一回パース する。 2015/3/12 Nginx Hacking Guide 45
static void ngx_http_process_request_line(ngx_event_t *rev) { c = rev->data; // ngx_connection_t
r = c->data; // ngx_http_request_t if (rev->timedout) { ... } rc = NGX_AGAIN; for ( ;; ) { // recv してバッファに突っ込む n = ngx_http_read_request_header(r); if (n == NGX_AGAIN || n == NGX_ERROR) return; // パースしてみる rc = ngx_http_parse_request_line(r, r->header_in); if (rc == NGX_OK) { // NGX_OK: リクエストラインの処理は終わった ... } if (rc != NGX_AGAIN) エラー処理 // NGX_AGAIN: リクエストライン処理はまだ終わってないので // もう一度 ngx_http_read_request_header を呼ぶ if (r->header_in->pos == r->header_in->end) バッファが少ないので増やす } } 2015/3/12 Nginx Hacking Guide 46
static ssize_t ngx_http_read_request_header(ngx_http_request_t *r) { c = r->connection; // ngx_connection_t
rev = c->read; // ngx_http_request_t // バッファの中に未読がある場合はそれを返す n = r->header_in->last - r->header_in->pos; if (n > 0) return n; n = c->recv(c, r->header_in->last, r->header_in->end - r->header_in->last); if (n == NGX_AGAIN) { if (!rev->timer_set) タイマをセット ngx_handle_read_event(rev, 0); return NGX_AGAIN; } if (n == 0 || n == NGX_ERROR) { // EOF かエラーの場合は BAD REQUEST にする c->error = 1; ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; } r->header_in->last += n; return n; } 2015/3/12 Nginx Hacking Guide 47
パーサー • nginx は何故か再帰下降型パーサではなく手書きステー トマシンで頑張っている。 • 死ぬほど読みづらい。 • → src/http/ngx_http_parse.c
2015/3/12 Nginx Hacking Guide 48
nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む
フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 49
フェイズ処理 • HTTP Header を読んだ後はフェイズ処理に入る。 • フェイズは Rewrite Phase, Access
Phase, Content Phase など色々ある。 • 各フェイズでは、設定ファイルの内容に応じて色々な ことが起こる。 • Rewrite Phase では URI の書き換え、Access Phase では認証、 Content Phase ではレスポンスの作成など 2015/3/12 Nginx Hacking Guide 50
フェイズ処理 location /hoge/ { if ($http_user_agent ~ ‘curl’) { return
200 “You are curl¥r¥n”; } allow 127.0.0.1; deny all; proxy_pass http://localhost:3000/; } rewrite phase access phase content phase 2015/3/12 Nginx Hacking Guide 51
フェイズと http modules • src/http/modules/ 以下に http モジュールが大量に定義 されている •
これらのモジュールはフェイズごとに必要なハンドラを 突っ込むことで実装されている。 • 自分で独自のモジュールを定義することで新しいディレ クティブを追加できる。 2015/3/12 Nginx Hacking Guide 52
nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む
フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 53
finalize • finalize は魔界 • void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc);
• static void ngx_http_terminate_request(ngx_http_request_t *r, ngx_int_t rc); • void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc); • static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error); • エラーページの処理とか…。 2015/3/12 Nginx Hacking Guide 54
まとめ的な 2015/3/12 Nginx Hacking Guide 55
まとめ • nginx は全体的に効率を追求した質の高いコード。 • さすがロシア • 効率を追求しすぎてマジカルな実装になっているところもある。 • C
言語で非同期IOを使うのは修行力が要求される。 2015/3/12 Nginx Hacking Guide 56