nginx subrequestの実装解析

13967 ワード

nginxにはsubrequestの概念、すなわちサブリクエストがあることはよく知られています.http標準の概念ではありません.現在のリクエストで開始された新しいリクエストで、独自のngx_を持っています.http_request_t構造,uriおよびargs.一般的にsubrequestを使用する効率は、server rewriteからrequest処理のPHASEを再開する必要があるため、いくつかの場合に便利になりますが、subrequestを使用してupstreamのバックエンドにアクセスし、ngx_を与えるのが一般的です.http_post_subrequest_tのコールバックhandlerは、非同期の関数呼び出しに少し似ています.upstreamから返されるデータの場合、subrequestでは、作成時に指定したflagに基づいて、ユーザが自分で処理するか(handlerにコールバックするか)upstreamモジュールがout put filterに直接送信するかを決定できます.簡単にsubrequestの動作を説明します.nginxはsubrequestを使用してあるlocationにアクセスし、対応するデータを生成し、nginx出力チェーンの対応する位置(subrequestを作成する際の位置)に挿入します.次にagentzh(章亦春、以前は会社の北京の同僚で、最近退職しました.家に帰ってオープンソースに専念しているそうです)のechoモジュールを使います.(https://github.com/agentzh/echo-nginx-module)を例に挙げて説明します.
location /main {
    echo nginx;
    echo_location /sub;
    echo world;
}
location /sub {
    echo hello;
}
アクセス/mainでは、次のような応答が得られます.
nginx
hello
world
       上のecho_location命令はsubrequestを起動して/subにアクセスし、echoはshellの中のechoのようなものを指定し、その後の文字列を出力するために使用されます.ちなみにechoモジュールには他にも多くの命令があります.このモジュールはテスト時に非常に役立ちます.
       ソースコード解析を行う前に、私たち自身がsubrequestを実装する上記の動作を考えてみると、どうすればいいのでしょうか.subrequestには独自のsubrequestがある可能性があり、各subrequestの出力データは必ずしも作成された順序ではないので、簡単にチェーンテーブルを採用するのは実現しにくいので、ツリーを採用できることをさらに連想します主な要求はルートノードであり,各ノードには独自のサブノードがあり,あるノードを遍歴してある要求を処理することを表すが,ここでは当然後ルート(シーケンス)で遍歴する方法である可能性が考えられる.間違いなく,実際にIgorはツリーとチェーンテーブルを組み合わせた方式でsubrequestの機能を実現しているが,ノード(要求)生成されたデータの順序は、ノード作成順(左->右)に固定されているわけではなく、複数回に分けて生成される可能性があり、簡単に後根(順序)で遍歴することはできません.Igorは、2つのチェーンテーブルの構造を使用して実現されます.1つ目は、各リクエストに存在するpostponedチェーンテーブルで、一般的に、各チェーンテーブルノードは、このリクエストのサブリクエストを保存しています.このチェーンテーブルノードは、以下のように定義されています.
struct ngx_http_postponed_request_s {
    ngx_http_request_t               *request;
    ngx_chain_t                      *out;
    ngx_http_postponed_request_t     *next;
};
        サブリクエストを保存するために使用できるrequestフィールドと、ngx_chain_tタイプのoutフィールドが表示されます.実際には、サブリクエストを保存するノードのほかに、リクエストが生成したデータを保存するノードがあります.outフィールドに保存されます.2つ目は、現在遍歴する必要があるposted_requestsチェーンテーブルです.プライマリ・リクエスト(ルート・ノード)のposted_requestsフィールドに保存されるリクエスト(ノード)です.チェーン・テーブル・ノードは次のように定義されます.
struct ngx_http_posted_request_s {
    ngx_http_request_t               *request;
    ngx_http_posted_request_t        *next;
};

        ngx_http_run_posted_requests関数では、プライマリリクエストのposted_requestsチェーンテーブルを順に巡回します.
void
ngx_http_run_posted_requests(ngx_connection_t *c)
{
    ...
    for ( ;; ) {
        /*       ,     */
        if (c->destroyed) {
            return;
        }

        r = c->data;
        /*  posted_requests          */
        pr = r->main->posted_requests;

        if (pr == NULL) {
            return;
        }
      

        /*                */
        r->main->posted_requests = pr->next;
        /*             */
        r = pr->request;

        ctx = c->log->data;
        ctx->current_request = r;

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                       "http posted request: \"%V?%V\"", &r->uri, &r->args);
        /*      (  ) */
        r->write_event_handler(r);
    }
}
        ngx_http_run_posted_requests関数の呼び出し点については後述します.
        OK、いくつかの実装の原理を理解して、コードを見ると簡単になりました.今、subrequestのソースコード解析を正式に行います.まずsubrequestを作成する関数定義を見てみましょう.
ngx_int_t
ngx_http_subrequest(ngx_http_request_t *r,
    ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr,
    ngx_http_post_subrequest_t *ps, ngx_uint_t flags)
       パラメータrは現在の要求であり、uriとargsは新しい開始するuriとargsであり、もちろんargsはNULLであり、psrはngx_http_request_tポインタを指すポインタであり、その役割は作成されたサブ要求を得ることであり、psのタイプはngx_http_post_subrequest_tであり、その定義は以下の通りである.
typedef struct {
    ngx_http_post_subrequest_pt       handler;
    void                             *data;
} ngx_http_post_subrequest_t;

typedef ngx_int_t (*ngx_http_post_subrequest_pt)(ngx_http_request_t *r,
    void *data, ngx_int_t rc);
        これは前述のコールバックhandlerであり、構造内のhandlerタイプはngx_http_post_subrequest_ptであり、関数ポインタであり、dataはhandlerに伝達される追加パラメータである.さらにngx_http_subrequest関数の最後の1つはflagsであり、現在のソースコードでは実際には2種類のflagしかなく、それぞれNGX_HTTP_SUBREQUEST_IN_MEMORYとNGX_HTTP_SUBREQUEST_WAITEDである1つ目は、文章の冒頭で述べたサブリクエストのupstreamがデータを処理する方法を指定し、2つ目のパラメータは、サブリクエストが事前に完了した場合(後続の遍歴順)を示す.、ステータスをdoneに設定するかどうか、パラメータを設定すると、事前に完了するとdoneが設定され、設定しないと、サブリクエストがその前のサブリクエスト処理が完了するまで待機させ、ステータスをdoneに設定します.
        ngx_http_subrequest関数の内部に入ってみましょう.
{
    ...
    /*   flags, subrequest_in_memory upstream       ,
         body downsstream    */
    sr->subrequest_in_memory = (flags & NGX_HTTP_SUBREQUEST_IN_MEMORY) != 0;
    sr->waited = (flags & NGX_HTTP_SUBREQUEST_WAITED) != 0;

    sr->unparsed_uri = r->unparsed_uri;
    sr->method_name = ngx_http_core_get_method;
    sr->http_protocol = r->http_protocol;

    ngx_http_set_exten(sr);
    /*       main    */
    sr->main = r->main;
    /*          */   
    sr->parent = r;
    /*     handler   ,       ,     */
    sr->post_subrequest = ps;
    /*    handler           ,                   ;
          handler ngx_http_handler,    phase */
    sr->read_event_handler = ngx_http_request_empty_handler;
    sr->write_event_handler = ngx_http_handler;

    /* ngx_connection_s data      ,         out chain       ,
                     */
    if (c->data == r && r->postponed == NULL) {
        c->data = sr;
    }
    /*           ,                  ,             */
    sr->variables = r->variables;

    sr->log_handler = r->log_handler;

    pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t));
    if (pr == NULL) {
        return NGX_ERROR;
    }

    pr->request = sr;
    pr->out = NULL;
    pr->next = NULL;
    /*              postponed      */
    if (r->postponed) {
        for (p = r->postponed; p->next; p = p->next) { /* void */ }
        p->next = pr;

    } else {
        r->postponed = pr;
    }
    /*         ,     internal   location */
    sr->internal = 1;
    /*            */
    sr->discard_body = r->discard_body;
    sr->expect_tested = 1;
    sr->main_filter_need_in_memory = r->main_filter_need_in_memory;

    sr->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;

    tp = ngx_timeofday();
    r->start_sec = tp->sec;
    r->start_msec = tp->msec;

    r->main->subrequests++;
    /*          ,        ngx_http_finalize_request          
                */
    r->main->count++;

    *psr = sr;
    /*             posted_requests     */
    return ngx_http_post_request(sr, NULL);
}
        はい、サブリクエストの作成が完了しました.一般的に、サブリクエストの作成は、あるリクエストのcontent handlerまたはfilter内で行われます.上の関数から、サブリクエストはすぐに実行されず、メインリクエストのposted_requestsチェーンテーブルにマウントされているだけで、いつ実行できるのでしょうか.posted_requestsチェーンテーブルといえばngx_http_run_postedです_requests関数では、ngx_http_run_posted_requests関数はいつ呼び出されますか?実際には、あるリクエストの読み取り(書き込み)イベントのhandlerで、リクエストに関連する処理が実行された後に呼び出されます.たとえば、プライマリリクエストがPHASEを1回実行したときにngx_http_run_posted_requestsが呼び出され、サブリクエストが実行されます.
         この場合、nginxはマルチプロセスであり、任意にブロックできないため、実際にはもう1つの問題が解決される必要があります(1つのリクエストが現在のプロセスをブロックしている場合、このプロセスacceptから他のすべてのリクエストをブロックすることに相当し、プロセスもacceptの新しいリクエストをブロックすることはできません).1つのリクエストは、何らかの理由でブロックする必要がある可能性があります(例えばioへのアクセス)nginxのアプローチは、リクエストのステータスを設定し、epollに対応するイベントを追加してから、他のリクエストを処理し、イベントが来るまで処理を継続することです.このような動作は、1つのリクエストが複数回の実行機会を必要とする可能性があることを意味します.1つのリクエストの複数のサブリクエストにとって、それらの完了の前後順序がそれと一致する可能性があることを意味します.作成順序が異なるため、Outchainに直接出力するのではなく、事前に完了したサブリクエストに生成されたデータを保存するメカニズムが必要であり、現在out chainにデータを出力できるリクエストをタイムリーに出力できるようにするメカニズムが必要である.著者Igorは、ngx_connection_tのdataフィールドと、ngx_http_postpone_fiのbody filterを採用しているlterは、この問題を解決するためにngx_http_finalize_request関数のいくつかの論理もあります.
        次の図で説明します.次の図は、ある時点であるプライマリ・リクエストとそのすべての子孫リクエストのツリー構造です.
         図中のrootノードはメインリクエストであり、そのpostponedチェーンテーブルには左から右に3つのノードがマウントされている.SUB 1はその最初のサブリクエストであり、DATA 1はそれによって生成されたデータであり、SUB 2はその2番目のサブリクエストであり、この2つのサブリクエストにはそれぞれ独自のサブリクエストとデータが格納されている.ngx_connection_tのdataフィールドには、現在out chainにデータを送信できるリクエストが保存されているで、冒頭でクライアントに送信するデータはサブリクエスト作成順に送信する必要があると述べたが、ここでは後続の遍歴方法(SUB 11->DATA 11->SUB 12->DATA 12->(SUB 1)->DATA 1->SUB 21->SUB 22->(SUB 2)->(ROOT))であり、上図では現在クライアント(out chain)に送信可能であるデータの送信要求は明らかにSUB 11であり、SUB 12が事前に実行済みであり、データDATA 121が生成された場合、先にノードが送信済みである限り、DATA 121はSUB 12のpostponedチェーンテーブルの下に先にマウントするしかない.ここで注意しなければならないのはc->dataの設定であり、SUB 11が実行済みでデータの送信が完了した後、次の送信するノードはDATA 11であるべきであるが、このノードは実際にはサブリクエストではなくデータが保存されているので,c->dataはこの場合,変更ノードを持つSUB 1リクエストを指すべきである.        次に、ソースコードがどのように実装されているかを見てみましょう.まず、ngx_http_postpone_filter関数です.
static ngx_int_t
ngx_http_postpone_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ...
    /*        out chain    ,       ,      ,
                 postponed  。                */
    if (r != c->data) {   

        if (in) {
            ngx_http_postpone_filter_add(r, in);
            return NGX_OK;
        }
        ...
        return NGX_OK;
    }
    /*    ,         out chain    ,    postponed        ,     ,
                   in      out chain             */
    if (r->postponed == NULL) {  
    							
        if (in || c->buffered) {
            return ngx_http_next_filter(r->main, in);
        }
        /*               */
        return NGX_OK;
    }
    /*      postponed               ,       ,         in,
             postponed   */
    if (in) {  
        ngx_http_postpone_filter_add(r, in);
    }
    /*   postponed       */
    do {   
        pr = r->postponed;
        /*               ,         posted_requests   ,
                 ngx_http_run_posted_requests  ,       */
        if (pr->request) {

            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                           "http postpone filter wake \"%V?%V\"",
                           &pr->request->uri, &pr->request->args);

            r->postponed = pr->next;

            /*            ,      (  )        (  ),
                         ,            。
                            out chain       。  */
            c->data = pr->request;
            /*            posted_requests   */
            return ngx_http_post_request(pr->request, NULL);
        }
        /*            ,         ,     out chain */
        if (pr->out == NULL) {
            ngx_log_error(NGX_LOG_ALERT, c->log, 0,
                          "http postpone filter NULL output",
                          &r->uri, &r->args);

        } else {
            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                           "http postpone filter output \"%V?%V\"",
                           &r->uri, &r->args);

            if (ngx_http_next_filter(r->main, pr->out) == NGX_ERROR) {
                return NGX_ERROR;
            }
        }

        r->postponed = pr->next;

    } while (r->postponed);

    return NGX_OK;
}
          さらにngx_http_finalzie_request関数を見てみましょう.
void
ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc) 
{
  ...
    /*             ,        handler,       */
    if (r != r->main && r->post_subrequest) {
        rc = r->post_subrequest->handler(r, r->post_subrequest->data, rc);
    }

  ...
    
    /*     */
    if (r != r->main) {  
        /*                    */
        if (r->buffered || r->postponed) {
            /*             ,      write event hander,
                              ,             ngx_http_output_filter  ,
                      ngx_http_postpone_filter     */
            if (ngx_http_set_write_handler(r) != NGX_OK) {
                ngx_http_terminate_request(r, 0);
            }

            return;
        }
        ...
              
        pr = r->parent;
        

        /*           ,            ,          , */
        if (r == c->data) { 

            r->main->count--;

            if (!r->logged) {

                clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

                if (clcf->log_subrequest) {
                    ngx_http_log_request(r);
                }

                r->logged = 1;

            } else {
                ngx_log_error(NGX_LOG_ALERT, c->log, 0,
                              "subrequest: \"%V?%V\" logged again",
                              &r->uri, &r->args);
            }

            r->done = 1;
            /*             ,      postponed      */
            if (pr->postponed && pr->postponed->request == r) {
                pr->postponed = pr->postponed->next;
            }
            /*            ,               postponed     
                      ,                   */
            c->data = pr;   

        } else {
            /*                  ,           ,        
                    ,    ngx_http_request_finalzier  ,       
               ngx_http_finalzie_request(r,0),        ,          ,
               ngx_http_finalzie_request          postponed      */
            r->write_event_handler = ngx_http_request_finalizer;

            if (r->waited) {
                r->done = 1;
            }
        }
        /*       posted_request  ,         */
        if (ngx_http_post_request(pr, NULL) != NGX_OK) {
            r->main->count++;
            ngx_http_terminate_request(r, 0);
            return;
        }

        return;
    }
    /*              ,                     ,
                 ,      write event hander,
                       */
    if (r->buffered || c->buffered || r->postponed || r->blocked) {

        if (ngx_http_set_write_handler(r) != NGX_OK) {
            ngx_http_terminate_request(r, 0);
        }

        return;
    }

 ...
} 
           まとめると、nginxのsubrequestのコード実装はまだ少し分かりにくいので、まずその原理を理解しなければなりません.