文章目录
3.2 nginx架构
架构基础
3.2.1 nginx进程结构
任何Unix应用程序的基本基础都是线程或进程。
NGINX Worker流程内部
每个NGINX工作进程都使用NGINX配置初始化,并且由主进程提供一组侦听套接字。
NGINX工作进程始于等待侦听套接字上的事件(accept_mutex和内核套接字分片)。事件由新的传入连接启动。这些连接被分配给状态机 -HTTP状态机是最常用的,但是NGINX还为流(原始TCP)流量和许多邮件协议(SMTP,IMAP和POP3)实现状态机。
状态机本质上是一组指令,用于告诉NGINX如何处理请求。大多数执行与NGINX相同功能的Web服务器都使用类似的状态机-区别在于实现。
1.单进程结构
用于开发,调试环境
2.多进程结构
3.参考链接
https://www.nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale/
3.2.2 epoll模型
Linux中IO复用工具:在Linux中先后出现了select、poll、epoll等,FreeBSD的kqueue也是非常优秀的IO复用工具,kqueue的原理和epoll很类似
参考知乎文章:https://www.zhihu.com/question/20122137/answer/14049112
流:一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象。
阻塞:比如某个时候你在等快递,但是你不知道快递什么时候过来,而且你没有别的事可以干(或者说接下来的事要等快递来了才能做);那么你可以去睡觉了,因为你知道快递把货送来时一定会给你打个电话(假定一定能叫醒你)。
非阻塞忙轮询:接着上面等快递的例子,如果用忙轮询的方法,那么你需要知道快递员的手机号,然后每分钟给他挂个电话:“你到了没?”
为了避免CPU空转,可以引进了一个代理(一开始有一位叫做select的代理,后来又有一位叫做poll的代理,不过两者的本质是一样的)。
这个代理比较厉害,可以同时观察许多流的I/O事件,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中醒来,于是我们的程序就会轮询一遍所有的流(于是我们可以把“忙”字去掉了)。
于是,如果没有I/O事件产生,我们的程序就会阻塞在select处。
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll只会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的。(复杂度降低到了O(k),k为产生I/O事件的流的个数,也有认为O(1)的)
注:单个select最多只能同时处理1024个socket。
select的做法:
步骤1的解法:select创建3个文件描述符集,并将这些文件描述符拷贝到内核中,这里限制了文件句柄的最大的数量为1024(注意是全部传入—第一次拷贝);
步骤2的解法:内核针对读缓冲区和写缓冲区来判断是否可读可写,这个动作和select无关;
步骤3的解法:内核在检测到文件句柄可读/可写时就产生中断通知监控者select,select被内核触发之后,就返回可读可写的文件句柄的总数;
步骤4的解法:select会将之前传递给内核的文件句柄再次从内核传到用户态(第2次拷贝),select返回给用户态的只是可读可写的文件句柄总数,再使用FD_ISSET宏函数来检测哪些文件I/O可读可写(遍历);
步骤5的解法:select对于事件的监控是建立在内核的修改之上的,也就是说经过一次监控之后,内核会修改位,因此再次监控时需要再次从用户态向内核态进行拷贝(第N次拷贝)
epoll的做法:
步骤1的解法:首先执行epoll_create在内核专属于epoll的高速cache区,并在该缓冲区建立红黑树和就绪链表,用户态传入的文件句柄将被放到红黑树中(第一次拷贝)。
步骤2的解法:内核针对读缓冲区和写缓冲区来判断是否可读可写,这个动作与epoll无关;
步骤3的解法:epoll_ctl执行add动作时除了将文件句柄放到红黑树上之外,还向内核注册了该文件句柄的回调函数,内核在检测到某句柄可读可写时则调用该回调函数,回调函数将文件句柄放到就绪链表。
步骤4的解法:epoll_wait只监控就绪链表就可以,如果就绪链表有文件句柄,则表示该文件句柄可读可写,并返回到用户态(少量的拷贝);
步骤5的解法:由于内核不修改文件句柄的位,因此只需要在第一次传入就可以重复监控,直到使用epoll_ctl删除,否则不需要重新传入,因此无多次拷贝。
简单说:epoll是继承了select/poll的I/O复用的思想,并在二者的基础上从监控IO流、查找I/O事件等角度来提高效率,具体地说就是内核句柄列表、红黑树、就绪list链表来实现的。
epoll高效的原因:
这是由于我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个list链表,用于存储准备就绪的事件.
当epoll_wait调用时,仅仅观察这个list链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。
五种网络IO模型和select/epoll对比:https://zhuanlan.zhihu.com/p/38277885
3.3 nginx模块
3.3.1 动态模块
nginx在编译时可以将模块编程成动态模块,需要使用模块时被可执行文件加载
./configure --XXX=dynamic
3.3.2 模块本质
指令
内置变量
3.3.3 模块指令
值指令:可以被局部的值指令覆盖
动作指令:一旦满足某条件直接执行
3.3.4 请求处理的11个阶段
1.nginx模块简介
- 核心功能(Core Functionality)
参考网址:http://nginx.org/en/docs/ngx_core_module.html
功能:主要负责nginx的main区块和event区块,定义了nginx的全局参数
- http标准功能模块集合
参考网址:http://nginx.org/en/docs/
模块列表
2.请求处理流程示意图
3.请求处理的11个阶段
1.POST_READ
读取请求头后
realip
REALIP_MODULE模块:https://my.oschina.net/xxj123go/blog/907041/print
1.1 模块功能
在复杂集群环境中remote_addr 为反向代理的地址,而不是真实客户端ip,realip用于获取真正客户端地址
1.2 模块指令
set_real_ip_from 定义可信任的地址
real_ip_header 定义使用那个请求头部
real_ip_recursive 如果最后一个地址是本机地址则去除,去去除后的非本机地址
1.3 模块变量
$realip_remote_addr 替换之前的remote_addr
$realip_remote_port 替换之前的remote_port
1.4 模块实战
1.编辑hosts
192.168.217.155 www.pl.com realip.pl.com
2.安装nginx(编译realip到nginx)
./configire --prefix=/usr/local/nginx --with-http_realip_module
make
3.配置
nginx.conf
include conf.d/*.conf;
vi /usr/local/nginx/conf/conf.d/realip.c