概述
nginx接收来自后端服务器响应包括两个阶段,一个是接收来自后端服务器的http响应头部, 在上一篇文章已经详细分析了这个过程;另一个是接收来自后端服务器的http响应包体。 不论接收来自后端服务器的http响应头部还是http响应包体, 这都是一个接收过程。
以此同时nginx作为代理服务器,在接收到来自后端服务器的响应后,是需要把响应数据发给下游客户端的, 这是一个发送过程。发送http响应头部是使用过滤器模块来发送的,这块比较简单些。但发送http响应头部那就麻烦些了。 目前nginx实现了两种策略向下游客户端发送http响应包体,一种是下游网速优先,即下游客户端接收速度比nignx接收来自后端服务器的响应包体更快。也正是因为nginx接收后端服务器响应速度更慢些,在这种策略下,nginx开辟一个固定大小的缓冲区,一边接收后端服务器的响应包体,一边向下游客户端发送响应包体。因为nginx接收上游客户端的速度更慢些,因此开辟一个缓冲区就足够了。当缓冲区中的数据为空后,下游客户端会等待缓冲区中有数据后继续接收。当然了,如果在某种情况下上游网速更快些时,这是有可能的,导致这个缓冲区很快就满了,则nginx会停止接收来自后端服务器的响应包体,等待发送给下游客户端的http响应包体全部发送完成后在开始接收。下图是下游网速优先情况下nginx维护的数据结构,其中固定大小的接收缓冲区用于接收来自后端服务器的响应包体,而链表发送缓冲区指的是待发送给下游客户端的响应包体, nginx为了节约内存的考虑,不会拷贝内存缓冲区,因此每个链表节点中的数据都指向固定大小接收缓冲区中的某一个区域。
另一种发送策略是上游网速优先,即nginx接收后端服务器的响应包体要比下游客户端接收来自nginx的响应包体更快。在这种情况下,nginx需要开辟多个缓冲区,尽可能多的缓存来自后端服务器的响应。如果这多个缓冲区都满了,还会把来自后端服务器的响应包体写入临时文件。 当然了临时文件的大小也是有限制的,不可能无限大。当缓冲区与临时文件都满了时, nginx会停止接收来自后端服务器的响应,等待下游客户端接收完部分数据后(不需要接收完缓冲区或者临时文件中的全部数据), 缓冲区中又有剩余的空间了,此时可以继续接收来自后端服务器的响应包体。
本篇文章先分析下游网速优先策略下, nginx是如何接收来自后端服务器的响应包体,以及把来自后端服务器的响应包体发送给客户端。
一、接收响应包体前的初始化工作
nginx在开始接收来自后端服务器的响应包体前, 需要做些初始化的准备功能。例如设置读写事件的回调,以便事件触发时,该回调能够被调用;以及设置接收到响应包体后,是否需要做些过滤操作在发给客户端浏览器等;清空接收缓冲区以便留出足够的空间接收响应包体。
//边接收来自后端服务器的响应包体,一边向客户端发送响应包体
static void ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u)
{
//值为0表示下游网络优先, 使用固定大小的缓冲区缓存响应
if (!u->buffering)
{
//注册处理响应包体的回调。这个回调会对来自后端服务器的响应包体进行处理,将接收到的响应包体加入都输出队列中
//输出队列中的数据是要发给客户端的
if (u->input_filter == NULL)
{
u->input_filter_init = ngx_http_upstream_non_buffered_filter_init;
u->input_filter = ngx_http_upstream_non_buffered_filter;
u->input_filter_ctx = r;
}
//设置接收来自上游服务器响应包体的回调
u->read_event_handler = ngx_http_upstream_process_non_buffered_upstream;
//nginx向下游服务器发送来自上游服务器包体的回调
r->write_event_handler = ngx_http_upstream_process_non_buffered_downstream;
//对接收来自上游服务器响应包体进行初始化
if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR)
{
ngx_http_upstream_finalize_request(r, u, 0);
return;
}
//接收响应头部时,也接收到了响应包体, 则处理这部分接收到的响应包体,加入到输出队列中
if (n)
{
u->buffer.last = u->buffer.pos;
u->state->response_length += n;
//处理来自上游服务器响应的部分包体
if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR)
{
ngx_http_upstream_finalize_request(r, u, 0);
return;
}
//发送来自上游服务器响应的这部分包体给下游客户端
ngx_http_upstream_process_non_buffered_downstream(r);
}
else
{
//清空接收缓冲区,这个缓冲区用于接收来自后端服务器的响应包体
u->buffer.pos = u->buffer.start;
u->buffer.last = u->buffer.start;
//如果套接字可读,则立即开始接收来自上游服务器的响应包体
if (u->peer.connection->read->ready)
{
ngx_http_upstream_process_non_buffered_upstream(r, u);
}
}
}
}
在ngx_http_upstream_send_response函数内,会设置负载均衡模块的读事件回调为:
ngx_http_upstream_process_non_buffered_upstream, 用于接收来自后端服务器的响应包体,负载均衡模块的写事件回调为:
ngx_http_upstream_process_non_buffered_downstream,用于将来自后端服务器的响应包体发给客户端。以此同时会注册处理接收到的响应包体的方法为ngx_http_upstream_non_buffered_filter,用于对接收到的响应包体做些过滤操作,例如是否需要转换等,在这里只是将接收缓冲区中的数据插入到输出队列中。准备工作完成后,如果读事件已经就绪了,则立即开始接收来自后端服务器的响应包体了,否则等读事件触发时在开始接收。
二、接收与发送响应包体
不论是负载均衡模块的读回调ngx_http_upstream_process_non_buffered_upstream,还是写回调ngx_http_upstream_process_non_buffered_downstream函数。最终都会调用ngx_http_upstream_process_non_buffered_request,由这个函数来一边读取后端服务器的响应包体,一边把响应包体发给下游客户端。
//参数: do_write 0表示接受来自上游服务器的响应包体; 1表示nginx向下游客户端发送响应包体
static void ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r, ngx_uint_t do_write)
{
b = &u->buffer;
for ( ;; )
{
//发送来自上游服务器的响应包体给下游客户端
if (do_write)
{
//发送缓冲区out_bufs不为空,则发送来自上游服务器的响应包体给客户端。out_bufs保存的是每次接收到最新的响应包体。
//busy_bufs保存的是上一次还没有发送完的包体,那这部分数据什么时候发送呢?
//实际上在发送响应包体时,out_bufs中未发送的数据被保存到了r的out缓冲区中, 如果上一次没有发送完成,
//下一次会从r的out位置开始继续发送响应包体给客户端。因此不需要发送busy_bufs中的数据了,因为busy_bufs也是指向r的out缓冲区
if (u->out_bufs || u->busy_bufs)
{
rc = ngx_http_output_filter(r, u->out_bufs);
//更新缓冲区链表
ngx_chain_update_chains(&u->free_bufs, &u->busy_bufs, &u->out_bufs, u->output.tag);
}
//所有缓冲区的内容都已经发送给了下游客户端,则清空缓存
if (u->busy_bufs == NULL)
{
b->pos = b->start;
b->last = b->start;
}
}
//有缓冲区并且已经准备好读取来自上游服务的响应包体,
//如果缓冲空间不够,则退出循环,等待下一次epoll事件被调用。size为0表示接收缓冲区满了
if (size && upstream->read->ready)
{
//接收包体
n = upstream->recv(upstream, b->last, size);
//处理包体
if (n > 0)
{
u->input_filter(u->input_filter_ctx, n);
}
//标记置为1,准备下游客户端发送这部分的响应包体
do_write = 1;
continue;
}
break;
}
}
nginx使用一个固定大小的接收缓冲区一边接收来自后端服务器的响应包体,一边向下游客户端发送响应包体。nginx每收到一部分来自后端服务器的响应包体,都会将这部分数据插入到输出链表中(也就是最开始的那张图), 这个输出链表中的数据是最终需要发送给客户端的。nginx为了节省内存的考虑,每个输出链表节点中的数据并没有从接收缓冲区中拷贝而来, 而是使用指针指向接收缓冲区中相应位置。如果接收缓冲区满了,则nginx停止接收来自后端服务的响应, 并把缓冲区中的所有数据发送给客户端后,才会继续接收来自后端服务器的响应。
//功能: 将来自上游服务器想响应包体全部存放到内存中的out_bufs链表中
//参数: data 为ngx_http_request_t接收
// bytes 本次接收到这部分包体的大小
//返回值; 成功或失败
static ngx_int_t ngx_http_upstream_non_buffered_filter(void *data, ssize_t bytes)
{
//找到最后一个链表节点
for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next)
{
ll = &cl->next;
}
//创建一个新节点,将新节点插入到链表尾部
cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs);
*ll = cl;
b = &u->buffer;
//新链表节点内容指向刚刚接收到的这部分包体,并没有开辟缓冲区空间
cl->buf->pos = b->last;
b->last += bytes;
cl->buf->last = b->last;
}
需要注意的是,nginx使用ngx_http_output_filter过滤器发送响应包体给客户端浏览器,有可能一次发送是不能把所有的响应包体都发送给客户端的,此时过滤器模块内部会自动保存上次未发送完的数据到到ngx_http_request_t结构中的out成员中。下次写事件触发时继续把剩余的响应包体发送给客户端。这一点可以从最后一个过滤模块ngx_http_write_filter_module的包体过滤函数ngx_http_write_filter看出。当然了,nginx还会重新注册读写事件到epoll中,以便下次事件触发时继续处理。
ngx_int_t ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_chain_t * chain;
//调用ngx_linux_sendfile_chain发送数据,返回值是chain未发送完的数据
chain = c->send_chain(c, r->out, limit);//ngx_writev_chain
//清空已经发送的缓冲
for (cl = r->out; cl && cl != chain; )
{
ln = cl;
cl = cl->next;
ngx_free_chain(r->pool, ln);
}
//保存未发送完成的数据到ngx_http_request_t结构中的out成员中
r->out = chain;
}
考虑一个问题,为什么nginx需要维护一个u->out_bufs输出链表呢, 岂不是多此一举,为什么不直接把每次接收到的u->buffer中的部分数据发给客户端浏览器呢? 如果真要这么做也并非不可以,但会破坏分层思想。接收缓冲区是nginx与后端服务器之间维护的数据结构, 而输出链表则是nginx与下游客户端维护的数据结构,两种分开对待,不会耦合在一起,这很好体现了分层的设计思想。
到此为止,下线网速优先策略下,nginx是如何接收来自后端服务器的响应包体,以及如何发送来自后端服务器的响应包体给客户端已经分析完成了。下一篇文章将分析上游网速优先策略下的数据收发流程。
最后
以上就是灵巧冷风为你收集整理的nginx处理post请求(http响应包体收发之下游网速优先策略)的全部内容,希望文章能够帮你解决nginx处理post请求(http响应包体收发之下游网速优先策略)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复