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 工作进程的设计方式。优点:
- 充分利用多核系统的并发处理能力 - Nginx 中所有的 worker 工作进程都是完全平等的
- 多个 worker 进程通过进程间通信来实现负载均衡
- 当工作进程出现问题时,管理进程可以启动新的工作进程来避免系统性能的下降,并支持服务运行时的程序升级、配置修改
8.2.5 平台无关的代码实现
Nginx 尽量减少使用与 OS 相关的代码,核心代码都使用了与 OS 无关的实现。造就了 Nginx 的可移植性。
8.2.6 内存池的设计
为了避免出现内存碎片、减少向 OS 申请内存的次数,Nginx 设计了简单的内存池。这样,把多次向 OS 申请内存的操作整合成一次,大大减少了 CPU 消耗与内存碎片。Nginx 为每一个 HTTP 请求申请了独立的内存池,为每一个 TCP 连接也申请了独立的内存池。内存池会随着请求的结束和连接的关闭而被销毁。用户甚至可以不同关心内存的释放问题。减少内存碎片能够提高内存利用率,增强网络性能。
8.2.7 使用统一管道过滤器模式的 HTTP 过滤模块
每一个过滤模块都有输入端和输出端,输入端和输出端都具有统一的接口。每个过滤模块都完全独立,处理输入端接收的数据,由输出端传递给下一个过滤模块。HTTP 过滤系统的输入输出被简化为一个个过滤模块的简单组合,并且完全支持并发执行。