Nginx分析

时间:2021-01-25 00:54:38

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请求处理

Nginx在启动时会以daemon形式在后台运行,采用多进程+异步非阻塞IO事件模型来处理各种连接请求。多进程模型包括一个master进程,多个worker进程,一般worker进程个数是根据服务器CPU核数来决定的。master进程负责管理Nginx本身和其他worker进程。