Nginx基础知识. Nginx网络属性

来源:互联网 发布:淘宝修改店铺最低折扣 编辑:程序博客网 时间:2024/06/05 02:37
Nginx基础概念
先是对connection的概念介绍
    nginx中的connection就是对tcp连接的封装, 其中包括连接的socket, 读事件, 写事件. 所以, 利用此connection, 就可以与任何后端服务打交道.
    nginx是如何处理一个连接的呢? 
           首先, nginx在启动时, 解析配置文件, 得到需要监听的端口与ip地址, 然后在nginx的master进程里先初始化好这个监听的socket(即一些列的socket, bind, listen系统调用). 接着再fork出多个子进程, 这样, 多个子进程在监听端口有数据时就竞争accept连接(nginx解决惊群的方案是加锁accept). 在客户端与服务器的三次握手完成后, 某个子进程就能获得这个连接, 然后创建对这个连接的connection封装, 即 ngx_connection_t 结构体. 最后就是数据交换, 某一方结束连接

            与此同时, nginx也可以作为客户端, 请求其他server的数据(比如upstream模块), 此时, 与其他server的连接也封装在ngx_connection_t中. 作为客户端, nginx先获得一个ngx_connection_t结构体, 然后创建socket, 设置socket属性, 接着 connect/read/write来处理连接, 直到关闭socket.

    我们知道, worker_connectons是nginx配置文件中对每个进程最大连接数的限制. 我们也知道, linux中, 每个进程又有最大文件描述符的限制. 所以, 当worker_connectons值大于系统限制, nginx会有警告. nginx实现时, 是通过一个连接池来管理的, 每个worker进程都有一个独立的连接池, 该池大小即为worker_connectons, 这里的连接池里面只是一个worker_connections大小的一个ngx_connection_t结构的数组。并且,nginx会通过一个链表free_connections来保存所有的空闲ngx_connection_t,每次获取一个连接时,就从空闲连接链表中获取一个,用完后,再放回空闲连接链表里面。

    上面提到, nginx解决多个worker进程竞争accept的问题是使用锁, 这里看看其伪代码实现.
//ngx_accept_disabled可以控制此进程是否参与accept锁的竞争ngx_accept_disabled = ngx_cycle->connection_n / 8    - ngx_cycle->free_connection_n;                        //当已有的连接数的1/8都大于剩余的连接数时, 此进程一段时间不参与竞争if (ngx_accept_disabled > 0) {    ngx_accept_disabled--;                //逐渐减小, 一段时间后(当然, 可以看出单位并不是时间)就能再次参与accept锁竞争} else {    if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {        return;    }    if (ngx_accept_mutex_held) {        flags |= NGX_POST_EVENTS;    } else {        if (timer == NGX_TIMER_INFINITE                || timer > ngx_accept_mutex_delay)        {            timer = ngx_accept_mutex_delay;        }    }} 






接着, 另一个基础是request.
    这里指的是http请求. 对应的数据结构是ngx_http_request_t
    一个http请求, 往往包含请求行, 请求头, 请求体, 响应行, 响应头, 响应体

    应对http请求, 常常是一行一行的进行处理. 按照一般逻辑, 通常是在连接建立好后等待客户的发来的请求. 首先读取一行, 分析出请求行中包含的method, uri, http_version 这些信息. 然后一行行的读取处理请求头, 并根据请求method与请求头的信息来决定是否有请求体以及其长度. 接着读取请求体. 这样一来, 我们就可以根据请求来产生相应的输出, 生成响应行, 响应头, 响应体. nginx大体是按着流程走的, 但又些许不同. 比如当请求头读取完后, 就开始了请求的处理了. ngx_http_request_t 就是用来保存解析请求与输出响应相关数据的.

    对于nginx来说, 处理一个完整的请求过程大致是如下这样的. 
            一般请求从ngx_http_init_request开始
                    在此函数中, 首先网络读事件(读取客户请求)被设置为ngx_http_process_request_line.
                    可以从函数名看出, 此函数用于处理请求行
                    通过ngx_http_read_request_header读取请求数据
                    然后调用ngx_http_parse_request_line函数解析请求行
                    整个请求行解析到的参数,会保存到ngx_http_request_t结构当中
            解析完请求行后, 读事件的handler被设置为ngx_http_process_request_headers
                    调用ngx_http_read_request_header来读取请求头
                    调用ngx_http_parse_header_line来解析一行请求头
                    解析到的请求头会保存到ngx_http_request_t的域headers_in中,headers_in中的headers是一个链表结构,保存所有的请求头.HTTP中有些请求是需要特别处理的,这些请求头与请求处理函数存放在一个映射表里面,即ngx_http_headers_in,在初始化时,会生成一个hash表,当每解析到一个请求头后,就会先在这个hash表中查找,如果有找到,则调用相应的处理函数来处理这个请求头。比如:Host头的处理函数是ngx_http_process_host

            之后, 会解析到回车换行符, 表示请求头到此结束. 此时调用ngx_http_process_request来处理请求.
                    当前连接读写事件被设置为ngx_http_request_handler. 之所以拥有读和写的能力, 是因为在真正调用时会根据读事件还是写事件调用ngx_http_request_t中的read_event_handler或是write_event_handler
                    nginx读取完请求头后就开始处理, 而不是继续读取请求体, 于是设置read_event_handler为ngx_http_block_reading,即不读取数据了
                    然后再调用ngx_http_handler来真正开始处理一个完整的http请求
                    这个函数会设置write_event_handler为ngx_http_core_run_phases,并执行ngx_http_core_run_phases函数. 最终产生响应头放在headers_out中
                    最后会调用filter来过滤数据,对数据进行加工,如truncked传输、gzip压缩等




然后, 是HTTP的pipeline属性
    pipeline是基于长连接的,目的就是利用一个连接做多次请求
    如果客户端有多个请求, 对于keepalive来说, 第二个请求必须等到第一个请求的响应接收完后才能发送, 与TCP的停止等待协议类似
    对于pipeline来说, 客户端不必等到第一个请求处理完后,就可以马上发起第二个请求
    但是,nginx对pipeline中的多个请求的处理却不是并行的,依然是一个请求接一个请求的处理,只是在处理第一个请求的时候,客户端就可以发起第二个请求
    nginx在读取数据时,会将读取的数据放到一个buffer里面,所以,如果nginx在处理完前一个请求后,如果发现buffer里面还有数据,就认为剩下的数据是下一个请求的开始,然后就接下来处理下一个请求





最后是 lingering_close属性
    nginx在接收客户端的请求时,可能由于客户端或服务端出错了,要立即响应错误信息给客户端,而nginx在响应错误信息后,大分部情况下是需要关闭当前连接。nginx执行完write()系统调用把错误信息发送给客户端,write()系统调用返回成功并不表示数据已经发送到客户端,有可能还在tcp连接的write buffer里。接着如果直接执行close()系统调用关闭tcp连接,内核会首先检查tcp的read buffer里有没有客户端发送过来的数据留在内核态没有被用户态进程读取,如果有则发送给客户端RST报文来关闭tcp连接丢弃write buffer里的数据,如果没有则等待write buffer里的数据发送完毕,然后再经过正常的4次分手报文断开连接。所以,当在某些场景下出现tcp write buffer里的数据在write()系统调用之后到close()系统调用执行之前没有发送完毕,且tcp read buffer里面还有数据没有读,close()系统调用会导致客户端收到RST报文且不会拿到服务端发送过来的错误信息数据。

    要解决问题的话,目的是让服务器不发送RST到客户端. 对于全双工的TCP连接来说, 服务器在发送完错误信息后只要关掉写端就可以了, 读可以继续进行, 丢弃读到的数据就可以. 如此一来, 客户端再发送过来数据, 也不会受到RST. 只是最终我们还是需要关闭这个连接, 于是设置一个超时时间, 这段时间过后, 就关闭连接.不过大多客户端都会在超时时间内收到错误信息从而主动关闭连接.

    不过nginx是自己实现的lingering_close

    关于系统自带的linger属性, 下面文章中有介绍

    http://blog.csdn.net/u012062760/article/details/45173351
0 0