1. 简介

Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。

Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

1.1 正向代理

如图所示:

当我们想访问​​YouTube​​网站时,由于防火墙的原因,我们并不能直接访问。此时我们就需要借助​​VPN​​,当我们访问目标服务器时,通过​​VPN Client​​将我们的请求代理到​​VPN Server​​,​​VPN Server​​接收到请求后直接访问目标地址,最后将访问到的信息返回给用户。

正向代理:“代理”的是客户端。

Nginx-基础_配置文件

1.2 反向代理

如图所示:

反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,我们只

需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返

回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器

地址,隐藏了真实服务器 IP 地址。

反向代理:“代理”的是服务端。

Nginx-基础_html_02

1.3 负载均衡

如图所示:

客户端发送多个请求到服务器,服务器处理请求,有一些可能要与数据库进行交互,服务器处理完毕后,再将结果返回给客户端。

这种架构模式对于早期的系统相对单一,并发请求相对较少的情况下是比较适合的,成本也低。但是随着信息数量的不断增长,访问量和数据量的飞速增长,以及系统业务的复杂度增加,这种架构会造成服务器相应客户端的请求日益缓慢,并发量特别大的时候,还容易造成服务器直接崩溃。很明显这是由于服务器性能的瓶颈造成的问题,那么如何解决这种情况呢?

我们首先想到的可能是升级服务器的配置,比如提高 CPU 执行频率,加大内存等提高机器的物理性能来解决此问题,但是我们知道摩尔定律的日益失效,硬件的性能提升已经不能满足日益提升的需求了。最明显的一个例子,天猫双十一当天,某个热销商品的瞬时访问量是极其庞大的,那么类似上面的系统架构,将机器都增加到现有的顶级物理配置,都是不能够满足需求的。那么怎么呢?

上面的分析我们去掉了增加服务器物理配置来解决问题的办法,也就是说纵向解决问题的办法行不通了,那么横向增加服务器的数量呢?这时候集群的概念产生了,单个服务器解决不了,我们增加服务器的数量,然后将请求分发到各个服务器上,将原先请求集中到单个服务器上的情况改为将请求分发到多个服务器上,将负载分发到不同的服务器,也就是我们所说的负载均衡

Nginx-基础_MiddleWare_03

1.4 动静分离

如图所示:

我们将请求的资源分为静态和动态资源,其中静态资源指的就是前端资源(html、css、js、img等),我们将这些静态的资源剥离出来,单独部署到静态资源文件服务器(直接通过nginx代理即可),而动态资源(接口请求的数据)通过nginx负载到后端的集群机器上。

这样做可以把动态页面和静态页面由不同的服务器来解析,加快解析速度。降低原来单个服务器的压力。

Nginx-基础_服务器_04

2. 安装

2.1 官方

我们可以参照​​官方安装文档​​实现基于下载源的方式安装。

2.2 docker-compose

推荐使用docker-compose的方式安装nginx

小插曲:

因为需要将容器中的配置文件挂载到宿主机中,然而当我们通过​​volumes​​挂载的时候,docker其自身的挂载机制是将宿主机中的文件挂载到容器中,所以会导致宿主机将容器中的配置文件给覆盖掉了(丢失了),所以我们还有些前置的工作。

# 先启动一个零时的nginx容器
docker run --rm -d --name=tempnginx nginx
mkdir conf
# 然后将零时容器中的配置文件拷贝到宿主机上
# 拷贝nginx.conf配置文件
docker cp tempnginx:/etc/nginx/nginx.conf ./conf/
# 拷贝conf.d文件夹
docker cp tempnginx:/etc/nginx/conf.d/ ./conf/
# 停止零时容器
docker stop tempnginx


​docker-compose.yaml​​配置信息如下

version: '3'
services:
nginx:
# 容器名称
container_name: nginx
# 默认nginx:latest
image: nginx
# 自启动
restart: always
# 宿主机端口80:容器默认端口80
ports:
- 80:80
# 文件映射
volumes:
- ./conf/nginx.conf:/etc/nginx/nginx.conf
- ./conf/conf.d/:/etc/nginx/conf.d/
- ./html/:/usr/share/nginx/html/
- ./logs/:/var/log/nginx/
# 时区设置
environment:
- TZ=Asia/Shanghai


3. 常用命令

进入nginx的sbin目录中

  • ​./nginx -v​​:查看nginx版本号
  • ​./nginx​​: 启动nginx
  • ​./nginx -s stop​​: 停止nginx
  • ​./nginx -s quit​​: 优雅的停止服务
  • ​./nginx -s reload​​:重新加载nginx(不是重启)
  • ​ nginx -t <指定配置文件路径>​​: 查看配置文件书写是否正确

4. 配置文件

4.1 结构

  1. 全局块:配置影响nginx全局的指令。一般有运行nginx服务器的用户组,nginx进程pid存放路径,日志存放路径,配置文件引入,允许生成worker process数等。
  2. events块:配置影响nginx服务器或与用户的网络连接。有每个进程的最大连接数,选取哪种事件驱动模型处理连接请求,是否允许同时接受多个网路连接,开启多个网络连接序列化等。
  3. http块:可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置。如文件引入,mime-type定义,日志自定义,是否使用sendfile传输文件,连接超时时间,单连接请求数等。
  4. server块:配置虚拟主机的相关参数,一个http中可以有多个server。
  5. location块:配置请求的路由,以及各种页面的处理情况,一个 server 块可以配置多个 location 块。
 #全局块
...

#events块
events {...}

#http块
http {
#http全局块
...
#server块
server {
#server全局块
...
#location块
location [PATTERN] {...}
location [PATTERN] {...}
}
server {...}
#http全局块
...
}


4.2 配置信息

4.2.1 nginx.conf

​/etc/nginx/nginx.conf​

user  nginx; # nginx 会使用这个指定的用户启动工作进程( worker process)
worker_processes 1; # 允许生成的worker process数,worker_processes 值越大,可以支持的并发处理量也越多,但是会受到硬件、软件等设备的制约

error_log /var/log/nginx/error.log warn; # 错误日志存放路径
pid /var/run/nginx.pid; # 进程PID存放路径

events {
worker_connections 1024; # 每个worker process可以同时支持的最大连接数
}

http {
include /etc/nginx/mime.types; # 文件扩展名与文件类型映射表
default_type application/octet-stream; # 默认文件类型
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"'; # 日志格式
access_log /var/log/nginx/access.log main; # 访问日志存储路径 并指定了日志格式为main
sendfile on; # 允许sendfile方式传输文件,可以在http块,server块,location块
keepalive_timeout 65; # 连接超时时间
include /etc/nginx/conf.d/*.conf; # 引入/etc/nginx/conf.d/ 下的所有配置文件,使其生效
}


4.2.2 default.conf

​/etc/nginx/conf.d/default.conf​

server {
# 监听所有的ipv4的地址 端口80
listen 80;
# 监听所有的ipv6的地址 端口80
listen [::]:80;
# 本虚拟主机的名称或 IP 配置
server_name localhost;
# 字符集
#charset koi8-r;
# 访问日志地址及日志格式
#access_log /var/log/nginx/host.access.log main;
# 匹配所有请求
location / {
# 访问的根目录
root /usr/share/nginx/html;
# 访问默认页
index index.html index.htm;
}

# 重定向访问系统404页面至 /404.html;
#error_page 404 /404.html;

# 重定向系统错误页面到指定的静态页面 /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}


4.3 location 语法

该指令用于匹配URL

语法如下

location [ = | ~ | ~* | ^~] /uri/ {...}


  • = : 用于不含正则表达式的 uri 前,要求请求字符串与 uri ​​严格匹配​​,如果匹配成功,就停止继续向下搜索并立即处理该请求
  • ~ : 用于表示 uri ​​包含正则表达式​​,并且​​区分大小写​
  • ~* : 用于表示 uri ​​包含正则表达式​​,并且​​不区分大小写​
  • !~ : 用于表示 uri ​​包含正则表达式​​, 并且​​区分大小写不匹配​
  • !~* : 用于表示 uri ​​包含正则表达式​​,并且​​不区分大小写不匹配​
  • ^~ : 用于​​不含正则表达式​​的 uri 前,要求 Nginx 服务器找到标识 uri 和请求字符串匹配度最高的 location 后,立即使用此 location 处理请求,而不再使用 location 块中的正则 uri 和请求字符串做匹配

注意: 如果 uri 包含正则表达式,则必须要有 ~ 或者 ~* 标识

5. 反向代理

实现效果: 用户访问nginx服务,通过nginx将请求代理到后台服务中。

5.1 实例1

  1. 启动一个后台服务student其端口为8080,项目启动后直接在地址栏上访问localhost:8080,返回I am student
    Nginx-基础_html_05
  2. 修改本地hosts文件,修改完成后,我们便可以通过www.ldx.com:8080访问到第一步的初始化页面。那么如何只需要输入 www.ldx.com 便可以跳转到第一步的初始界面呢?便用到 nginx的反向代理。
    # nginx test 127.0.0.1 www.ldx.com
  3. default.conf配置文件中增加如下配置
    监听80端口,访问域名为www.ldx.com,访问该域名时会被nginx代理到192.168.0.107:8080路径上。
    server { listen 80; listen [::]:80; server_name www.ldx.com; location / { # 将真实的客户端信息转发到服务 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://192.168.0.107:8080; } }
  4. 访问结果
    Nginx-基础_MiddleWare_06

5.2 实例2

  1. 我们再起一个后台服务teacher,其端口为8081且上下文地址为teacher,启动后直接在地址栏上访问localhost:8081/teacher,返回I am teacher
  2. student服务添加上下文地址student,启动后的访问地址为localhost:8080/student
    Nginx-基础_服务器_07
  3. default.conf配置文件中增加如下配置
    server { listen 80; listen [::]:80; server_name www.ldx.com; location ^~ /student/ { # 将真实的客户端信息转发到服务 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://192.168.0.107:8080; } location ^~ /teacher/ { # 将真实的客户端信息转发到服务 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://192.168.0.107:8081; } }
  4. 访问结果
    Nginx-基础_配置文件_08

6. 负载均衡

实现效果:用户通过nginx访问student集群服务,通过负载策略将请求分发到不同的服务上。

6.1 实例

  1. 通过idea设计器实现copy两个启动类的配置,并且将端口设置为动态传入,通过启动配置的环境变量传入8081 和 8083 两个端口,实现student服务的伪集群进行测试。
    Nginx-基础_html_09
  2. 修改default.conf配置文件
    通过添加upstream模块实现负载功能。
    upstream stu-server { server 192.168.0.107:8081; server 192.168.0.107:8083; } server { listen 80; listen [::]:80; server_name www.ldx.com; location ^~ /student/ { # 将真实的客户端信息转发到服务 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://stu-server; } }
  3. 访问结果
    Nginx-基础_nginx_10

6.2 负载策略

​官方文档地址​

1. Round Robin (default)

轮询 - 请求在服务器之间平均分配,同时考虑了服务器权重。默认情况下使用此方法(没有启用它的指令)

upstream backend {
server backend1.example.com;
server backend2.example.com;
}


2. Least Connections

最少连接 - 将请求发送到具有最少活动连接数的服务器,同时还要考虑服务器权重:

upstream backend {
least_conn;
server backend1.example.com;
server backend2.example.com;
}


3. IP Hash

IP哈希 -从客户端IP地址确定向其发送请求的服务器。在这种情况下,可以使用IPv4地址的前三个八位字节或整个IPv6地址来计算哈希值。该方法保证了来自相同地址的请求将到达同一服务器,除非该请求不可用。

upstream backend {
ip_hash;
server backend1.example.com;
server backend2.example.com;
}


如果其中一台服务器需要暂时从负载平衡循环中删除,则可以使用down参数对其进行标记,以保留客户端IP地址的当前哈希值。该服务器要处理的请求将自动发送到组中的下一个服务器:

upstream backend {
server backend1.example.com;
server backend2.example.com;
server backend3.example.com down;
}


4. Generic Hash(plugin)

通用哈希 –将请求发送到的服务器是根据用户定义的键确定的,该键可以是文本字符串,变量或组合。例如,密钥可以是成对的源IP地址和端口,或者是本示例中的URI:

upstream backend {
hash $request_uri consistent;
server backend1.example.com;
server backend2.example.com;
}


指令的可选​​consistent​​参数​​hash​​启用​​ketama​​一致性哈希负载平衡。根据用户定义的哈希键值,请求在所有上游服务器上平均分配。如果将上游服务器添加到上游组中或从上游组中删除,则只有少数几个键会被重新映射,从而在负载平衡缓存服务器或其他累积状态的应用程序的情况下最大程度地减少缓存丢失。

5. Fair(plugin)

按后端服务器的响应时间来分配请求,响应时间短的优先分配。

upstream backend {
fair;
server backend1.example.com;
server backend2.example.com;
}


6. weight

默认情况下,NGINX使用Round Robin方法根据请求的权重在组中的服务器之间分配请求。weight参数设置服务器的权重;默认值为1。

upstream backend {
server backend1.example.com weight=5;
server backend2.example.com;
server 192.0.0.1 backup;
}


在示例中,​​backend1.example.com​​具有weight ​​5​​;其他两台服务器的默认权重(​​1​​),但具有IP地址的​​192.0.0.1​​一台​​backup​​服务器被标记为备份服务器,除非其他两台服务器均不可用,否则不会接收请求。权重的这种配置,每​​6​​个请求,​​5​​个发送到​​backend1.example.com​​和​​1​​个​​backend2.example.com​​。

7. 动静分离

​ Nginx 动静分离简单来说就是把动态跟静态请求分开,不能理解成只是单纯的把动态页面和静态页面物理分离。严格意义上说应该是动态请求跟静态请求分开,可以理解成使用 Nginx(ngxin 本身就是个http服务器,可以直接使用nginx部署静态页面)处理静态页面,Tomcat 处理动态页面。动静分离从目前实现角度来讲大致分为两种:

1. 一种是纯粹把静态文件独立成单独的域名,放在独立的服务器上,也是目前主流推崇的方案;

2. 另外一种方法就是动态跟静态文件混合在一起发布,通过 nginx 来分开。通过 location 指定不同的后缀名实现不同的请求转发。通过 expires 参数设置,可以使浏览器缓存过期时间,减少与服务器之前的请求和流量。具体 Expires 定义:是给一个资源设定一个过期时间,也就是说无需去服务端验证,直接通过浏览器自身确认是否过期即可,所以不会产生额外的流量。此种方法非常适合不经常变动的资源。(如果经常更新的文件,不建议使用Expires 来缓存),我这里设置 3d,表示在这 3 天之内访问这个 URL,发送一个请求,比对服务器该文件最后更新时间没有变化,则不会从服务器抓取,返回状态码304,如果有修改,则直接从服务器重新下载,返回状态码 200。


7.1 部署静态资源

7.1.1 root

  1. 准备静态资源,放入/usr/share/nginx/html/用于访问。
    当前测试的静态资源是在gitee上找的纯前端项目:项目地址
  2. default.conf配置文件中增加如下配置用户直接访问/就会匹配到当前location,并直接访问/usr/share/nginx/html/hotel/目录下的文件,默认访问index.html页面。
  • 访问​​www.ldx.com​​ 实际请求地址就为​​www.ldx.com/index.html​​,路径为​​/usr/share/nginx/html/hotel/index.html​
  • 访问​​www.ldx.com/discount.html​​,访问路径为​​/usr/share/nginx/html/hotel/discount.html​
server {
listen 80;
listen [::]:80;
server_name www.ldx.com;

location / {
root /usr/share/nginx/html/hotel/;
index index.html;
}
}
  1. 访问结果
    Nginx-基础_html_11

7.1.2 alias

如果想给请求地址添加个上下文标识,可以使用​​alias​​(起个别名)

  1. default.conf配置文件中增加如下配置
    server { listen 80; listen [::]:80; server_name www.ldx.com; location /ldx-hotel { alias /usr/share/nginx/html/hotel/; index index.html; } }
  2. 访问结果
    Nginx-基础_html_12
  3. 但此时使用root方式就不行了,因为root方式会将location地址的匹配字符串拼接到访问目录地址中。错误日志如下

访问的真实路径变成了 /usr/share/nginx/html/hotel/ldx-hotel/index.html,将访问标识拼接上去了

  1. Nginx-基础_服务器_13
    2021/08/05 14:39:20 [error] 23#23: *2 "/usr/share/nginx/html/hotel/ldx-hotel/index.html" is not found (2: No such file or directory), client: 172.24.0.1, server: www.ldx.com, request: "GET /ldx-hotel/ HTTP/1.1", host: "localhost" 2021/08/05 14:39:34 [error] 23#23: *2 open() "/usr/share/nginx/html/hotel/ldx-hotel/discount.html" failed (2: No such file or directory), client: 172.24.0.1, server: www.ldx.com, request: "GET /ldx-hotel/discount.html HTTP/1.1", host: "localhost"

7.1.3 小节

root : 会将location表达式中的访问标识拼接到真实请求的路径中

alias : 可以自定义访问标识,其标识不会拼接到真实的请求路径中

7.2 静态资源索引

可以使用nginx搭建一个静态资源索引系统,可以用来查看附件索引并且可以使用其下载附件。

  1. 准备资源文件
    /usr/share/nginx/html/目录中添加如下文件
    ├── ldx │   └── 大帅比.txt ├── word.docx ├── 图片.jpg ├── 测试md.md └── 测试文件.txt 1 directory, 5 files
  2. default.conf配置文件中增加如下配置
    server { listen 80; listen [::]:80; server_name www.ldx.com; location /file { # 防止文件名中文乱码 charset utf-8,gbk; # 显示目录 autoindex on; # 显示文件大小 autoindex_exact_size off; # 显示文件时间 autoindex_localtime off; # 设置文件格式,防止浏览器直接打开文件(直接下载文件) if ($request_filename ~* ^.*?\.(txt|doc|pdf|rar|gz|zip|docx|exe|xlsx|ppt|pptx|crx)$){ add_header Content-Disposition attachment; } root /usr/share/nginx/html/; } }
  3. 访问结果
    Nginx-基础_MiddleWare_14
    点击下载/ldx/大帅比.txt文件
    Nginx-基础_MiddleWare_15

8. 处理跨域请求

当出现403跨域错误的时候 ​​No 'Access-Control-Allow-Origin' header is present on the requested resource​​,需要给Nginx服务器配置响应的header参数:

location / {  
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

if ($request_method = 'OPTIONS') {
return 204;
}
}


Access-Control-Allow-Origin

服务器默认是不被允许跨域的。给Nginx服务器配置`Access-Control-Allow-Origin *`后,表示服务器可以接受所有的请求源(Origin),即接受所有跨域的请求。


Access-Control-Allow-Headers 是为了防止出现以下错误:

Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.


这个错误表示当前请求Content-Type的值不被支持。其实是我们发起了"application/json"的类型请求导致的。这里涉及到一个概念:​​预检请求(preflight request)​​,请看下面"预检请求"的介绍。

Access-Control-Allow-Methods 是为了防止出现以下错误:

Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.


给​​OPTIONS​​ 添加 ​​204​​的返回,是为了处理在发送POST请求时Nginx依然拒绝访问的错误

发送"预检请求"时,需要用到方法 ​​OPTIONS​​ ,所以服务器需要允许该方法。

9. 配置 https

9.1 搞到ssl证书

注意: 如果你的证书是pfx格式的,需要将pfx格式的证书解析成pem格式,建议使用linux环境解析,本人试了windows环境解析后的文件一直有问题,强烈介意使用linux解析

linux环境本身就有openssl工具

  1. 将pfx证书上传到linux环境
  2. 执行此命令
    openssl pkcs12 -in your.pfx -nodes -out your.pem
  3. 输入pfx密码,得到pem文件
  4. 打开pem文件,内容中包含(取出多余的信息,只保留标签内的信息)
    -----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY----- -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----
  5. PRIVATE KEY内容取出放入到test.key文件
  6. 处理后的文件长这样
    Nginx-基础_html_16

9.2 配置nginx

  1. 在nginx目录下创建cert文件夹并将证书文件放进去
  2. 修改nginx配置文件以下ssl配置都是基于nginx server的(都是配置在server标签内的),当然也可以配置到http中(http标签内,server标签外),配置到不同的位置,作用域不同
  • 添加ssl证书信息
    ssl_certificate ../cert/test3.pem; ssl_certificate_key ../cert/test.key;
  • 限制对文件的访问,仅使用SSL / TLS的强版本和密码(保护证书)
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # 从1.9.1版开始,NGINX使用以下默认值 ssl_ciphers HIGH:!aNULL:!MD5;
  • https 服务器优化SSL操作会消耗额外的CPU资源。最耗CPU的操作是SSL握手。有两种方法可以最大程度地减少每个客户端执行这些操作的次数:
  • 启用保持连接以通过一个连接发送多个请求
  • 重用SSL会话参数以避免并行和后续连接的SSL握手
  • 会话存储在工作进程之间共享的SSL会话缓存中,并由ssl_session_cache伪指令配置。一兆字节的缓存包含大约4000个会话。默认的缓存超时为5分钟。可以使用ssl_session_timeout指令增加此超时。以下是针对具有10 MB共享会话缓存的多核系统进行优化的示例配置:
    ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m;
  • 配置同时支持两种协议(http/https)
    listen 80; listen 443 ssl;
  1. 拿来主义
    upstream test_name{ ip_hash; server 192.168.3.66:8888; server 192.168.3.66:8080; } server { # listen 80; listen 443 ssl; # 避免中文乱码 charset utf-8; server_name ludangxin.club; ssl_certificate ../cert/test3.pem; ssl_certificate_key ../cert/test.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; location / { proxy_pass http://test_name; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
  2. 虽然可以配置成两种协议都支持,但是大多数情况下希望用户访问http 80端口的时候被转发到https 443
    server { listen 80; server_name ludangxin.club; rewrite ^/(.*)$ https://ludangxin.club:443/$1 permanent; }