nginx slice模块的使用和源码分析

文章目录

  • 1. 为什么需要ngx_http_slice_module
  • 2. 配置指令
  • 3. 加载模块
  • 4. 源码分析
    • 4.1 指令分析
    • 4.2 模块初始化
    • 4.3 slice模块的上下文
    • 4.2 $slice_range字段值获取
    • 4.3 http header过滤处理
    • 4.4 http body过滤处理
    • 5 测试和验证

1. 为什么需要ngx_http_slice_module

顾名思义,nginx的slice模块的功能是在proxy代理的时候,会将代理到上游服务器的请求转换为若干个分片的子请求,最后将响应内容逐个返回给用户。
那么为什么要搞那么麻烦,将一个大文件切片成小的碎片文件来处理呢?原因有以下三点:
1. 大文件在整个下载的过程中持续的时间比较长,nginx和上游服务器(被代理的后端服务器)长时间建立连接,可能因为各种原因引起连接中断的概率大幅度上升。
2. 更重要的原因是大文件不利于cdn缓存。譬如著名的开源缓存服务器ats和squid,一般都需要等文件下载完全后才能将内容缓存到cache里面,如果用户下载了一半不下载了或者因为和上游服务器连接故障都会导致文件不能完整地被cache服务器下载下来而导致该文件不能被缓存,引起反复下载,降低内容的命中率。
3. 大文件在cdn架构中不容易平衡cache节点的负载,导致cache节点负载不平衡而影响用户体验。

而nginx slice模块的出现将大文件化整为零,很好地解决了以上这些问题。下面列出了一个CDN cache系统的典型架构,具体不做详述,后面可以另行撰文说明。

在这里插入图片描述

2. 配置指令

slice size;

  • 其中size是切片的大小,单位可以是K(千字节),M(兆字节),G(吉字节),单位大小写均可。

  • slice_size指令可以配置在"http", “server”, “location” 块中定义。

    但是真正要启用slice功能,还要设置两条指令:

    proxy_cache_key   $uri$is_args$args$slice_range;
    proxy_set_header  Range $slice_range;
第一条指令表示如果使用nginx的自带缓存功能,那么nginx会以切片为单位进行缓存,那么缓存的时候需要对同一个文件的不同分片进行区分,所以需要将cache_key和每个切片的标识进行关联,这里使用了$slice_range变量。
第二条指令表示如果向上游服务器进行请求的时候,需要增加的HTTP Range头,该头的内容就是$slice_range变量的值。
附带说明一下:
$slice_range变量本身是由ngx_http_slice_module来定义并赋值的, 值的内容如:`bytes=0-1048575`。

3. 加载模块

在configure的时候需要添加ngx_http_slice_module来将其编译进来,

命令如下:

./configure --with-http_slice_module

然后在nginx.conf 中添加以下配置,如:
location / {
    slice             1m;
    proxy_cache       cache;
    proxy_cache_key   $uri$is_args$args$slice_range;
    proxy_cache_valid 200 206 1h;
    proxy_set_header  Range $slice_range;
    proxy_pass        http://localhost:8000;
}
当然,如果不使用cache功能,只是单纯使用slice功能,那么proxy_cache、proxy_cache_key和proxy_cache_valid这些指令都不需要写了。

4. 源码分析

4.1 指令分析

static ngx_command_t  ngx_http_slice_filter_commands[] = {

    { ngx_string("slice"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_size_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_slice_loc_conf_t, size),
      NULL },

      ngx_null_command
};

从以上代码知道,slice指令可以在http server location块里面进行配置,一旦nginx发现slice指令,就调用ngx_conf_set_size_slot函数进行配置解析。ngx_conf_set_size_slot本身还是非常好理解的, 它是nginx在配置解析阶段用来解析大小的通用函数,解析的结果存放在ngx_http_slice_loc_conf_t的size字段中,如果size字段为0,标识不开启切片功能。当然需要说明一下,ngx_http_slice_loc_conf_t的实例会由ngx_http_slice_create_loc_conf来创建,并由ngx_http_slice_merge_loc_conf来合并,这方面的代码逻辑不再赘述。

4.2 模块初始化

static ngx_http_module_t  ngx_http_slice_filter_module_ctx = {
    ngx_http_slice_add_variables,          /* preconfiguration */
    ngx_http_slice_init,                   /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_slice_create_loc_conf,        /* create location configuration */
    ngx_http_slice_merge_loc_conf          /* merge location configuration */
};
从以上代码知道,slice模块是作为一个nginx的filter模块参与切片工作的,同时ngx_http_slice_init是模块的初始化函数,而ngx_http_slice_add_variables是在preconfiguration阶段,也就是在初始化函数之前执行的函数,用来向nginx http框架添加$slice_range变量,供处理http请求的时候使用。

先看一下ngx_http_slice_add_variables函数:
static ngx_int_t
ngx_http_slice_add_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t  *var;

    var = ngx_http_add_variable(cf, &ngx_http_slice_range_name, 0);
    if (var == NULL) {
        return NGX_ERROR;
    }

    var->get_handler = ngx_http_slice_range_variable;

    return NGX_OK;
}
以上代码非常简单,就是调用ngx_http_add_variable添加$slice_range变量,变量名存放在全局静态变量ngx_http_slice_range_name中,如下:
static ngx_str_t  ngx_http_slice_range_name = ngx_string("slice_range");
添加变量的时候还设置了获取该变量的回调函数为ngx_http_slice_range_variable,$slice_range变量只有get没有set,所以是一个只读类型的变量,其内容只能由slice模块内部进行更新,其他模块是不能对其进行更新操作的。

再看slice模块的初始化函数:
static ngx_int_t
ngx_http_slice_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_slice_header_filter;

    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_slice_body_filter;

    return NGX_OK;
}
显然,这就是典型的filter函数的初始化过程,就是将ngx_http_slice_header_filter和ngx_http_slice_body_filter分别以挂钩函数的方式挂入filter模块的两条调用链中,即header过滤器调用链和body过滤器调用链。

4.3 slice模块的上下文

在正式介绍请求处理逻辑之前,需要先了解一下ngx_http_slice_module模块的请求上下文,具体如下:
typedef struct {
    off_t                start;     /* 当前切片的起始偏移量 */
    off_t                end;       /* 请求内容的结束偏移量,不是指一个切片的结束偏移量,
                                       而是当前请求客户端需要的内容的结束偏移量 */
    ngx_str_t            range;     /* 存储$slice_range变量的字符串值 */
    ngx_str_t            etag;      /* 上游服务器响应的内容etag值,
                                       用来比对多个切片请求是否属于同一个切片 */
    unsigned             last:1;    /* 第一个切片请求是否已经完成了最后一个buf的处理 */
    unsigned             active:1;  /* 当前的切片请求响应处理过程执行中 */
    ngx_http_request_t  *sr;        /* 当前活跃中的子请求 */
} ngx_http_slice_ctx_t;

4.2 $slice_range字段值获取

为什么从$slice_range字段值的获取开始说的?因为,这个字段值的获取在nginx向上游服务器发起请求前,组织HTTP请求头的时候就会被调用了,执行顺序上面来说是放在执行http header和http body过滤函数的前面的。而且,获取这个字段值的时候,会创建slice模块的请求上下文ngx_http_slice_ctx_t, 另外需要明确的一点是,每发起一个向上游服务器的新的切片的请求前,都会重新获取这个字段值来组织新的请求头,所以在处理的过程中,这个变量的值是随着完成的切片的情况而需要不断更新的。
以下是字段值获取的回调函数的实现:
static ngx_int_t
ngx_http_slice_range_variable(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    u_char                     *p;
    ngx_http_slice_ctx_t       *ctx;
    ngx_http_slice_loc_conf_t  *slcf;
    
	/* 获取当前请求本filter模块的上下文信息*/
    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
    
    /* 如果为空表示上下文还没有创建,则需要创建一个新的上下文,
       这当然是在主请求上才会有这个情况,子请求不会出现这个情况除非当前filter是disable了,
       如果disable状态,当然返回当前$slice_range变量没有找到了 */
    if (ctx == NULL) { 
        if (r != r->main || r->headers_out.status) {
            v->not_found = 1;
            return NGX_OK;
        }
        
		/* 获取本filter模块的配置信息 */
        slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
        
		/* 如果配置的切片size为0,表示切片功能禁用了,
		   所以返回$slice_range变量找不到的错误信息 */
        if (slcf->size == 0) {
            v->not_found = 1;
            return NGX_OK;
        }
        
		/* 创建一个新的上下文并保存到当前request中 */
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_slice_ctx_t));
        if (ctx == NULL) {
            return NGX_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_slice_filter_module);
        
		/* 分配一块内存,用于保存$slice_range的值*/
        p = ngx_pnalloc(r->pool, sizeof("bytes=-") - 1 + 2 * NGX_OFF_T_LEN);
        if (p == NULL) {
            return NGX_ERROR;
        }
        
		/* 设置本次需要向上游服务器发起的起始位置,详见下文*/
        ctx->start = slcf->size * (ngx_http_slice_get_start(r) / slcf->size);

        ctx->range.data = p;
        ctx->range.len = ngx_sprintf(p, "bytes=%O-%O", ctx->start,
                                     ctx->start + (off_t) slcf->size - 1)
                         - p;
    }
    
	/* 设置将返回的变量的信息 */
    v->data = ctx->range.data;
    v->valid = 1;           /* 标识变量可用标记 */
    v->not_found = 0;       /* 标识变量找到标记 */
    v->no_cacheable = 1;    /* 标识变量不可缓存标记 */
    v->len = ctx->range.len;

    return NGX_OK;
}
这里需要再稍微解释一下下面这个语句:
ctx->start = slcf->size * (ngx_http_slice_get_start(r) / slcf->size); 
ngx_http_slice_get_start的函数调用是会去判断客户端的请求是否是range请求,如果不是range请求,那么很简单,就是从头获取文件的完整内容,所以必然是从0字节开始请求,否则需要解析当前客户端请求的HTTP Range头的信息,从而得到客户端希望的文件起始偏移量。得到的客户端实际希望的文件起始偏移量以后需要按照切片大小进行对齐后设置到ctx->start变量中,最后写入向上游服务器请求头中的HTTP Range字段。
那么为什么需要按照slice切片大小进行对齐向上游服务器请求呢?因为这样不会由于不同客户端请求的起始位置不同,导致产生大量的不同切片,引起缓存miss。对齐操作完全就是为了提升缓存的命中率。虽然在本次请求的时候向后端服务器多请求了一些内容,但是比起缓存hit带来的好处,还是非常非常值得的。

下面是ngx_http_slice_get_start的实现代码:
static off_t
ngx_http_slice_get_start(ngx_http_request_t *r)
{
    off_t             start, cutoff, cutlim;
    u_char           *p;
    ngx_table_elt_t  *h;
    
	/* 不是range请求,直接返回0表示向上游服务器从头开始请求*/
    if (r->headers_in.if_range) {
        return 0;
    }
	
	/* 解析HTTP Range请求头,获取起始偏移量 */
    h = r->headers_in.range;

    if (h == NULL
        || h->value.len < 7
        || ngx_strncasecmp(h->value.data, (u_char *) "bytes=", 6) != 0)
    {
        return 0;
    }

    p = h->value.data + 6;

    if (ngx_strchr(p, ',')) {
        return 0;
    }

    while (*p == ' ') { p++; }

    if (*p == '-') {
        return 0;
    }

    cutoff = NGX_MAX_OFF_T_VALUE / 10;
    cutlim = NGX_MAX_OFF_T_VALUE % 10;

    start = 0;

    while (*p >= '0' && *p <= '9') {
        if (start >= cutoff && (start > cutoff || *p - '0' > cutlim)) {
            return 0;
        }

        start = start * 10 + (*p++ - '0');
    }

    return start;
}

4.3 http header过滤处理

static ngx_int_t
ngx_http_slice_header_filter(ngx_http_request_t *r)
{
    off_t                            end;
    ngx_int_t                        rc;
    ngx_table_elt_t                 *h;
    ngx_http_slice_ctx_t            *ctx;
    ngx_http_slice_loc_conf_t       *slcf;
    ngx_http_slice_content_range_t   cr;
    
	/* 获取当前请求的slice模块的上下文, ctx上下文是在4.2节中描述的
	   ngx_http_slice_range_variable中创建的,没有创建就会返回NULL,
	   说明本次请求没有启用slice过滤模块,
	   那么直接调用ngx_http_next_header_filter执行filter链中的后续模块的处理函数。
	*/
    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);
    if (ctx == NULL) {
        return ngx_http_next_header_filter(r);
    }
	/*  调用本处理函数的时候,上游服务器发出的响应响应头已经被本nginx获取到了。
		如果响应的内容不是206,并且当前是第一个切片的请求
		    (第一个切片请求只能是主请求发起,不是子请求),
		说明上游服务器不支持Range请求,则禁用切片功能。
		如果是子请求,而上游服务器已经响应了非206,那么第一个切片和后续的切片响应
		前后不一致,只能报错了。
	*/
    if (r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT) {
        if (r == r->main) {
            ngx_http_set_ctx(r, NULL, ngx_http_slice_filter_module);
            return ngx_http_next_header_filter(r);
        }

        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "unexpected status code %ui in slice response",
                      r->headers_out.status);
        return NGX_ERROR;
    }
    
	/* 检查主请求和自请求中响应内容的etag是否一致,如果不一致,则认为不是一个内容
	   也只能报错了。
	*/
    h = r->headers_out.etag;

    if (ctx->etag.len) {
        if (h == NULL
            || h->value.len != ctx->etag.len
            || ngx_strncmp(h->value.data, ctx->etag.data, ctx->etag.len)
               != 0)
        {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "etag mismatch in slice response");
            return NGX_ERROR;
        }
    }
    
    /* 在上下文中存储当前的etag信息,用于下一个子请求header处理的时候进行比对 */
    if (h) {
        ctx->etag = h->value;  
    }
    
    /* 分析上游服务器的响应头中的Content-Range头中的信息 */
    if (ngx_http_slice_parse_content_range(r, &cr) != NGX_OK) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "invalid range in slice response");
        return NGX_ERROR;
    }
    
    /* 如果Content-Range头中没有整个内容的长度信息,那么不能进行切片处理,只能报错 */
    if (cr.complete_length == -1) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "no complete length in slice response");
        return NGX_ERROR;
    }

    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http slice response range: %O-%O/%O",
                   cr.start, cr.end, cr.complete_length);
                   
    /* 获取slice模块的配置信息 */
    slcf = ngx_http_get_module_loc_conf(r, ngx_http_slice_filter_module);
    /* 计算当前切片的结束偏移位置,也就是下一个切片的起始位置 */
    end = ngx_min(cr.start + (off_t) slcf->size, cr.complete_length);
	
	/* 判断希望请求的切片起止位置和实际上游服务器响应的起止位置是否一致 */
    if (cr.start != ctx->start || cr.end != end) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "unexpected range in slice response: %O-%O",
                      cr.start, cr.end);
        return NGX_ERROR;
    }
	
    ctx->start = end; /* 设置下一个切片的开始位置 */
    ctx->active = 1;  /* 设置当前的切片请求的响应进入活跃状态中 */
    
	/* 设置客户端响应的响应头信息,包括响应状态需要从206改成200, 
	   内容大小改成完整的大小而不是本次切片请求上游服务器返回的切片大小 
	*/
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.status_line.len = 0;
    r->headers_out.content_length_n = cr.complete_length;
    r->headers_out.content_offset = cr.start;
    r->headers_out.content_range->hash = 0;
    r->headers_out.content_range = NULL;
	
	/* 向客户端响应的时候需要清理掉Accept-Ranges头*/
    if (r->headers_out.accept_ranges) {
        r->headers_out.accept_ranges->hash = 0;
        r->headers_out.accept_ranges = NULL;
    }

    r->allow_ranges = 1;     /*设置允许ngx_http_range_filter_module执行Range处理*/
    r->subrequest_ranges = 1;/*本参数和allow_ranges的值一致的,可以忽略*/
    r->single_range = 1;     /*设置ngx_http_range_filter_module仅支持单个Range模式
                               不支持多Range模式 */
	/* 继续调用header filter链的下一个模块的处理函数,后续模块可能包括
	   ngx_http_range_filter_module */
    rc = ngx_http_next_header_filter(r);

    if (r != r->main) {      /* 如果不相等,表示是子请求 */
        return rc;           /* 如果是子请求就直接返回,不执行后面的代码了 */*
    }
    
	/* 以下代码近在主请求中只会被执行1次,子请求中则不会进入到以下代码 */
	/* preserve_body字段的作用就是控制在转发请求时是否保留请求体。
	当preserve_body字段设置为1时,Nginx将会保留请求体数据,
	并将其传递给上游服务器。当preserve_body字段设置为0时(默认值),
	Nginx会在转发请求时丢弃请求体数据,只传递请求头部和其他元数据。
	*/
    r->preserve_body = 1;
    
    /* 如果经过header filter的调用链处理后,
       ngx_http_range_filter_module处理了客户端发送来的Range请求,这个时候
       真正发送给客户端的状态是206响应,而不是前面设置的200。因为客户端的请求是Range请求,
       而当前处理的分片的起始范围在客户端请求要求的内容的起始偏移量前面,
       那么需要重新根据content_offset指定的偏移量调整向后端服务器请求的分片起始位置,
       而结束位置为客户端请求的结束位置偏移量。
    */
    if (r->headers_out.status == NGX_HTTP_PARTIAL_CONTENT) {
        if (ctx->start + (off_t) slcf->size <= r->headers_out.content_offset) {
            ctx->start = slcf->size
                         * (r->headers_out.content_offset / slcf->size);
        }

        ctx->end = r->headers_out.content_offset
                   + r->headers_out.content_length_n;

    } else {
        ctx->end = cr.complete_length;
    }

    return rc;
}

4.4 http body过滤处理

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;
    
    /* 获取当前请求的slice模块的上下文,如果为空,说明本次请求没有启用slice过滤模块,
	   那么直接调用ngx_http_next_header_filter执行filter链中的后续模块的处理函数。
	*/
    ctx = ngx_http_get_module_ctx(r, ngx_http_slice_filter_module);

    if (ctx == NULL || r != r->main) { /* 如果是子请求,直接进入后续的调用链 */
        return ngx_http_next_body_filter(r, in);
    }
    
	/* 以下都是在主请求中处理
	   如果last_buf此字段为1表明这是最后一个buf,但是对于整个请求来说只是一个切片的最后一块,
	   不是整个请求的最后一个buf块,所以需要重新调整为0,并设置last_in_chain=1用于表明是本
	   chain的最后一个buf块 */
    for (cl = in; cl; cl = cl->next) {
        if (cl->buf->last_buf) {          /* 当前子请求的最后一个buf并不是响应给客户端
                                             的最后一个buf,所以需要重新调整这个标记 */
            cl->buf->last_buf = 0;        /* 用于标识是否是最后一个缓冲区 */
            cl->buf->last_in_chain = 1;   /* 表示是否是链表中的最后一个缓冲区 */
            cl->buf->sync = 1;            /* 表示是否需要执行同步操作 */
            ctx->last = 1;                /* 第一个切片的最后一个buf以及获取到 */
        }
    }
	
	/* 调用body filter链后续filter模块的处理函数
	   第一个切片请求是在主请求中发生的,这时in里面带有待发送到客户端的数据
	   之后的切片请求是在子请求中发生的,这个时候子请求的调用链已经将数据发送到客户端了,
	   到子请求把当前的切片发送完毕后,会通过发送一个in=NULL空的包重新激活主请求,
	   这时主请求可以知道子请求已经完成了,从而可以根据需要开启一个新的子请求,
	   或者结束请求的处理。
	 */
    rc = ngx_http_next_body_filter(r, in);

    if (rc == NGX_ERROR || !ctx->last) {
        return rc;
    }
    
	/* 当前的子请求还没有处理完毕,返回nginx http框架,继续处理 */
    if (ctx->sr && !ctx->sr->done) {
        return rc;
    }
    
	/* ctx->active=1是在处理子请求头部信息即ngx_http_slice_header_filter函数中设置的
	    如果=0, 则表示当前切片请求的响应还没有活跃状态,但是却需要发送body,
	    应该是出了什么问题? 
	*/
    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);
        
        /* 这里会通知nginx框架发送一个last_buf设置为1的ngx_buf_t缓冲区表示内容发送完毕 */
        ngx_http_send_special(r, NGX_HTTP_LAST); 
        return rc;
    }

	/* buffered 字段是一个标志位,用于指示请求是否有未处理的请求体数据。
	   当客户端发送一个带有请求体的 HTTP 请求时,
	   请求体数据可能会被分成多个数据块(chunks)进行传输。
	   buffered 字段用于跟踪这些请求体数据的处理状态。
	   如果这个标记为1, 就暂时不能启动一个新的子请求 */
    if (r->buffered) {
        return rc;
    }
    
	/* 当前切片已全部响应给客户端,还有新的切片需要处理,开启一个新的子请求来获取新的切片 */
    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);
	
	/* 设置下一个切片的$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;
}

5 测试和验证

为了测试slice模块的效果,我们需要两个nginx服务,第一个nginx服务作为前端代理服务器,第二个nginx服务作为后端源服务器,为了简单起见,将这两个nginx服务都搭建在一台物理服务器上面。为了能够一目了然看清楚前端代理服务器确实向后端发送了切片请求,需要在后端nginx服务器的access日志上添加$http_range变量的输出。

下面先列出后端nginx的配置文件nginx.conf:
user  nobody;
worker_processes  1;  

error_log  logs/error.log;
pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" '
                      '"Range: $http_range';

    access_log  logs/access.log  main;
    gzip off;
     
    server {
        listen 8888;
        location / { 
             root html;
		}   
    }
}

这里需要特别注意的就是log_format 这个指令中添加了
'"Range: $http_range'
然后设置前端代理nginx的e配置文件nginx.conf:
user  nobody;
worker_processes  1;  

error_log  logs/error.log;
pid        logs/nginx.pid;


events {
    worker_connections  1024;
}



http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;
    
  	server {
        listen 9080;
		
        location / {
			slice 1m;
			proxy_set_header Range $slice_range;
            proxy_buffering off;
            proxy_pass http://127.0.0.1:8888;
        }
	}
}

主要需要注意的是location / { } 中的设置。
然后启动两个nginx服务,通过curl来验证,

测试用例1, 完整文件请求,如:

curl "http://127.0.0.1:9080/a.pdf" > /dev/null
查看第二个nginx的access.log日志,如下:
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=0-1048575
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=1048576-2097151
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=2097152-3145727
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=3145728-4194303
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=4194304-5242879
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=5242880-6291455
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=6291456-7340031
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=7340032-8388607
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=8388608-9437183
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=9437184-10485759
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=10485760-11534335
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=11534336-12582911
127.0.0.1 - - [04/Feb/2024:16:20:17 +0800] "GET /a.pdf HTTP/1.0" 206 1015274 "-" "curl/7.81.0" "-" "Range: bytes=12582912-13631487

一个客户端的http请求在第二个后端源nginx上收到了若干个响应为206的HTTP请求,表明前端nginx的切片功能已经正常开启了。

测试用例2, Range请求,如:

curl "http://127.0.0.1:9080/a.pdf" -H"Range: bytes=1048577-5788888" > /dev/null
查看第二个nginx的access.log日志,如下:
127.0.0.1 - - [04/Feb/2024:16:30:29 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=1048576-2097151
127.0.0.1 - - [04/Feb/2024:16:30:29 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=2097152-3145727
127.0.0.1 - - [04/Feb/2024:16:30:29 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=3145728-4194303
127.0.0.1 - - [04/Feb/2024:16:30:29 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=4194304-5242879
127.0.0.1 - - [04/Feb/2024:16:30:29 +0800] "GET /a.pdf HTTP/1.0" 206 1048576 "-" "curl/7.81.0" "-" "Range: bytes=5242880-6291455
这次可以看到,第二个nginx收到的第一个请求的Range范围是1048576-2097151,正好对应第二个切片的范围,虽然我们请求要求的起始位置是1048577;同时,最后一个切片请求的结束位置是6291455,而这个正好是第五个切片的最后一个字节的偏移量。这样子验证了nginx slice功能的切片功能是按照切片对齐的方式向上游服务器发送请求的。![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/6720d620740140a0b2c4145d52a5fb9f.png#pic_center)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/372115.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

配置Jenkins自动构建打包项目

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 需求说明 1、给A项目配置jenkins每2小时无条件自动构建一次&#xff0c;无论是否有代码提交。 2、给B项目配置jenkins每15分钟检…

FPGA高端项目:IMX327 MIPI 视频解码 USB3.0 UVC 输出,提供FPGA开发板+2套工程源码+技术支持

目录 1、前言免责声明 2、相关方案推荐我这里已有的 MIPI 编解码方案 3、本 MIPI CSI-RX IP 介绍4、个人 FPGA高端图像处理开发板简介5、详细设计方案设计原理框图IMX327 及其配置MIPI CSI RX图像 ISP 处理图像缓存UVC 时序USB3.0输出架构 6、vivado工程详解FPGA逻辑设计 7、工…

ChatGPT辅助编程,一次有益的尝试

如果大家想学习PCIe&#xff0c;搜索网上的信息&#xff0c;大概率会看到chinaaet上Felix的PCIe扫盲系列的博文 Felix-PCIe扫盲 每次看这个系列博文的时候&#xff0c;我都在想有没有什么方法可以把这个系列的博文都保存到一个pdf文件中&#xff0c;这样方便阅读。于是有了下…

异地办公必不可缺的远程控制软件,原理到底是什么?

目录 引言远程桌面连接软件的作用与重要性 基本概念与架构客户端-服务器模型网络通信协议 核心技术组件图形界面捕获与传输输入转发会话管理 性能优化策略带宽优化延迟优化 引言 远程桌面连接软件的作用与重要性 在当今这个高度数字化和网络化的时代&#xff0c;远程桌面连接软…

R语言学习case10:ggplot基础画图Parallel Coordinate Plot 平行坐标图

step1: 导入ggplot2库文件 library(ggplot2)step2&#xff1a;带入自带的iris数据集 iris <- datasets::irisstep3&#xff1a;查看数据信息 dim(iris)维度为 [150,5] head(iris)查看数据前6行的信息 step4&#xff1a;利用ggplot工具包绘图 plot5 <- ggparcoord(…

Linux目录:traceroute命令

目录 traceroute1、简介2、探测原理3、traceroute说明4、实例设置每跳探测数设置跳数探测包使用的基本UDP端口设置6789把对外发探测包的等待响应时间设置为3秒 总结 traceroute 1、简介 traceroute的主要功能是跟踪从IP网络发送到指定主机经过的网关的工具。它利用IP协议的生…

npm淘宝镜像源换新地址

新的淘宝npm镜像源地址&#xff1a;https://registry.npmmirror.com 切换新的镜像源 npm config set registry https://registry.npmmirror.com然后再执行以下操作查看是否成功 npm config list如果没安装过淘宝镜像源的&#xff0c;则直接安装 npm install -g cnpm --regi…

机器学习 - 梯度下降

场景 上一章学习了代价函数&#xff0c;在机器学习中&#xff0c;代价模型是用于衡量模型预测值与真实值之间的差异的函数。它是优化算法的核心&#xff0c;目标是通过调整模型的参数来最小化代价模型的值&#xff0c;从而使模型的预测结果更接近真实值。常见的代价模型是均方…

哪些因素会影响游戏服务器开发的性能?

在游戏开发中&#xff0c;服务器性能是一个关键因素&#xff0c;它影响着游戏的运行速度、稳定性以及玩家的游戏体验。游戏服务器需要处理大量的数据和请求&#xff0c;因此其性能受到多种因素的影响。本文将探讨影响游戏服务器开发性能的几个关键因素。 ​1、硬件资源 服务器…

互联网加竞赛 基于深度学习的目标检测算法

文章目录 1 简介2 目标检测概念3 目标分类、定位、检测示例4 传统目标检测5 两类目标检测算法5.1 相关研究5.1.1 选择性搜索5.1.2 OverFeat 5.2 基于区域提名的方法5.2.1 R-CNN5.2.2 SPP-net5.2.3 Fast R-CNN 5.3 端到端的方法YOLOSSD 6 人体检测结果7 最后 1 简介 &#x1f5…

PySpark(二)RDD基础、RDD常见算子

目录 RDD RDD五大特性 RDD创建 RDD算子 常见的Transformation算子 map flatMap mapValues reduceByKey groupBy filter distinct union join intersection glom groupByKey groupByKey和reduceByKey的区别 ? sortBy sortByKey 常见的action算子 countByKey…

Zoho联合知了标讯,为CRM系统加入智能招投标功能

随着我们在国内合作伙伴的名单不断扩充&#xff0c;CRM管理系统也在持续上线新功能。去年&#xff0c;Zoho联合知了标讯发布了智能招投标功能&#xff0c;集成在自己的CRM系统中&#xff0c;对有招投标需求的企业来说提供了莫大的便利。接下来&#xff0c;让我为您介绍&#xf…

【粉丝福利社】一书读懂物联网:基础知识+运行机制+工程实现(文末送书-完结)

&#x1f3c6; 作者简介&#xff0c;愚公搬代码 &#x1f3c6;《头衔》&#xff1a;华为云特约编辑&#xff0c;华为云云享专家&#xff0c;华为开发者专家&#xff0c;华为产品云测专家&#xff0c;CSDN博客专家&#xff0c;CSDN商业化专家&#xff0c;阿里云专家博主&#xf…

On the Spectral Bias of Neural Networks论文阅读

1. 摘要 众所周知&#xff0c;过度参数化的深度神经网络(DNNs)是一种表达能力极强的函数&#xff0c;它甚至可以以100%的训练精度记忆随机数据。这就提出了一个问题&#xff0c;为什么他们不能轻易地对真实数据进行拟合呢。为了回答这个问题&#xff0c;研究人员使用傅里叶分析…

vue3 + vite:打包部署后,动态组件渲染404问题解决

问题描述&#xff1a; 当需要渲染动态组件&#xff0c;动态的组件路径配置在数据库中时&#xff0c;如下图&#xff0c;本地运行能正常访问&#xff0c;用vite打包部署后&#xff0c;生产上改路径为404. 起初认为是&#xff0c;vite打包后的文件都是.js, 当页面加载后从数据库…

chisel之scala 语法

Chisel新手教程之Scala语言&#xff08;1&#xff09; Value & variable Value是immutable的&#xff0c;当它被分配一个数据后&#xff0c;无法进行重新分配。用 val 表示。 Variable是mutable的&#xff0c;可以重复赋值。用 var 表示。示例如下&#xff1a; val a …

vue2 el-table新增行内删除行内(两种写法)里面第一个是树组件,第二个是数字组件,第一个数组件只能勾选最后一个节点

第一种 <template><div class"time_table"><div style"margin-bottom: 10px"><el-button click"addRowFn">新增</el-button></div><el-form ref"costForm" :model"formData">&l…

普渡机器人CEO预测2024年服务机器人市场将扩大

原创 | 文 BFT机器人 根据普渡科技有限公司的报告&#xff0c;商用服务机器人在东亚地区的应用比其他地方更为广泛。然而&#xff0c;预计到2024年&#xff0c;全球其他地区也将迎头赶上。这家总部位于中国深圳的公司自豪地宣称&#xff0c;它已经成为中国最大的此类机器人出口…

基于QPSO-LSTM的短期风电负荷MATLAB预测程序

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 参考文献 基于QPSO-LSTM的短期风电负荷预测模型——谭才兴&#xff08;完全复现&#xff09; 程序简介 传统的LSTM神经网络超参数和拓扑结构通常是基于经验和试验确定&#xff0c;但这种方法容易受到人为因…

uniapp中配置开发环境和生产环境

uniapp在开发的时候&#xff0c;可以配置多种环境&#xff0c;用于自动切换IP地址&#xff0c;用HBuilder X直接运行的就是开发环境&#xff0c;用HBuilder X发布出来的&#xff0c;就是生产环境。 1.使用HBuilder X创建原生的uniapp程序 选择vue3 2.什么都不改&#xff0c;就…