Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Nginx Hacking Guide

Nginx Hacking Guide

nginx の実装の説明(@ KMC 春合宿 2015)

Yusuke Nojima

March 15, 2015
Tweet

More Decks by Yusuke Nojima

Other Decks in Programming

Transcript

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

  2. 自己紹介 • nojima (野島 裕輔) • 30代(たぶん) 会長 • サイボウズ株式会社のインフラチームに所属

    • ロードバランサとして Apache を使っていた状態から nginx に 完全移行する仕事をした。nginx 改造スキルが身についた。 2015/3/12 Nginx Hacking Guide 2
  3. nginx • 高速で高機能な HTTP サーバ • static なファイルの配信 • リバースプロキシ

    • ロシア製 • 同時接続処理にイベント駆動を利用しており、同時接続 が多い場合でも高いパフォーマンスを発揮する。 • 最近は Apache よりも nginx を採用するケースが増えて きている。 2015/3/12 Nginx Hacking Guide 3
  4. demo • configure • make / make install • 起動

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

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

  7. 同時接続の処理方法 • HTTP サーバは複数のコネクションを同時に捌けないと いけない。 • よく使われるコネクション処理のアーキテクチャは以下 の3つ • マルチプロセスモデル

    • マルチスレッドモデル • イベント駆動モデル 2015/3/12 Nginx Hacking Guide 7
  8. マルチプロセスモデル • コネクション一つにつき一つの プロセスを割り当てるモデル。 • 実装が簡単。 • オーバーヘッドが大きい。 • メモリ

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

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

    nginx はこのモデルを使っている。 Main Thread Conn 2 Conn 1 Conn 3 2015/3/12 Nginx Hacking Guide 10
  11. 比較 モデル サーバ マルチプロセスモデル Apache (perfork) passenger unicorn マルチスレッドモデル Apache

    (worker) イベント駆動モデル nginx node.js ※ 待ち受けだけをイベント駆動で行い、その後の処理を マルチスレッドで行うようなものもある (Apache Event MPM) 2015/3/12 Nginx Hacking Guide 11
  12. 複数のモデルの組み合わせ nginx unicorn rails rails rails nginx でバッファする 遅い多数のコネクション 高速な少数のコネクション

    少数のプロセス数で サーブできる。 2015/3/12 Nginx Hacking Guide 12
  13. nginx のプロセス管理 2015/3/12 Nginx Hacking Guide 13

  14. 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
  15. Master Process の役割 • Master Process はリクエスト処理は行わない。 (Single process mode

    の場合を除く) • Master は Worker を産んだり殺したりできる。 • この仕組みを使って Graceful Reload (設定再読み込み) や Graceful Restart が実装されている。 2015/3/12 Nginx Hacking Guide 15
  16. 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
  17. 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
  18. nginx のイベント処理 2015/3/12 Nginx Hacking Guide 18

  19. read(2) char buf[1024]; ssize_t n = read(fd, buf, sizeof(buf)); •

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

    buf read カーネル空間 ユーザー空間 2015/3/12 Nginx Hacking Guide 20
  21. 非同期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
  22. 非同期IO • read によって処理がブロックされてしまうと、 そのスレッドはしばらく何もできない。 • fd をノンブロッキングモードにしておくことで、 fd からデータを

    read できるようになるまで別の処理を 行うことができる。 2015/3/12 Nginx Hacking Guide 22
  23. 非同期 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
  24. write(2) char buf[1024]; size_t len = ...; ssize_t n =

    write(fd, buf, len); ソケットの送信用バッファ buf write カーネル空間 ユーザー空間 2015/3/12 Nginx Hacking Guide 24
  25. write(2) • write はカーネルのバッファに書き込むだけ。 • バッファ内のデータをいつ送信するのかは OS が決める。 • すぐに送りたい場合はプラットフォーム依存な方法で

    フラッシュするしかない。 • 普通はバッファに空きがなければ空きができるまで ブロックする。 • ソケットがノンブロッキングモードな場合、空きが 全くなければ EAGAIN が返ってくる。 2015/3/12 Nginx Hacking Guide 25
  26. ソケットの監視 • ノンブロッキング IO では、ソケットが読み書き可能に なったタイミングを自力で検出して、処理を再開させな いといけない。 • そこで epoll

    などの API を使ってソケットの監視を行う。 1. epoll に使っているソケットを登録しておく。 2. epoll_wait を呼び出すと、登録されたソケットの中で、読み書 き可能なものを教えてくれる。 3. それらのソケットに対して、中断していた処理を再開させる。 2015/3/12 Nginx Hacking Guide 26
  27. イベントループ (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
  28. イベントループ • epoll は Linux 特有。 • *BSD 系だと kqueue

    とかが使えるらしい。 (よく知らない) • select や poll は様々なプラットフォームで使えるが、 遅い。 • したがって、マルチプラットフォームなアプリケーショ ンではこれらを使い分ける必要がある。 2015/3/12 Nginx Hacking Guide 28
  29. event module • nginx はイベント処理を行うための独自のフレームワー クを持っている。 • プラットフォームごとの API の違いを吸収

    • これにより、プラットフォームが提供する API がレベルトリ ガーである場合でも上のレベルではエッジトリガーな API が使 える。 2015/3/12 Nginx Hacking Guide 29
  30. 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
  31. 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
  32. nginx のリクエスト処理 2015/3/12 Nginx Hacking Guide 32

  33. nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む

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

    フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 34
  35. 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
  36. 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
  37. 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
  38. 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
  39. nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む

    フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 41
  40. 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
  41. ngx_http_request_t • ngx_http_request_t は、一対のリクエストとレスポンス を表す。 • HTTP では一回のコネクションで複数個のリクエストを送れるこ とに注意。 •

    リバースプロキシする場合はバックエンドへのリクエス トとかの情報も持つ。 • read/write イベントハンドラを持つ • ngx_event_t よりもちょっと高レベル(HTTP より) 2015/3/12 Nginx Hacking Guide 43
  42. nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む

    フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 44
  43. ヘッダの処理 • おおまかな流れ • ソケットから読めるだけ読んでバッファに貯める。 • ブロックされる直前まで読んだら(あるいは EOF まで読んだ ら)パースする。(実際には一行ずつ)

    • パースできたら次のステップにすすむ。リクエストが読み切れ てなくてパースできなかったら次に読めた時にもう一回パース する。 2015/3/12 Nginx Hacking Guide 45
  44. 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
  45. 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
  46. パーサー • nginx は何故か再帰下降型パーサではなく手書きステー トマシンで頑張っている。 • 死ぬほど読みづらい。 • → src/http/ngx_http_parse.c

    2015/3/12 Nginx Hacking Guide 48
  47. nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む

    フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 49
  48. フェイズ処理 • HTTP Header を読んだ後はフェイズ処理に入る。 • フェイズは Rewrite Phase, Access

    Phase, Content Phase など色々ある。 • 各フェイズでは、設定ファイルの内容に応じて色々な ことが起こる。 • Rewrite Phase では URI の書き換え、Access Phase では認証、 Content Phase ではレスポンスの作成など 2015/3/12 Nginx Hacking Guide 50
  49. フェイズ処理 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
  50. フェイズと http modules • src/http/modules/ 以下に http モジュールが大量に定義 されている •

    これらのモジュールはフェイズごとに必要なハンドラを 突っ込むことで実装されている。 • 自分で独自のモジュールを定義することで新しいディレ クティブを追加できる。 2015/3/12 Nginx Hacking Guide 52
  51. nginx のリクエスト処理 ngx_http_request_t の作成 Request Line を読む HTTP Header を読む

    フェイズ処理 finalize ngx_connection_t の作成 2015/3/12 Nginx Hacking Guide 53
  52. 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
  53. まとめ的な 2015/3/12 Nginx Hacking Guide 55

  54. まとめ • nginx は全体的に効率を追求した質の高いコード。 • さすがロシア • 効率を追求しすぎてマジカルな実装になっているところもある。 • C

    言語で非同期IOを使うのは修行力が要求される。 2015/3/12 Nginx Hacking Guide 56