Slide 1

Slide 1 text

Nginx Hacking Guide KMC 春合宿 2015 野島 裕輔

Slide 2

Slide 2 text

自己紹介 • nojima (野島 裕輔) • 30代(たぶん) 会長 • サイボウズ株式会社のインフラチームに所属 • ロードバランサとして Apache を使っていた状態から nginx に 完全移行する仕事をした。nginx 改造スキルが身についた。 2015/3/12 Nginx Hacking Guide 2

Slide 3

Slide 3 text

nginx • 高速で高機能な HTTP サーバ • static なファイルの配信 • リバースプロキシ • ロシア製 • 同時接続処理にイベント駆動を利用しており、同時接続 が多い場合でも高いパフォーマンスを発揮する。 • 最近は Apache よりも nginx を採用するケースが増えて きている。 2015/3/12 Nginx Hacking Guide 3

Slide 4

Slide 4 text

demo • configure • make / make install • 起動 • server / location • proxy_pass • if • internal redirect • addon 2015/3/12 Nginx Hacking Guide 4

Slide 5

Slide 5 text

この講座では… • nginx の実装について説明します。 • 特に、nginx の非同期処理について説明します。 • 質問は逐次お願いします。 2015/3/12 Nginx Hacking Guide 5

Slide 6

Slide 6 text

同時接続処理 2015/3/12 Nginx Hacking Guide 6

Slide 7

Slide 7 text

同時接続の処理方法 • HTTP サーバは複数のコネクションを同時に捌けないと いけない。 • よく使われるコネクション処理のアーキテクチャは以下 の3つ • マルチプロセスモデル • マルチスレッドモデル • イベント駆動モデル 2015/3/12 Nginx Hacking Guide 7

Slide 8

Slide 8 text

マルチプロセスモデル • コネクション一つにつき一つの プロセスを割り当てるモデル。 • 実装が簡単。 • オーバーヘッドが大きい。 • メモリ • コンテキストスイッチ • Apache の Prefork MPM は このモデルを使っている。 Master Process Worker Process Worker Process Worker Process Conn 1 Conn 2 Conn 3 2015/3/12 Nginx Hacking Guide 8

Slide 9

Slide 9 text

マルチスレッドモデル • コネクション一つにつき一つの スレッドを割り当てるモデル。 • 実装が簡単。 • マルチプロセスモデル程ではな いが、オーバーヘッドが大きい。 • メモリ • コンテキストスイッチ • Apache の Worker MPM はこ のモデルを使っている。 Main Thread Worker Thread Worker Thread Worker Thread Conn 1 Conn 2 Conn 3 2015/3/12 Nginx Hacking Guide 9

Slide 10

Slide 10 text

イベント駆動モデル • 非同期IOを駆使して、一つの スレッドで全てのコネクションを 捌くモデル。 • 実装が大変。 • オーバーヘッドは小さい。 • nginx はこのモデルを使っている。 Main Thread Conn 2 Conn 1 Conn 3 2015/3/12 Nginx Hacking Guide 10

Slide 11

Slide 11 text

比較 モデル サーバ マルチプロセスモデル Apache (perfork) passenger unicorn マルチスレッドモデル Apache (worker) イベント駆動モデル nginx node.js ※ 待ち受けだけをイベント駆動で行い、その後の処理を マルチスレッドで行うようなものもある (Apache Event MPM) 2015/3/12 Nginx Hacking Guide 11

Slide 12

Slide 12 text

複数のモデルの組み合わせ nginx unicorn rails rails rails nginx でバッファする 遅い多数のコネクション 高速な少数のコネクション 少数のプロセス数で サーブできる。 2015/3/12 Nginx Hacking Guide 12

Slide 13

Slide 13 text

nginx のプロセス管理 2015/3/12 Nginx Hacking Guide 13

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Master Process の役割 • Master Process はリクエスト処理は行わない。 (Single process mode の場合を除く) • Master は Worker を産んだり殺したりできる。 • この仕組みを使って Graceful Reload (設定再読み込み) や Graceful Restart が実装されている。 2015/3/12 Nginx Hacking Guide 15

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

nginx のイベント処理 2015/3/12 Nginx Hacking Guide 18

Slide 19

Slide 19 text

read(2) char buf[1024]; ssize_t n = read(fd, buf, sizeof(buf)); • read はファイルやソケットからデータを読む関数。 • 以下では fd が TCP ソケットである場合のみを扱う。 • 意外と OS のレイヤーで色々隠蔽されているので、実際 に何が起こるのかが微妙に分かりにくい。 2015/3/12 Nginx Hacking Guide 19

Slide 20

Slide 20 text

read(2) char buf[1024]; ssize_t n = read(fd, buf, sizeof(buf)); ソケットに割り当てられたバッファ buf read カーネル空間 ユーザー空間 2015/3/12 Nginx Hacking Guide 20

Slide 21

Slide 21 text

非同期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

Slide 22

Slide 22 text

非同期IO • read によって処理がブロックされてしまうと、 そのスレッドはしばらく何もできない。 • fd をノンブロッキングモードにしておくことで、 fd からデータを read できるようになるまで別の処理を 行うことができる。 2015/3/12 Nginx Hacking Guide 22

Slide 23

Slide 23 text

非同期 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

Slide 24

Slide 24 text

write(2) char buf[1024]; size_t len = ...; ssize_t n = write(fd, buf, len); ソケットの送信用バッファ buf write カーネル空間 ユーザー空間 2015/3/12 Nginx Hacking Guide 24

Slide 25

Slide 25 text

write(2) • write はカーネルのバッファに書き込むだけ。 • バッファ内のデータをいつ送信するのかは OS が決める。 • すぐに送りたい場合はプラットフォーム依存な方法で フラッシュするしかない。 • 普通はバッファに空きがなければ空きができるまで ブロックする。 • ソケットがノンブロッキングモードな場合、空きが 全くなければ EAGAIN が返ってくる。 2015/3/12 Nginx Hacking Guide 25

Slide 26

Slide 26 text

ソケットの監視 • ノンブロッキング IO では、ソケットが読み書き可能に なったタイミングを自力で検出して、処理を再開させな いといけない。 • そこで epoll などの API を使ってソケットの監視を行う。 1. epoll に使っているソケットを登録しておく。 2. epoll_wait を呼び出すと、登録されたソケットの中で、読み書 き可能なものを教えてくれる。 3. それらのソケットに対して、中断していた処理を再開させる。 2015/3/12 Nginx Hacking Guide 26

Slide 27

Slide 27 text

イベントループ (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

Slide 28

Slide 28 text

イベントループ • epoll は Linux 特有。 • *BSD 系だと kqueue とかが使えるらしい。 (よく知らない) • select や poll は様々なプラットフォームで使えるが、 遅い。 • したがって、マルチプラットフォームなアプリケーショ ンではこれらを使い分ける必要がある。 2015/3/12 Nginx Hacking Guide 28

Slide 29

Slide 29 text

event module • nginx はイベント処理を行うための独自のフレームワー クを持っている。 • プラットフォームごとの API の違いを吸収 • これにより、プラットフォームが提供する API がレベルトリ ガーである場合でも上のレベルではエッジトリガーな API が使 える。 2015/3/12 Nginx Hacking Guide 29

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

nginx のリクエスト処理 2015/3/12 Nginx Hacking Guide 32

Slide 33

Slide 33 text

nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 33

Slide 34

Slide 34 text

nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 34

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 41

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

ngx_http_request_t • ngx_http_request_t は、一対のリクエストとレスポンス を表す。 • HTTP では一回のコネクションで複数個のリクエストを送れるこ とに注意。 • リバースプロキシする場合はバックエンドへのリクエス トとかの情報も持つ。 • read/write イベントハンドラを持つ • ngx_event_t よりもちょっと高レベル(HTTP より) 2015/3/12 Nginx Hacking Guide 43

Slide 42

Slide 42 text

nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 44

Slide 43

Slide 43 text

ヘッダの処理 • おおまかな流れ • ソケットから読めるだけ読んでバッファに貯める。 • ブロックされる直前まで読んだら(あるいは EOF まで読んだ ら)パースする。(実際には一行ずつ) • パースできたら次のステップにすすむ。リクエストが読み切れ てなくてパースできなかったら次に読めた時にもう一回パース する。 2015/3/12 Nginx Hacking Guide 45

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

パーサー • nginx は何故か再帰下降型パーサではなく手書きステー トマシンで頑張っている。 • 死ぬほど読みづらい。 • → src/http/ngx_http_parse.c 2015/3/12 Nginx Hacking Guide 48

Slide 47

Slide 47 text

nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 49

Slide 48

Slide 48 text

フェイズ処理 • HTTP Header を読んだ後はフェイズ処理に入る。 • フェイズは Rewrite Phase, Access Phase, Content Phase など色々ある。 • 各フェイズでは、設定ファイルの内容に応じて色々な ことが起こる。 • Rewrite Phase では URI の書き換え、Access Phase では認証、 Content Phase ではレスポンスの作成など 2015/3/12 Nginx Hacking Guide 50

Slide 49

Slide 49 text

フェイズ処理 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

Slide 50

Slide 50 text

フェイズと http modules • src/http/modules/ 以下に http モジュールが大量に定義 されている • これらのモジュールはフェイズごとに必要なハンドラを 突っ込むことで実装されている。 • 自分で独自のモジュールを定義することで新しいディレ クティブを追加できる。 2015/3/12 Nginx Hacking Guide 52

Slide 51

Slide 51 text

nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 53

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

まとめ的な 2015/3/12 Nginx Hacking Guide 55

Slide 54

Slide 54 text

まとめ • nginx は全体的に効率を追求した質の高いコード。 • さすがロシア • 効率を追求しすぎてマジカルな実装になっているところもある。 • C 言語で非同期IOを使うのは修行力が要求される。 2015/3/12 Nginx Hacking Guide 56