在当今移动互联网时代,网站的高并发、高性能成为了决胜关键。作为一款出色的开源Web服务器,Nginx以其非凡的高并发处理能力成为了架构师的利器。让我们一同揭开Nginx源码的神秘面纱,探索它高效处理海量请求的秘密。
一、为何高并发是如此重要?
2000年代以来,随着PC、移动设备的普及,互联网用户数激增。网站的并发连接数也随之大幅提高,常见的网站要承载数十万甚至上百万的并发请求。与此同时,Ajax和HTTP长连接的应用使并发压力进一步加大。传统的多进程/多线程模型在处理高并发场景时,将消耗大量内存和CPU资源,难以满足性能需求。
二、Nginx架构独树一帜
传统Web服务器如Apache在接受新连接时会为之创建新的进程或线程,这种模式在高并发下会带来不小的系统开销。
1、Nginx则採用了全新的设计
-
采用单线程异步事件驱动模型,无需为每个请求派生新线程,避免了线程切换和内存占用的开销。
-
利用操作系统提供的多种IO复用epoll、kqueue等机制,可以同时监听和响应大量客户端连接请求。
-
master-workers模型,master进程负责读取配置、监控worker进程;worker进程专注于网络事件的接收和发送。
-
模块化设计,方便功能扩展和代码热升级,核心代码紧凑,占用资源少。
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/ec220653dad94f3a8696ef50d940f1d9.png#pic_center)
2、nginx的架构优势
nginx采用事件驱动、异步、单线程、非阻塞的架构:
- 不为每个连接创建新进程/线程
- 使用事件通知机制
- 在有限的单线程worker进程中处理连接
- 每个worker可以处理数千个并发连接
3、高性能缓存架构
Nginx提供了健全的缓存方案,可将Web内容缓存到文件系统,充分利用操作系统的文件系统缓存,从而大幅提升访问速度。
Nginx实现了基于MD5hash的缓存key算法,将缓存内容存储在磁盘的目录结构中,再由缓存加载器和缓存管理器进程高效地将元数据加载到内存中。当访问一个已缓存页面时,Nginx可直接从内存中读取,命中率非常高。
//nginx缓存相关数据结构
typedef struct {
ngx_uint_t age;
ngx_uint_t valid_sec;
ngx_uint_t valid;
ngx_uint_t date;
ngx_str_t response_cache_control;
} ngx_http_cache_control_t;
//计算缓存key
static ngx_int_t
ngx_http_file_cache_set_path(ngx_http_request_t *r)
{
ngx_md5_t md5;
u_char md5_file[NGX_HTTP_CACHE_MD5_FILE_LEN];
ngx_http_cache_t *cache;
cache = r->cache;
if (ngx_http_cache_md5_create_key(r->cache->keys, &md5) != NGX_OK) {
return NGX_ERROR;
}
ngx_md5_final(md5_file, &md5);
ngx_snprintf(cache->file, NGX_HTTP_CACHE_FILE_MAX_LEN, "%V/%*xXXXXXXXXXXXXXXXXXXXXXX"
"%xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
&cache->line->path, 16, md5_file);
return NGX_OK;
}
4、Nginx启动流程演示
下面是Nginx启动流程的示例代码:
int main(int argc, char *const *argv)
{
...
//初始化内存池
cycle_pool = ngx_create_pool(cycle->log);
//初始化主进程循环
init_cycle_cpus(cycle);
//初始化进程信号处理
sigemptyset(&set);
//创建守护进程
if (ngx_daemon(cycle->log) == NGX_INVALID_PID) {
return NGX_INVALID_PID;
}
//创建worker子进程
for(i=0;i<cycle->worker_processes;i++) {
ngx_spawn_process(cycle,"worker",ngx_worker_process_cycle);
}
//master进程主循环
for(;;) {
sigsuspend();
...
ngx_master_process_cycle();
}
}
5、配置文件设计优雅简洁
Nginx的配置文件采用了直观简洁的分段式设计,核心思想是一目了然、易于维护。下面是一个配置文件示例:
events {
worker_connections 1024;
}
http {
upstream backend {
server backend1.example.com weight=5;
server backend2.example.com;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}
该配置文件定义了一个负载均衡器backend,并将所有对server的请求转发给后端服务器组。区块划分清晰,层层递进,修改维护都非常方便。
三、内部实现原理剖析
Nginx 的核心架构由核心代码和众多模块构成。核心代码主要负责构建 Web 服务器的基础功能,包括 Web 和邮件的反向代理服务,同时支持底层网络协议的使用、运行环境的构建以及不同模块间的协调工作。而具体的协议和应用程序功能则大多由模块来实现。
在 Nginx 的内部,连接是通过模块管道或链路进行处理的。每个操作都由特定的模块来执行,如数据压缩、内容修改、服务器端包含执行、与 FastCGI 或 uwsgi 协议的上游应用服务器通信,或是与 memcached 的交互。
在核心与功能模块之间,存在一些中间模块,如 http 和 mail 模块。它们为 HTTP、SMTP 或 IMAP 等应用层协议的事件序列提供了额外的抽象层,并确保了对功能模块的正确调用顺序。HTTP 协议目前是作为 http 模块的一部分实现的,但为了支持如 SPDY 等其他协议,未来计划将其分离为独立的功能模块。
功能模块可以进一步划分为事件模块、阶段处理程序、输出过滤器、变量处理程序、协议、上游和负载均衡器等。这些模块主要增强了 Nginx 的 HTTP 功能,尽管事件模块和协议模块也被用于邮件处理。事件模块提供了依赖于操作系统的事件通知机制,如 kqueue 或 epoll,其使用取决于操作系统特性和构建配置。协议模块则允许 Nginx 通过 HTTPS、TLS/SSL、SMTP、POP3 和 IMAP 等协议进行通信。
1、HTTP 请求的处理周期
HTTP 请求的处理周期通常包括以下步骤:
- 客户端发送 HTTP 请求。
- Nginx 核心根据请求匹配的配置选择相应的阶段处理程序。
- 如有配置,负载均衡器会选择上游服务器进行代理。
- 阶段处理程序完成工作,并将输出缓冲区传递给第一个过滤器。
- 过滤器链依次处理输出,直至最终响应发送给客户端。
- Nginx 模块的调用具有高度的可定制性,通过回调指针指向可执行函数来实现。
模块可以附加在多个位置,例如:
- 配置文件读取和处理之前。
- 每个配置指令的位置和服务器。
- 主配置初始化时。
- 服务器(主机/端口)初始化时。
- 服务器配置与主配置合并时。
- 位置配置初始化或与其父服务器配置合并时。
- 主进程启动或退出时。
- 工作进程启动或退出时。
- 请求处理时。
- 响应头和主体过滤时。
- 上游服务器请求的选择、发起和重试时。
- 上游服务器响应处理时。
- 与上游服务器交互完成时。
2、内部工作循环
在内部工作循环中,Nginx 通过以下步骤处理请求:
- 开始 ngx_worker_process_cycle()。
- 使用操作系统特定的事件处理机制(如 epoll 或 kqueue)。
- 接受事件并分派动作。
- 处理/代理请求的标头和正文。
- 生成响应内容并流式传输到客户端。
- 完成请求。
- 重新初始化计时器和事件。
3、HTTP 请求处理视图
HTTP 请求的详细处理视图包括:
-
初始化请求处理。
-
处理标头。
-
处理主体。
-
调用相关处理程序。
-
运行处理阶段。
Nginx 在处理 HTTP 请求时,会将其传递给多个处理阶段,每个阶段都有相应的处理程序。阶段处理程序通常执行以下操作:获取位置配置、生成响应、发送标头和正文。处理程序接收一个描述请求的结构作为参数,该结构包含请求方法、URI 和标头等信息。
4、 HTTP 请求标头处理
当读取 HTTP 请求标头时,Nginx 会查找虚拟服务器配置。如果找到,请求将经历以下阶段:
-
服务器重写阶段。
-
定位阶段。
-
位置重写阶段。
-
访问控制阶段。
-
try_files 阶段。
-
日志记录阶段。
5、内容处理
为了生成响应内容,Nginx 将请求传递给适当的内容处理程序。根据配置,Nginx 可能首先尝试无条件处理程序,如 perl、lua 等。如果不匹配,则按顺序尝试 proxy_pass、flv、mp4、random、index、autoindex、gzip_static、static 等处理程序。
6、过滤器
内容处理程序的输出随后被传递给过滤器。过滤器可以为一个位置配置多个,并且执行顺序在编译时确定。过滤器只能进行出站更改,目前没有输入内容转换的机制。过滤器链的设计模式是:调用、工作、调用下一个过滤器,直至链尾。生成的输出响应可以在收到上游服务器完整响应前传递给客户端。
过滤器分为标头过滤器和主体过滤器,分别处理响应的标头和主体。标头过滤器的基本步骤包括:决定是否操作响应、根据响应进行操作、调用下一个过滤器。主体过滤器则转换生成的内容,如服务器端包含、XSLT 过滤、图像过滤、字符集修改、gzip 压缩、分块编码等。
在过滤器链之后,响应被传递给写入器。除此之外,还有 copy 和 postpone 特殊用途过滤器。copy 过滤器负责用响应内容填充内存缓冲区,而 postpone 过滤器用于子请求。
7、子请求
子请求是 Nginx 强大的机制之一,允许从不同 URL 返回结果。子请求可以嵌套和分层,适用于根据原始响应数据插入其他内容。例如,SSI 模块使用过滤器解析文档内容,并用指定 URL 的内容替换。
8、负载均衡器
上游和负载均衡器模块用于实现反向代理。上游模块准备发送到上游服务器的请求并接收响应,而负载均衡器模块则在多个上游服务器中选择一个。目前,Nginx 支持循环和 ip-hash 两种负载平衡规则。
Nginx 还计划对负载平衡器进行改进,包括故障检测和健康检查。此外,还有一些模块提供额外的变量,如 geo 和 map 模块,用于根据客户端 IP 地址跟踪客户端和灵活映射主机名等。
9、内存分配机制
Nginx 的内存分配机制受到 Apache 的启发,动态分配内存缓冲区用于存储和操作请求和响应的标头和正文,并在释放连接时释放。Nginx 尽量避免内存中的数据复制,并通过指针传递数据。
模块生成响应时,内容会被放入内存缓冲区并添加到缓冲区链中。缓冲区链的管理非常复杂,因为存在多种处理场景。Nginx 提供了低级 API 来操作缓冲区链,但第三方模块开发人员需要非常熟悉这一部分。
内存分配由 Nginx 的池分配器管理,共享内存区域用于互斥锁、缓存元数据、SSL 会话缓存等。Nginx 实现了 slab 分配器来管理共享内存分配,并提供了红黑树实现来组织复杂的数据结构。
五、持续优化的无限可能
Nginx一直在不断优化和创新,以满足日益增长的性能需求。例如引入了线程池和Lua协程等新功能,旨在解决阻塞、嵌入式脚本等痛点。值得一提的是,Nginx已向Windows系统渗透,为跨平台应用提供了更多可能。总之,Nginx的发展前景一片光明,继续期待它在Web性能领域创造更多奇迹吧!