如果你曾被Nginx的强大性能所吸引,却又对它的内部工作机制感到困惑,那么你来对地方了。
1. Nginx简介:不只是Web服务器
Nginx(发音为“engine x”)最初由Igor Sysoev开发,是一个HTTP Web服务器、反向代理、内容缓存、负载均衡器、TCP/UDP代理服务器和邮件代理服务器。
它以其灵活性和高性能以及低资源利用率而闻名,如今已经成为世界上最流行的Web服务器之一。
Nginx由内核和模块组成。内核的设计非常微小和简洁,完成的工作也非常简单,仅仅通过查找配置文件将客户端请求映射到一个location block(location是Nginx配置中的一个指令,用于URL匹配)。
而在这个location中所配置的每个指令将会启动不同的模块去完成相应的工作。
2. Nginx模块化架构:乐高积木式的设计哲学
Nginx采用模块化设计,从结构上可以分为核心模块、基础模块和第三方模块。
HTTP模块、EVENT模块和MAIL模块等属于核心模块,HTTP Access模块、HTTP FastCGI模块、HTTP Proxy模块和HTTP Rewrite模块属于基础模块。
而用户根据自己的需要开发的模块都属于第三方模块。
从功能角度,Nginx的模块可以分为三类:
- Handlers(处理器模块):直接处理请求,并进行输出内容和修改headers信息等操作。Handlers处理器模块一般只能有一个。
- Filters(过滤器模块):主要对其他处理器模块输出的内容进行修改操作,最后由Nginx输出。
- Proxies(代理类模块):Nginx的HTTP Upstream之类的模块,这些模块主要与后端一些服务比如FastCGI等进行交互,实现服务代理和负载均衡等功能。
这种模块化架构使得Nginx具备了极强的扩展性,开发者可以根据自己的需要开发自定义模块,嵌入到Nginx的整个工作流程中,实现更加便捷的个性化功能定制与开发。
3. Nginx处理引擎:HTTP请求的11个阶段之旅
为什么Nginx要把HTTP请求的处理过程分为多个阶段呢?这就像工厂的流水线一样,通过分工提高效率。
Nginx的模块化设计使得每一个HTTP模块可以仅专注于完成一个独立的、简单的功能,而一个请求的完整处理过程可以由无数个HTTP模块共同合作完成。
当多个HTTP模块流水式地处理同一个请求时,单一的处理顺序是无法满足灵活性需求的。因此,HTTP框架依据常见的处理流程将处理阶段划分为11个阶段。
3.1 11个处理阶段的详细解析
虽然搜索结果中没有完整列出11个阶段的具体名称,但我们可以了解到Nginx处理请求的基本流程和阶段划分的重要性。
这种阶段划分的设计让每一个正在处理请求的HTTP模块能够灵活、有效地指定下一个HTTP处理模块是哪一个,同时让每一个HTTP模块能正确地将自己插入到完整流程的合适位置中。
对于TCP/UDP会话,Nginx也采用了类似的阶段处理方式,会话会按顺序经过多个称为阶段的步骤进行处理:
- Post-accept(接受后):接受客户端连接后的第一个阶段
- Pre-access(访问前):初步的访问检查
- Access(访问):在实际数据处理之前限制客户端访问
- SSL:TLS/SSL终止
- Preread(预读):将初始数据字节读取到预读缓冲区中
- Content(内容):实际处理数据,通常将数据代理到上游服务器或返回指定值给客户端
- Log(日志):记录客户端会话处理的结果
3.2 请求处理的完整流程
Nginx处理HTTP请求的完整过程,对应到底层,就是读写socket事件。它包括:
- 建立连接
- 读取请求:解析请求
- 处理请求
- 响应请求
具体来说,接收请求时,Nginx会逐行读取请求行和请求头,判断有请求体后读取请求体;然后处理请求;最后根据处理结果返回响应,生成相应的HTTP请求(响应行、响应头、响应体)。
4. Nginx的进程模型:高效并发的秘密
Nginx之所以能实现高性能和高并发,与其精巧的进程模型密不可分。Nginx服务器正常运行过程中采用多进程架构:
- Master进程:管理Worker进程,对外接口接收外部操作(信号),对内转发根据外部操作的不同通过信号管理Worker,并监控worker进程的运行状态,worker进程异常终止后自动重启worker进程。
- Worker进程:所有Worker进程都是平等的,实际处理网络请求由Worker进程处理。Worker进程数量在nginx.conf中配置,一般设置为核心数,充分利用CPU资源,同时避免进程数量过多导致进程竞争CPU资源,增加上下文切换的损耗。
Nginx采用多进程+异步非阻塞方式(IO多路复用epoll)处理请求。这种设计使得Nginx能够高效处理大量并发连接,每个工作进程可以处理数千个并发连接,而不会产生严重的性能开销。
启动时,Master进程加载配置文件,初始化监听的socket,然后fork出多个Worker进程。Worker进程竞争新的连接,获胜方通过三次握手建立Socket连接,并处理请求。
5. Nginx配置与模块开发:实战入门
5.1 开发一个简单的HTTP模块
开发一个完整的简单的HTTP模块需要几个步骤(以模块名为ngx_http_mytest_module为例):
- 编写config文件:为了让nginx在configure过程能找到编写的模块。
- 编写模块结构ngx_http_mytest_module:定义模块的上下文结构和命令结构。
- 编写模块上下文结构ngx_http_mytest_module_ctx:指定nginx在触发了模块运行的时候,如何处理已经在其他http、server、location定义过的上下文。
- 编写模块命令结构ngx_http_mytest_commands:指定nginx在配置文件中触发了哪些命令,包括触发命令的回调函数。
- 编写触发命令的回调函数ngx_http_mytest:设置对http请求的具体处理方法。
- 编写对http请求的具体处理方法ngx_http_mytest_handler:从参数中获取http请求结构,并设置http返回。
5.2 使用NJS脚本语言扩展Nginx
除了使用C语言开发模块外,Nginx还提供了NJS脚本语言,通过JavaScript脚本扩展服务器的功能。使用NJS可以:
- 在请求到达上游服务器之前进行复杂的访问控制和安全检查
- 操作响应头
- 编写灵活的异步内容处理程序和过滤器
以下是一个基本的NJS示例:
创建一个NJS脚本文件,例如http.js:
function hello(r) {
r.return(200, "Hello world!");
}
export default {hello};
在nginx.conf文件中,启用ngx_http_js_module模块并使用http.js脚本文件:
load_module modules/ngx_http_js_module.so;
events {}
http {
js_import http.js;
server {
listen 8000;
location / {
js_content http.hello;
}
}
}
这种方式比开发完整的C模块要简单得多,适合实现一些自定义逻辑。
6. 性能优化:让Nginx飞起来
要最大化Nginx的性能,需要考虑多个方面的优化:
- Worker进程数:一般设置为核心数,充分利用CPU资源。
- 事件处理模型:使用epoll(Linux 2.6+)等高效的事件处理机制。
- 文件AIO:在支持的系统上(FreeBSD 4.3+、Linux 2.6.22+)启用文件异步I/O。
- 直接I/O:在适当情况下使用DIRECTIO(FreeBSD 4.4+、Linux 2.4+、Solaris 2.6+、macOS)。
- 连接限制:限制来自单个地址的并发连接或请求数,防止某些客户端耗尽资源。
- 响应速率限制:控制响应输出的速率。
7. 完整示例:构建一个自定义内容过滤器
让我们通过一个完整的示例,演示如何开发一个简单的HTTP模块,用于在响应中添加自定义页脚。
7.1 编写config文件
ngx_addon_name=ngx_http_mytest_module
HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"
7.2 编写模块源代码
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r);
static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_command_t ngx_http_mytest_commands[] = {
{
ngx_string("mytest"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,
ngx_http_mytest,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
static ngx_http_module_t ngx_http_mytest_module_ctx = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
ngx_module_t ngx_http_mytest_module = {
NGX_MODULE_V1,
&ngx_http_mytest_module_ctx,
ngx_http_mytest_commands,
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
static char *ngx_http_mytest(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_mytest_handler;
return NGX_CONF_OK;
}
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out;
rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
ngx_str_t response = ngx_string("Hello, this is my test module!");
b = ngx_create_temp_buf(r->pool, response.len);
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_memcpy(b->pos, response.data, response.len);
b->last = b->pos + response.len;
b->last_buf = 1;
b->last_in_chain = 1;
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = response.len;
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(r, &out);
}
7.3 编译和配置
将以上代码保存为ngx_http_mytest_module.c,然后在编译Nginx时添加该模块:
./configure --add-module=/path/to/module
make && make install
在Nginx配置中使用:
location /test {
mytest;
}
8. 结语
Nginx的HTTP处理框架是一个精心设计的系统,它通过阶段划分、模块化设计和高效的事件处理机制,实现了极高的性能和可扩展性。
理解Nginx的HTTP处理引擎,不仅有助于我们更好地使用Nginx,更能为我们设计高性能系统提供宝贵的思路和启示。
无论是使用现有的Nginx功能,还是开发自定义模块,深入理解Nginx的HTTP框架都是非常有价值的。随着HTTP/3等新协议的支持,Nginx仍在不断进化,继续为构建高性能Web服务提供坚实的基础。
Nginx HTTP处理引擎详解
328

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



