当前位置: 首页 > news >正文

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)

相关文章:

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

文章目录 1. 为什么需要ngx_http_slice_module2. 配置指令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 顾名思义&#…...

AI应用开发-python实现redis数据存储

AI应用开发相关目录 本专栏包括AI应用开发相关内容分享&#xff0c;包括不限于AI算法部署实施细节、AI应用后端分析服务相关概念及开发技巧、AI应用后端应用服务相关概念及开发技巧、AI应用前端实现路径及开发技巧 适用于具备一定算法及Python使用基础的人群 AI应用开发流程概…...

2024年Java架构篇之设计模式

2024年Java实战面试题_java 5 年 面试-CSDN博客 1、单例模式...

搭建macOS开发环境-1:准备工作

请记住&#xff1a; 最重要的准备工作永远是&#xff1a;备份数据 !!! 通过图形界面检查 Mac 的 CPU 类型&#xff1a; 在搭载 Apple 芯片的 Mac 电脑上&#xff0c;“关于本机”会显示一个标有“芯片”的项目并跟有相应芯片的名称&#xff1a; 通过命令行检查Mac的CPU类型 …...

【Makefile语法 02】Makefile语法基础

目录 一、Makefile概述 二、Makefile变量 三、Makefile符号 一、Makefile格式 1. 基本格式&#xff1a; targets : prerequisties [tab键]command target&#xff1a;目标文件&#xff0c;可以是 OjectFile&#xff0c;也可以是执行文件&#xff0c;还可以是一个标签&…...

如何写一个其他人可以使用的GitHub Action

前言 在GitHub中&#xff0c;你肯定会使用GitHub Actions自动部署一个项目到GitHub Page上&#xff0c;在这个过程中总要使用workflows工作流&#xff0c;并在其中使用action&#xff0c;在这个使用的过程中&#xff0c;总会好奇怎么去写一个action呢&#xff0c;所以&#xff…...

排序算法的时间复杂度存在下界问题

对于几种常用的排序算法&#xff0c;无论是归并排序、快速排序、以及更加常见的冒泡排序等&#xff0c;这些排序算法的时间复杂度都是大于等于O(n*lg(n))的&#xff0c;而这些排序算法存在一个共同的行为&#xff0c;那就是这些算法在对元素进行排序的时候&#xff0c;都会进行…...

详解洛谷P2016 战略游戏/BZOJ0495. 树的最小点覆盖之战略游戏(贪心/树形DP)

Description Bob喜欢玩电脑游戏&#xff0c;特别是战略游戏。但是他经常无法找到快速玩过游戏的办法。现在他有个问题。 他要建立一个古城堡&#xff0c;城堡中的路形成一棵树。他要在这棵树的结点上放置最少数目的士兵&#xff0c;使得这些士兵能了望到所有的路。 注意&…...

解决The Tomcat connector configured to listen on port 8080 failed to start

问题 启动javar报错&#xff0c;提示如下 Description: The Tomcat connector configured to listen on port 8080 failed to start. The port may already be in use or the connector may be misconfigured. Action: Verify the connector’s configuration, identify a…...

深度学习自然语言处理(NLP)模型BERT:从理论到Pytorch实战

文章目录 深度学习自然语言处理&#xff08;NLP&#xff09;模型BERT&#xff1a;从理论到Pytorch实战一、引言传统NLP技术概览规则和模式匹配基于统计的方法词嵌入和分布式表示循环神经网络&#xff08;RNN&#xff09;与长短时记忆网络&#xff08;LSTM&#xff09;Transform…...

C语言的循环结构

目录 前言 1.三种循环语句 1.while循环 2.for循环 2.1缺少表达式的情况 3.do while循环 2.break语句和continue语句 2.1在while循环中 2.2在for循环中 2.3在do while 循环中 3.循环的嵌套 4.go to语句 前言 C语⾔是结构化的程序设计语⾔&#xff0c;这⾥的结构指的是…...

C#用Array类的FindAll方法和List<T>类的Add方法按关键词在数组中检索元素并输出

目录 一、使用的方法 1. Array.FindAll(T[], Predicate) 方法 &#xff08;1&#xff09;定义 &#xff08;2&#xff09;示例 2.List类的常用方法 &#xff08;1&#xff09;List.Add(T) 方法 &#xff08;2&#xff09;List.RemoveAt(Int32) 方法 &#xff08;3&…...

【前后端接口AES+RSA混合加解密详解(vue+SpringBoot)附完整源码】

前后端接口AES+RSA混合加解密详解(vue+SpringBoot) 前后端接口AES+RSA混合加解密一、AES加密原理和为什么不使用AES加密二、RSA加密原理和为什么不使用rsa加密三、AES和RSA混合加密的原理四、代码样例前端1. 请求增加加密标识2. 前端加密工具类3.前端axios请求统一封装,和返…...

React环境配置

1.安装Node.js Node.js官网&#xff1a;https://nodejs.org/en/ 下载之后按默认选项安装好 重启电脑即可自动完成配置 2.安装React 国内使用 npm 速度很慢&#xff0c;可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm。 ①使用 winR 输入 cmd 打开终端 ②依…...

Pandas 数据处理-排序与排名的深度探索【第69篇—python:文本数据处理】

文章目录 Pandas 数据处理-排序与排名的深度探索1. sort_index方法2. sort_values方法3. rank方法4. 多列排序5. 排名方法的参数详解6. 处理重复值7. 对索引进行排名8. 多级索引排序与排名9. 更高级的排序自定义10. 性能优化技巧10.1 使用nsmallest和nlargest10.2 使用sort_val…...

第8节、双电机多段直线运动【51单片机+L298N步进电机系列教程】

↑↑↑点击上方【目录】&#xff0c;查看本系列全部文章 摘要&#xff1a;前面章节主要介绍了bresenham直线插值运动&#xff0c;本节内容介绍让两个电机完成连续的直线运动,目标是画一个正五角星 一、五角星图介绍 五角星总共10条直线&#xff0c;10个顶点。设定左下角为原点…...

Elasticsearch:基本 CRUD 操作 - Python

在我之前的文章 “Elasticsearch&#xff1a;关于在 Python 中使用 Elasticsearch 你需要知道的一切 - 8.x”&#xff0c;我详细讲述了如何建立 Elasticsearch 的客户端连接。我们也详述了如何对数据的写入及一些基本操作。在今天的文章中&#xff0c;我们针对数据的 CRUD (cre…...

1992-2022年全国及31省对外开放度测算数据(含原始数据+计算结果)(无缺失)

1992-2022年全国及31省对外开放度测算数据&#xff08;含原始数据计算结果&#xff09;&#xff08;无缺失&#xff09; 1、时间&#xff1a;1992-2022年 2、来源&#xff1a;各省年鉴、国家统计局、统计公报、 3、指标&#xff1a;进出口总额&#xff08;万美元&#xff09…...

JVM之GC垃圾回收

GC垃圾回收 如何判断对象可以回收 引用计数法 如果有对象引用计数加一&#xff0c;没有对象引用&#xff0c;计数减一&#xff0c;如果计数为零&#xff0c;则回收 但是如果存在循环引用&#xff0c;即A对象引用B对象&#xff0c;B对象引用A对象&#xff0c;会造成内存泄漏 可…...

自然语言学习nlp 六

https://www.bilibili.com/video/BV1UG411p7zv?p118 Delta Tuning&#xff0c;尤其是在自然语言处理&#xff08;NLP&#xff09;和机器学习领域中&#xff0c;通常指的是对预训练模型进行微调的一种策略。这种策略不是直接更新整个预训练模型的权重&#xff0c;而是仅针对模型…...

fpga 需要掌握哪些基础知识?

个人根据自己的一些心得总结一下fpga 需要掌握的基础知识&#xff0c;希望对你有帮助。 1、数电&#xff08;必须掌握的基础&#xff09;&#xff0c;然后进阶学模电&#xff0c; 2、掌握HDL&#xff08;verilog或VHDL&#xff09;一般建议先学verilog&#xff0c;然后可以学…...

Qt未来市场洞察

跨平台开发&#xff1a;Qt作为一种跨平台的开发框架&#xff0c;具有良好的适应性和灵活性&#xff0c;未来将继续受到广泛应用。随着多设备和多平台应用的增加&#xff0c;Qt的前景在跨平台开发领域将更加广阔。 物联网应用&#xff1a;由于Qt对嵌入式系统和物联网应用的良好支…...

GPT-4模型中的token和Tokenization概念介绍

Token从字面意思上看是游戏代币&#xff0c;用在深度学习中的自然语言处理领域中时&#xff0c;代表着输入文字序列的“代币化”。那么海量语料中的文字序列&#xff0c;就可以转化为海量的代币&#xff0c;用来训练我们的模型。这样我们就能够理解“用于GPT-4训练的token数量大…...

宽字节注入漏洞原理以及修复方法

漏洞名称:宽字节注入 漏洞描述: 宽字节注入是相对于单字节注入而言的&#xff0c;该注入跟HTML页面编码无关&#xff0c;宽字节注入常见于mysql中&#xff0c;GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节&#xff0c;实际上只有两字节。宽字节带来的安全问…...

【Linux】SystemV IPC

进程间通信 一、SystemV 共享内存1. 共享内存原理2. 系统调用接口&#xff08;1&#xff09;创建共享内存&#xff08;2&#xff09;形成 key&#xff08;3&#xff09;测试接口&#xff08;4&#xff09;关联进程&#xff08;5&#xff09;取消关联&#xff08;6&#xff09;释…...

iview 页面中判断溢出才使用Tooltip组件

使用方法 <TextTooltip :content"contentValue"></TextTooltip> 给Tooltip再包装一下 <template><Tooltip transfer :content"content" :theme"theme" :disabled"!showTooltip" :max-width"300" :p…...

如何使用websocket

如何使用websocket 之前看到过一个面试题&#xff1a;吃饭点餐的小程序里&#xff0c;同一桌的用户点餐菜单如何做到的实时同步&#xff1f; 答案就是&#xff1a;使用websocket使数据变动时服务端实时推送消息给其他用户。 最近在我们自己的项目中我也遇到了类似问题&#xf…...

C++ 调用lua 脚本

需求&#xff1a; 使用Qt/C 调用 lua 脚本 扩展原有功能。 步骤&#xff1a; 1&#xff0c;工程中引入 头文件&#xff0c;库文件。lua二进制下载地址&#xff08;Lua Binaries&#xff09; 2&#xff0c; 调用脚本内函数。 这里调用lua 脚本中的process函数&#xff0c;并…...

Centos 内存和硬盘占用情况以及top作用

目录 只查看内存使用情况&#xff1a; 内存使用排序取前5个&#xff1a; 硬盘占用情况 定位占用空间最大目录 top查看cpu及内存使用信息 前言-与正文无关 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中&…...

【数据结构】堆(创建,调整,插入,删除,运用)

目录 堆的概念&#xff1a; 堆的性质&#xff1a; 堆的存储方式&#xff1a; 堆的创建 &#xff1a; 堆的调整&#xff1a; 向下调整&#xff1a; 向上调整&#xff1a; 堆的创建&#xff1a; 建堆的时间复杂度&#xff1a; 向下调整&#xff1a; 向上调整&#xff…...

v-if 和v-for的联合规则及示例

第073个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使用&#xff0c;computed&a…...

各互联网企业测绘资质调研

公司子公司产品产品介绍资质获得资质时间阿里巴巴高德高德地图作为阿里的全资子公司&#xff0c;中国领先的数字地图内容、导航和位置服务解决方案提供商&#xff0c;互联网地图行业龙头&#xff0c;2021年4月高德实现全月平均日活跃用户数超过1亿的重要里程碑&#xff0c;稳居…...

C++自定义函数详解

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 铁汁们新年好呀&#xff0c;今天我们来了解自定义函数。 文章目录 1.数学中的函数 2.什么是自定义函数 3.自定义函数如何使用&#xff1f; 4.值传递和引用传递&#xff08;形参和实参区分&#xff09; …...

flask+vue+python跨区通勤人员健康体检预约管理系统

跨区通勤人员健康管理系统设计的目的是为用户提供体检项目等功能。 与其它应用程序相比&#xff0c;跨区通勤人员健康的设计主要面向于跨区通勤人员&#xff0c;旨在为管理员和用户提供一个跨区通勤人员健康管理系统。用户可以通过系统及时查看体检预约等。 跨区通勤人员健康管…...

Spring Boot动态加载Jar包与动态配置技术探究

Spring Boot动态加载Jar包与动态配置技术探究 1. 引言 在当今快节奏的软件开发领域&#xff0c;高效的开发框架是保持竞争力的关键。Spring Boot作为一款快速开发框架&#xff0c;以其简化配置、内嵌Web服务器、强大的开发工具等特性&#xff0c;成为众多开发者的首选。其背后…...

Lua metatable metamethod

示例代码 《programming in lua》里有一个案例很详细&#xff0c;就是写一个集合类的table&#xff0c;其负责筛选出table中不重复的元素并组合成一个新的table。本人按照自己的方式默写了一次&#xff0c;结果发现大差不差&#xff0c;代码如下&#xff1a; Set {} --集合--…...

HCIA-HarmonyOS设备开发认证V2.0-3.2.轻量系统内核基础-任务管理

目录 一、任务管理1.1、任务状态1.2、任务基本概念1.3、任务管理使用说明1.4、任务开发流程1.5、任务管理接口 一、任务管理 从系统角度看&#xff0c;任务是竞争系统资源的最小运行单元。任务可以使用或等待CPU、使用内存空间等系统资源&#xff0c;并独立于其它任务运行。 O…...

中小型网络系统总体规划与设计方法

目录 1.基于网络的信息系统基本结构 2.网络需求调研与系统设计原则 3.网络用户调查 4.网络节点地理位置分布情况 5.网络需求详细分析 6.应用概要分析 7.网络工程设计总体目标与设计原则 8.网络结构与拓扑构型设计方法 9.核心层网络结构设计 10.接入核心路由器 11.汇聚…...

以管理员权限删除某文件夹

到开始菜单中找到—命令提示符—右击以管理员运行 使用&#xff1a;del /f /s /q “文件夹位置” 例&#xff1a;del /f /s /q "C:\Program Files (x86)\my_code\.git"...

JenkinsGitLab完成自动化构建部署

关于GitLab安装:GitLab安装-CSDN博客 Docker中安装GitLab:Docker下安装GitLab-CSDN博客 安装JenKins Jenkins官网:Jenkins 中文版:Jenkins 安装时候中文页面的war包下不来 在英文页面 记得装JDK8以上 JenKins使用java写的 运行JenKins需要JDK环境 我这里已经装好了 将下…...

JVM 性能调优 - 参数基础(2)

查看 JDK 版本 $ java -version java version "1.8.0_151" Java(TM) SE Runtime Environment (build 1.8.0_151-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode) 查看 Java 帮助文档 $ java -help 用法: java [-options] class [args...] …...

大型软件编程实例分享,诊所门诊处方笺管理系统多台电脑同时使用的软件教程

大型软件编程实例分享&#xff0c;诊所门诊处方笺管理系统多台电脑同时使用的软件教程 一、前言 以下教程以 佳易王诊所门诊电子处方管理系统V17.2 为例说明 软件资源可以点击最下方官网卡片了解详情 软件左侧为导航栏 1、系统参数设置&#xff1a;可以设置打印等参数 2、…...

Java基于微信小程序的医院挂号系统

文章目录 1 简介2 技术栈3 系统目标3.2 系统功能需求分析3.2.1 功能需求分析 4 系统模块设计4.1 数据库模块设计 5 系统的实现5.1 微信小程序个人中心5.2 科**室内容查看的实现**5.3 预约挂号的实现5.4 后台管理界面实现5.5 医生预约管理5.6 医生信息管理 参考文献7 推荐阅读8 …...

你是在独立思考,还是在被洗脑?

你有过这样的经历吗&#xff1f; 老板走过来&#xff0c;急匆匆丢给你一句&#xff1a;帮我整理一下那个客户的资料&#xff0c;下午给我。你抬头&#xff0c;应道「好好好」。老板扬长而去。你转念一想&#xff1a; 等等&#xff0c;哪个客户&#xff1f;什么资料&#xff1f;…...

在django中集成markdown文本框

首先需要下载开源组件&#xff1a;http://editor.md.ipandao.com/&#xff0c;可能需要挂梯子。 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1D9o3P8EQDqSqfhAw10kYkw 提取码&#xff1a;eric 1.在html代码中生成一个div&#xff0c;ideditor <div c…...

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Slider组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Slider组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Slider组件 滑动条组件&#xff0c;通常用于快速调节设置值&#xff0c;如音量调…...

django admin 自定义界面时丢失左侧导航 nav_sidebar

只显示了自定义模板的内容&#xff0c;左侧导航没有显示出来。 原因&#xff1a;context 漏掉了&#xff0c;要补上。 # 错误写法&#xff08;左侧导航不显示&#xff09;def changelist_view(self, request, extra_contextNone):form CsvImportForm()payload {"form&qu…...

JSP原理简述

JSP动态网页技术&#xff0c;可以定义html&#xff0c;css&#xff0c;js等静态内容&#xff0c;还可以定义java代码等动态内容。 注意导入坐标时&#xff0c;JSP的scope标签是provided&#xff0c;和servlet一样&#xff0c;否则会报错。 JSP本质上就是一个Servlet&#xff0c…...

C/C++ - 异常处理

目录 错误处理 异常处理 异常传播 异常规划 标准异常 自定异常 错误处理 在C语言中&#xff0c;错误通常通过函数的返回值来表示。 错误返回值 对于能返回特殊值&#xff08;如NULL或负值&#xff09;的函数&#xff0c;在调用时检查这些值来处理错误。 #include <st…...

十、项目开发总结报告(软件工程)

1&#xff0e;引言 1.1编写目的 1.2项目背景 1.3定义 1.4参考资料 2&#xff0e;开发结果 2.1产品 2.2主要功能及性能 2.3所用工时 2.4所用机时 2.5进度 2.6费用 3&#xff0e;评价 3.1生产率评价 3.2技术方案评价 3.3产品质量评价 4&#xff…...