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功能的切片功能是按照切片对齐的方式向上游服务器发送请求的。
相关文章:

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应用开发相关内容分享,包括不限于AI算法部署实施细节、AI应用后端分析服务相关概念及开发技巧、AI应用后端应用服务相关概念及开发技巧、AI应用前端实现路径及开发技巧 适用于具备一定算法及Python使用基础的人群 AI应用开发流程概…...
2024年Java架构篇之设计模式
2024年Java实战面试题_java 5 年 面试-CSDN博客 1、单例模式...

搭建macOS开发环境-1:准备工作
请记住: 最重要的准备工作永远是:备份数据 !!! 通过图形界面检查 Mac 的 CPU 类型: 在搭载 Apple 芯片的 Mac 电脑上,“关于本机”会显示一个标有“芯片”的项目并跟有相应芯片的名称: 通过命令行检查Mac的CPU类型 …...
【Makefile语法 02】Makefile语法基础
目录 一、Makefile概述 二、Makefile变量 三、Makefile符号 一、Makefile格式 1. 基本格式: targets : prerequisties [tab键]command target:目标文件,可以是 OjectFile,也可以是执行文件,还可以是一个标签&…...

如何写一个其他人可以使用的GitHub Action
前言 在GitHub中,你肯定会使用GitHub Actions自动部署一个项目到GitHub Page上,在这个过程中总要使用workflows工作流,并在其中使用action,在这个使用的过程中,总会好奇怎么去写一个action呢,所以ÿ…...

排序算法的时间复杂度存在下界问题
对于几种常用的排序算法,无论是归并排序、快速排序、以及更加常见的冒泡排序等,这些排序算法的时间复杂度都是大于等于O(n*lg(n))的,而这些排序算法存在一个共同的行为,那就是这些算法在对元素进行排序的时候,都会进行…...
详解洛谷P2016 战略游戏/BZOJ0495. 树的最小点覆盖之战略游戏(贪心/树形DP)
Description Bob喜欢玩电脑游戏,特别是战略游戏。但是他经常无法找到快速玩过游戏的办法。现在他有个问题。 他要建立一个古城堡,城堡中的路形成一棵树。他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能了望到所有的路。 注意&…...
解决The Tomcat connector configured to listen on port 8080 failed to start
问题 启动javar报错,提示如下 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实战
文章目录 深度学习自然语言处理(NLP)模型BERT:从理论到Pytorch实战一、引言传统NLP技术概览规则和模式匹配基于统计的方法词嵌入和分布式表示循环神经网络(RNN)与长短时记忆网络(LSTM)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语⾔是结构化的程序设计语⾔,这⾥的结构指的是…...

C#用Array类的FindAll方法和List<T>类的Add方法按关键词在数组中检索元素并输出
目录 一、使用的方法 1. Array.FindAll(T[], Predicate) 方法 (1)定义 (2)示例 2.List类的常用方法 (1)List.Add(T) 方法 (2)List.RemoveAt(Int32) 方法 (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官网:https://nodejs.org/en/ 下载之后按默认选项安装好 重启电脑即可自动完成配置 2.安装React 国内使用 npm 速度很慢,可以使用淘宝定制的 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步进电机系列教程】
↑↑↑点击上方【目录】,查看本系列全部文章 摘要:前面章节主要介绍了bresenham直线插值运动,本节内容介绍让两个电机完成连续的直线运动,目标是画一个正五角星 一、五角星图介绍 五角星总共10条直线,10个顶点。设定左下角为原点…...

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

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

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

自然语言学习nlp 六
https://www.bilibili.com/video/BV1UG411p7zv?p118 Delta Tuning,尤其是在自然语言处理(NLP)和机器学习领域中,通常指的是对预训练模型进行微调的一种策略。这种策略不是直接更新整个预训练模型的权重,而是仅针对模型…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

FFmpeg avformat_open_input函数分析
函数内部的总体流程如下: avformat_open_input 精简后的代码如下: int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...

图解JavaScript原型:原型链及其分析 | JavaScript图解
忽略该图的细节(如内存地址值没有用二进制) 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么:保存在堆中一块区域,同时在栈中有一块区域保存其在堆中的地址(也就是我们通常说的该变量指向谁&…...

Centos 7 服务器部署多网站
一、准备工作 安装 Apache bash sudo yum install httpd -y sudo systemctl start httpd sudo systemctl enable httpd创建网站目录 假设部署 2 个网站,目录结构如下: bash sudo mkdir -p /var/www/site1/html sudo mkdir -p /var/www/site2/html添加测试…...

Java多线程从入门到精通
一、基础概念 1.1 进程与线程 进程是指运行中的程序。 比如我们使用浏览器,需要启动这个程序,操作系统会给这个程序分配一定的资源(占用内存资源)。 线程是CPU调度的基本单位,每个线程执行的都是某一个进程的代码的某…...

Modbus转ETHERNET IP网关:快速冷却系统的智能化升级密钥
现代工业自动化系统中,无锡耐特森Modbus转Ethernet IP网关MCN-EN3001扮演着至关重要的角色。通过这一技术,传统的串行通讯协议Modbus得以在更高速、更稳定的以太网环境中运行,为快速冷却系统等关键设施的自动化控制提供了强有力的支撑。快速冷…...

在 Vue 的template中使用 Pug 的完整教程
在 Vue 的template中使用 Pug 的完整教程 引言 什么是 Pug? Pug(原名 Jade)是一种高效的网页模板引擎,通过缩进式语法和简洁的写法减少 HTML 的冗长代码。Pug 省略了尖括号和闭合标签,使用缩进定义结构,…...