Nginx有多种多样的用途,可以作为HTTP服务器、反向代理服务器、邮件代理服务器、普通的TCP/UDP代理服务器。在生产中,我们一般将它作为访问的第一层负载均衡层来使用。这里简单记录下nginx的基本知识。(Nginx的文档在这里点我)
模块化
Nginx整体采用模块化的结构,类似的还有web服务器jetty,采用模块化额好处就是可以高度定制,可以选择自己需要的模块,也可以自己开发相应的模块以应付自己的特殊需求。我们可以将nginx的模块分为如下几大类:core(核心模块,包括权限管理、进程管理等),http(http模块,实现http功能),mail(邮件模块),第三方模块(常用的比如lua脚本支持模块等)
进程模型
Nginx高性能的原因之一在于它的进程模型。Nginx采用多进程模型处理请求,注意是多进程而非多线程。采用进程而非线程可以避免线程上下文切换,频繁创建销毁线程的开销,也由于每个进程在内存空间上相互独立,无需进行加锁操作。
Nginx中有两种进程,一个master进程和多个worker进程(一般设置成与CPU核心数相同),master进程是主进程,负责对worker进程进行监护,它不需要处理网络事件,不负责业务的执行,只会通过管理worker进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。每个worker进程都是从master进程fork过来,在master进程里面,先建立好需要listen的socket(listenfd)之后,然后再fork出多个worker进程。所有worker进程的listenfd会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有worker进程在注册listenfd读事件前抢accept_mutex(锁),抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该连接。当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后断开连接,这样就完成了一个完整的请求。我们可以看到,请求完全由worker进程来处理,而且只在一个worker进程中处理。
对于客户端的连接,nginx采用基于事件驱动异步非阻塞模式,这也是大部分高性能框架所采用的的连接模式(Netty等),底层采用epoll实现。关于常见的IO模型看这里UNIX中5种IO模型
几个问题
1.Nginx如何做到热部署?
我们在修改配置文件nginx.conf后,不需要重启Nginx就可以使配置生效。Nginx使用Master进程来重新生成新的worker进程这种方式来实现,新的请求会交给新的worker进程来执行,新的worker进程会以新的配置进行处理请求,至于老的worker进程,等把那些以前的请求处理完毕后,kill掉即可。
2.进程在处理连接时为什么需要加锁
master进程先建好需要listen的socket后,然后再fork出多个woker进程,这样每个work进程都可以去accept这个socket。当一个client连接到来时,所有accept的work进程都会受到通知,但只有一个进程可以accept成功,其它的则会accept失败。Nginx提供了一把共享锁accept_mutex来保证同一时刻只有一个work进程在accept连接,从而解决惊群问题。当一个worker进程accept这个连接后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就结束了。
Keepalived+Nginx实现高可用
利用cpu和进程的亲缘性将进程和特定cpu绑定,避免了进程上下文切换的开销,从而减少了cpu占用
CPU亲缘性:通过Linux提供的相关CPU亲缘性设置接口,显示的指定某个进程固定的某个处理器上运行。在多核运行的机器上,每个CPU本身自己会有缓存,缓存着进程使用的信息,而进程可能会被OS调度到其他CPU上,如此,CPU cache命中率就低了,当绑定CPU后,程序就会一直在指定的cpu跑,不会由操作系统调度到其他CPU上,性能有一定的提高。
惊群现象:master进程会事先创建好监听套接字,然后fork worker子进程时,会继承监听套接字,当listen socket可读时,所有进程都将被唤醒,都会去accept这个请求,最终只有一个进程会成功,其他则失败,这就是惊群现象。