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.7k
Other Decks in Programming
See All in Programming
Pinia Colada が実現するスマートな非同期処理
naokihaba
4
220
Enabling DevOps and Team Topologies Through Architecture: Architecting for Fast Flow
cer
PRO
0
330
Less waste, more joy, and a lot more green: How Quarkus makes Java better
hollycummins
0
100
NSOutlineView何もわからん:( 前編 / I Don't Understand About NSOutlineView :( Pt. 1
usagimaru
0
330
Quine, Polyglot, 良いコード
qnighy
4
640
Jakarta EE meets AI
ivargrimstad
0
610
2024/11/8 関西Kaggler会 2024 #3 / Kaggle Kernel で Gemma 2 × vLLM を動かす。
kohecchi
5
920
受け取る人から提供する人になるということ
little_rubyist
0
230
C++でシェーダを書く
fadis
6
4.1k
Webの技術スタックで マルチプラットフォームアプリ開発を可能にするElixirDesktopの紹介
thehaigo
2
1k
弊社の「意識チョット低いアーキテクチャ」10選
texmeijin
5
24k
どうして僕の作ったクラスが手続き型と言われなきゃいけないんですか
akikogoto
1
120
Featured
See All Featured
Agile that works and the tools we love
rasmusluckow
327
21k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
246
1.3M
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
0
89
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
25
1.8k
Designing for Performance
lara
604
68k
Side Projects
sachag
452
42k
Build your cross-platform service in a week with App Engine
jlugia
229
18k
Become a Pro
speakerdeck
PRO
25
5k
Thoughts on Productivity
jonyablonski
67
4.3k
Being A Developer After 40
akosma
86
590k
RailsConf 2023
tenderlove
29
900
Building Flexible Design Systems
yeseniaperezcruz
327
38k
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