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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size 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 full-size 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 full-size slide

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

    View full-size 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 full-size 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 full-size 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 full-size slide

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

    View full-size 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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size 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 full-size 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 full-size slide

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

    View full-size 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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size 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 full-size 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 full-size slide

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

    View full-size slide

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

    View full-size 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 full-size slide

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

    View full-size slide

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

    View full-size slide