leevis.com
leevis.com copied to clipboard
nginx的反向代理
nginx框架通过upstream这个机制可以和上游服务器建立4层的连接。 http模块通过一个扩展的模块ngx_http_proxy_module.c提供了一个proxy_pass的命令支持7层的反向代理的功能。
下面就通过代码来看下nginx是如何实现的。
配置解析与初始化
upstream 模块
upstream backend {
server backend1.example.com weight=5;
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server unix:/tmp/backend3;
server backup1.example.com backup;
}
upstream实现在ngx_http_upstream_module中,通过定义两个配置指令来配置上游服务器地址。
upstream的模块也是从定义了ngx_module_t。ctx定义了3个函数,ngx_http_upstream_add_variables添加了变量,ngx_http_upstream_create_main_conf创建配置文件所需要的内存结构。ngx_http_upstream_init_main_conf初始化配置结果。通过upstream指令可以配置一组上游服务器,该指令在nginx框架由ngx_http_upstream函数处理,把upstream的srv挂到upstream模块main配置的数组上(uscfp = ngx_array_push(&umcf->upstreams);
)。在upstream block内通过server指令指定上游服务器的ip地址,如上述官方给的例子。在nginx内部,server指令通过ngx_http_upstream_server函数处理,该函数把具体的上游服务器挂到所属upstream的servers数组下(us = ngx_array_push(uscf->servers);
)。
解析完配置文件后,nginx框架会调用ngx_http_upstream_init_main_conf函数,初始化upstream模块的配置,该函数会遍历所有的upstream,调用该ups的初始化函数,如果该ups配置了初始化函数就调用配置的初始化函数,否则就调用系统默认的处理函数ngx_http_upstream_init_round_robin,待会儿会过来再看该函数。调用完初始化函数后,初始化hash的header。
再来看下ngx_http_upstream_init_round_robin函数。上述仅仅是把配置都到内存中,该函数才是组织上游服务器的重点。不同的初始化函数实现不一样,最终还是要填充nginx upstream机制提供的ngx_http_upstream_srv_conf_s结构体的peer变量。其类型为ngx_http_upstream_peer_t如下:
typedef struct {
ngx_http_upstream_init_pt init_upstream;
ngx_http_upstream_init_peer_pt init;
void *data;
} ngx_http_upstream_peer_t;
设置init回调为ngx_http_upstream_init_round_robin_peer,遍历ngx_http_upstream_srv_conf_s下servers数组(通过upstream下server指令配置的上游服务器地址),把上游服务器ip组织到ngx_http_upstream_rr_peers_s结构为首包含的链表中,链表节点类型为ngx_http_upstream_rr_peer_s。
upstream模块初始化完毕。
proxy 模块
该模块提供了proxy_pass指令,通过在location中配置,把请求发往一组upstream。配置实例:
location / {
proxy_pass http://backend;
}
proxy模块也是从定义了ngx_module_t结构开始,通过ctx结构的create函数指针创建内存配置结构,通过cmds数组的指令解析配置文件。对此应该很熟悉了,我就不做很细的介绍了,重点介绍proxy_pass的配置。但是有一点还是要提一下的,upstream有一个配置结构体ngx_http_upstream_conf_t,该结构体中的变量在upstream中会用到,该结构体的设置是通过proxy模块的指令设置的,具体实现是把该结构体嵌入到ngx_http_proxy_loc_conf_t中实现的,例如proxy_connect_timeout配置就是设置ngx_http_upstream_conf_t下的connect_timeout的。
proxy_pass指令是通过ngx_http_proxy_pass函数解析的。该模块通过设置ngx_http_core_loc_conf_t结构的handler回调独占了NGX_HTTP_CONTENT_PHASE阶段的处理,就是说在请求执行到11个阶段的content阶段时,只会调用ngx_http_proxy_handler函数处理,其他模块添加进来的handler不会被调用。这也是content阶段特有的潜入handler方式。 该指令支持一个参数URL,用于指定上游服务器,支持变量。先判断该url是否包含变量,如果包含变量则调用ngx_http_script_compile函数编译后返回(变量的实现请看nginx 的变量),其中获取变量的值的回调函数被设置到plcf->proxy_lengths和plcf->proxy_values数组中。不包含变量则通过调用ngx_http_upstream_add获取一个upstream配置块赋值给plcf->upstream.upstream,看上面配置解析与初始化这一节的upstream模块的umcf->upstreams。该函数遍历umcf->upstreams数组,通过比较host检索一组上游服务器配置。 总结一下,该指令设置了plcf->upstream.upstream 回调函数为ngx_http_proxy_handler。如果url有变量,设置了plcf->proxy_lengths和plcf->proxy_values。如果url没有变量设置了plcf->upstream.upstream和plcf->vars还有plcf->url。
请求处理
nginx解析完header后调用ngx_http_process_request处理请求,调用ngx_http_core_run_phases执行11个阶段,执行到content阶段会单独调用proxy模块设置的ngx_http_proxy_handler回调函数。 该函数首先会调用ngx_http_upstream_create函数创建一个ngx_http_upstream_t结构体赋给request的upstream指针。然后在request的ctx数组的proxy模块下标设置一个ngx_http_proxy_ctx_t结构体。设置upstream结构体的几个回调函数,如下:
u->create_request = ngx_http_proxy_create_request;
u->reinit_request = ngx_http_proxy_reinit_request;
u->process_header = ngx_http_proxy_process_status_line;
u->abort_request = ngx_http_proxy_abort_request;
u->finalize_request = ngx_http_proxy_finalize_request;
最后调用ngx_http_read_client_request_body,先读取body再调用ngx_http_upstream_init初始化上游请求。 看下ngx_http_read_client_request_body函数,为读取body分配ngx_http_request_body_t结构赋值给r->request_body。判断r->header_in这个buf中是否已经有读取回来的body内容,有读取回来的内容则调用ngx_http_request_body_filter函数,把body保存到r->request_body结构体。然后调用ngx_http_upstream_init初始化ups请求。这时已经读取完所有的请求包括body,所以要把可读事件的超时定时删除,再调用ngx_http_upstream_init_request初始化ups的request,如果有body,u->request_bufs指向请求body。调用u->create_request设置的回调函数ngx_http_proxy_create_request创建上游的请求内容存储到u->request_bufs中。u->upstream指向一组上游服务器的配置uscf,并调用uscf->peer.init设置的ngx_http_upstream_init_round_robin_peer函数,该函数设置了upstream结构体peer成员,peer成员类型为ngx_peer_connection_t,该成员用于nginx主动发起连接的结构体,和ngx_connection_t一样,而ngx_connection_t是作为被动接收连接的结构体。设置ngx_peer_connection_t的data指针指向上游服务器ip地址链表,get函数指针用来从这一组上游服务器从选一个上游,free函数用来释放一个上游连接。 设置好回调函数后,就可以调用ngx_http_upstream_connect向上游发起连接请求了,该函数会调用ngx_event_connect_peer向上游发起连接请求并把该事件添加到epoll中。如果返回失败调用ngx_http_upstream_next尝试下一个上游服务器。成功则设置连接的可读可写回调函数为ngx_http_upstream_handler,设置upstream可读可写事件回调分别为ngx_http_upstream_send_request_handler和ngx_http_upstream_process_header。 然后会调用ngx_http_upstream_send_request发送请求到上游服务器,该函数会调用ngx_http_upstream_send_request_body发送请求。发送完请求可写事件回调已经没用了,删除可写事件回调超时,设置可写回调函数为ngx_http_upstream_dummy_handler。添加可读事件的超时,如果可读事件准备好了,则调用ngx_http_upstream_process_header函数处理上游的resp,该函数实际上是调用了u->process_header(r)处理resp的。处理完毕后调用ngx_http_upstream_process_headers函数设置请求的headers_out。最后调用ngx_http_upstream_send_response把上游的resp发到下游。该函数ngx_http_upstream_send_response首先调用ngx_http_send_header把响应头发送到客户端,然后根据影响状态码确定是否需要升级协议,需要则调用ngx_http_upstream_upgrade函数,不需要则处理正常响应body,根据proxy_buffering和是否需要缓存分为不同的处理方式,开启了buffering则设置u->pipe,调用ngx_http_upstream_process_upstream接收上游响应,调用ngx_http_upstream_process_downstream把resp body发送到下游。 无论是接收上游响应还是发送响应到下游都是调用了ngx_event_pipe函数,该函数封装了ngx_event_pipe_read_upstream和ngx_event_pipe_write_to_downstream