nginx之实现概况

nginx是近年来兴起的web高性能服务器中的佼佼者,现在已经成长为了世界第二大web服务器,其在负载均衡、反向 代理等领域表现的尤为突出。我们组也需要nginx来做云平台的负载均衡,所以顺便学习了一下nginx,它的源码设计 到很多高级数据结构和网络编程方面的细节,读起来难度很大,所以这里把我觉得最梗概的部分总结一下。

why nginx?

在nginx流行之前,Apache早已成为了web服务器的不二选择,以至于今日它的地位仍无法撼动。nginx与Apache之间 的孰优孰劣也是众说纷纭,这就不得不说他们是两种不同架构思想的实践:

  1. 事件驱动模型,nginx是全异步事件驱动的,其特点是特别适合于io密集型的处理程序,它能够处理上百
  万级别的tcp连接,在这一点上Apache望尘莫及。

  2. 并行处理,Apache会为每个连接单独分配一个进程或者线程,这样会耗费大量内存空间,而且在Apache在做进程之间的切换的时候也会耗费大量的CPU资源,所以Apache不可能像nginx那样有那么高的性能。

nginx的优势不仅体现在高性能方面,同样其超高稳定性和平滑的升级方式也是它的突出优点,而且nginx的内存占用率 也比其他服务器小,这些都得益于nginx对内存的谨慎控制和数据结构的良好设计。

nginx架构如何?

nginx在启动后由一个master进程和若干个worker进程组成,master主进程和worker进程之间的通信主要是依靠信号, 这样master进程就能够监控工作进程的状态和控制其行为。

  • 为什么需要多个worker进程?

    最底层也是最关键的原因是为了利用多核的优势,我们都知道事件驱动程序一般都只有一个进程来处理所有的 事件,在多核时代显然利用不了多核的优势,所以多个worker进程来充分利用CPU资源,最好的办法就是一个CPU绑定 一个worker进程,这样的效率更高。

    多个work进程也可以负载均衡,提高网络的处理效率。

    • 如何处理阻塞方法?

      在事件编程中首先得保证主进程不能被阻塞或者长时间占用,这样其他的请求就会得不到相应,所以需要将 阻塞方法改为非阻塞的方法调用,比如在监听客户端请求的时候web服务器显然不能阻塞在accept(),这里需要使用 非阻塞的TCP监听,再者发送函数send()在成功发出数据之后才能返回,如果发送的数据量很大,这是决不允许的, 所以可以使用非阻塞的socket句柄,发送完成后触发send结果返回阶段。

    • 如何避免内存碎片?

      nginx并不是多进程或者线程架构,这也说明其不会在栈上进行每次连接的内存申请。但是在堆上malloc进行 内存申请就是对性能的一大杀伤,为了减少内存申请的次数,nginx设计了简单的内存池。针对每个连接connection 和请求http_request申请一块较大的内存,供本次连接和请求使用,这个大小默认是16KB(4个内存页),这样大大减 少了内存的动态申请次数,减少了内存碎片。

epoll为何高效?

nginx高效率的首要功臣是epoll,在linux的IO复用上如何没有epoll,而是原始的select,那么效率将会大打折扣。 select或者poll的工作方式是遍历整个描述符表,看有没有事件发生,如何连接巨大,这样的效率可想而知,而epoll 则处理得更为精细,流程如下:

  1. epoll_create系统调用

    其函数原型: int epoll_create(int size);

    当进程调用这个方法时,linux内核会创建一个eventpoll结构体,其有两个关键成员:

    struct eventpoll {
        struct rb_root rbr;  //红黑树根节点,这棵树中存储着所有添加到epoll中的事件
        struct list_head rdllist; //双向链表保存着通过epoll_wait返回给用户的、满足条件的事件
      }
    

    其返回一个句柄来标识这个epoll。

  2. epoll_ctl系统调用

    c函数原型: int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);

    此函数返回错误码,参数epfd是epoll句柄,op表示添加、修改或者删除感兴趣的事件,fd是待监测的连接套接字, event就是事件,其结构如下:

     struct epoll_event{
       __uint32_t events;//标记关心哪种事件,可读或者可写
       epoll_data_t data;//与具体的使用方式有关
     }
    
  3. epoll_wait系统调用

    c函数原型:int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);

    第一个参数epfd是epoll的描述符,events是已经分配好的epoll_event结构体数组,这个函数将会把发生的 事件返回到events中。返回值显示事件发生的个数。

如何处理http?

http模块异常复杂,所以只是简单地梳理一下,这里我最关心的是nginx是如何实现传输层TCP与应用层HTTP的衔接 的,因为所有的http数据包都要通过TCP转发出去。对于TCP网络事件,可以粗略地分为可读和可写事件,可写事件 在http请求处理将要结束时进行相对简单,可读事件可以细分为:

 1. 收到SYN包带来的新连接事件

 2. 收到FIN包带来的连接关闭事件

 3. 套接字缓冲区上真正收到TCP流

TCP连接的建立过程在http请求处理开始之前,如果连接建立成功,http也不会立即初始化HTTP的请求结构体,而是 在这个链接对应的套接字缓冲区上确实收到了用户发来的请求内容时才进行,这种设计体现了Nginx出于高性能的 考虑,不会进行无谓的内存消耗,ngx_http_request_t是标识http请求的最重要的结构体。

当http请求建立以后,将TCP字节流协议化为http最好的方式就是状态机了,然后http开始接受请求行、请求头部、 (如果是post请求还需接收请求包体)、处理http请求、发送http响应、结束http请求等。

由HTTP协议的无状态性可知,HTTP的请求生命周期就此结束,但是TCP连接是有状态的,一般情况下tcp的connection 会随着http连接的结束而终止,但是如果设置了keepalive后tcp连接会被保持,继续等待http请求的到来,当然 这个连接的过期时间很短,一般是几秒钟。这样在一条连接的周期内可以快速地传输多个web页组件,而不会绑定 服务器太久。



Previous     Next
zhing /
Published under (CC) BY-NC-SA in categories nginx  tagged with nginx