在互联网技术飞速发展的今天,Nginx已成为全球最流行的Web服务器之一,它背后的HTTP框架设计精妙,是支撑其高性能的关键。
1 引言:Nginx的非凡之处
Nginx(发音为“engine x”)是一个HTTP Web服务器、反向代理、内容缓存、负载均衡器、TCP/UDP代理服务器和邮件代理服务器,最初由Igor Sysoev编写。
Nginx以其灵活性和高性能以及低资源利用率而闻名,它是世界上最流行的Web服务器,始终是最受欢迎的Docker镜像之一,并为多个Kubernetes入口控制器提供支持。
那么,是什么让Nginx如此强大?答案就藏在它的模块化架构和精巧的HTTP框架设计中。
想象一下,Nginx就像一个现代化的餐厅:有专门迎宾的接待员(监听端口)、点餐的服务员(接收请求)、后台厨师(处理请求)和传菜员(发送响应)。每个人各司其职,协同工作,从而实现了高效运营。
2 Nginx基础:模块化设计
高度模块化的设计是Nginx的架构基础。Nginx服务器被分解为多个模块,每个模块就是一个功能模块,只负责自身的功能,模块之间严格遵循“高内聚,低耦合”的原则。
2.1 五大功能模块
Nginx内部由核心模块和其他功能模块组成,这种简单的结构分层便于进行功能的扩展,代码更加清晰易于维护。我们通常将其分为五大模块:
- 核心模块:是Nginx服务器正常运行必不可少的模块,提供错误日志记录、配置文件解析、事件驱动机制、进程管理等核心功能。就像是餐厅的总经理,负责整体运营管理。
- 标准HTTP模块:提供HTTP协议解析相关的功能,如端口配置、网页编码设置、HTTP响应头设置等。如同餐厅的标准服务流程,确保基本服务规范。
- 可选HTTP模块:主要用于扩展标准的HTTP功能,让Nginx能处理一些特殊的服务,如Flash多媒体传输、解析GeoIP请求、SSL支持等。好比餐厅的特色服务,如生日庆祝、特殊餐饮需求等。
- 邮件服务模块:主要用于支持Nginx的邮件服务,包括对POP3协议、IMAP协议和SMTP协议的支持。可以比作餐厅的外卖配送部门,处理外部订单。
- 第三方模块:为了扩展Nginx服务器应用,完成开发者自定义功能。如同餐厅引进的特色厨师或特色菜品,丰富服务内容。
这种模块化设计就像是乐高积木,每个模块都有特定功能,可以按需组合,构建出强大而灵活的系统。
3 Nginx HTTP框架简介
3.1 HTTP框架的构成
Nginx的HTTP框架是由core模块ngx_http_module和http模块ngx_http_core_module共同定义的。
ngx_http_module定义了指令http,保存和管理各个层次里所有http模块的配置数据;而ngx_http_core_module则是http模块的“核心”模块,它定义了listen、server、location等http核心指令,搭建了Nginx的HTTP处理框架。
3.2 HTTP模块分类
在Nginx的HTTP框架中,模块按照功能可以分为四种类型:
- handler模块:直接处理客户端的请求,产生响应数据,是最常用的模块。好比餐厅中直接制作菜品的厨师。
- filter模块:对handler模块产生的数据做各种加工过滤处理。如同餐厅的摆盘师傅,让菜品更加美观。
- upstream模块:实现反向代理功能,转发请求到上游服务器,从后端获取响应数据再发回给客户端。就像餐厅的外购专员,当某些食材缺货时去其他市场采购。
- load-balance模块:不直接处理数据,而是实现负载均衡算法,从upstream块的配置里选择一个合适的上游服务器。如同餐厅的调度员,决定由哪位外购专员去采购。
4 Nginx的架构设计
4.1 Master-Worker多进程模式
Nginx服务器使用master/worker多进程模式。多线程启动和执行的流程如下:主程序Master process启动后,通过一个for循环来接收和处理外部信号;主进程通过fork()函数产生子进程,每个子进程执行一个for循环来实现Nginx服务器对事件的接收和处理。
- Master进程:也叫监控进程,主要监听外部请求和管理Worker子进程,还有负责更换日志文件、配置读取、平滑升级等。如同餐厅的店长,不直接服务客人,但管理所有服务员。
- Worker进程:也叫工作进程,负责与后端服务器通信,接收处理结果,Cache和响应客户端请求。好比餐厅的服务员,直接为客人服务。
一般推荐worker进程数与CPU内核数一致,这样一来不存在大量的子进程生成和管理任务,避免了进程之间竞争CPU资源和进程切换的开销。
4.2 异步非阻塞请求处理
Nginx是一个高性能的Web服务器,能够同时处理大量的并发请求。它结合多进程机制和异步机制,异步机制使用的是异步非阻塞方式。
每个工作进程使用异步非阻塞方式,可以处理多个客户端请求。当某个工作进程接收到客户端的请求以后,调用IO进行处理,如果不能立即得到结果,就去处理其他的请求(即为非阻塞);而客户端在此期间也无需等待响应,可以去处理其他事情(即为异步);当IO返回时,就会通知此工作进程;该进程得到通知,暂时挂起当前处理的事务去响应客户端请求。
这就像餐厅的高效服务员,不会等一道菜做完才服务下一桌,而是同时照顾多桌客人,厨房的菜好了再去取。
5 Nginx HTTP请求处理流程
如果我们忽略Nginx的异步机制,把注意力集中在处理逻辑上,那么可以看到Nginx的主处理流程与基本的HTTP处理流程区别并不大,它的工作流程如下:
- 监听端口,设置回调为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()按阶段处理请求。这是HTTP框架的核心部分,大部分http模块都在这里运行,最终产生响应数据;
- 调用ngx_http_send_header()发送响应头,从函数指针ngx_http_top_header_filter开始,通过header filter模块链过滤处理,最终发送处理过的响应头;
- 调用ngx_http_output_filter()发送响应体,从函数指针ngx_http_top_body_filter开始,通过body filter模块链过滤处理,最终发送处理过的响应体;
- 处理完毕,记录访问日志。
可以将这个过程比作医院的诊疗流程:挂号(接收连接)、分诊(解析请求)、医生诊断(处理请求)、开药方(生成响应)、取药(发送响应)、病历归档(记录日志)。
6 完整示例:编写自定义HTTP模块
6.1 开发HTTP模块流程
开发一个完整的简单的HTTP模块需要下面几个步骤(以模块名为ngx_http_mytest_module为例):
- 编写config文件(这是为了让nginx在configure过程能找到编写的模块)
- 编写模块结构 ngx_http_mytest_module
- 编写模块上下文结构 ngx_http_mytest_module_ctx
- 编写模块命令结构 ngx_http_mytest_commands
- 触发命令的回调函数 ngx_http_mytest
- 对http请求的具体处理方法 ngx_http_mytest_handler
6.2 示例代码实现
6.2.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"
HTTP_MODULES是设置HTTP需要加载的模块列表,在具体编译的时候会生成modules的数组,然后根据数组的先后顺序一个一个加载。
6.2.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 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, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configuration */
NULL /* merge location configuration */
};
// 定义模块
ngx_module_t ngx_http_mytest_module = {
NGX_MODULE_V1,
&ngx_http_mytest_module_ctx, /* module context */
ngx_http_mytest_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
6.2.3 编写命令处理函数
// 命令回调函数
static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
// 找到location配置
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
// 设置处理函数
clcf->handler = ngx_http_mytest_handler;
return NGX_CONF_OK;
}
6.2.4 编写请求处理函数
// 请求处理函数
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
ngx_buf_t *b;
ngx_chain_t out;
ngx_int_t rc;
u_char html_content[] = "<html><body><h1>Hello from mytest module!</h1></body></html>";
// 只支持GET和HEAD方法
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
}
// 丢弃请求体
rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
// 设置响应头
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = sizeof(html_content) - 1;
r->headers_out.content_type.len = sizeof("text/html") - 1;
r->headers_out.content_type.data = (u_char *) "text/html";
// 发送响应头
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
// 如果有请求体,准备发送
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
out.buf = b;
out.next = NULL;
b->pos = html_content;
b->last = html_content + sizeof(html_content) - 1;
b->memory = 1;
b->last_buf = (r == r->main) ? 1 : 0;
b->last_in_chain = 1;
// 发送响应体
return ngx_http_output_filter(r, &out);
}
6.3 编译和配置
6.3.1 编译Nginx
将上面的C代码保存为ngx_http_mytest_module.c,config文件保存为config,然后编译Nginx:
./configure --add-module=/path/to/your/module
make
make install
6.3.2 配置Nginx
在Nginx配置文件中添加:
server {
listen 80;
server_name localhost;
location /test {
mytest;
}
}
6.3.3 测试模块
重启Nginx后,访问http://your-server/test,你会看到显示"Hello from mytest module!"。
这个简单的示例展示了Nginx HTTP模块的基本结构。就像学会了做一个简单的菜,虽然简单,但包含了烹饪的基本步骤。
7 Nginx配置实例
7.1 常用指令详解
7.1.1 error_page指令
定义状态码跳转页面。状态码必须在300和599之间。
语法:error_page code ... [=[response]] uri;
常见用法:
error_page 500 502 503 504 /50x.html;
# 将404状态码改成200,并返回empty.gif
error_page 404 =200 /empty.gif;
# 使用变量的方式
location / {
error_page 404 = @fallback;
}
location @fallback {
proxy_pass http://backend;
}
# 使用url进行重定向
error_page 403 http://example.com/forbidden.html;
7.1.2 log_format指令
指定日志格式,可以定义多个,但是名称不能一样。
语法:log_format name [escape=default|json|none] string ...;
示例:
log_format test '"客户端地址:"$remote_addr ";客户端名称:" $remote_user'
'";请求协议:" $request ";请求地址:" $http_host ";请求状态:" $status'
'";浏览器信息:" $http_user_agent "===" $bytes_sent "===" $connection_requests "===" $request_length:"===" $request_time';
access_log logs/test.log test;
7.1.3 upstream指令
定义一组server。
语法:upstream name { ... }
示例:
upstream backend {
server backend1.example.com weight=5;
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server unix:/tmp/backend3;
server backup1.example.com backup;
}
接收到请求会进行轮询访问每个节点,如果其中一个节点宕机会尝试下一个节点,直到最后一个地址或者最后一个错误地址的通信信息。
参数说明:
weight:配置权重,默认是1max_fails:失败几次后会被剔除fail_timeout:失败重试时间backup:备用机(其他服务挂掉之后,才会被访问)down:标识服务器节点不可用
7.2 完整配置示例
下面是一个综合配置示例,展示了Nginx作为反向代理和负载均衡器的典型用法:
# 工作进程配置
worker_processes auto;
# 错误日志配置
error_log /var/log/nginx/error.log warn;
# 事件模块配置
events {
worker_connections 1024;
use epoll;
}
# HTTP模块配置
http {
# 基本设置
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
log_format detailed '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
# 访问日志
access_log /var/log/nginx/access.log main;
# 优化设置
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# 上游服务器配置
upstream backend {
# 负载均衡方法
least_conn;
server 10.0.0.1:8080 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 weight=2 max_fails=3 fail_timeout=30s;
server 10.0.0.3:8080 weight=1 max_fails=3 fail_timeout=30s;
server 10.0.0.4:8080 backup;
}
# 服务器配置
server {
listen 80;
server_name example.com;
# 根位置配置
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 10s;
}
# 健康检查
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# 静态文件服务
location /static/ {
alias /var/www/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# API接口
location /api/ {
proxy_pass http://backend/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# CORS设置
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, DELETE";
add_header Access-Control-Allow-Headers "Authorization, Content-Type";
# 限制请求大小
client_max_body_size 10m;
}
# 错误页面
error_page 404 /404.html;
location = /404.html {
internal;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
internal;
}
}
# 第二个虚拟主机
server {
listen 443 ssl http2;
server_name secure.example.com;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# 安全设置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
ssl_prefer_server_ciphers off;
location / {
root /var/www/secure;
index index.html;
# 安全头
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
}
}
}
这个配置就像是一个餐厅的完整运营手册,包含了从接待客人(监听端口)、菜单设计(location配置)、厨师团队(upstream配置)到服务标准(超时设置、缓存策略)等各个方面。
8 总结
Nginx的HTTP框架是一个经过精心设计的、高度模块化的系统,它通过事件驱动模型和异步非阻塞处理方式实现了高性能。其Master-Worker多进程模式既保证了稳定性,又充分利用了现代多核CPU的计算能力。
通过本文的介绍,我们了解了:
- Nginx的模块化设计,包括五大功能模块;
- HTTP框架的基本构成和四种HTTP模块类型;
- Nginx的请求处理流程;
- 如何编写自定义HTTP模块;
- Nginx的常用指令和配置方法。
Nginx就像一个瑞士军刀,看似简单,但内含多种功能,只要掌握得当,就能应对各种复杂的网络场景。
学习Nginx HTTP框架就像学习烹饪,开始时可能只会煮方便面(配置简单服务器),随着经验的积累,逐步能做出满汉全席(配置复杂的反向代理、负载均衡集群)。希望本文能为你的Nginx学习之旅打下良好基础!
553

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



