不管是C/C++技术栈,还是PHP,Java技术栈,从事后端开发的朋友对nginx一定不会陌生。
![6176491b26f3c555653e89a3ad557d11.png](https://i-blog.csdnimg.cn/blog_migrate/4d5df945adb53581884e684eff0496e7.png)
想要深入学习nginx,阅读源码一定是非常重要的一环,但nginx源码量毕竟还是不算少,一不小心就容易陷入某个细节,迷失在茫茫码海之中。
如果有一张地图,让我们开启上帝视角,总览全局,帮助我们快速学习整体框架结构,又能不至于迷失其中那就再好不过了!
看到这篇文章的你有福了,笔者花了不少时间,把这件事给做了,先来看个全貌(限于平台图片尺寸设定,这里只能看个大概,想获取高清大图请看文末):
![8b636c1a86cd7013473c979c84119d29.png](https://i-blog.csdnimg.cn/blog_migrate/e4ebaff5f49eff7c774cae6d8c7eaa6e.png)
下面选取一些关键部分来一窥神秘的nginx。
主进程启动
nginx主进程启动后,进行一系列的初始化,包括但不限于:
- 命令行参数解析
- 时间初始化
- 日志初始化
- ssl初始化
- 操作系统相关初始化
- 一致性hash表初始化
- 模块编号处理
![37b0d430247ff9d72523f5b46b3b4f1c.png](https://i-blog.csdnimg.cn/blog_migrate/924075aa67addb32cb04f7f7670895e8.png)
核心初始化
另外一个最重要的初始化由ngx_init_cycle()
函数完成,该函数围绕nginx中非常核心的一个全局数据结构ngx_cycle_t
展开。
![5e63e0a9382efc8cef1943f2027799d9.png](https://i-blog.csdnimg.cn/blog_migrate/468454c27b8aedd8afb42111841a564c.png)
该函数完成了几个核心初始化:
- 配置文件解析
- 创建并监听socket
- 初始化nginx各模块
![10cbdc3722b023e8a10de568fc1ebf1e.png](https://i-blog.csdnimg.cn/blog_migrate/6bc24b05f73434186af942c0f82a3b29.png)
nginx核心模块群
nginx是一个模块化设计的软件,优秀的架构设计使得nginx可以扩展非常多的模块。
![a097c608c7f1dc66ec48a2f807431e43.png](https://i-blog.csdnimg.cn/blog_migrate/070611309d2a7338cf1a6440fa5016dc.png)
要一一描绘出这些模块显得有些杂乱和工作量巨大,仅选取一些关键核心模块进行了展示:
![568df3c60b1ed602490d40c60b901add.png](https://i-blog.csdnimg.cn/blog_migrate/871ec26fc2f2d224547184d1a6618d56.png)
每个模块有一个支持的命令解析列表,在初始化过程中,主进程将会遍历所有模块的命令列表,进行配置文件中的命令解析,如经常用的ngx_http_proxy_module
:
![c65af13438c9fcd725dccb18bde1b29b.png](https://i-blog.csdnimg.cn/blog_migrate/c0f644a47bfab0e9bb1644c0d75cff88.png)
ngx_http_core_module
模块:
![b1e0e3747864656ec62f4a61704e579d.png](https://i-blog.csdnimg.cn/blog_migrate/993471cc864b735b134b430165f8c63b.png)
main函数的最后,根据是否启用多进程模型,分别进入多进程版本的ngx_master_process_cycle
和单进程版本的ngx_single_process_cycle()
。
以常见的多进程版本为例,进入该函数后,首先设置进程名称为:"master process",随后启动各工作子进程。
启动子进程
经过几层封装,最终通过fork
启动多个子进程:
![c6fb57b7b7a3d43287021167bef51cc1.png](https://i-blog.csdnimg.cn/blog_migrate/1f8308daed240fad990cc7ce151a5388.png)
除了工作子进程,还启动了缓存管理进程。
之后主进程进入工作循环,周期性更新时间并检查各全局标记,根据不同情况给子进程发送不同信号。
![cccf78a906fcdf90d27ba8fe201865fc.png](https://i-blog.csdnimg.cn/blog_migrate/2b1c0fede7134fccefe9d981f4e832f5.png)
子进程工作循环
子进程启动后,进入ngx_worker_process_cycle
,进行一些工作进程的初始化,随后修改进程名称为:"worker process"。
接着进入工作循环函数ngx_process_events_and_timers
,在该函数中主要负责:
- 竞争互斥锁,拿到锁的进程才能执行accept接受新的连接,以此在多进程之间解决惊群效应
- 通过epoll异步IO模型处理网络IO事件,包括新的连接事件和已建立连接发生的读写事件
- 处理定时器队列中到期的定时器事件,定时器通过红黑树的方式存储
![8b773a590860d56ae4d0e43784ce03b7.png](https://i-blog.csdnimg.cn/blog_migrate/acd0d9b2ccf011ebc65f555fdcd1a18b.png)
HTTP请求预处理
当连接有数据产生时,工作线程读取socket中到来的数据,并根据HTTP协议格式进行解析,最终封装成ngx_request_t
请求对象,提交处理。
![2b8c9b0f643ec4ae97a2f72743450355.png](https://i-blog.csdnimg.cn/blog_migrate/33da36292835179be05c2b50f50ac2da.png)
HTTP请求处理的11个阶段
在nginx中各HTTP模块是以挂载的形式串接而成,以流水线工作模式进行HTTP请求的处理,nginx将一个HTTP请求的处理划分为11个阶段。
typedef
每阶段(部分阶段保留,不允许挂载)允许多个模块挂载,一个模块也可以挂载到多个阶段。因此,初次完成挂载的存储结构是一个二维数组的形式。
不过在初始化过程中,ngx_http_init_phase_handlers
函数将该二维数组转换成了一维数组。下图是nginx中各模块挂载情况:
![4aaf38bdbec2649254e87a62f14c7638.png](https://i-blog.csdnimg.cn/blog_migrate/9c239cda2a399c6bd1f48a85532ec9a2.png)
全景图
最后,再来看一看全貌:
![8b636c1a86cd7013473c979c84119d29.png](https://i-blog.csdnimg.cn/blog_migrate/e4ebaff5f49eff7c774cae6d8c7eaa6e.png)
总结
nginx不仅是一款优秀的高性能web服务器,对于C/C++技术栈的同学来说,还是一个很好的学习对象,其良好的架构设计,优美的代码风格和经典的编程技法无一不值得细细品来。
不过限于笔者水平和时间有限,虽然号称全景图,但依然无法覆盖到nginx的方方面面,欢迎读者朋友留言交流,让此图日渐完善,谢谢大家。
推荐阅读:
为何要将 listenfd 设置成非阻塞的?
C语言为什么不会过时?
如何调试多线程程序
为什么像王者荣耀这样的游戏 Server 不愿意使用微服务?
Tomcat 架构原理解析到架构设计借鉴
Windows应用程序调试原理全景图
redis 分布式锁
弹窗!到底谁是幕后黑手?
如果你想加入后端开发微信交流群,可以先加我微信 easy_coder,备注 “加微信群”,我拉你入群,备注不对不加哦。
点【在看】是最大的支持