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
    野島 裕輔

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide