Nginx特点:模块化、事件驱动、异步、非阻塞、多进程单线程,模块化设计增强了nginx源码的可读性、可扩展性、可维护性。
Nginx总共有5个大类模块:core、conf、event、http、mail 和 48个二类模块。每个模块都有属于自己的配置项,由commands字段决定;每个模块在初始化和退出销毁时均有回调函数。
实现多进程模式主要四个模块分别:脚本初始化、静态初始化、动态初始化、进程初始化。
- 脚本初始化是指在安装nginx时,由configure脚本生成的相关文件,比如ngx_modules.c文件包含了gnginx的所有模块;
- 静态初始化在编译时就完成,主要通过定义全局变量实现;
- 动态初始化在运行时完成,主要通过master进程main函数,ngx_init_cycle函数,及各模块文件内定义的init函数实现;
- 进程初始化是指各worker进程执行init_process函数。当nginx退出或重读配置文件或nginx平滑升级时,worker进程会调用各模块的exit_process函数来销毁资源。
四种角色模块
1、core(核心模块):构建nginx基础服务、管理其他模块;
2、handlers(处理模块):用于处理http请求,然后产生输出;
3、filters(过滤模块):过滤handler产生的输出;
4、load-balancers(负载均衡模块):当有多于一台的后端备选服务器时,选择一台转发http请求。
Nginx的核心模块主要负责建立nginx服务模型、管理网络层和应用层协议、以及启动针对特定应用的一系列候选模块。
1、当nginx发送文件或者转发请求到其他服务器,由handlers(处理模块)或load-balancers(负载均衡模块)提供服务。
2、当需要nginx把输出压缩或者在服务端一些东西,由filters(过滤模块)提供谷服务。
模块数据结构
核心:ngx_module_t(nginx所有模块的数据结构模板)
typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s {
// 下面的几个成员通常使用宏NGX_MODULE_V1填充
// 每类(http/event)模块各自的index, 初始化为-1
ngx_uint_t ctx_index;
// 在ngx_modules数组里的唯一索引,main()里赋值
// 使用计数器变量ngx_max_module
ngx_uint_t index;
// 1.10,模块的名字,标识字符串,默认是空指针
// 由脚本生成ngx_module_names数组,然后在ngx_preinit_modules里填充
// 动态模块在ngx_load_module里设置名字
char *name;
// 两个保留字段,1.9之前有4个
ngx_uint_t spare0;
ngx_uint_t spare1;
// nginx.h:#define nginx_version 1010000
ngx_uint_t version;
// 模块的二进制兼容性签名,即NGX_MODULE_SIGNATURE
const char *signature;
// 模块不同含义不同,通常是函数指针表,是在配置解析的某个阶段调用的函数
// core模块的ctx
//typedef struct {
// ngx_str_t name;
// void *(*create_conf)(ngx_cycle_t *cycle);
// char *(*init_conf)(ngx_cycle_t *cycle, void *conf);
//} ngx_core_module_t;
void *ctx;
// 模块支持的指令,数组形式,最后用空对象表示结束
ngx_command_t *commands;
// 模块的类型标识,相当于RTTI,如CORE/HTTP/STRM/MAIL等
ngx_uint_t type;
// 以下7个函数会在进程的启动或结束阶段被调用
// init_master目前nginx不会调用
ngx_int_t (*init_master)(ngx_log_t *log);
// 在ngx_init_cycle里被调用
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
// 在ngx_single_process_cycle/ngx_worker_process_init里调用
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
// init_thread目前nginx不会调用
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
// exit_thread目前nginx不会调用
void (*exit_thread)(ngx_cycle_t *cycle);
// 在ngx_worker_process_exit调用
void (*exit_process)(ngx_cycle_t *cycle);
// 在ngx_master_process_exit(os/unix/ngx_process_cycle.c)里调用
void (*exit_master)(ngx_cycle_t *cycle);
// 下面8个成员通常用用NGX_MODULE_V1_PADDING填充
// 暂时无任何用处
uintptr_t spare_hook0;
uintptr_t spare_hook1;
uintptr_t spare_hook2;
uintptr_t spare_hook3;
uintptr_t spare_hook4;
uintptr_t spare_hook5;
uintptr_t spare_hook6;
uintptr_t spare_hook7;
};
配置文件指令:ngx_command_t(主要负责模块与配置文件nginx.conf的交互)
struct ngx_command_s {
ngx_str_t name;
ngx_uint_t type;
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_uint_t conf;
ngx_uint_t offset;
void *post;
};
name:本条指令的名字,例如worker_processes 1;对应的ngx_command_s.name就是worker_processes。
set:函数指针,所以set用来表示,当Nginx解析配置文件,碰到指令时,该执行怎样的操作。而该操作本身,自然是用来设置本模块所对应的ngx_module name>_conf_t结构体。
conf:这个变量只在NGX_HTTP_MODULE类型的模块的ngx_command_t使用。这个变量和今天讨论的话题关系不大。暂不讨论。
offset:这个变量用来标记ngx_module name>_conf_t中某成员变量的偏移量,纯粹是为使用方便。
type:配置指令属性的集合。例如,worker_processes这条指令对应的type定义为:
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1
其中,各个宏定义如下:
#define NGX_MAIN_CONF 0x01000000
#define NGX_DIRECT_CONF 0x00010000
#define NGX_CONF_TAKE1 0x00000002
NGX_MAIN_CONF是指,该指令用于main上下文。
NGX_DIRECT_CONF是指,该指令是用于main上下文的简单指令。
NGX_CONF_TAKE1是指,该指令后跟一个参数,例如worker_processes 1;的1就是指后面跟一个参数,这个参数的数目由NGX_CONF_TAKE1指定。
指令配置:ngx_conf_t(解析某个具体的指令)
typedef char *(*ngx_conf_handler_pt)(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
typedef struct ngx_conf_s ngx_conf_t;
struct ngx_conf_s {
char *name; // 指令名
ngx_array_t *args; // 指令后的参数
ngx_cycle_t *cycle; // 全局数据结构
ngx_pool_t *pool; // 内存池
ngx_pool_t *temp_pool; // 临时内存池
ngx_conf_file_t *conf_file; // 指令所在配置文件
ngx_log_t *log; // 日志记录
void *ctx; // 模块上下文
ngx_uint_t module_type; // 模块类型
ngx_uint_t cmd_type; // 指令类型
ngx_conf_handler_pt handler; // set函数指针
char *handler_conf; // set函数返回值
};
全局配置:ngx_cycle_t(nginx绝大部分初始化操作都围绕该结构体)
typedef struct ngx_cycle_s ngx_cycle_t;
struct ngx_cycle_s {
void ****conf_ctx; // 配置上下文数组(含所有模块)
ngx_pool_t *pool; // 内存池
ngx_log_t *log; // 日志
ngx_log_t new_log;
ngx_connection_t **files; // 连接文件
ngx_connection_t *free_connections; // 空闲连接
ngx_uint_t free_connection_n; // 空闲连接个数
ngx_queue_t reusable_connections_queue; // 再利用连接队列
ngx_array_t listening; // 监听套接字数组
ngx_array_t pathes; // 路径数组
ngx_list_t open_files; // 打开文件链表
ngx_list_t shared_memory; // 共享内存链表
ngx_uint_t connection_n; // 连接个数
ngx_uint_t files_n; // 打开文件个数
ngx_connection_t *connections; // 连接
ngx_event_t *read_events; // 读事件
ngx_event_t *write_events; // 写事件
ngx_cycle_t *old_cycle; // old cycle指针
ngx_str_t conf_file; // 配置文件
ngx_str_t conf_param; // 配置参数
ngx_str_t conf_prefix; // 配置文件目录
ngx_str_t prefix; // 程序工作目录
ngx_str_t lock_file; // 锁文件,用在不支持accept_mutex的系统中
ngx_str_t hostname; // 主机名
};
模块调用流程
当服务器启动,每个handlers(处理模块)都有机会映射到配置文件中定义的特定位置(location);如果有多个handlers(处理模块)映射到特定位置时,只有一个会“赢”(说明配置文件有冲突项,应该避免发生)。处理模块以三种形式返回:
OK
ERROR
或者放弃处理这个请求而让默认处理模块来处理(主要是用来处理一些静态文件,事实上如果是位置正确而真实的静态文件,默认的处理模块会抢先处理)。
如果handlers(处理模块)把请求反向代理到后端的服务器,就变成另外一类的模块:load-balancers(负载均衡模块)。负载均衡模块的配置中有一组后端服务器,当一个HTTP请求过来时,它决定哪台服务器应当获得这个请求。Nginx的负载均衡模块采用两种方法:
轮转法,它处理请求就像纸牌游戏一样从头到尾分发;
IP哈希法,在众多请求的情况下,它确保来自同一个IP的请求会分发到相同的后端服务器。
如果handlers(处理模块)没有产生错误,filters(过滤模块)将被调用。多个filters(过滤模块)能映射到每个位置,所以(比如)每个请求都可以被压缩成块。它们的执行顺序在编译时决定。filters(过滤模块)是经典的“接力链表(CHAIN OF RESPONSIBILITY)”模型:一个filters(过滤模块)被调用,完成其工作,然后调用下一个filters(过滤模块),直到最后一个filters(过滤模块)。过滤模块链的特别之处在于:
每个filters(过滤模块)不会等上一个filters(过滤模块)全部完成;
它能把前一个过滤模块的输出作为其处理内容;有点像Unix中的流水线。
(过滤模块能以buffer(缓冲区)为单位进行操作,这些buffer一般都是一页(4K)大小,当然你也可以在nginx.conf文件中进行配置。这意味着,比如,模块可以压缩来自后端服务器的响应,然后像流一样的到达客户端,直到整个响应发送完成。)
过滤模块链以流水线的方式高效率地向客户端发送响应信息。
客户端发送HTTP请求 –>
Nginx基于配置文件中的位置选择一个合适的处理模块 ->
(如果有)负载均衡模块选择一台后端服务器 –>
处理模块进行处理并把输出缓冲放到第一个过滤模块上 –>
第一个过滤模块处理后输出给第二个过滤模块 –>
然后第二个过滤模块又到第三个 –>
依此类推 –> 最后把响应发给客户端。
Nginx请求处理
Nginx在启动时会以daemon形式在后台运行,采用多进程+异步非阻塞IO事件模型来处理各种连接请求。多进程模型包括一个master进程,多个worker进程,一般worker进程个数是根据服务器CPU核数来决定的。master进程负责管理Nginx本身和其他worker进程。