初识 Nginx(一):理解原理和功能

前言

在工作中常常会接触到 Nginx,而本人却对它一知半解,便想在空余时间详细的了解一下 Nginx。本篇是我学习 Nginx 系列的开篇,主要内容讲述 Nginx 的基本概念,然后介绍一下 Nginx 的主要功能,最后探讨一下 Nginx 的模块化的组织架构,以及各个模块的分类、工作方式、职责和提供的相关指令。

什么是 Nginx?

Nginx(”enginex”)是一款是由俄罗斯的程序设计师 Igor Sysoev 所开发高性能的 Web 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器,主要用于提供网上信息浏览服务,为高并发网站的应用场景而设计。Nginx 最初是作为一个 Web 服务器创建的,用于解决 C10K[1] 的问题。

在高连接并发的情况下,Nginx 是 Apache 服务器不错的替代品。它的设计充分使用异步事件模型,削减上下文调度的开销,提高服务器并发能力。采用了模块化设计,提供了丰富模块的第三方模块。所以关于 Nginx,有这些标签:「异步」「事件」「模块化」「高性能」「高并发」「反向代理」「负载均衡」。

Nginx 的各种功能以模块(module)的形式提供,只有在编译安装时可以选择安装或不安装哪些模块,在源码编译后,或通过 Linux 软件管理包工具安装 Nginx,不能再加载或去除模块。

Nginx 能做什么?

Nginx 在不依赖第三方模块能处理的事情包括:反向代理、正向代理、负载均衡、HTTP 服务器。

反向代理

什么是反向代理?

反向代理:指以代理服务器来接受 internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。

简单来说,反向代理对于客户端而言就是目标服务器,客户端向反向代理服务器发送请求后,反向代理服务器将该请求转发给内部网络上的后端服务器,并从后端服务器上得到的响应结果返回给客户端。

反向代理即是服务端代理,代理服务端,客户端不知道实际提供服务的服务端。

211606-20180719195549207-493435997

代码实现

1
2
3
4
5
6
7
8
9
server {
listen 80;
server_name blog.maoning.pro;

location / {
proxy_pass http://118.25.39.41:4000;
proxy_set_header Host $host:$server_port;
}
}

保存配置文件后,重启 Nginx,访问 blog.maoning.pro,相当于访问 118.25.39.41:4000。

正向代理

什么是正向代理?

正向代理:是一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。

正向代理即是客户端代理,代理客户端,服务端不知道实际发起请求的客户端。

211606-20180719170736932-1700528935

反向代理和正向代理区别?

前面已经分别介绍了反向代理和正向代理,那么二者到底有什么区别呢?此处借用一下知乎网友的一张图片来表达。

2582ac2a1366e3e12acf274265ea80f3

负载均衡

什么是负载均衡?

当一台服务器的性能达到极限时,我们可以使用服务器集群来提高网站的整体性能。那么,在服务器集群中,需要有一台服务器充当调度者的角色,用户的所有请求都会首先由它接收,调度者再根据每台服务器的负载情况将请求分配给某一台后端服务器去处理。

那么在这个过程中,调度者如何合理分配任务,保证所有后端服务器都将性能充分发挥,从而保持服务器集群的整体性能最优,这就是负载均衡问题。

Nginx 负载均衡

Nginx 目前支持自带 3 种负载均衡策略,还有 2 种常用的第三方策略(fair 和 url_hash 需要安装第三方模块才能使用)。下面分别介绍 Nginx 的负载均衡策略。

RR 方式(默认)

负载均衡默认设置方式,每个请求按照时间顺序逐一分配至不同的后端服务器急性处理,如果有服务器宕机,会自动剔除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 设定负载均衡服务器列表
upstream test {
server 192.168.0.1:8080;
server 192.168.0.2:8080;
}

server {
listen 80;
server_name localhost;
client_max_body_size 1024M;

location / {
proxy_pass http://test;
proxy_set_header Host $host:$server_port;
}
}
weight 方式

利用 weight 指定轮训的权重比率,与访问率成正比,用于后端服务器性能不均的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
upstream test {
server 192.168.0.1:8080 weight=9;
server 192.168.0.2:8080 weight=1;
}

server {
listen 80;
server_name localhost;
client_max_body_size 1024M;

location / {
proxy_pass http://test;
proxy_set_header Host $host:$server_port;
}
}
ip_hash 方式

每个请求按访问 IP 的 hash 结果分配,这样可以使每个访客固定访问一个后端服务器,可以解决 Session 共享的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
upstream test {
ip_hash;
server 192.168.0.1:8080;
server 192.168.0.2:8080;
}

server {
listen 80;
server_name localhost;
client_max_body_size 1024M;

location / {
proxy_pass http://test;
proxy_set_header Host $host:$server_port;
}
}
fair 方式(第三方)

按照每台服务器的影响时间分配请求,响应时间短的优先分配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
upstream test {
fair;
server 192.168.0.1:8080;
server 192.168.0.2:8080;
}

server {
listen 80;
server_name localhost;
client_max_body_size 1024M;

location / {
proxy_pass http://test;
proxy_set_header Host $host:$server_port;
}
}
url_hash 方式(第三方)

按访问 url 的 hash 结果来分配请求,使每个 url 定向到同一个后端服务器,后端服务器为缓存时比较有效。P.S. 在 upstream 中加入 hash 语句,server 语句中不能写入 weight 等其他的参数,hash_method 是使用的 hash 算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
upstream test {
hash $request_uri;
hash_method crc32;
server 192.168.0.1:8080;
server 192.168.0.2:8080;
}

server {
listen 80;
server_name localhost;
client_max_body_size 1024M;

location / {
proxy_pass http://test;
proxy_set_header Host $host:$server_port;
}
}

HTTP 服务器

HTTP 服务器

Nginx 本身也是一个静态资源的服务器,当只有静态资源的时候,就可以使用 Nginx 来做服务器。

1
2
3
4
5
6
7
8
9
10
server {
listen 80;
server_name localhost;
client_max_body_size 1024M;

location / {
root /home/www/maoning/public;
index index.html;
}
}

动静分离

动静分离是将网站静态资源(Html,JavaScript,Css,Img 等文件)与后台应用分开部署,提高用户访问静态代码的速度,降低对后台应用访问。

动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
server {
listen 80;
server_name localhost;
client_max_body_size 1024M;

location / {
root /home/www/maoning/public;
index index.html;
}

# 所有静态请求由 nginx 处理
location ~ \.(gif|jpg|jpeg|png|bmp|swf|css|js)$ {
root /home/www/maoning/public;
}

# 所有动态请求转发给后端服务器处理
location ~ \.(jsp|do)$ {
proxy_pass http://test;
}

# 错误页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /home/www/maoning/public;
}
}

Nginx 核心进程模型

Nginx 整体架构

Nginx 分为 Single 和 Master 两种进程模型,Single 模型即为单进程方式工作,具有较差的容错能力,常常在开发环境调试时候使用,不适合生产之用。Master 模型即为一个 master 进程 + N 个 worker 进程的工作方式,生产环境都是用 master-worker 模型来工作。

26142403-71f4cccd438240f086019a43e11ad879

多进程网络模型

Nginx 在启动后,会有一个 master 进程和多个 worker 进程。Nginx 的主进程将充当监控进程,而由主进程 fork()出来的子进程则充当工作进程。Nginx 包括一个 master 进程和数个 worker 进程,master 进程用于读取、解析配置文件和管理 worker 进程,worker 进程实际处理请求。Nginx 实现了基于事件的模型和操作系统机制驱动的请求分发。

master 主进程

监控进程充当整个进程组与用户的交互接口,同时对进程进行监护。它不需要处理网络事件,不负责业务的执行,只会通过管理 worker 进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。

主进程启动以后首先初始化系统相应的信号量标志位,然后根据配置参数 (子进程个数、最大连接数等) 通过 fork 复制创建工作进程,worker 进程与 master 进程具有相同的上下文环境,接下来主进程和工作进程进入不同的循环,主进程保存子进程返回的 pid 写入文件,接着主进程进入信号处理的循环,监听系统接收到的 (例如 nginx-reload) 信号并进行相关的处理。
master 进程在接收到信号后,会先重新加载配置,然后再启动新进程开始接收新请求,并向所有老进程发送信号告知不再接收新请求并在处理完所有未处理完的请求后自动退出,这就是 Nginx 的从容重启。

22312037_13832287181u34

master 进程主要用来管理 worker 进程,具体包括如下 4 个主要功能:
(1)接收来自外界的信号。
(2)向各 worker 进程发送信号。
(3)监控 woker 进程的运行状态。
(4)当 woker 进程退出后(异常情况下),会自动重新启动新的 woker 进程。

worker 工作进程

worker 进程的主要任务是完成具体的任务逻辑。通过主进程 fork 复制出 worker 进程,worker 进程环境变量 (监听 socket、文件描述符等) 都一样,因此各个 worker 进程完成等同,在相同的 socket 端口监听,请求到来时,每个 worker 工作进程都会监听到,但最终只会有一个 worker 进程会接受并处理(Nginx 提供了一把共享锁 accept_mutex 来保证同一时刻只有一个 work 进程在 accept 连接,从而解决惊群问题 [2])。创建工作进程以后,工作进程进入循环,首先处理退出信号,然后进入事件处理过程 ngx_process_events_and_timers(cycle),在进程处理函数中,首先处理定时任务,然后处理读取任务再处理写任务。

26142517-dc5d6df334a642178baa98b5e61084c

Nginx 是如何处理一个请求?

首先,Nginx 在启动时,会解析配置文件,得到需要监听的端口与 ip 地址,然后在 Nginx 的 master 进程里面,先初始化好这个监控的 socket(创建 socket,设置 addrreuse 等选项,绑定到指定的 ip 地址端口,再 listen),然后再 fork(一个现有进程可以调用 fork 函数创建一个新进程。由 fork 创建的新进程被称为子进程)出多个子进程出来,然后子进程会竞争 accept 新的连接。此时,客户端就可以向 Nginx 发起连接了。当客户端与 Nginx 进行三次握手,与 Nginx 建立好一个连接后,此时,某一个子进程会 accept 成功,得到这个建立好的连接的 socket,然后创建 Nginx 对连接的封装,即 ngx_connection_t 结构体。接着,设置读写事件处理函数并添加读写事件来与客户端进行数据的交换。最后,Nginx 或客户端来主动关掉连接,到此,一个连接就寿终正寝了。

当然,Nginx 也是可以作为客户端来请求其它 server 的数据的(如 upstream 模块),此时,与其它 server 创建的连接,也封装在 ngx_connection_t 中。作为客户端,Nginx 先获取一个 ngx_connection_t 结构体,然后创建 socket,并设置 socket 的属性(比如非阻塞)。然后再通过添加读写事件,调用 connect/read/write 来调用连接,最后关掉连接,并释放 ngx_connection_t。


参考博文

[1]. 认识 Nginx,理解原理和功能
[2]. nginx 的五种负载算法模式
[3]. 全面了解 Nginx 到底能做什么
[4]. Nginx 详解 - 服务器集群


注脚

[1]. C10K:随着互联网的普及,应用的用户群体几何倍增长,此时服务器性能问题就出现。最初的服务器是基于进程 / 线程模型。假如有 C10K(即单机 1 万个并发连接),就需要创建 1W 个进程,可想而知单机是无法承受的。那么如何突破单机性能是高性能网络编程必须要面对的问题,进而这些局限和问题就统称为 C10K 问题。
[2]. 惊群问题: 惊群问题是由于系统中有多个进程在等待同一个资源,当资源可用的时候,系统会唤醒所有或部分处于休眠状态的进程去争抢资源,但是最终只会有一个进程能够成功的响应请求并获得资源,但在这个过程中由于系统要对全部的进程唤醒,导致了需要对这些进程进行不必要的切换,从而会产生系统资源的浪费。


初识 Nginx 系列


谢谢你长得那么好看,还打赏我!😘
0%