Mr Dk.'s BlogMr Dk.'s Blog
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🗜️ Understanding Nginx
    • Part 1 - Nginx 能帮我们做什么

      • Chapter 1 - 研究 Nginx 前的准备工作
      • Chapter 2 - Nginx 的配置
    • Part 2 - 如何编写 HTTP 模块

      • Chapter 7 - Nginx 提供的高级数据结构
    • Part 3 - 深入 Nginx

      • Chapter 8.1-8.2 - Nginx 基础架构
      • Chapter 8.3-8.6 - Nginx 框架核心结构体
      • Chapter 8.7 - Nginx 内存池
      • Chapter 9.1-9.3 - 事件处理框架
      • Chapter 9.4-9.6 - 事件驱动模块与 EPOLL
      • Chapter 9.7-9.8 - 定时器事件与事件驱动框架处理流程
      • Chapter 9.9-9.10 - 文件的异步 I/O 与 TCP
      • Chapter 10.1-10.2 - HTTP 框架的配置解析与合并
      • Chapter 10.3-10.7 - HTTP 阶段划分与框架初始化
      • Chapter 11 - HTTP 框架的执行流程
      • Chapter 12.1-12.4 - Upstream 与上游服务器通信
      • Chapter 12.5 - 接收上游服务器响应并处理
      • Chapter 12.6 - 12.9 - 转发响应并结束请求
      • Chapter 13.1-13.5 - 邮件代理模块 - 认证服务器
      • Chapter 13.6-13.7 - 邮件代理模块 - 上游
      • Chapter 14 - 进程间通信机制
      • Chapter 16 - slab 共享内存

Chapter 8.1-8.2 - Nginx 基础架构

Created by : Mr Dk.

2020 / 07 / 19 14:09

Nanjing, Jiangsu, China


8.1 Web 服务器设计中的关键约束

(可能是架构师要考虑的问题...但我好希望有朝一日我也能有机会考虑这些)

  • 性能 (由于 Nginx 是一个 Web 服务器,因此性能主要从网络角度出发)
    • 网络性能 - 不同负载下,Web 服务在网络通信上的吞吐量 (并发数上升)
    • 单次请求延迟 - 针对用户而言
    • 网络效率 - 使用长连接减少建立 / 关闭连接带来的开销,使用压缩算法,使用缓存等
  • 可伸缩性 - 降低组件间耦合度、简化组件、服务拆分
  • 简单性
  • 可修改性 - 当前架构下对系统做出修改的难易程度
  • 可见性 - 关键组件运行情况可以被监控
  • 可移植性 - 跨平台运行
  • 可靠性 - 架构容易收到系统层面故障影响的程度

8.2 Nginx 的架构设计

8.2.1 优秀的模块化设计

Nginx 中除了少量核心代码,其它一切皆模块。这种高度模块化的设计具有以下几个优点:

  • 高度抽象的模块接口 - 所有模块都遵循相同的接口 ngx_module_t

  • 模块接口简单 - 只涉及模块初始化、退出、配置处理等,且特权级高

  • 配置模块 - 使 Nginx 具有高可配置性、高可扩展性、高可定制性、高可伸缩性

  • 官方提供六个核心模块 NGX_CORE_MODULE,其它非核心模块只需要关心如何调用核心模块即可

    typedef struct {
        ngx_str_t name; // 核心模块名称
        void *(*create_conf) (ngx_cycle_t *cycle); // 解析配置项前由 Nginx 框架调用
        char *(*init_conf) (ngx_cycle_t *cycle, void *conf); // 配置项解析完毕后,通过配置项初始化模块
    } ngx_core_module_t;
    

    每个核心模块又可以定义各自的新类型:

    • NGX_EVENT_MODULE - 事件模块类型
    • NGX_HTTP_MODULE - HTTP 模块类型
    • NGX_MAIL_MODULE - 邮件模块类型
  • 多层次、多类别的模块设计 - Nginx 官方共有五大类型模块:

    • 核心模块
    • 配置模块
    • 事件模块
    • HTTP 模块
    • MAIL 模块

    后三个模块会再次具体化 ngx_module_t 接口,派生出负责各自功能的模块

8.2.2 事件驱动架构

由事件发生源产生事件,由一个或多个事件收集器来收集、分发事件,事件处理器注册自己感兴趣的事件并消费这些事件。具体到 Nginx 来说,由网卡、磁盘产生事件,由事件模块负责事件的收集、分发,所有的模块都可能是事件消费者。

对于传统的 Web 服务器,事件驱动局限于 TCP 连接建立、关闭,在连接建立后到关闭之前,所有操作都不再是事件驱动。因此,在连接存在期间,每个请求将一直占用系统资源,造成了服务器资源的浪费。传统的 Web 服务器通常把进程或线程作为事件消费者。当一个请求被一个进程处理时,请求将一直占用进程直至结束。这将导致大量的上下文切换开销。

Nginx 不使用进程或线程作为事件消费者,事件消费者只能是某个模块。Nginx 在收集完事件后,开始使用当前进程分发事件,调用相应的事件消费者模块来处理事件。与前者的区别是,前者每个事件消费者独占一个进程资源,后者事件消费者只是被事件分发者进程 短期调用。这种设计使得网络吞吐量得到提升。但是这要要求了事件消费者 不能有阻塞行为,否则会长时间占用事件分发者进程导致其它事件得不到响应 - 每个事件消费者不可以让进程转换为 休眠状态 或 阻塞状态。

8.2.3 请求的多阶段异步处理

把一个请求的处理过程按照事件的触发方式 划分为多个阶段,每个阶段都可以由事件分发器触发。每个阶段中的事件消费者不知道本次完整操作什么时候会完成,只能被动等待下一次被调用。这种处理方式极高地提升了网络的性能,使得每一个进程都能全力运转,不会或尽可能少地出现进程休眠的状况。一旦出现进程休眠,为了能够及时处理更多的请求,服务器只能增加进程的数量,进程数量过多就会带来上下文切换的开销,也会使得进程占用的内存得不到及时的释放。

划分阶段的原则 - 找到请求处理流程中的 阻塞方法:

  • 将阻塞方法改为非阻塞方法,第一阶段执行到非阻塞方法执行完毕为止 (立刻将控制权归还进程),第二阶段用于处理非阻塞方法的结果
  • 按时间分解阻塞方法 - 10MB 的文件 I/O,分解为每次 10KB (?)
  • 使用定时器来实现需要 CPU 空转等待结果的场景 (定期检查是否完成,如果没有完成则立刻归还控制权,并设置下一个定时器事件)
  • 如果阻塞方法无法划分,则必须使用独立的进程执行阻塞方法 (需要审视这样的事件消费者是否合理)

8.2.4 管理进程、多工作进程设计

Nginx 采用一个 master 管理进程,多个 worker 工作进程的设计方式。优点:

  1. 充分利用多核系统的并发处理能力 - Nginx 中所有的 worker 工作进程都是完全平等的
  2. 多个 worker 进程通过进程间通信来实现负载均衡
  3. 当工作进程出现问题时,管理进程可以启动新的工作进程来避免系统性能的下降,并支持服务运行时的程序升级、配置修改

8.2.5 平台无关的代码实现

Nginx 尽量减少使用与 OS 相关的代码,核心代码都使用了与 OS 无关的实现。造就了 Nginx 的可移植性。

8.2.6 内存池的设计

为了避免出现内存碎片、减少向 OS 申请内存的次数,Nginx 设计了简单的内存池。这样,把多次向 OS 申请内存的操作整合成一次,大大减少了 CPU 消耗与内存碎片。Nginx 为每一个 HTTP 请求申请了独立的内存池,为每一个 TCP 连接也申请了独立的内存池。内存池会随着请求的结束和连接的关闭而被销毁。用户甚至可以不同关心内存的释放问题。减少内存碎片能够提高内存利用率,增强网络性能。

8.2.7 使用统一管道过滤器模式的 HTTP 过滤模块

每一个过滤模块都有输入端和输出端,输入端和输出端都具有统一的接口。每个过滤模块都完全独立,处理输入端接收的数据,由输出端传递给下一个过滤模块。HTTP 过滤系统的输入输出被简化为一个个过滤模块的简单组合,并且完全支持并发执行。

Edit this page on GitHub
Next
Chapter 8.3-8.6 - Nginx 框架核心结构体