Nginx源码入门指南
引言:
Nginx作为我们常用的反向代理服务,其优秀的性能和扩展性获得了众多大厂的青睐,Nginx的源码在设计上有很多值得我们学习的地方,本文将介绍一些Nginx源码的一些基础概念,为读者阅读源码扫除一部分障碍。
通过阅读本文,你可以了解到Nginx源码目录层级的安排,Nginx中实现的一些巧妙的数据结构,如何通过模块为Nginx扩展新的功能,以及如何单步调试Nginx源码。另外,本文编写内容均假定Nginx运行在linux系统上。
1 窥探Nginx源码结构
虽然Nginx的源码包很小,以nginx 1.12.5版本为例,在压缩的情况下只会占用1M左右的存储空间,但是完整阅读Nginx源码仍然是一个非常具有挑战性的工作,所以需要提前了解每个文件夹具体负责哪些功能,然后根据我们感兴趣的点进行选择性的阅读。
以下简略地展示了nginx源码的目录结构:
1 | nginx-1.21.5 |
这里推荐大家可以简单地阅读一下core/ngx_cycle.c、os/linux/ngx_process.c和os/linux/ngx_process_cycle.c这三个文件,主要负责nginx的进程管理,这里相当于是进程启动后的一条主线,可以帮助理解nginx的多进程模型。
2 Nginx数据结构一览
数据结构是软件运行的基石,如果抛开数据结构直接去学习软件的逻辑和算法设计无疑是非常困难的,所以我们有必要先简单了解下nginx中一些常用的数据结构。
2.1 基础数据结构
nginx支持linux,windows等多个平台,nginx源码为了支持多平台,对很多基础的数据结构都进行了封装,这里简单列举几个。
1 | (1) ngx_int_t和ngx_uint_t: nginx中对整型和无符号整型的封装 |
2.2 容器数据结构
我们在编写代码时免不了需要使用容器,nginx在实现常用容器数据结构的过程中,一方面贴合了实际的功能需求,另一方面着重考虑的节约内存和高性能,其对数据结构的定制非常值得我们学习。以下列举了几个在nginx源码中常出现的几个数据结构,并简单说明其特点。
1 | (1) ngx_array_t: nginx中动态数组的实现,由于nginx使用纯C开发的,所以并不能直接使用C++ STL中的verctor容器,ngx_array_t可以在数组大小达到分配容量上限时自动扩容 |
2.3 内存池和连接池
nginx中有两个内存池的实现,一个是位于core/ngx_palloc.h文件里定义的ngx_pool_t数据结构,另一个是位于core/ngx_slab.h文件里定义的ngx_slab_pool_t数据结构,它们之间的区别如下:
1 | (1) ngx_pool_t:进程内的内存池,nginx中分配内存时基本上都是通过此数据结构,所以在nginx源码中被大量地使用 |
除了内存池,nginx中为了提高性能,还有连接池ngx_connection_t,连接池内封装了每个连接的读写事件,该连接池主要使用在ngx_cycle_t结构体中,ngx_cycle_t中有connections和free_connections,分别代表当前正在使用的连接和空闲连接。
2.4 模块相关数据结构
在学习nginx模块代码或者编写自定义模块时,经常会看到以下几个数据结构
1 | (1) ngx_module_t: 这个数据结构定义了模块执行时的几个生命周期,通过传入回调函数的方式,可以执行模块内的自定义逻辑 |
3 Nginx模块之旅
总的来说nginx的源码其实就是一个框架加上一堆模块实现的,模块化的设计无疑是nginx的一大亮点之一,其中nginx模块可分为官方模块和第三方模块,官方模块即随nginx源码一起发布的模块,而第三方模块通常需要用户额外下载,然后通过--add-module选项添加到nginx源码中编译的;下面将介绍一些常用的Nginx模块,在了解这些模块后,你一定会惊讶于nginx还能做这些事。
3.1 Nginx官方模块
以下模块代码直接包含在nginx的源码中,默认编译配置下部分功能可能未开启,此时只需要通过--with-XXX选项开启即可使用。
| 模块 | 功能 |
|---|---|
| ngx_http_auth_basic_module | 该模块可以为代理服务增加简单的用户名和密码的验证 |
| ngx_http_autoindex_module | 使用该模块后可以获取到服务器上某个文件夹下的文件列表 |
| ngx_http_geoip_module | 这个模块通过连接MaxMind 数据库获取ip的所在地 |
| ngx_http_memcached_module | 可以通过调用http接口的方式操作memcached |
| ngx_http_secure_link_module | 为nginx增加防盗链功能 |
| ngx_http_realip_module | 获取反向代理过程中客户端的真实ip |
| ngx_http_mp4_module | 提供mp4流媒体功能 |
| ngx_http_access_module | 实现ip黑名单和白名单的功能 |
| ngx_http_core_module | 处理http请求的核心模块 |
| ngx_http_mirror_module | http请求镜像,可以将生产环境的流量镜像一份到测试环境 |
| ngx_http_rewrite_module | http url路径重写功能 |
| ngx_stream_core_module | 处理tcp请求的核心模块 |
更多nginx官方模块可参考: https://nginx.org/en/docs/
nginx官方模块为处理http请求过程中增加了很多实用的功能,除此以外,nginx还可以处理邮件相关协议,也可以直接代理tcp协议请求,通过灵活使用这个功能可以减少很多我们日常的开发工作。
3.2 Nginx第三方模块
nginx除了官方模块,更是有很多优秀的第三方模块,很多优秀的开源项目诸如openresty和kong网关都是基于nginx的第三方模块扩展开发的。
| 模块 | 功能 |
|---|---|
| nginx-clojure/nginx-clojure | 让nginx可以内嵌java编程语言 |
| openresty/lua-nginx-module | 让nginx可以内嵌lua编程语言,基于该模块nginx可以直接实现简单的web接口 |
| youzee/nginx-unzip-module | 该模块可以让nginx直接从zip压缩包中获取文件 |
| arut/nginx-rtmp-module | 让nginx支持rtmp推流协议 |
| ngx_http_redis | 可以通过http请求的方式操作redis |
| mdirolf/nginx-gridfs | 可以让nginx直接访问mongodb的gridfs |
| evanmiller/mod_zip | 动态压缩文件 |
| nginx-goodies/nginx-sticky-module-ng | 基于cookie实现的会话保持功能 |
| openresty/echo-nginx-module | 可以将nginx的变量直接通过http响应返回 |
更多nginx第三方模块可参考: https://www.nginx.com/resources/wiki/modules/
nginx中lua的写法可参考: https://github.com/openresty/lua-nginx-module#content_by_lua
3.3 Nginx模块源码分析
本小结首先简单分析sticky会话保持模块的代码,然后引出如何编写自己的nginx模块。
3.3.1 sticky模块代码分析
sticky模块的主要作用是对请求进行会话保持,可以让同一个客户端的多次请求都连接到同一个上游服务,如果部署或调用过事业部内的AI+能力平台对该模块应该会有一定的了解。该模块属于nginx的第三方模块,在编译nginx时需要额外下载。
sticky模块的源码下载地址: https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng/get/master.tar.gz
相比nginx官方源码而言,sticky模块的注释比较多,阅读起来会更加轻松一些,这也是这里选择sticky模块作为例子的原因
以下梳理了sticky模块的主要流程图,方便后面对代码的学习:
1 | flowchart LR |
sticky模块的源码非常简洁,核心处理逻辑就是以下几个函数:
1 | //执行一些初始化工作 |
3.3.2 如何自定义Nginx模块
当我们需要一些功能,而nginx官方模块或第三方模块都没有提供时,就可以通过自定义模块实现了,编写一个nginx模块其实是非常简单的,比如上面的sticky模块源码中,实际有用的文件就只有4个。
编写一个nginx的步骤如下:
1 | flowchart LR |
config文件其实是一个shell脚本,sticky模块的config文件内容如下:
1 | ngx_addon_name=ngx_http_sticky_module |
如果只想开发一个HTTP模块,那么config文件中至少需要定义以下三个变量:
- ngx_addon_name: 仅在configure执行时使用,一般设置为模块名称
- HTTP_MODULES: 保持所有HTTP模块的名称,每个HTTP模块之间由空格符相连
- NGX_ADDON_SRCS: 用于指定新增模块的源代码,多个待编译的源代码之间用空格隔开
除了HTTP_MODULES表示HTTP模块,还包含CORE_MODULES(核心模块),EVENT_MODULES(事件模块),HTTP_FILTER_MODULES(HTTP过滤模块),HTTP_HEADERS_FILTER_MODULES(HTTP头部过滤模块)
在编写完config文件后,需要在模块源码中设置ngx_command_t和ngx_module_t结构体,为理解ngx_command_t和ngx_module_t的使用方法,下面先列出了这几个结构体的定义:
1 | struct ngx_command_s { |
以下为对应sticky模块中对这几个结构体的设置
1 | static ngx_command_t ngx_http_sticky_commands[] = { |
通过在以上结构体中加入自己编写的回调函数,执行configure时使用--add-module选项指定模块的路径,即可将业务逻辑代码嵌入到nginx中执行了。
4 浅谈Nginx源码调试
通常在学习开源组件的源码过程中,如果可以将程序运行并单步执行将会大大降低我们理解代码的难度,为此这里介绍通过vscode调试nginx的方法。
(1) 首先我们需要基于源码编译出nginx的可执行文件,执行configure生成Makefile (nginx默认是依赖openssl,pcre和zlib这三个库的其中openssl用来支持https协议,pcre用于正则表达式匹配,zlib主要影响gzip压缩功能)。
1 | 执行./configure --help可以查询configure支持的所有选项 |
(2) 修改objs/Makefile,对CFLAGS增加-O0和-g选项,其中-O0是禁用编译优化而-g是为编译的二进制文件增加debug信息,修改完成后执行make编译nginx可执行文件。
1 | CC = cc |
(3) vscode安装C/C++插件。
(4) vscode的调试器launch.json配置。
1 | { |
(5) 修改nginx.conf配置文件,增加以下配置项,禁止nginx daemon模式和多进程模式。
1 | daemon off; |
经过以上配置,通过nginx上的调试按钮启动后,就可以进行常用的调试操作了。
5 总结
在阅读过以上内容后,相信大家对nginx的源码已经有了一个比较基础的了解了,本文仅介绍了nginx源码的冰山一角,更多的内容需要大家自己去探索了,另外由于nginx的源码缺乏注释,建议学习时结合一些书籍,可以帮助更快的理解。
由于编者自己水平也有限,以上如有错误的地方还烦请指正,感谢!
参考资料:
nginx源码下载地址: https://nginx.org/en/download.html
nginx官方模块: https://nginx.org/en/docs/
nginx第三方模块: https://www.nginx.com/resources/wiki/modules/
nginx http核心模块: https://nginx.org/en/docs/http/ngx_http_core_module.html
书籍推荐: 《深入理解Nginx:模块开发与架构解析(第2版)》
- Title: Nginx源码入门指南
- Author: liminjun
- Created at: 2022-03-13 14:45:54
- Updated at: 2023-05-15 10:39:06
- Link: https://olldbg.github.io/2022/03/13/Nginx源码入门指南/
- License: This work is licensed under CC BY-NC-SA 4.0.