前言
本篇是我学习 Nginx 的一些笔记,主要内容讲述了一些了解 Nginx 需要的基本概念。然后探讨一下 Nginx 的模块化的组织架构,以及各个模块的分类、工作方式、职责和提供的相关指令。
主要达到以下目的:
- 了解 Nginx 的大概运行原理
- 了解 Nginx 的基本概念
- 知道怎么看官方文档。
关于Nginx
Nginx 是一款面向性能设计的 HTTP 服务器,能反向代理 HTTP,HTTPS 和邮件相关(SMTP,POP3,IMAP)的协议链接。并且提供了负载均衡以及 HTTP 缓存。
它的设计充分使用异步事件模型,削减上下文调度的开销,提高服务器并发能力。
采用了模块化设计,提供了丰富模块的第三方模块。
所以关于 Nginx,有这些标签:「异步」「事件」「模块化」「高性能」「高并发」「反向代理」「负载均衡」
基本概念
进程模型
Nginx 的进程是使用经典的「Master-Worker」模型。Nginx在启动后,会有一个 master 进程和多个 worker 进程。
master 进程主要用来管理 worker 进程,包含:接收来自外界的信号,向各 worker 进程发送信号,监控 worker 进程的运行状态,当 worker 进程退出后(异常情况下),会自动重新启动新的 worker 进程。
worker
进程主要处理基本的网络事件,多个 worker 进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。需要注意的是,每个
Worker 只有主线程,即所谓的「单线程」。一个请求,只可能在一个 worker 进程中处理,一个 worker
进程,不可能处理其它进程的请求。
worker
进程的个数是可以设置的,一般会设置与机器 cpu 核数一致,这里面的原因与 nginx 的进程模型以及事件处理模型是分不开的。nginx
为了更好的利用多核特性,提供了 cpu 亲缘性的绑定选项,我们可以将某一个进程绑定在某一个核上,这样就不会因为进程的切换带来 cache
的失效。更多的 worker 数,只会导致进程来竞争 cpu 资源。
事件模型
Nginx 对于事件,以「异步非阻塞」方式来实现。
异步和非异步,阻塞和非阻塞是两组不同的概念,前者更多对于应用程序而言,而后者更多对于 CPU 来说:
- 异步:执行一个动作之后,可以去操作别的操作,然后等待通知再回来执行刚才没执行完的操作。
- 非异步(同步):执行一个操作之后,等待结果,然后才继续执行下面的操作。
- 阻塞:给 CPU 传达任务之后,一直等待 CPU 处理完毕(即使会产生I/O),然后才执行下面操作。
- 非阻塞:给 CPU 传达任务之后,继续处理后面的操作,隔段时间再来询问之前的操作是否完成。这样的及过程也叫「轮询」
Nginx 的「异步非阻塞」方式,具体到系统调用的话,就是像 select/poll/epoll/kqueue 这样的系统调用。它们提供了一种机制,让你可以同时监控多个事件,调用他们是阻塞的,但可以设置超时时间,在超时时间之内,如果有事件准备好了,就返回。
epoll 是在 Linux 上关于事件的实现,而 kqueue 是 OpenBSD 或 FreeBSD 操作系统上采用类似 epoll 的事件模型。
所以重点讲解一下 epoll 的模型:
该方案给是 Linux 下效率最高的 I/O 事件通知机制,在进入轮询的时候如果没有检查到 I/O 事件,将会进入休眠,直到事件将它唤醒。它是真实利用了事件通知、执行回调的方式,而不是遍历查询,所以不会浪费 CPU,执行效率较高。
反向代理
要了解「反向代理」,首先需要知道什么是「代理服务器」和「正向代理」
代理服务器
在网络中,客户端发起一个请求,获取服务器端的资源。它们之间并不是建立一条直接的通道,而是被代理服务器所转发。
代理服务器作为网络中的媒介将互联网上获取的资源返回给相关的客户端。我们通常所说的代理,一般都指的是「正向代理」,是相对于客户端来说的。比方说我链接了一个
VPN,我访问 Google 的时候,客户端发起的请求到了 VPN,VPN 帮忙转发请求 Google 的服务器,然后把 Google
响应返回给客户端。这个过程,VPN 就充当了「正向代理服务器」的角色。
反向代理
和「正向代理」不同,「反向代理」的说法面向于服务器端。一个客户端请求来到代理服务器,代理服务器根据客户端的请求的不同而把请求转发到不同的服务器,这个过程在「负载均衡」中,也会发生两个一样的请求,会转发到完全不一样的服务器中的情况。
「正向代理」是「负载均衡」实现的前提,正因为代理服务器有了解析请求,分发请求的能力,才能实现负载均衡,降低每一台服务器的负荷。利用「反向代理」,除了实现负载均衡,还可以实现诸如:SSL 加密,静态内容缓存,gzip 压缩,减速上传,安全等功能
负载均衡
负载均衡(Load balancing)是一种计算机网络技术,用来在多个服务器中分配负载,以达到最佳化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。
使用带有负载均衡的多个服务器组件,取代单一的组件,可以通过冗余提高可靠性。负载平衡服务的实现可以通过软件和硬件来实现。
负载均衡的分发,一般都会有多套算法来处理分发问题。
连接 Connection
在 nginx 中 connection 就是对 tcp 连接的封装,其中包括连接的 socket,读事件,写事件。利用 nginx 封装的 connection,我们可以很方便的使用 nginx 来处理与连接相关的事情,比如,建立连接,发送与接受数据等。
而 nginx 中的 http 请求的处理就是建立在 connection 之上的,所以 nginx 不仅可以作为一个 web 服务器,也可以作为邮件服务器。当然,利用 nginx 提供的 connection,我们可以与任何后端服务打交道。
最大连接数
在nginx中,每个进程会有一个连接数的最大上限,这个上限与系统对fd的限制不一样。
在操作系统中,通过
ulimit -n,我们可以得到一个进程所能够打开的 fd 的最大数,即 nofile,因为每个socket连接会占用掉一个
fd,所以这也会限制我们进程的最大连接数,当然也会直接影响到我们程序所能支持的最大并发数,当 fd 用完后,再创建 socket 时,就会失败。
nginx 通过设置worker_connectons
来设置每个进程支持的最大连接数。如果该值大于 nofile,那么实际的最大连接数是 nofile,nginx 会有警告。
nginx 在实现时,是通过一个连接池来管理的,每个worker进程都有一个独立的连接池,连接池的大小是worker_connections
。这里的连接池里面保存的其实不是真实的连接,它只是一个worker_connections
大小的一个ngx_connection_t
结构的数组。并且,nginx 会通过一个链表free_connections
来保存所有的空闲ngx_connection_t
,每次获取一个连接时,就从空闲连接链表中获取一个,用完后,再放回空闲连接链表里面。
所以,一个 nginx 能建立的最大连接数:worker_connections * worker_processes
,如果当 nginx 作为反向代理的话,因为一个请求 nginx 要建立客户端和服务器的请求,所以最大连接数是:worker_connections * worker_processes / 2
请求 Request
在 nginx 中我们指 http 请求,具体到 nginx 中的数据结构是ngx_http_request_t
。它是对一个 http 请求的封装,nginx 通过ngx_http_request_t
来保存解析请求与输出响应相关的数据。一个 http 请求,包含请求行、请求头、请求体、响应行、响应头、响应体。
一般性的网络请求处理过程是:
- 客户端会发送请求过来。
- 然后我们读取一行数据,分析出请求行中包含的 method、uri、http_version 信息。
- 然后再一行一行处理请求头,并根据请求 method 与请求头的信息来决定是否有请求体以及请求体的长度,然后再去读取请求体。
- 得到请求后,我们处理请求产生需要输出的数据,然后再生成响应行,响应头以及响应体。
- 在将响应发送给客户端之后,一个完整的请求就处理完了。
而 nginx 处理请求的时候会有一些小小的区别,比如,当请求头读取完成后,就开始进行请求的处理了。
Nginx 处理请求过程
nginx 处理一个请求的抽象概念过程:
- request 请求进来
- 初始化www1.qixoo.com HTTP Request, 生成 HTTP Request 对象
- 处理请求头
- 处理请求体
- 调用与此请求关联的 handler(根据你URL或者Location配置)
- 依次调用各 phase handler 进行处理
- 获取 location 配置
- 产生适当的响应
- 发送 response header
- 发送 response body
基本数据结构
nginx 的作者为追求极致的高效,自己实现了很多颇具特色的 nginx 风格的数据结构以及公共函数。比如,nginx 提供了带长度的字符串,根据编译器选项优化过的字符串拷贝函数 ngx_copy 等。
ps: 下横线分割是C语言的变量名风格
Data Structure | Description |
---|---|
ngx_str_t | 字符串封装 |
ngx_pool_t | 提供一种机制,帮助管理一系列的资源(内存,文件) |
ngx_array_t | 数组结构 |
ngx_chain_t | 主要用于模块之间数据传递的链表实现 |
ngx_buf_t | 就是 ngx_chain_t 链表的每个节点的实际实现,代表某种具体的数据。 |
ngx_list_t | list 数据结构的实现,以及增强 |
ngx_queue_t | 实现的双向链表 |
ngx_hash_t | hash 表的实现 |
ngx_hash_wildcard_t | 为处理带有通配符域名的匹配问题实现的 hash 表结构 |
ngx_combinded_t | 在于提供一个方便的容器包含三个类型的 hash 表 |
ngx_hash_keys_arrays_t | 用于构建其他类型的 hash 的辅助类 |
配置
nginx 的配置系统由一个主配置文件和其他一些辅助的配置文件构成。这些配置文件均是纯文本文件,全部位于 nginx 安装目录下的 conf 目录下。
指令由 nginx 的各个模块提供,不同的模块会提供不同的指令来实现配置。
指令除了 Key-Value 的形式,还有作用域指令。
nginx.conf 中的配置信息,根据其逻辑上的意义,对它们进行了分类,也就是分成了多个作用域,或者称之为配置指令上下文。不同的作用域含有一个或者多个配置项。
下面的这些上下文指令是用的比较多:
Directive | Description | Contains Directive |
---|---|---|
main | nginx 在运行时与具体业务功能(比如 http 服务或者 email 服务代理)无关的一些参数,比如工作进程数,运行的身份等。 | user, worker_processes, error_log, events, http, mail |
http | 与提供 http 服务相关的一些配置参数。例如:是否使用 keepalive 啊,是否使用 gzip 进行压缩等。 | server |
server | http 服务上支持若干虚拟主机。每个虚拟主机一个对应的 server 配置项,配置项里面包含该虚拟主机相关的配置。在提供 mail 服务的代理时,也可以建立若干 server. 每个 server 通过监听的地址来区分。 | listen, server_name, access_log, location, protocol, proxy, smtp_auth, xclient |
location | http 服务中,某些特定的 URL 对应的一系列配置项。 | index, root |
实现 email 相关的 SMTP/IMAP/POP3 代理时,共享的一些配置项(因为可能实现多个代理,工作在多个监听地址上)。 | server, http, imap_capabilities |
模块
nginx
将各功能模块组织成一条链,当有请求到达的时候,请求依次经过这条链上的部分或者全部模块,进行处理。每个模块实现特定的功能。例如,实现对请求解压缩的模块,实现
SSI 的模块,实现与上游服务器进行通讯的模块,实现与 FastCGI 服务进行通讯的模块。
模块分三类:
- 核心模块
- 辅助模块
- 第三方模块
根据官方文档排版,辅助模块还分了以下几类:
- http
- stream
而根据其功能可以分成这几大类:
- handler 模块
此类型的模块也被直接称为 handler 模块。主要负责处理客户端请求并产生待响应内容,比如 ngx_http_static_module 模块,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。 - filter 模块
过滤响应头和内容的模块,可以对回复的头和内容进行处理。它的处理时间在获取回复内容之后,向用户发送响应之前。 - upstream 模块
upstream 模块实现反向代理的功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回客户端。upstream 模块是一种特殊的 handler,只不过响应内容不是真正由自己产生的,而是从后端服务器上读取的。 - load balance 模块
负载均衡模块,实现特定的算法,在众多的后端服务器中,选择一个服务器出来作为某个请求的转发服务器
结尾
本文讲述了 Nginx 的一些基本概念。
Nginx
是线程模型是 Master-Worker 模式的,每个 worker
是单线程的,也就是处理请求是单线程处理的。而单线程并发的事件模型是「异步非阻塞 I/O」模型。并且讲述了「反向代理」「负载均衡」的概念,这是
nginx 能高性能处理高并发的原因之一。Nginx 对于网络请求是有 Connection 和 Request 的概念和封装的。
Nginx 的源码组织架构是模块化的,不同的模块实现不一样的职责,然后它们被连接起来一起干一件大事,知道模块有哪些分类,可以让我们知道怎么查找官方文档。
在没有看过有哪些指令,哪些指令有什么功能之前,是不能完全知道 nginx 提供什么样的功能的,那就抱着,那就抱着「能想到的别人都想到并实现了」的想法来使用 nginx 吧。Nginx 作为一个代理服务,在中间想做什么都可以啦。