[nginx] proxy和upstream模块

  • proxy模块负责nginx的http反向代理,默认为HTTP\1.0,会把Client发来的http headers和http body都传递给upstream
  • upstream模块负责与上游(后端)服务器通信,其中包括负载均衡的轮询策略、长连接队列

[nginx] proxy和upstream模块_第1张图片

一、配置文件

http{}配置内

# 上游(后端)服务
upstream http_backend {
    server 192.168.1.1:8080;
    server 192.168.1.2:8080;
    ...
    keepalive 16;
    check interval=5000 rise=1 fall=3 timeout=4000 default_down=false type=http;
    check_http_send "GET / HTTP/1.0\r\n\r\n";
}

server {
    listen 8080;
    location / {
        proxy_method GET;
        proxy_pass http://http_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}
  • 长连接配置:proxy默认是HTTP 1.0版本,需要手动设置1.1,并且由于proxy模块默认会把Header中的Connection填充close,所以也要手动覆盖一下,才能实现与upstream的长链接。keepalive表示长链接队列的大小,LRU淘汰策略;
  • 状态检查:检查上游服务器健康状态,是nginx第三方库nginx_upstream_check_module提供的功能,interval检查间隔(微秒ms),rise检查成功次数后才认为该服务健康,fall检查失败次数后才认为该服务异常,timeout响应超时,default_down设置初始后端服务状态(默认为失败状态),type访问后端服务的协议。

二、模块初始化

[nginx] proxy和upstream模块_第2张图片

  • upstream指令ngx_http_upstream(),向ngx_http_upstream_main_conf_t.upstreams数组中增加(有判重)一个ngx_http_upstream_srv_conf_t对象,并收录server配置
  • proxy_pass指令ngx_http_proxy_pass(),根据参数解析出host,通过ngx_http_upstream_add()找到相应的ngx_http_upstream_srv_conf_t对象并关联到plcf->upstream->upstream
  • 负载均衡器初始化ngx_http_upstream_keepalive_module模块先初始化,设置初始化upstream的函数回调,在ngx_http_upstream_init_main_conf()函数执行该回调,对每个upstream server产生peer对象,设置权重、超时、最大连接次数等,并设置了us->peer.init = ngx_http_upstream_init_round_robin_peer,在每次请求时会被调用
kcf->original_init_upstream = uscf->peer.init_upstream
                              ? uscf->peer.init_upstream
                              : ngx_http_upstream_init_round_robin;
uscf->peer.init_upstream = ngx_http_upstream_init_keepalive;
  • http headers初始化,proxy模块的ngx_http_proxy_merge_loc_conf最后会对反向迭代转发的headers进地初始化,主要是收集ngx_http_proxy_headers和headers_source(这个变量保存了proxy_set_header指令设置的变量),收集http头的长度和内容,包括GET /uri和配置文件,比如$proxy_host为请求的host

三、运行逻辑

处理请求时进入handler环节,由于在与proxy_pass绑定的ngx_http_proxy_pass()设置了clcf->handler = ngx_http_proxy_handler,所以在NGX_HTTP_FIND_CONFIG_PHASE阶段的ngx_http_update_location_config()最后会设置r->content_handler = clcf->handler,接着在NGX_HTTP_CONTENT_PHASE阶段会执行r->content_handler(r),正式进入proxy_handler处理部分

3.1、获取一个后端服务

调用链ngx_http_proxy_handler() ==> ngx_http_upstream_init() ==> ngx_http_upstream_init_request()

  • 调用u->create_request(),实际调用ngx_http_proxy_create_request(),生成请求的Header,先统计Header总长度,再填充buf
  • 调用uscf->peer.init(),实际调用ngx_http_upstream_init_keepalive_peer() ==> ngx_http_upstream_init_round_robin_peer(),代码中tried是位图,用来标识在一轮选择中,各个后端服务器是否已经被选择过。该过程中注册了即将调用的get和free方法,平滑的加权轮询策略,参考
    Nginx模块开发(十二)(续):upstream负载均衡

3.2、向后端发送请求

继续在ngx_http_upstream_init_request()函数选中了后端服务器,进入函数ngx_http_upstream_connect()

  • ngx_event_connect_peer()
    • 调用pc->get(),获取一个连接节点,实际调用ngx_http_upstream_get_keepalive_peer() ==> ngx_http_upstream_get_round_robin_peer()
    • ngx_get_connection(),从链接池中获取一个ngx_connection_t对象
    • 调用ngx_add_conn(),实际调用ngx_epoll_add_connection()
  • 设置write、read方法,因为是非阻塞套接字,所以为异步connect(),再由ngx_epoll_process_events()中epoll_wait触发write方法,调用链为ngx_http_upstream_handler() ==> ngx_http_upstream_send_request_handler() ==> ngx_http_upstream_send_request()
c->write->handler = ngx_http_upstream_handler;  // 写回调函数
c->read->handler = ngx_http_upstream_handler;   // 读回调

u->write_event_handler = ngx_http_upstream_send_request_handler;
u->read_event_handler = ngx_http_upstream_process_header;

展开ngx_http_upstream_send_request()

  • ngx_http_upstream_test_connect(),探测后端服务是否活着
  • ngx_output_chain(),因为在proxy的ngx_http_proxy_create_request()函数中已生成了待发送的数据,所以该步执行数据发送

参考:nginx中upstream的设计和实现(二)

3.3、接收后端数据发往前端

ngx_http_upstream_process_header()为处理后端服务的读函数,从upstream读取数据,并发送给client

  • 调用u->process_header(),实际调用ngx_http_proxy_process_status_line(),下列操作主要是将http header拷贝进u->headers_in,在下面函数中实现
    • ngx_http_parse_status_line(),解析response中第一行表示的状态:200 OK
    • ngx_http_proxy_process_header(),解析头填充r->upstream->headers_in.headers
  • ngx_http_upstream_process_headers()将r->upstream ->headers_in拷贝到r->headers_out,注意,这时还没有往client端发送数据。header拷贝过程中如果命中hide_headers_hash,该header将传递到headers_out里,再若某个header的copy_handler句柄设置了忽略该header,也会造成不传递到headers_out里,比如connection的设置copy_handler为ngx_http_upstream_ignore_header_line()
  • ngx_http_upstream_send_response(),发送数据client
    • ngx_http_send_header(),发送Header,进入ngx_http_top_header_filter的调用链,最后到达ngx_http_header_filter()
      • 计算headers的字符串长度,包括status、内置header(Server、Date、Content-Type、Content-Length、Last-Modified、Connection等)、r->headers_out收集的header
      • 申请buf,将status、内置header、r->headers_out拷贝到buf中
      • 调用ngx_http_write_filter()发送数据
    • ngx_http_upstream_process_upstream(),初始化pipe,记录upstream和downstream
      • ngx_event_pipe()
        • ngx_event_pipe_read_upstream(),接收数据:p->preread_bufs如果还有空间则先用该buf接收数据,然后若p->free_raw_bufs非空则使用该buf,否则ngx_create_temp_buf生成新buf,供数据接收使用
          • 调用p->upstream->recv_chain(),实际为调用ngx_readv_chain(),这是在ngx_event_connect_peer()函数中设置的upstream(这里的upstream为ngx_connection_t)
          • 调用p->input_filter(),实际为调用ngx_http_proxy_copy_filter(),对接收到的数据进行操作,该函数在ngx_http_proxy_handler()进行的挂载
        • 上个函数执行完会把do_write=1,再次循环时会进入ngx_event_pipe_write_to_downstream()分支,将数据发送给client
          • 调用p->output_filter(),实际为调用ngx_http_output_filter(),**,该函数在设置pipe时的ngx_http_upstream_send_response()进行的挂载
            • 调用ngx_http_top_body_filter(),实际为调用ngx_http_range_body_filter(),无实现操作,接着调用ngx_http_copy_filter(),执行ngx_output_chain(),调用ctx->output_filter(),实际为调用ngx_http_charset_body_filter(),经过一系列filter函数后,进入ngx_http_write_filter()函数
              • 调用c->send_chain(),实际调用ngx_linux_sendfile_chain()函数,发送chain buffer

接着分析ngx_http_write_filter()

  • 检查r->out的buf(上次未发送完),有没有flush(表示buf是否要立即发送),有没有recycled(表示buf是否要被回收循环使用),有没有last(表示buf响应的最后一部分内容)
  • 检查当前要发送的buf链表,添加到r->out后面,同时也检查flush、recycled、last
  • 当有上述标志时才执行接下来的数据发送,使数据能集中发送
  • 调用c->send_chain(),实际调用ngx_linux_sendfile_chain(),在接收连接的函数ngx_event_accept()里初始化ngx_connection_t时设置c->send_chain = ngx_send_chain,而宏定义了#define ngx_send_chain ngx_io.send_chainextern ngx_os_io_t ngx_io,其中ngx_os_io_t是ngx_linux_init.c文件中定义的结构体,封装了linux下高级的socket函数

你可能感兴趣的:(nginx)