揭秘Nginx架构-10000并发连接背后的秘密


在当今移动互联网时代,网站的高并发、高性能成为了决胜关键。作为一款出色的开源Web服务器,Nginx以其非凡的高并发处理能力成为了架构师的利器。让我们一同揭开Nginx源码的神秘面纱,探索它高效处理海量请求的秘密。


一、为何高并发是如此重要?


2000年代以来,随着PC、移动设备的普及,互联网用户数激增。网站的并发连接数也随之大幅提高,常见的网站要承载数十万甚至上百万的并发请求。与此同时,Ajax和HTTP长连接的应用使并发压力进一步加大。传统的多进程/多线程模型在处理高并发场景时,将消耗大量内存和CPU资源,难以满足性能需求。


二、Nginx架构独树一帜


传统Web服务器如Apache在接受新连接时会为之创建新的进程或线程,这种模式在高并发下会带来不小的系统开销。


1、Nginx则採用了全新的设计

  • 采用单线程异步事件驱动模型,无需为每个请求派生新线程,避免了线程切换和内存占用的开销。

  • 利用操作系统提供的多种IO复用epoll、kqueue等机制,可以同时监听和响应大量客户端连接请求。

  • master-workers模型,master进程负责读取配置、监控worker进程;worker进程专注于网络事件的接收和发送。

  • 模块化设计,方便功能扩展和代码热升级,核心代码紧凑,占用资源少。


    揭秘Nginx架构-10000并发连接背后的秘密_第1张图片


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;iworker_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 请求的处理周期通常包括以下步骤:

  1. 客户端发送 HTTP 请求。
  2. Nginx 核心根据请求匹配的配置选择相应的阶段处理程序。
  3. 如有配置,负载均衡器会选择上游服务器进行代理。
  4. 阶段处理程序完成工作,并将输出缓冲区传递给第一个过滤器。
  5. 过滤器链依次处理输出,直至最终响应发送给客户端。
  6. Nginx 模块的调用具有高度的可定制性,通过回调指针指向可执行函数来实现。

模块可以附加在多个位置,例如:

  • 配置文件读取和处理之前。
  • 每个配置指令的位置和服务器。
  • 主配置初始化时。
  • 服务器(主机/端口)初始化时。
  • 服务器配置与主配置合并时。
  • 位置配置初始化或与其父服务器配置合并时。
  • 主进程启动或退出时。
  • 工作进程启动或退出时。
  • 请求处理时。
  • 响应头和主体过滤时。
  • 上游服务器请求的选择、发起和重试时。
  • 上游服务器响应处理时。
  • 与上游服务器交互完成时。

2、内部工作循环

在内部工作循环中,Nginx 通过以下步骤处理请求:

  1. 开始 ngx_worker_process_cycle()。
  2. 使用操作系统特定的事件处理机制(如 epoll 或 kqueue)。
  3. 接受事件并分派动作。
  4. 处理/代理请求的标头和正文。
  5. 生成响应内容并流式传输到客户端。
  6. 完成请求。
  7. 重新初始化计时器和事件。

3、HTTP 请求处理视图

HTTP 请求的详细处理视图包括:

  1. 初始化请求处理。

  2. 处理标头。

  3. 处理主体。

  4. 调用相关处理程序。

  5. 运行处理阶段。

Nginx 在处理 HTTP 请求时,会将其传递给多个处理阶段,每个阶段都有相应的处理程序。阶段处理程序通常执行以下操作:获取位置配置、生成响应、发送标头和正文。处理程序接收一个描述请求的结构作为参数,该结构包含请求方法、URI 和标头等信息。


4、 HTTP 请求标头处理

当读取 HTTP 请求标头时,Nginx 会查找虚拟服务器配置。如果找到,请求将经历以下阶段:

  1. 服务器重写阶段。

  2. 定位阶段。

  3. 位置重写阶段。

  4. 访问控制阶段。

  5. try_files 阶段。

  6. 日志记录阶段。


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性能领域创造更多奇迹吧!

你可能感兴趣的:(Nginx,nginx,架构,运维)