nginx高效原理及源码编译安装(nginx-1.6.2)

目录:

1、nginx为何如此高效

  1.1、进程模型(master-worker)

  1.2、事件处理模型(异步非阻塞的事件处理机制)

  1.3、支持sendfile,提升文件传输性能

  1.4、支持AIO

  1.5、支持mmap

  1.6、小结:

2、源码编译安装nginx

1、nginx为何如此高效

    nginx特点:nginx是一个高度模块化,基于事件驱动,异步,单线程,非阻塞架构的软件。nginx为何如此高效?为何在相同的硬件资源下能承受比http大得多并发连接?现总结以下几点,本人技术有限,错误再所难免,若有错误请指正。

1.1、进程模型(master-worker,单线程模型)

    nginx对http请求的处理方式和apache对请求的处理方式截然不同,nginx采用单线程、异步非阻塞的模型,nginx启用后,会有一个master进程和多个worker进程,master进程的主要功能是用来管理worker进程,包括接收外界的信息,向worker进程发送信号,监管worker进程的运行状态等,而worker进程则是真实用来处理网络事件的,多个worker进程间是相互对等的,对客户端的响应是拥有相同的响应等级,各个worker竞争对请求的响应,各个进程之间相互独立,互不影响,一个进程有异常退出后不会影响其他的进程运行,服务不会因此而中断。

    nginx在提供服务时,其内部发了什么呢?当提供一个80端口的http服务时,一个连接请求过来,每个worker进程都对这个请求拥有平等的处理权限,那是怎么挑选出一个worker进程来响应的呢?首先在master进程里向内核注册需要listen的socket(listenfd),再fork出各个worker进程,worker进程继承了master的所有属性(当然也包括已建立好的socket,注意这不是同一个socket,只是每个进程的这个socket会监听在同一个ip地址与端口,这个在网络协议里面是允许的),各个worker进程在注册listenfd读事件前抢accept_mutex(可以理解成加在accept上的一个互斥锁,只有得到这把锁后,worker进程注册listenfd读事件,在读事件里调用accept),抢到互斥锁后,worker就开始读取请求,解析请求,处理请求,得到数据后,再返回给客户端,最后才断开连接,这样一个完整的请求结束。可见,一个请求完全由worker进程来处理,且只是在一个worker进程里完成。

    nginx采用这种master与多个worker的进程模型的优点:

    1、节省锁带来的开销。每个worker进程相互独立,不需要加锁,所以省掉了锁带来的开销,同时在编程以及问题排查上也会方便很多;

    2、独立进程工作,减少风险。worker进程间互相不会影响,一个进程退出后,其他进程还在工作,服务不会中断,master进程则很快重新启动新的worker进程。若因程序bug导致worker异常退出,会导致worker上的所有请求失败,但不会影响到所有的请求,所以降低了风险。

    master进程的主要工作:

   1、读取和校验配置文件

    2、创建、绑定、关闭套接字

    3、启动、终止、维护所配置数目的worker进程

    4、不中断服务刷新配置文件

    5、重新打开日志文件

    6、编译嵌入Perl脚本

    worker进程主要完成的任务包括:

    1、接收、传入并处理来自客户端的连接

    2、提供反向代理及过虑功能

    3、nginx任何能完成的其它任务

1.2、事件处理模型(异步非阻塞的事件处理机制) 

    在进程模型中我们知道,nginx是一个master和多个worker进程模型,一个worker只有一个主线程,有多少个worker进程不就是只能处理多少个并发吗?这何来高并发呢?那增加worker进程不就可以增加并发了,如果真是这样做了,那不是像apache一样了(每个请求独占一个工作线程,当并发上千时,同时也有上千的工作线程处理请求,这对操作系统来说是一个挑战,对内存的占用非常大,线程的上下文切换带来的cpu开销也很大,自然性能就上不去,而这些开销完全是没有意义的)。nginx没有采用apache一样的多线程模式处理事件,而是采用了异步非阻塞的方式来处理请求。

    何为异步非阻塞?一个完整的请求过程是这样的,客户端的请求过来,要建立连接,然后接收数据,再处理请求(生成请求数据),再回送数据。从系统底层来看,就是读写事件,当读写事件没有准备好时,如果不是非阻塞的方式来调用,那就是阻塞方式来调用,这样,事件没有准备好,那只有等待,等事件准备妥当,再继续。阻塞调用会进入内核等待,cpu会让出给其他进程用,这对单线程的worker来说,阻塞调用显然不合适,当网络事件越多,大家都被阻塞,等待事件的完成,大量cpu资源空闲下来,cpu利用率上不去,高并发是不能实现的。那非阻塞调用呢?当请求过来,读写事件没有完成,但马上给你返回一个信号,告诉你,你请求的事件还没有准备好,别着急,你过一会再过来看看。这样你就过一会儿来看看事件的状态(忙等模式),直到事件准备好了为止,在这期间,你就可以去做其它的事情,然后抽个时间来看看事件准备好没有,这种调用方式虽然没有阻塞,但你得不时地回来检查一下事件的状态,这样你可以做更多的事,但带来的开销也是挺大的,这种这是同步非阻塞的调用方式。为了节省开销,提高性能,所以有了异步非阻塞的调用机制,具体到系统调用就是像select/poll/epoll/kqueue这样的系统调用。这种机制允许你可以同时监控多个事件,拿epoll来举例,如果事件没有准备好,那就放到epoll里面,事件准备好了,我们就去读写,当读写返回EAGAIN时,我们再次将它加入到epoll里面。这样,只要有事件准备好了,我们就去处理它,当所有的事件都没有准备好时,就在epoll里面等着,通过这种方式我们就可以并发处理大量的并发请求了,注意这里所说的并发请求是指未处理完的请求,然尔,线程只有一个,所以同时能处理的请求也只有一个,只是在请求间不断地切换而已,切换也是因为异步事件未准备好而主动让出的。这里的切换是没有任何代价的,你可以理解为循环处理多个准备好的事件。与多线程相比,这种事件处理方式有很大的优势,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量。并发数再多也不会导致无谓的资源浪费(因为是单线程,没有上下文切换),只是会占用更多的内存而已。有人对连接数进行过测试,在24G内存的服务器上,处理的并发请求数达到过200万。

1.3、支持sendfile,提升文件传输性能

    web服务器从存储取得一个资源来作为请求端的响应,这个过程一般是这样的:

    1、调用read函数,把数据从存储设备copy到内核空间;

    2、把数据从内核空间copy到用户空间;

    3、调用write函数,把数据从用户空间copy到内核中与socket相关的缓冲空间;

    4、从socket缓冲地区copy数据到相关的协议栈引擎;

    5、最后把数据封装成数据帧发向网卡设备。

    而sendfile系统调用是这样来传输数据的:

    1、sendfile系统调用把数据从存储设备copy到内核空间;

    2、从内核空间直接把数据copy到socket相关的缓冲空间;

    3、从socket缓冲区copy数据到相关的协议栈引擎;

    4、最后把数据封装成数据帧发向网卡设备。

    sendfile系统调用是在2.1内核才引用的,这种机制省去了数据从内核空间copy到用户空间的过程,文件的传输性能得到了提高,而在2.4的内核中,又对这种机制进行了进一步的优化,在把数据从内核空间copy到socket相关的缓冲区这一操作中在2.4内核中不再是copy数据本身,而是把数据在内核空间的地址及数据长度发送到socket相关缓冲区中,再由DMA(Direct Memory Access直接内存访问)模块让相关的协议引擎直接访问数据,这样又减少了一次数据的copy。

1.4、支持AIO

    AIO是指异步时间非阻塞IO,用户向内核发起IO请求后,不用等待IO事件的真正发生,而是直接返回给IO请求后,断续做其他的事情,等IO操作完成后,内核通过回调函数或信号机制通知用户进程它之前的IO请求已完成,这能提高系统的吞吐量。

1.5、支持mmap

    mmap(Memory Map,内存映射)的最终目的是将设备或文件映射到用户进程的虚拟地址空间,实现用户进程对文件的直接读写,避免read()、write()的系统调用,这样减少了数据在内核空间与用户空间的数据copy及相应的上下文切换,降低了因磁盘IO发生的开销,提升了web服务的响应速度。当有数据需要从内核空间复制数据到用户空间时,采用mmap这种机制,特别是在高并发的环境下,对性能是有提升的。

1.6、小结:

    基于单个master与多个worker进程的进程模型(且是单线程工作,一个worker只有一个主线程)、基于事件驱动的事件处理机制、支持linux内核的sendfile、AIO、mmap等特性的nginx必将是一个高效可靠的服务。


2、源码编译安装nginx

2.1、系统环境

[root@zhaochj software]# pwd
/root/software
[root@zhaochj software]# cat /etc/issue
CentOS release 6.4 (Final)
Kernel \r on an \m

[root@zhaochj software]# uname -r
2.6.32-358.el6.x86_64

2.2、处理依赖关系及建立运行nginx的用户

[root@zhaochj software]# yum -y install pcre-devel
[root@zhaochj software]# useradd -r -s /sbin/nologin -M nginx

2.3、编译、配置、安装

软件包这里获取:nginx-1.6.2.tar.gz

[root@zhaochj nginx]# pwd
/root/software/nginx
[root@zhaochj nginx]# ls
nginx-1.6.2.tar.gz
[root@zhaochj nginx]# tar xf nginx-1.6.2.tar.gz
[root@zhaochj nginx]# cd nginx-1.6.2
[root@zhaochj nginx-1.6.2]# ./configure \
--prefix=/opt/lemp/nginx16 \
--sbin-path=/opt/lemp/nginx16/sbin/nginx \
--conf-path=/etc/nginx16/nginx.conf \
--error-log-path=/var/log/nginx16/error.log  \
--http-log-path=/var/log/nginx16/access.log \
--pid-path=/var/run/nginx16.pid \
--lock-path=/var/lock/subsys/nginx16 \
--user=nginx \
--group=nginx \
--with-file-aio \
--with-http_ssl_module \
--with-http_flv_module \
--with-http_mp4_module \
--with-http_gzip_static_module \
--with-http_stub_status_module \
--http-client-body-temp-path=/var/tmp/nginx16/client \
--http-proxy-temp-path=/var/tmp/nginx16/proxy \
--http-fastcgi-temp-path=/var/tmp/nginx16/fastcgi \
--http-uwsgi-temp-path=/var/tmp/nginx16/uwsgi \
--http-scgi-temp-path=/var/tmp/nginx16/scgi \
--with-pcre

[root@zhaochj nginx-1.6.2]# make && make install

2.4、为nginx提供启动脚本

[root@zhaochj ~]# vim /etc/rc.d/init.d/nginx16
#!/bin/bash
##
#nginx - this script starts and stops the nginx daemon
#
# chkconfig:   - 85 15 
# description:  Nginx is an HTTP(S) server, HTTP(S) reverse \
#               proxy and IMAP/POP3 proxy server
# processname: nginx
# config:      /etc/nginx16/nginx.conf
# pidfile:     /var/run/nginx16.pid

# Source function library.
. /etc/rc.d/init.d/functions

# Source networking configuration.
. /etc/sysconfig/network

# Check that networking is up.
[ "$NETWORKING" = "no" ] && exit 0

nginx="/opt/lemp/nginx16/sbin/nginx"
prog=$(basename $nginx)
nginx_config_file="/etc/nginx16/nginx.conf"
lockfile=/var/lock/subsys/nginx16

make_dirs() {
   # make required directories
   user=`$nginx -V 2>&1 | grep "configure arguments:" | sed 's/[^*]*--user=\([^ ]*\).*/\1/g' -`
   options=`$nginx -V 2>&1 | grep 'configure arguments:'`
   for opt in $options; do
       if [ `echo $opt | grep '.*-temp-path'` ]; then
           value=`echo $opt | cut -d "=" -f 2`
           if [ ! -d "$value" ]; then
               # echo "creating" $value
               mkdir -p $value && chown -R $user $value
           fi
       fi
   done
}

start() {
    [ -x $nginx ] || exit 5
    [ -f $nginx_config_file ] || exit 6
    make_dirs
    echo -n $"Starting $prog: "
    daemon $nginx -c $nginx_config_file
    retval=$?
    echo
    [ $retval -eq 0 ] && touch $lockfile
    return $retval
}

stop() {
    echo -n $"Stopping $prog: "
    killproc $prog -QUIT
    retval=$?
    echo
    [ $retval -eq 0 ] && rm -f $lockfile
    return $retval
}

restart() {
    configtest || return $?
    stop
    sleep 1
    start
}

reload() {
    configtest || return $?
    echo -n $"Reloading $prog: "
    killproc $nginx -HUP
    RETVAL=$?
    echo
}

force_reload() {
    restart
}

configtest() {
  $nginx -t -c $nginx_config_file
}

rh_status() {
    status $prog
}

rh_status_q() {
    rh_status >/dev/null 2>&1
}
case "$1" in
    start)
        rh_status_q && exit 0
        $1
        ;;
    stop)
        rh_status_q || exit 0
        $1
        ;;
    restart|configtest)
        $1
        ;;
    reload)
        rh_status_q || exit 7
        $1
        ;;
    force-reload)
        force_reload
        ;;
    status)
        rh_status
        ;;
    condrestart|try-restart)
        rh_status_q || exit 0
            ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}"
        exit 2
esac

[root@zhaochj ~]# chmod +x /etc/rc.d/init.d/nginx16 
[root@zhaochj ~]# service nginx16 start
[root@zhaochj ~]# chkconfig --add nginx16
[root@zhaochj ~]# chkconfig nginx16 on
[root@zhaochj ~]# ps aux | grep nginx

你可能感兴趣的:(nginx,高并发,高效原理)