Nginx学习笔记(一)
Nginx初识
Nginx架构
Nginx在后台运行包含一个master进程和多个worker进程。所以Nginx一般以多进程方式运行,且支持**多线程**,在调试时也可以选择以单进程方式运行。
master进程用来管理worker进程,可以接受外界信号,向worker发送信号,监控worker状态。worker平等且独立处理各个请求,worker进程的个数可以设置,一般与CPU核数一致。
Nginx进程模型如图1.1:

Nginx采用**异步非阻塞**的方式处理请求。完整的请求过程如下:
事件进入后,若没有准备好,返回EAGAIN,设置超时时间,同一时间内监控多个事件,在此期间可以处理其他事情,在此时间内有事件准备好就返回。
基础概念
connection
connection是对TCP连接的封装,包括连接的socket,读事件,写事件。http请求也是建立在connection上的,所以Nginx可以作为web服务器,也可以作为邮件服务器。
Nginx处理连接的过程如下:
Nginx在启动时会解析配置文件,得到监听端口和IP地址,然后在master进程中初始化socket,fork多个子进程。此时客户端可以发起连接,当客户端与服务器三次握手建立连接后,某一子进程会accept成功,得到建立好的连接的socket,然后创建对连接的封装,进行事件的处理与数据交换。最后,Nginx或客户端主动关闭连接。
在操作系统中,通过ulimit -n,我们可以得到一个进程所能打开的fd(file descriptor)的最大数,即nofile,这会限制我们进程的最大连接数,影响程序所能支持的最大并发数。Nginx通过设置worker_connections来设置每个进程的支持的最大连接数,如果该值大于noflie,那实际最大连接数就是noflie。Nginx在实现时,通过连接池管理,每个worker进程有一个独立的连接池,连接池大小是worker_connections。Nginx所能建立的最大连接数应是:worker_connections * worker_processes,对于HTTP请求本地资源也是同样,如果HTTP作为反向代理,最大并发量应为:worker_connections * worker_processes / 2。因为作为反向代理服务器,每个并发会建立与客户端的连接和与后端服务的连接,会占用两个连接。
request
request指的是HTTP请求,在Nginx中的数据结构是ngx_http_request_t。ngx_http_request_t是对一个HTTP请求的封装,包括请求行,请求头,请求体,响应行,响应头,响应体。
Nginx中一个HTTP请求的生命周期如图1.2.2

keepalive
HTTP请求是请求应答式的,如果能知道每个请求头与响应体的长度,就可以在一个连接上执行多个请求,这就是长连接。
如果客户端的请求头中的 connection为close,则表示客户端需要关掉长连接,如果为 keep-alive,则客户端需要打开长连接,如果客户端的请求中没有 connection 这个头,那么根据协议,如果是 http1.0,则默认为 close,如果是 http1.1,则默认为 keep-alive。当 Nginx 设置了 keepalive 等待下一次的请求时,同时也会设置一个最大等待时间,这个时间是通过选项 keepalive_timeout 来配置的,如果配置为 0,则表示关掉 keepalive,此时,http 版本无论是 1.1 还是 1.0,客户端的 connection 不管是 close 还是 keepalive,都会强制为 close。一般来说,当客户端的一次访问,需要多次访问同一个 server 时,打开 keepalive 的优势非常大,比如图片服务器。打开 keepalive 会大量减少 time-wait 的数量。
pipe
在 http1.1 中,引入了一种新的特性,即 pipeline,流水线作业,它可以看作为 keepalive 的一种升华,因为 pipeline 也是基于长连接的,目的就是利用一个连接做多次请求。如果客户端要提交多个请求,对于keepalive来说,那么第二个请求,必须要等到第一个请求的响应接收完全后,才能发起,这和 TCP 的停止等待协议是一样的,得到两个响应的时间至少为2*RTT。而对 pipeline 来说,客户端不必等到第一个请求处理完后,就可以马上发起第二个请求,得到两个响应的时间可能能够达到1*RTT。
Nginx 对 pipeline 中的多个请求的处理不是并行的,依然是一个请求接一个请求的处理,只是在处理第一个请求的时候,客户端就可以发起第二个请求。Nginx 利用 pipeline 减少了处理完一个请求后,等待第二个请求的请求头数据的时间。
lingering_close
lingering_close是延迟关闭,也就是说,当 Nginx 要关闭连接时,并非立即关闭连接,而是先关闭 tcp 连接的写,再等待一段时间后再关掉连接的读。
基本数据结构
ngx_str_t
带长度的字符串结构,原型如下:
typedef struct { size_t len; u_char *data; } ngx_str_t;
在结构体当中,data 指向字符串数据的第一个字符,字符串的结束用长度来表示,而不是由'\\0'来表示结束。基于此特性,在 Nginx 中,必须谨慎的去修改一个字符串。
这样做有两点好处,首先,通过长度来表示字符串长度,减少计算字符串长度的次数。其次,Nginx 可以重复引用一段字符串内存,data 可以指向任意内存,长度表示结束,而不用去 copy 一份自己的字符串(因为如果要以'\\0'结束,而不能更改原字符串,所以势必要 copy 一段字符串),减少了很多不必要的内存分配与拷贝。
Nginx 提供的操作字符串相关的 API 如下:
#define ngx_string(str) { sizeof(str) - 1, (u_char *) str }
ngx_string(str) 是一个宏,它通过一个以'\\0'结尾的普通字符串 str 构造一个 Nginx 的字符串,鉴于其中采用 sizeof 操作符计算字符串长度,因此参数必须是一个常量字符串。
#define ngx_null_string { 0, NULL }
定义变量时,使用 ngx_null_string 初始化字符串为空字符串,长度为 0,data 为 NULL。
#define ngx_str_set(str, text) (str)->len = sizeof(text) - 1; (str)->data = (u_char *) text
ngx_str_set 用于设置字符串 str 为 text,由于使用 sizeof 计算长度,故 text 必须为常量字符串。
#define ngx_str_null(str) (str)->len = 0; (str)->data = NULL
ngx_str_null 用于设置字符串 str 为空串,长度为 0,data 为 NULL。
ngx_pool_t
ngx_pool_t 提供了一种机制,帮助管理一系列的资源(如内存,文件等),使得对这些资源的使用和释放统一进行,免除了使用过程中考虑到对各种各样资源的什么时候释放,是否遗漏了释放的担心。
typedef struct ngx_pool_s ngx_pool_t; struct ngx_pool_s { ngx_pool_data_t d; size_t max; ngx_pool_t *current; ngx_chain_t *chain; ngx_pool_large_t *large; ngx_pool_cleanup_t *cleanup; ngx_log_t *log; };
ngx_pool_t 的相关操作如下:
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log);
创建一个初始节点大小为 size 的 pool,log 为后续在该 pool 上进行操作时输出日志的对象。 需要说明的是 size 的选择,size 的大小必须小于等于 NGX_MAX_ALLOC_FROM_POOL,且必须大于 sizeof(ngx_pool_t)。
void *ngx_palloc(ngx_pool_t *pool, size_t size);
从这个 pool 中分配一块为 size 大小的内存。函数分配的内存的起始地址按照 NGX_ALIGNMENT 进行了对齐。对齐操作会提高系统处理的速度,但会造成少量内存的浪费。
void *ngx_pnalloc(ngx_pool_t *pool, size_t size);
从这个 pool 中分配一块为 size 大小的内存。但是此函数分配的内存并没有像上面的函数那样进行过对齐。
void *ngx_pcalloc(ngx_pool_t *pool, size_t size);
该函数也是分配size大小的内存,并且对分配的内存块进行了清零。
void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);
按照指定对齐大小 alignment 来申请一块大小为 size 的内存。此处获取的内存不管大小都将被置于大内存块链中管理。
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p);
对于被置于大块内存链,也就是被 large 字段管理的一列内存中的某块进行释放。该函数的实现是顺序遍历 large 管理的大块内存链表,所以效率比较低下。
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size);
ngx_pool_t 中的 cleanup 字段管理着一个特殊的链表,该链表的每一项都记录着一个特殊的需要释放的资源。对于这个链表中每个节点所包含的资源如何去释放,是自说明的。这也就提供了非常大的灵活性。这个链表每个节点的类型如下:
typedef struct ngx_pool_cleanup_s ngx_pool_cleanup_t; typedef void (*ngx_pool_cleanup_pt)(void *data); struct ngx_pool_cleanup_s { ngx_pool_cleanup_pt handler; void *data; ngx_pool_cleanup_t *next; };
- data: 指明了该节点所对应的资源。
- handler: 是一个函数指针,指向一个可以释放 data 所对应资源的函数。该函数只有一个参数,就是 data。
- next: 指向该链表中下一个元素。
void ngx_destroy_pool(ngx_pool_t *pool);
该函数就是释放 pool 中持有的所有内存,以及依次调用 cleanup 字段所管理的链表中每个元素的 handler 字段所指向的函数,来释放掉所有该 pool 管理的资源。并且把 pool 指向的 ngx_pool_t 也释放掉了。
void ngx_reset_pool(ngx_pool_t *pool);
该函数释放 pool 中所有大块内存链表上的内存,小块内存链上的内存块都修改为可用。
ngx_array_t
ngx_array_t 是 Nginx 的数组结构。
typedef struct ngx_array_s ngx_array_t; struct ngx_array_s { void *elts; size_t size; ngx_pool_t *pool; };
- elts: 指向实际的数据存储区域。
- nelts: 数组实际元素个数。
- size: 数组单个元素的大小,单位是字节。
- nalloc: 数组的容量。表示该数组在不引发扩容的前提下,可以最多存储的元素的个数。当 nelts 增长到达 nalloc 时,如果再往此数组中存储元素,则会引发数组的扩容。数组的容量将会扩展到原有容量的 2 倍大小。实际上是分配新的一块内存,新的一块内存的大小是原有内存大小的 2 倍。原有的数据会被拷贝到新的一块内存中。
- pool: 该数组用来分配内存的内存池。
ngx_array_t 相关操作函数如下:
ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size);
创建一个新的数组对象,并返回这个对象。
-
p: 数组分配内存使用的内存池;
-
n: 数组的初始容量大小,即在不扩容的情况下最多可以容纳的元素个数。
-
size: 单个元素的大小,单位是字节。
void ngx_array_destroy(ngx_array_t *a);
销毁该数组对象,并释放其分配的内存回内存池。
void *ngx_array_push(ngx_array_t *a);
在数组 a 上新追加一个元素,并返回指向新元素的指针。
void *ngx_array_push_n(ngx_array_t *a, ngx_uint_t n);
在数组 a 上追加 n 个元素,并返回指向这些追加元素的首个元素的位置的指针。
ngx_hash_t
ngx_hash_t 是 Nginx 的 hash 表。
ngx_hash_t 的实现有几个显著的特点:
- ngx_hash_t 不像其他的 hash 表的实现,可以插入删除元素,它只能一次初始化,构建起整个 hash 表以后,既不能再删除,也不能在插入元素了。
- ngx_hash_t 的开链并不是真的开了一个链表,实际上是开了一段连续的存储空间,几乎可以看做是一个数组。这是因为 ngx_hash_t 在初始化的时候,会经历一次预计算的过程,提前把每个桶里面会有多少元素放进去给计算出来,这样就提前知道每个桶的大小了。那么就不需要使用链表,一段连续的存储空间就足够了。这也从一定程度上节省了内存的使用。
ngx_hash_t 的初始化如下:
ngx_int_t ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts);
names 是初始化一个 ngx_hash_t 所需要的所有 key 的一个数组。而 nelts 就是 key 的个数。
以下是ngx_hash_init_t 类型,该类型提供了初始化一个 hash 表所需要的一些基本信息。
typedef struct { ngx_hash_t *hash; ngx_hash_key_pt key; ngx_uint_t max_size; ngx_uint_t bucket_size; char *name; ngx_pool_t *pool; ngx_pool_t *temp_pool; } ngx_hash_init_t;
- hash: 该字段如果为 NULL,那么调用完初始化函数后,该字段指向新创建出来的 hash 表。如果该字段不为 NULL,那么在初始的时候,所有的数据插入了这个字段所指的 hash 表中。
- key: 指向从字符串生成 hash 值的 hash 函数。Nginx 的源代码中提供了默认的实现函数 ngx_hash_key_lc。
- max_size: hash 表中的桶的个数。该字段越大,元素存储时冲突的可能性越小,每个桶中存储的元素会更少,则查询的速度更快。这个值越大,越造成内存的浪费也越大。
- bucket_size: 每个桶的最大限制大小,单位是字节。如果在初始化一个 hash 表的时候,发现某个桶里面无法存的下所有属于该桶的元素,则 hash 表初始化失败。
- name: 该 hash 表的名字。
- pool: 该 hash 表分配内存使用的 pool。
- temp_pool: 该 hash 表使用的临时 pool,初始化完成后,该 pool 可以被释放和销毁。
存储 hash 表 key 的数组的结构如下:
typedef struct { ngx_str_t key; ngx_uint_t key_hash; void *value; } ngx_hash_key_t;
ngx_hash_wildcard_t
Nginx 为了处理带有通配符的域名的匹配问题,实现了 ngx_hash_wildcard_t 这样的 hash 表。它可以支持两种类型的带有通配符的域名。一种是通配符在前的,例如:*.abc.com,这样的 key,可以匹配 www.abc.com,qqq.www.abc.com 之类的。另外一种是通配符在末尾的,例如:mail.xxx.*,请特别注意通配符在末尾的不像位于开始的通配符可以被省略掉。这样的通配符,可以匹配 mail.xxx.com、mail.xxx.com.cn、mail.xxx.net 之类的域名。
ngx_int_t ngx_hash_wildcard_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts);
该函数用来构建一个可以包含通配符 key 的 hash 表。
void *ngx_hash_find_wc_head(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);
该函数查询包含通配符在前的 key 的 hash 表的。
- hwc: hash 表对象的指针。
- name: 需要查询的域名,例如: www.abc.com。
- len: name 的长度。
该函数返回匹配的通配符对应 value。如果没有查到,返回 NULL。
void *ngx_hash_find_wc_tail(ngx_hash_wildcard_t *hwc, u_char *name, size_t len);
该函数查询包含通配符在末尾的 key 的 hash 表的。
ngx_hash_combined_t
组合类型 hash 表,该 hash 表的定义如下:
typedef struct { ngx_hash_t hash; ngx_hash_wildcard_t *wc_head; ngx_hash_wildcard_t *wc_tail; } ngx_hash_combined_t;
该类型实际上包含了三个 hash 表,一个普通 hash 表,一个包含前向通配符的 hash 表和一个包含后向通配符的 hash 表。
该 hash 表的查询,Nginx 提供了一个方便的函数 ngx_hash_find_combined。
void *ngx_hash_find_combined(ngx_hash_combined_t *hash, ngx_uint_t key, u_char *name, size_t len);
该函数在此组合 hash 表中,依次查询其三个子 hash 表,看是否匹配,一旦找到,立即返回查找结果,如果有多个可能匹配,则只返回第一个匹配的结果。
- hash: 此组合 hash 表对象。
- key: 根据 name 计算出的 hash 值。
- name: key 的具体内容。
- len: name 的长度。
返回查询的结果,未查到则返回 NULL。
ngx_list_t
他是一个类似 list 的数据结构。它的节点不像我们常见的 list 的节点,只能存放一个元素,ngx_list_t 的节点实际上是一个固定大小的数组。
list定义如下:
typedef struct { ngx_list_part_t *last; ngx_list_part_t part; size_t size; ngx_uint_t nalloc; ngx_pool_t *pool; } ngx_list_t;
- last: 指向该链表的最后一个节点。
- part: 该链表的首个存放具体元素的节点。
- size: 链表中存放的具体元素所需内存大小。
- nalloc: 每个节点所含的固定大小的数组的容量。
- pool: 该 list 使用的分配内存的 pool。
节点的定义如下:
typedef struct ngx_list_part_s ngx_list_part_t; struct ngx_list_part_s { void *elts; ngx_uint_t nelts; ngx_list_part_t *next; };
- elts: 节点中存放具体元素的内存的开始地址。
- nelts: 节点中已有元素个数。这个值是不能大于链表头节点 ngx_list_t 类型中的 nalloc 字段的。
- next: 指向下一个节点。
ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size);
该函数创建一个 ngx_list_t 类型的对象,并对该 list 的第一个节点分配存放元素的内存空间。
- pool: 分配内存使用的 pool。
- n: 每个节点(ngx_list_part_t)固定长度的数组的长度,即最多可以存放的元素个数。
- size: 每个元素所占用的内存大小。
- 返回值: 成功返回指向创建的 ngx_list_t 对象的指针,失败返回 NULL。
void *ngx_list_push(ngx_list_t *list);
该函数在给定的 list 的尾部追加一个元素,并返回指向新元素存放空间的指针。如果追加失败,则返回 NULL。
ngx_queue_t
ngx_queue_t 是 Nginx 中的双向链表。
typedef struct ngx_queue_s ngx_queue_t; struct ngx_queue_s { ngx_queue_t *prev; ngx_queue_t *next; };
不同于教科书中将链表节点的数据成员声明在链表节点的结构体中,ngx_queue_t 只是声明了前向和后向指针。
Nginx配置系统
配置系统
Nginx的配置系统由一个祝配置文件和其他辅助配置文件构成。这些文件均是纯文本文件,全位于安装目录下的conf目录下。
配置文件中以#开始的行,或前面有若干空格或者 TAB,然后再跟#的行,都被认为是注释。
在 nginx.conf 中,包含若干配置项。每个配置项由配置指令和指令参数 2 个部分构成。指令参数也就是配置指令对应的配置值。
指令概述
配置指令是一个字符串,可以用单引号或者双引号括起来,也可以不括。但是如果配置指令包含空格,一定要引起来。
指令参数
指令的参数使用一个或者多个空格或者 TAB 字符与指令分开。指令的参数有一个或者多个 TOKEN 串组成。TOKEN 串之间由空格或者 TAB 键分隔。
TOKEN 串分为简单字符串或者是复合配置块。复合配置块即是由大括号括起来的一堆内容。一个复合配置块中可能包含若干其他的配置指令。
如果一个配置指令的参数全部由简单字符串构成,也就是不包含复合配置块,那么我们就说这个配置指令是一个简单配置项,否则称之为复杂配置项。例如下面这个是一个简单配置项:
error_page 500 502 503 504 /50x.html;
对于简单配置,配置项的结尾使用分号结束。对于复杂配置项,包含多个 TOKEN 串的,一般都是简单 TOKEN 串放在前面,复合配置块一般位于最后,而且其结尾,并不需要再添加分号。例如下面这个复杂配置项:
location {root /home/jizhao/nginx-book/build/html;index index.html index.htm;
}
指令上下文
nginx.conf 中的配置信息,根据其逻辑上的意义,对它们进行分类,也就是分成了多个作用域,或称之为配置指令上下文。不同的作用域含有一个或者多个配置项。
当前 Nginx 支持的几个指令上下文:
- main: Nginx 在运行时与具体业务功能(比如http服务或者email服务代理)无关的一些参数,比如工作进程数,运行的身份等。
- http: 与提供 http 服务相关的一些配置参数。例如:是否使用 keepalive ,是否使用gzip进行压缩等。
- server: http 服务上支持若干虚拟主机。每个虚拟主机一个对应的 server 配置项,配置项里面包含该虚拟主机相关的配置。在提供 mail 服务的代理时,也可以建立若干 server,每个 server 通过监听的地址来区分。
- location: http 服务中,某些特定的URL对应的一系列配置项。
- mail: 实现 email 相关的 SMTP/IMAP/POP3 代理时,共享的一些配置项。
模块化体系结构
模块化体系结构
Nginx 的内部结构是由核心部分和一系列的功能模块所组成。这样划分是为了使得每个模块的功能相对简单,便于开发,同时也便于对系统进行功能扩展。
Nginx 提供了 Web 服务器的基础功能,也提供了 Web 服务反向代理,Email 服务反向代理功能。Nginx 的核心功能部分实现了底层的通讯协议,为其他模块和 Nginx 进程构建了基本的运行时环境,并且构建了其他各模块的协作基础。
模块的分类
Nginx 的模块根据其功能基本上可以分为以下几种类型:
- event module: 搭建了独立于操作系统的事件处理机制的框架,及提供了各具体事件的处理。包括 ngx_events_module, ngx_event_core_module 和 ngx_epoll_module 等。Nginx 具体使用何种事件处理模块,依赖于具体的操作系统和编译选项。
- phase handler: 此类型的模块也被直接称为 handler 模块。主要负责处理客户端请求并产生待响应内容,比如 ngx_http_static_module 模块,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。
- output filter: 也称为 filter 模块,主要是负责对输出的内容进行处理,可以对输出进行修改。例如,可以实现对输出的所有 html 页面增加预定义的 footbar 一类的工作,或者对输出的图片的 URL 进行替换之类的工作。
- upstream: upstream 模块实现反向代理的功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回客户端。upstream 模块是一种特殊的 handler,只不过响应内容不是真正由自己产生的,而是从后端服务器上读取的。
- load-balancer: 负载均衡模块,实现特定的算法,在众多的后端服务器中,选择一个服务器出来作为某个请求的转发服务器。
请求处理
请求的处理流程
实际上业务处理逻辑都在 worker 进程中。worker 进程中有一个函数,执行无限循环,不断处理收到的来自客户端的请求,并进行处理,直到整个 Nginx 服务被停止。
worker 进程中,ngx_worker_process_cycle()函数就是处理函数。在这个函数中,一个请求的简单处理流程如下:
- 操作系统提供的机制(例如 epoll, kqueue 等)产生相关的事件。
- 接收和处理这些事件,如接受到数据,则产生更高层的 request 对象。
- 处理 request 的 header 和 body。
- 产生响应,并发送回客户端。
- 完成 request 的处理。
- 重新初始化定时器及其他事件。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
