你以为Nginx只是简单的网页服务器?它的内部架构藏着让你惊叹的智慧!
1. 引言:万能的网络小助手
Nginx(发音为“engine-x”)是一个开源的高性能HTTP服务器和反向代理服务器。它由Igor Sysoev开发,起初是为了解决C10K问题,即如何在单个服务器上同时处理超过1万个并发连接。
想象一下,Nginx就像是网络世界的空中交通管制员,专注于为数以千计的网络请求快速、高效地引导到正确的目的地。
那么,它是如何做到如此高效的呢?答案就藏在它的源码和架构设计中。
2. Nginx基础入门:初识神秘面纱
2.1 安装与启动
在Windows上安装Nginx,只需下载预编译的二进制包,解压后运行start nginx命令即可。Linux上则更简单,使用包管理器如sudo apt-get install nginx就能完成安装。
启动Nginx后,访问http://localhost,看到“Welcome to nginx!”页面,恭喜,你的Nginx小助手已上线!
2.2 核心配置入门
Nginx的核心配置文件是nginx.conf,通常位于/etc/nginx/(Linux)或C:\nginx\conf\(Windows)。
一个简单的静态Web服务器配置看起来是这样的:
http {
server {
listen 80;
server_name localhost;
location / {
root /var/www/mysite;
index index.html index.htm;
}
}
}
配置完成后,运行sudo systemctl reload nginx(Linux)或nginx -s reload(Windows)重载配置,你的静态网站就准备好了。
3. Nginx架构设计:高性能的秘诀
3.1 模块化设计:高度模块化的设计是Nginx的架构基础
Nginx服务器被分解为多个模块,每个模块就是一个功能模块,只负责自身的功能,模块之间严格遵循“高内聚,低耦合”的原则。
这些模块可以分为:
- 核心模块:提供错误日志记录、配置文件解析、事件驱动机制、进程管理等核心功能。
- 标准HTTP模块:提供HTTP协议解析相关的功能,如端口配置、网页编码设置、HTTP响应头设置等。
- 可选HTTP模块:主要用于扩展标准的HTTP功能,让Nginx能处理一些特殊的服务,如Flash多媒体传输、解析GeoIP请求、SSL支持等。
- 邮件服务模块:支持Nginx的邮件服务,包括对POP3协议、IMAP协议和SMTP协议的支持。
- 第三方模块:为了扩展Nginx服务器应用,完成开发者自定义功能。
3.2 多进程与异步非阻塞
Nginx结合多进程机制和异步机制,异步机制使用的是异步非阻塞方式。
每个工作进程使用异步非阻塞方式,可以处理多个客户端请求。当某个工作进程接收到客户端的请求以后,调用IO进行处理,如果不能立即得到结果,就去处理其他的请求(即为非阻塞)。
当IO返回时,就会通知此工作进程;该进程得到通知,暂时挂起当前处理的事务去响应客户端请求。
3.3 事件驱动模型
Nginx的事件驱动模型由事件收集器、事件发送器和事件处理器三部分基本单元组成。
- 事件收集器:负责收集worker进程的各种IO请求。
- 事件发送器:负责将IO事件发送到事件处理器。
- 事件处理器:负责各种事件的响应工作。
事件发送器将每个请求放入一个待处理事件的列表,使用非阻塞I/O方式调用“事件处理器”来处理该请求。其处理方式称为“多路IO复用方法”,常见的包括select模型、poll模型、epoll模型。
4. HTTP框架核心:源码深入解析
4.1 HTTP模块的数据结构
定义HTTP模块方式很简单,例如:
ngx_module_t ngx_http_mytest_module;
其中,ngx_module_t是一个Nginx模块的数据结构。我们来分析一下这个结构中关键的成员:
struct ngx_module_s {
ngx_uint_t ctx_index;
ngx_uint_t index;
void *ctx;
ngx_command_t *commands;
ngx_uint_t type;
// 初始化函数等...
};
ctx_index:对于一类模块而言,表示当前模块在这类模块中的序号。index:表示当前模块在ngx_modules数组中的序号。ctx:指向一类模块的上下文结构体。commands:将处理nginx.conf中的配置项。type:表示该模块的类型。
定义一个HTTP模块时,务必把type字段设为NGX_HTTP_MODULE。
4.2 HTTP模块的上下文
对于HTTP类型的模块来说,ngx_module_t中的ctx指针必须指向ngx_http_module_t接口。这个结构体定义了8个回调方法:
typedef struct {
ngx_int_t (*preconfiguration)(ngx_conf_t *cf);
ngx_int_t (*postconfiguration)(ngx_conf_t *cf);
void *(*create_main_conf)(ngx_conf_t *cf);
char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
void *(*create_srv_conf)(ngx_conf_t *cf);
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
void *(*create_loc_conf)(ngx_conf_t *cf);
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;
HTTP框架在读取、重载配置文件时定义了由ngx_http_module_t接口描述的8个阶段,会在每个阶段中调用相应的方法。
4.3 配置命令解析
commands数组用于定义模块的配置文件参数,每一个数组元素都是ngx_command_t类型,数组的结尾用ngx_null_command表示。
每个ngx_command_t结构体定义了自己感兴趣的一个配置项:
struct ngx_command_s {
ngx_str_t name;
ngx_uint_t type;
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_uint_t conf;
ngx_uint_t offset;
void *post;
};
name:配置项名称,如“gzip”。type:指定配置项可以出现的位置和携带的参数个数。set:出现配置项后,调用set方法处理配置项的参数。
5. HTTP请求处理的11个阶段
在Nginx中,各HTTP模块是以挂载的形式串接而成,以流水线工作模式进行HTTP请求的处理。nginx将一个HTTP请求的处理划分为11个阶段。
typedef enum {
NGX_HTTP_POST_READ_PHASE = 0,
NGX_HTTP_SERVER_REWRITE_PHASE,
NGX_HTTP_FIND_CONFIG_PHASE,
NGX_HTTP_REWRITE_PHASE,
NGX_HTTP_POST_REWRITE_PHASE,
NGX_HTTP_PREACCESS_PHASE,
NGX_HTTP_ACCESS_PHASE,
NGX_HTTP_POST_ACCESS_PHASE,
NGX_HTTP_PRECONTENT_PHASE,
NGX_HTTP_CONTENT_PHASE,
NGX_HTTP_LOG_PHASE
} ngx_http_phases;
每阶段(部分阶段保留,不允许挂载)允许多个模块挂载,一个模块也可以挂载到多个阶段。因此,初次完成挂载的存储结构是一个二维数组的形式,不过在初始化过程中,ngx_http_init_phase_handlers函数将该二维数组转换成了一维数组。
这11个阶段就像是一条流水线上的11个工作站,每个HTTP请求都会依次经过这些工作站,每个工作站都有机会对请求进行加工处理。
6. 完整示例:编写自定义Nginx模块
理论说了这么多,现在让我们动手实践,创建一个简单的“Hello World”Nginx模块。
6.1 创建模块文件
首先,我们需要创建模块文件夹和文件。模块主文件(例如ngx_http_hello_world_module.c)内容如下:
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static char *ngx_http_hello_world(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_int_t ngx_http_hello_world_handler(ngx_http_request_t *r);
static u_char ngx_hello_world[] = "hello world";
static ngx_command_t ngx_http_hello_world_commands[] = {
{
ngx_string("hello_world"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_hello_world,
0,
0,
NULL
},
ngx_null_command
};
static ngx_http_module_t ngx_http_hello_world_module_ctx = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
ngx_module_t ngx_http_hello_world_module = {
NGX_MODULE_V1,
&ngx_http_hello_world_module_ctx,
ngx_http_hello_world_commands,
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
static char *ngx_http_hello_world(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_http_hello_world_handler;
return NGX_CONF_OK;
}
static ngx_int_t ngx_http_hello_world_handler(ngx_http_request_t *r)
{
ngx_http_complex_value_t cv;
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
}
ngx_http_discard_request_body(r);
ngx_str_t val = ngx_string("text/plain");
r->headers_out.content_type = val;
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = sizeof(ngx_hello_world) - 1;
ngx_http_send_header(r);
ngx_buf_t *b = ngx_create_temp_buf(r->pool, sizeof(ngx_hello_world) - 1);
b->last = ngx_cpymem(b->pos, ngx_hello_world, sizeof(ngx_hello_world) - 1);
b->last_buf = 1;
ngx_chain_t out;
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(r, &out);
}
6.2 模块代码分析
让我们分析一下这个helloworld模块的关键部分:
ngx_command_t函数用于定义包含模块指令的静态数组ngx_http_hello_world_commands。static u_char ngx_hello_world[] = "hello world"是输出到屏幕的字符串。ngx_http_module_t用来定义结构体ngx_http_hello_world_module_ctx。ngx_module_t定义结构体ngx_http_hello_world_module。- 处理函数
ngx_http_hello_world_handler是hello world模块的核心部分。
6.3 编译与配置
下载nginx源码包,与helloworld模块一起编译nginx:
./configure --add-module=/path/to/hello_world/module
make
make install
然后在nginx.conf中配置:
location /hello {
hello_world;
}
启动nginx,访问http://localhost/hello,就可以看到编写的helloworld模块输出的文字了。
7. Nginx启动过程解析
7.1 主进程启动
nginx主进程启动后,进行一系列的初始化,包括命令行参数解析、时间初始化、日志初始化、SSL初始化、操作系统相关初始化、一致性hash表初始化、模块编号处理等。
7.2 核心初始化
最重要的初始化由ngx_init_cycle()函数完成,该函数围绕nginx中非常核心的一个全局数据结构ngx_cycle_t展开。该函数完成了几个核心初始化:
- 配置文件解析
- 创建并监听socket
- 初始化nginx各模块
7.3 进程模型
主程序的最后,根据是否启用多进程模型,分别进入多进程版本的ngx_master_process_cycle和单进程版本的ngx_single_process_cycle。以常见的多进程版本为例,进入该函数后,首先设置进程名称为:"master process",随后启动各工作子进程。
经过几层封装,最终通过fork启动多个子进程。除了工作子进程,还启动了缓存管理进程。之后主进程进入工作循环,周期性更新时间并检查各全局标记,根据不同情况给子进程发送不同信号。
7.4 子进程工作循环
子进程启动后,进入ngx_worker_process_cycle,进行工作进程的初始化,随后修改进程名称为:"worker process"。接着进入工作循环函数ngx_process_events_and_timers,在该函数中主要负责:
- 竞争互斥锁,拿到锁的进程才能执行accept接受新的连接,以此在多进程之间解决惊群效应
- 通过epoll异步IO模型处理网络IO事件,包括新的连接事件和已建立连接发生的读写事件
- 处理定时器队列中到期的定时器事件,定时器通过红黑树的方式存储
8. HTTP请求处理流程
8.1 通用处理流程
Web服务器的HTTP请求处理流程可以粗略地叙述为:
- 监听端口,接受客户端的连接
- 读取请求头,包括请求行(request line)和请求头(headers)
- 读取或者丢弃请求体(body)
- 生成并发送响应头
- 生成并发送响应体
8.2 Nginx的处理流程
如果我们忽略Nginx的异步机制,把注意力集中在处理逻辑上,那么Nginx的工作流程如下:
- 监听端口,设置回调为
ngx_http_init_connection() - 接受客户端的连接,调用
ngx_http_wait_request_handler() - 调用
ngx_http_create_request()创建请求对象 - 接收数据,调用
ngx_http_process_request_line()解析请求行 - 请求行接收完毕,继续接收数据,调用
ngx_http_process_request_headers()解析请求头 - 请求头接收完毕,调用
ngx_http_process_request()设置异步读写事件 - 调用
ngx_http_handler()开始真正处理请求 - 调用
ngx_http_core_run_phases()按阶段处理请求 - 调用
ngx_http_send_header()发送响应头 - 调用
ngx_http_output_filter()发送响应体 - 处理完毕,记录访问日志
当连接有数据产生时,工作线程读取socket中到来的数据,并根据HTTP协议格式进行解析,最终封装成ngx_request_t请求对象,提交处理。
9. 总结与学习建议
通过本文,我们对Nginx HTTP框架的整体架构有了一个全面的认识,包括其模块化的设计、多进程和异步非阻塞的请求处理方式、事件驱动模型等。
Nginx不仅是一款优秀的高性能web服务器,对于C/C++技术栈的同学来说,还是一个很好的学习对象,其良好的架构设计,优美的代码风格和经典的编程技法无一不值得细细品来。
想要深入学习Nginx,阅读源码是非常重要的一环。以下是一些建议,可以帮助你更深入地学习Nginx:
- 阅读官方文档:Nginx的官方文档是学习的宝库。详细阅读文档,可以让你更深入地了解Nginx的功能和配置。
- 实践项目:将Nginx应用到实际项目中,可以帮助你在实践中巩固知识。
- 参加社区讨论:加入Nginx社区,与其他开发者交流,可以让你了解到更多的经验和技巧。
- 阅读源代码:如果你对Nginx的内部实现感兴趣,阅读源代码是最直接的方法。
希望通过这篇文章,能帮助你开启Nginx代码分析的学习之旅,并在成为Nginx大师的道路上越走越远!
以上内容仅供参考,具体细节以实际应用和官方文档为准。欢迎交流指正!
93

被折叠的 条评论
为什么被折叠?



