目录:
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