Nginx的文件分片-slice模塊

Nginx的slice模塊能夠將一個請求分解成多個子請求,每一個子請求返回響應內容的一個片斷,讓大文件的緩存更有效率。html

HTTP Range請求

HTTP客戶端下載文件時,若是發生了網絡中斷,必須從新向服務器發起HTTP請求,這時客戶端已經有了文件的一部分,只須要請求剩餘的內容,而不須要傳輸整個文件,Range請求就能夠用來處理這種問題。nginx

若是HTTP請求的頭部有Range字段,以下面所示緩存

Range: bytes=1024-2047

表示客戶端請求文件的第1025到第2048個字節,這時服務器只會響應文件的這部份內容,響應的狀態碼爲206,表示返回的是響應的一部分。若是服務器不支持Range請求,仍然會返回整個文件,這時狀態碼還是200。服務器

Nginx啓用slice模塊

ngx_http_slice_filter_module模塊默認沒有編譯到Nginx程序中,須要編譯時添加--with-http_slice_module選項。網絡

編譯完成後, 須要在Nginx配置文件中開啓,配置以下所示less

location / {
    slice             1m;
    proxy_cache       cache;
    proxy_cache_key   $uri$is_args$args$slice_range;
    proxy_set_header  Range $slice_range;
    proxy_cache_valid 200 206 1h;
    proxy_pass        http://localhost:8000;
}

slice指令設置分片的大小爲1m。 這裏使用了proxy_set_header指令,在取源時的HTTP請求中添加了Range頭部,向源服務器請求文件的一部分,而不是所有內容。在proxy_cache_key中添加slice_range變量這樣能夠分片緩存。函數

slice_range變量

slice_range這個變量做用很是特殊,這個變量的值是當前須要向源服務器請求的分片,若是分片的大小爲1m,那麼最開始變量的值爲bytes=0-1048575,經過配置文件中的proxy_set_header Range $slice_range;能夠知道取源時請求的Range頭部爲Range:bytes=0-1048575,源服務器若是支持Range請求,便會返回響應的前1m字節,獲得這個響應後slice_range變量的值變爲bytes=1048576-2097171 ,再次取源時便會取後1m字節,依次直到取得所有響應內容。debug

Nginx分片的實現

Nginx的slice模塊是經過掛載filter模塊來起做用的,處理流程以下所示code

輸入圖片說明

  1. 每次取源時都會攜帶Range頭部,
  2. 第一次取源請求前1m內容,若是響應在1m之內,或者源服務器不支持Range請求,返回狀態碼爲200,這時會直接跳過slice模塊。
  3. 在body_filter中向客戶端發送獲得的當前的分片,而後檢查是否到達文件末尾,若是沒有則生成一個子請求,子請求會向源服務器請求下一個分片,依次循環。

slice模塊的body_filter處理在ngx_http_slice_body_filter函數中server

static ngx_int_t
ngx_http_slice_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t                   rc;
    ngx_chain_t                *cl;
    ngx_http_slice_ctx_t       *ctx;
    ngx_http_slice_loc_conf_t  *slcf;

    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);

        /* 若是當前請求是子請求,直接調用下一個body_filter回調而後返回,*/
    if (ctx == NULL || r != r->main) {
        return ngx_http_next_body_filter(r, in);
    }

    for (cl = in; cl; cl = cl->next) {
        if (cl->buf->last_buf) {
            cl->buf->last_buf = 0;
            cl->buf->last_in_chain = 1;
            cl->buf->sync = 1;
            ctx->last = 1;
        }
    }

    /* 向客戶端發送當前的分片 */
    rc = ngx_http_next_body_filter(r, in);

    if (rc == NGX_ERROR || !ctx->last) {
        return rc;
    }

    /* 
        若是當前的子請求尚未接受徹底部的響應會直接返回,這裏子請求向源請求,獲得響應後由這裏的主請求發送給客戶端,子請求只負責取源。當前子請求接收徹底部的響應(這時
        ctx->sr->done爲1)後,主請求才會生成下一個子請求去取下一個分片
    */
    if (ctx->sr && !ctx->sr->done) {
        return rc;
    }

    if (!ctx->active) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "missing slice response");
        return NGX_ERROR;
    }

    /*
        若是已經到達文件末尾,則返回
    */
    if (ctx->start >= ctx->end) {
        ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module);
        ngx_http_send_special(r, NGX_HTTP_LAST);
        return rc;
    }

    if (r->buffered) {
        return rc;
    }

    /*
        生成子請求,注意這裏的NGX_HTTP_SUBREQUEST_CLONE,默認生成子請求從server_rewrite階段執行並跳過access階段,這裏NGX_HTTP_SUBREQUEST_CLONE使生成的子請求從主請求的當前階段(即content階段)開始執行
    */
    if (ngx_http_subrequest(r, &r->uri, &r->args, &ctx->sr, NULL,
                            NGX_HTTP_SUBREQUEST_CLONE)
        != NGX_OK)
    {
        return NGX_ERROR;
    }

    ngx_http_set_ctx(ctx->sr, ctx, ngx_http_slice_filter_module);

    slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);

    /*
    ctx->range指向的使slice_range變量的值
    */
    ctx->range.len = ngx_sprintf(ctx->range.data, "bytes=%O-%O", ctx->start,
                                 ctx->start + (off_t) slcf->size - 1)
                     - ctx->range.data;

    ctx->active = 0;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http slice subrequest: \"%V\"", &ctx->range);

    return rc;
}

Range的範圍

請求中的Range範圍可能會超過文件的大小,如第一次取源時,Nginx並不知道實際文件的大小,因此Nginx請求時老是按照分片的大小設置Range範圍,如slice設置爲1m,那麼第一次取bytes=0-1048575,若是文件不足1m,響應狀態嗎爲200,表示不須要分片。若是超過1m,第二次取源時Range字段爲bytes=1048576-2097171,即便這時能夠知道文件實際大小。

線上使用時就遇到過一次源服務器對Range請求支持不完善的問題,文件大小爲1.5m,第一次取源狀態碼爲206,返回1m內容,第二次取源使Range字段爲bytes=1048576-2097171,可是文件不足2m,源服務器發現這個範圍超過了文件大小,因此返回了整個文件,狀態碼爲200,這時Nginx就不能理解了,直接報錯中斷了響應。

開始覺得是Nginx的問題,而後查看了下RFC文檔,發現有解釋這種狀況

A client can limit the number of bytes requested without knowing the size of the selected representation. If the last-byte-pos value is absent, or if the value is greater than or equal to the current length of the representation data, the byte range is interpreted as the remainder of the representation (i.e., the server replaces the value of last-byte-pos with a value that is one less than the current length of the selected representation).

大體意思是說,若是請求的分片的後一個偏移超過了文件的實際大小,服務器應該返回剩餘的部份內容。這個問題應該是源服務器的實現並無按照RFC文檔的要求。

參考連接

相關文章
相關標籤/搜索