[toc]
0.前言
从开始使用nginx到现在至少也有六年多的时间,一直停留在应用的层面上,这次抽了一个多星期的时间简单看了一下这两本书。本文不能说是对于nginx 的解析,在长期对nginx的模糊认识的情况下又有了更深的认识。包括对于master-worker的认识,对于nginx 使用epoll 事件模型的理解等。另外又看了一些应用层面的配置。
以下记录了几点简单的知识点,贴了一些链接都是在看书时产生的问题,搜索之后找到比较满意答案的链接。依旧是看得多记得少。长期更新。
1.Nginx应用层面问题
1.1 pid文件的作用
记录Master 进程id,通过cli 向master进程发送信号,操控运行中的nginx。
1.2 worker进程数的设置
worker的进程数设置为cpu核心数相等时,进程间切换的代价还是最小的。
1.3 非守护进程运行
daemon on | off;
注意docker 下 daemon 需要设置为off.否则无法启动成功.详见
1.4 带有@的location
不直接处理用户请求,用于处内部请求重定向
理解为其他location转发.
1.5 try_files的应用
不是一个问题,但是继续强调一下.
单页应用中(spa)路由跳转重定向到index.html
location / {
try_files $uri $uri/ /index.html;
}
2.Nginx如何处理HTTP 请求
https://www.kancloud.cn/digest/understandingnginx/202586
https://www.cnblogs.com/jimodetiantang/p/8952141.html
https://blog.csdn.net/xiajun07061225/article/details/9260535
2.1 请求处理
nginx使用一个多进程模型来对外提供服务,其中一个master进程,多个worker进程。master进程负责管理nginx本身和其他worker进程。
所有实际上的业务处理逻辑都在worker进程。worker进程中有一个函数,执行无限循环,不断处理收到的来自客户端的请求,并进行处理,直到整个nginx服务被停止。
worker进程中,ngx_worker_process_cycle()函数就是这个无限循环的处理函数。在这个函数中,一个请求的简单处理流程如下:
- 操作系统提供的机制(例如epoll, kqueue等)产生相关的事件。
- 接收和处理这些事件,如是接受到数据,则产生更高层的request对象。
- 处理request的header和body。
- 产生响应,并发送回客户端。
- 完成request的处理。
- 重新初始化定时器及其他事件。
2.2 处理流程
- 初始化HTTP Request(读取来自客户端的数据,生成HTTP Request对象,该对象含有该请求所有的信息)。
- 处理请求头。
- 处理请求体。
- 如果有的话,调用与此请求(URL或者Location)关联的handler。
- 依次调用各phase handler进行处理。
2.3 为何高效
epoll模型
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
- 链表记录监听句柄,可以监听更多的文件句柄。
- epoll_wait 近返回有事件发生的句柄,减少从系统态复制到用户态的数据。
为什么nginx可以采用异步非阻塞的方式来处理呢,或者异步非阻塞到底是怎么回事呢?我们先回到原点,看看一个请求的完整过程。首先,请求过来,要建立连接,然后再接收数据,接收数据后,再发送数据。具体到系统底层,就是读写事件,而当读写事件没有准备好时,必然不可操作,如果不用非阻塞的方式来调用,那就得阻塞调用了,事件没有准备好,那就只能等了,等事件准备好了,你再继续吧。阻塞调用会进入内核等待,cpu就会让出去给别人用了,对单线程的worker来说,显然不合适,当网络事件越多时,大家都在等待呢,cpu空闲下来没人用,cpu利用率自然上不去了,更别谈高并发了。好吧,你说加进程数,这跟apache的线程模型有什么区别,注意,别增加无谓的上下文切换。所以,在nginx里面,最忌讳阻塞的系统调用了。不要阻塞,那就非阻塞喽。非阻塞就是,事件没有准备好,马上返回EAGAIN,告诉你,事件还没准备好呢,你慌什么,过会再来吧。好吧,你过一会,再来检查一下事件,直到事件准备好了为止,在这期间,你就可以先去做其它事情,然后再来看看事件好了没。虽然不阻塞了,但你得不时地过来检查一下事件的状态,你可以做更多的事情了,但带来的开销也是不小的。所以,才会有了异步非阻塞的事件处理机制,具体到系统调用就是像select/poll/epoll/kqueue这样的系统调用。它们提供了一种机制,让你可以同时监控多个事件,调用他们是阻塞的,但可以设置超时时间,在超时时间之内,如果有事件准备好了,就返回。这种机制正好解决了我们上面的两个问题,拿epoll为例(在后面的例子中,我们多以epoll为例子,以代表这一类函数),当事件没准备好时,放到epoll里面,事件准备好了,我们就去读写,当读写返回EAGAIN时,我们将它再次加入到epoll里面。这样,只要有事件准备好了,我们就去处理它,只有当所有事件都没准备好时,才在epoll里面等着。这样,我们就可以并发处理大量的并发了,当然,这里的并发请求,是指未处理完的请求,线程只有一个,所以同时能处理的请求当然只有一个了,只是在请求间进行不断地切换而已,切换也是因为异步事件未准备好,而主动让出的。这里的切换是没有任何代价,你可以理解为循环处理多个准备好的事件,事实上就是这样的。与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换)。更多的并发数,只是会占用更多的内存而已。 我之前有对连接数进行过测试,在24G内存的机器上,处理的并发请求数达到过200万。现在的网络服务器基本都采用这种方式,这也是nginx性能高效的主要原因。
定时器实现
- 将未完成的连接处理放入超时管理中,超时未完成剔除,避免无效连接占用资源。
- 超时对象的组织形式采用红黑树,提供较高性能的删除和插入操作.