一、简述
1.1 介绍
传统的 Web 服务器,每个客户端连接作为一个单独的进程或线程处理,需在切换任务时将 CPU 切换到新的任务并创建一个新的运行时上下文,消耗额外的内存和 CPU 时间,当并发请求增加时,服务器响应变慢,从而对性能产生负面影响。
Nginx 是开源、高性能、高可靠的 Web 和反向代理服务器,而且支持热部署,几乎可以做到 7 * 24 小时不间断运行,即使运行几个月也不需要重新启动,还能在不间断服务的情况下对软件版本进行热更新。性能是 Nginx 最重要的考量,其占用内存少、并发能力强、能支持高达 5w 个并发连接数,最重要的是,Nginx 是免费的并可以商业化,配置使用也比较简单。
Nginx 的最重要的几个使用场景:
- 静态资源服务,通过本地文件系统提供服务;
- 反向代理服务,延伸出包括缓存、负载均衡等;
- API 服务,OpenResty ;
对于前端来说 Node.js 不陌生了,Nginx 和 Node.js 的很多理念类似,HTTP 服务器、事件驱动、异步非阻塞等,且 Nginx 的大部分功能使用 Node.js 也可以实现,但 Nginx 和 Node.js 并不冲突,都有自己擅长的领域。Nginx 擅长于底层服务器端资源的处理(静态资源处理转发、反向代理,负载均衡等),Node.js 更擅长上层具体业务逻辑的处理,两者可以完美组合,共同助力前端开发
1.2 安装启动
(1)Linux 安装Nginx
- 安装之前可以查看nginx包情况及版本
apt list | grep nginx
root@rion:~# apt list | grep nginx
WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
......
nginx-common/focal-updates,focal-security 1.18.0-0ubuntu1.4 all
nginx-core/focal-updates,focal-security 1.18.0-0ubuntu1.4 amd64
nginx-doc/focal-updates,focal-security 1.18.0-0ubuntu1.4 all
nginx-extras/focal-updates,focal-security 1.18.0-0ubuntu1.4 amd64
nginx-full/focal-updates,focal-security 1.18.0-0ubuntu1.4 amd64
nginx-light/focal-updates,focal-security 1.18.0-0ubuntu1.4 amd64
nginx/focal-updates,focal-security 1.18.0-0ubuntu1.4 all
.....
- 安装nginx,并查看nginx版本
apt install nginx
root@rion:~# nginx -v
nginx version: nginx/1.18.0 (Ubuntu)
- 使用
whereis nginx
来查看nginx相关的文件的位置
root@rion:~# whereis nginx
nginx: /usr/sbin/nginx /usr/lib/nginx /etc/nginx /usr/share/nginx /usr/share/man/man8/nginx.8.gz
# 文件路径说明
# /usr/sbin/nginx 终端nginx服务管理的命令
# /usr/lib/nginx nginx 相关的包文件
# /etc/nginx nginx 配置文件存放路径
# /usr/share/nginx 资源存放路径
- 防火墙配置(Centos 需要配置)
systemctl start firewalld # 开启防火墙
systemctl stop firewalld # 关闭防火墙
systemctl status firewalld # 查看防火墙开启状态,显示running则是正在运行
firewall-cmd --reload # 重启防火墙,永久打开端口需要reload一下
# 添加开启端口,--permanent表示永久打开,不加是临时打开重启之后失效
firewall-cmd --permanent --zone=public --add-port=8888/tcp
# 查看防火墙,添加的端口也可以看到
firewall-cmd --list-all
- 启动nginx
systemctl start nginx
(2)Docker 安装Nginx
- 查询nginx镜像
docker search nginx
- 下载镜像,查看镜像
docker pull nginx
docker images
- 创建nginx配置文件
# -p 表示递归创建文件夹,不加-p只能创建一层文件夹,创建多层则会报错
mkdir -p /app/nginx/conf # /app 是隶属于根下的文件夹,个人喜好
启动前需要先创建Nginx外部挂载的配置文件( /home/nginx/conf/nginx.conf)
之所以要先创建 , 是因为Nginx本身容器只存在/etc/nginx 目录 , 本身就不创建 nginx.conf 文件
当服务器和容器都不存在 nginx.conf 文件时, 执行启动命令的时候 docker会将nginx.conf 作为目录创建 , 这并不是我们想要的结果 。
- 创建配置相关文件
root@rion:~# docker run --name nginx-test -d nginx
1e3a6ea2eae268e0fcd6a3a5209f26fce8f173ecde42536d6de5ab5fba345fd6
root@rion:~# docker ps -a | grep nginx
1e3a6ea2eae2 nginx "/docker-entrypoint.…" 11 seconds ago Up 10 seconds 80/tcp nginx-test
docker 相关命令介绍:
run 表示启动一个镜像
–name 表示给将要启动的镜像起个别名
-d 表示后台启动
- 复制nginx中的配置等文件
# 生成容器
docker run --name nginx-test -d nginx
# 将容器nginx.conf文件复制到宿主机
docker cp nginx-test:/etc/nginx/nginx.conf /app/nginx/conf
# 将容器conf.d文件夹下内容复制到宿主机
docker cp nginx-test:/etc/nginx/conf.d /app/nginx/conf/conf.d
# 将容器中的html文件夹复制到宿主机
docker cp nginx-test:/usr/share/nginx/html /app/nginx
docker cp [SrcAddress] [DstFileAddress]
栗子:
docker cp nginx-test:/etc/nginx/nginx.conf /home
// 表示将nginx-test容器中/etc/nginx/nginx.conf 复制到宿主机(本机)的home目录下
docker cp /home/1.txt nginx-test:/root
// 表示将宿主机/home 下的1.txt文件拷贝到nginx-test 容器的/root/目录下
- 目录结果查看
root@rion:/app/nginx# tree
.
├── conf
│ ├── conf.d
│ │ └── default.conf
│ └── nginx.conf
└── html
├── 50x.html
└── index.html
- 创建nginx容器
# 1. 停止nginx-test容器
docker stop nginx-test
# 2. 删除前面创建的nginx-test容器,默认不能直接删除运行中的容器
docker rm nginx-test
# 3. 创建nginx容器
docker run \
-p 80:80 \
--name nginx \
-v /app/nginx/conf/nginx.conf:/etc/nginx/nginx.conf \
-v /app/nginx/conf/conf.d:/etc/nginx/conf.d \
-v /app/nginx/log:/var/log/nginx \
-v /app/nginx/html:/usr/share/nginx/html \
-d nginx
-v 表示挂载文件,宿主机文件路径 : 容器文件路径
-p 表示将容器的 80(后面那个) 端口映射到主机的 80(前面那个) 端口
\ shell 命令换行
- 测试nginx
root@rion:/app/nginx# curl http://127.0.0.1
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
成功访问了nginx的默认页面,我们修改html中的index.html 文件内容
root@rion:/app/nginx# vim html/index.html
<!DOCTYPE html>
<html>
<body>
<h1>Welcome To RION Blog</h1>
</body>
</html>
再次访问,成功获取修改后的内容。
root@rion:/app/nginx# curl http://127.0.0.1
<!DOCTYPE html>
<html>
<body>
<h1>Welcome To RION Blog</h1>
</body>
</html>
(3)Windows 安装nginx
nginx 下载地址:http://nginx.org/en/download.html
访问nginx下载地址,下载解压后双击nginx.exe程序,此时nginx服务就启动了。如果出错了请检测本机80端口是否被占用了。
1.3 nginx 基本命令
nginx 命令
nginx -s reload # 向主进程发送信号,重新加载配置文件,热重启
nginx -s reopen # 重启 Nginx
nginx -s stop # 快速关闭
nginx -s quit # 等待工作进程处理完成后关闭
nginx -T # 查看当前 Nginx 最终的配置
nginx -t -c <配置路径> # 检查配置是否有问题,如果已经在配置目录,则不需要-c
systemctl 运行 nginx相关
systemctl start nginx # 启动 Nginx
systemctl stop nginx # 停止 Nginx
systemctl restart nginx # 重启 Nginx
systemctl reload nginx # 重新加载 Nginx,用于修改配置后
systemctl enable nginx # 设置开机启动 Nginx
systemctl disable nginx # 关闭开机启动 Nginx
systemctl status nginx # 查看 Nginx 运行状态
1.4 Nginx 运行基本原理
二、Nginx 配置文件
2.1 默认nginx.conf 配置文件
nginx主要的配置文件为/etc/nginx/nginx.conf
文件,内部结构大体如下:
# 主要包含三大块
main # 全局配置,对全局生效,即最外层的配置
├── events # 配置影响 Nginx 服务器或与用户的网络连接
├── http # 配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置
│ ├── upstream # 配置后端服务器具体地址,负载均衡配置不可或缺的部分
│ ├── server # 配置虚拟主机的相关参数,一个 http 块中可以有多个 server 块
│ ├── server
│ │ ├── location # server 块可以包含多个 location 块,location 指令用于匹配 uri
│ │ ├── location
│ │ └── ...
│ └── ...
└── ...
一个 Nginx 配置文件的结构就像 nginx.conf 显示的那样,配置文件的语法规则:
- 配置文件由指令与指令块构成;
- 每条指令以
;
分号结尾,指令与参数间以空格符号分隔;- 指令块以
{}
大括号将多条指令组织在一起;include
语句允许组合多个配置文件以提升可维护性;- 使用
#
符号添加注释,提高可读性;- 使用
$
符号使用变量;- 部分指令的参数支持正则表达式;
main 全局块
user nginx; # 以nignx用户运行
worker_processes auto; # Nginx 进程数,auto为自动调节
error_log /var/log/nginx/error.log notice; # Nginx 的错误日志存放目录
pid /var/run/nginx.pid; # Nginx 服务启动时的 pid 存放位置
events 块
events {
worker_connections 1024; # 每个进程允许最大并发数
}
http 块
# 配置最频繁的部分,代理、缓存、日志等大部分功能和第三方配置
http {
include /etc/nginx/mime.types; # 文件扩展名与对应的映射表,即http mime类型
default_type application/octet-stream; # 如果mime类型没匹配上,默认使用二进制流的方式传输。
# 设置日志的记录格式
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; # Nginx 访问日志记录位置
# sendfile on; # 开启高效传输模式,也就是数据0拷贝
#tcp_nopush on; # 减少网络报文段的数量
keepalive_timeout 65; # 保持连接的时间,也称为超时时间,单位秒
include /etc/nginx/conf.d/*.conf; # 最后会加载conf.d 文件夹下的配置文件
# /etc/nginx/conf.d/default.conf
server {
listen 80; # 监听端口
server_name localhost; # 配置域名
location / { # 匹配请求网址
root /usr/share/nginx/html; # 网址根目录,且全局有且仅有一个root
index index.html index.htm; # 默认首页文件
deny 172.168.22.11; # 禁止访问的ip地址,可以为all
allow 172.168.33.44; # 允许访问的ip地址,可以为all
}
#error_page 404 /404.html; # 404 状态码显示的网页
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html; # http状态码为50x的时候对应的页面
location = /50x.html {
root /usr/share/nginx/html;
}
}
}
server 块中可以包含多个location,每个location 支持正则匹配
匹配规则
=
精确匹配路径,用于不含正则表达式的 uri 前,如果匹配成功,不再进行后续的查找;^~
用于不含正则表达式的 uri 前,表示如果该符号后面的字符是最佳匹配,采用该规则,不再进行后续的查找;~
表示用该符号后面的正则去匹配路径,区分大小写;~*
表示用该符号后面的正则去匹配路径,不区分大小写。跟 ~ 优先级都比较低,如有多个location的正则能匹配的话,则使用正则表达式最长的那个;如果 url 包含正则表达式,则必须要有 ~ 或 ~* 标志。
2.2 Nginx 全局变量
$host
:请求信息中的 Host,如果请求中没有 Host 行,则等于设置的服务器名,不包含端口$request_method
:客户端请求类型,如 GET、POST$remote_addr
:客户端的 IP 地址$args
:请求中的参数$arg_PARAMETER
:GET 请求中变量名 PARAMETER 参数的值,例如:$http_user_agent(Uaer-Agent 值), $http_referer...
$content_length
:请求头中的 Content-length 字段$http_user_agent
:客户端agent信息$http_cookie
:客户端cookie信息$remote_addr
:客户端的IP地址$remote_port
:客户端的端口$http_user_agent
:客户端agent信息$server_protocol
:请求使用的协议,如 HTTP/1.0、HTTP/1.1$server_addr
:服务器地址$server_name
:服务器名称$server_port
:服务器的端口号$scheme
:HTTP 方法(如http,https)
三、Nginx代理
3.1 正向代理(Forward Proxy)
正常情况下,用户要访问网站直接访问即可,此时用户不想让对方服务器知道自己的信息(比如自己的IP等),就可以通过配置代理服务器,让我们的代理服务器来代替我们发送请求获取数据,并将数据返回给我们。这就叫做正向代理
正向代理隐藏了真实的客户端信息,对方服务器获取的都是代理服务器的信息
3.2 反向代理
(1)概念
当用户访问网站时,使用反向代理服务器之后,用户访问的是我们的代理服务器,而代理服务器会根据nginx配置项来请求真正的服务器,nginx将获取的数据返回给用户。
反向代理隐藏了服务器信息,在处理跨域时比较常用。
(2)实例1 - 实现所求请求转发到指定的后端服务
- 需求说明
当用户访问 www.rion.top:8080
网站时,nginx将用户的请求转发到一个flask程序中去。
这里简单的介绍一下用户输入网址的一个解析流程
用户输入网址后,本机先会检测
hosts
文件是否有IP和域名的对应关系。
Windows在C:\Windows\System32\drivers\etc\hosts
Linux 在/etc/hosts
2.配置hosts文件
因为互联网上没有www.rion.top 这个域名,所以我们需改hosts文件,使其指向本地的127.0.0.1,推荐使用vscode打开,其他软件打开可能会出现无法修改的情况
3.启动一个web服务
本次以flask程序为例,提供了2个api接口
from flask import Flask
app = Flask(__name__) # Flask 实例对象
@app.route("/api/home") # 路由地址
def home(): # 处理函数
return "Home Page" # 响应数据
@app.route("/api/login")
def login():
return "Please Login"
if __name__ == "__main__":
app.run() # 运行flask程序,默认监听本机127.0.0.1:5000
4.配置nginx反向解析
server {
listen 8080;
server_name www.rion.top;
location /api {
proxy_pass http://127.0.0.1:5000;
}
# proxy_pass 将匹配到的请求转发到后面的url地址去
检测nginx配置是否错误,并重载配置文件
5.测试
当用户输入http://127.0.0.1:8080/api/home
时,nginx会将请求转发到我们的flask程序上。
6.总结
通过proxy_pass
来实现请求转发,默认location /
表示截获所有的请求。
(3)实例2 - 实现url前缀不同访问不同的后端服务
1.需求
当用户访问http://127.0.0.1:8080/flask
时,访问我们的flask服务
当用户访问http://127.0.0.1:8080/django
时,访问我们的django服务
django 的这边定义了2个简单的api接口,
# 伪代码,细节代码就不展示了。flask 程序和上面的一样,返回值一点点不一样
# urls.py
urlpatterns = [
path('api/login', views.login),
path('api/home', views.home)
]
# views.py
def home(request):
return HttpResponse("Django Home !")
def login(request):
return HttpResponse("Django Login")
2.nginx 配置
server {
listen 8080;
server_name www.rion.top;
location /flask/ {
proxy_pass http://127.0.0.1:5000/;
}
location /django/ {
proxy_pass http://127.0.0.1:8000/;
}
}
3.测试
测试前记得重载nginx配置文件
- 访问flask
- 访问django
4.总结
本质就是通过location 来区分不同的前缀,当nginx发现前缀是/flask
时,就会将请求转发到flask程序上,同理访问/django
时,就会将请求转发到django程序上
3.3 location 配置(带/ 和 不带/ 的区别)
为什么这里要单独作为一个小节呢?在配置反向代理过程中这里其实是有坑的。因为 location 配置代理时,location 地址匹配结尾带/
和不带/
获取的url会不一样。而proxy_pass
url末尾带/
和 不带/
也会出现不同的情况。所以一共会出现四种不同的情况。
(1)location 结尾不带/ ,url 结尾也不带 /
- 当我们的location 不带/ 时,django程序接收的请求
location /django { # 后续location这里的地址匹配我称呼为location 带不带 /
proxy_pass http://127.0.0.1:8000; # 后续proxy_pass 后面url地址我称呼为url 带不带/
}
- 访问django程序时,前端页面提示找不到这个url
此时我们查看django后端接收到的请求路径,发现接收的是/django/api/login
,所以django程序返回了404 ,因为压根就没有这个api接口。
- 当我们尝试访问访问
/djangoxxx/api/login
,看后端能不能接收到这个请求
你会发现nginx将/djangoxxx
的请求也转发到了django程序
总结:
当location 不带/ ,url 不带/ 时,nginx会匹配以 /django
开头的字符,不管django末尾是什么字符都会成功匹配,并将匹配到的内容追加在url的末尾。就类似于模糊匹配,不过指定了开头。
如上述实际请求的是http://www.rion.top/djangoxxx/api/login
(2)location 带 / ,url 不带 /
- nginx 配置
location /django/ { # 后续location这里的地址匹配我称呼为location 带不带 /
proxy_pass http://127.0.0.1:8000; # 后续proxy_pass 后面url地址我称呼为url 带不带/
}
- 用户访问
/django/api/login
时,nginx 将请求转发到了django程序,此时django程序获取的请求地址为/django/api/login
- 用户访问
/djangoxxx/api/login
时,nginx 并没有将请求转发到django,说明location 匹配失败了,此时我们的django程序也没有收到任何请求。
- 当用户访问
/django
时,nginx 也将请求转发到了django程序,所以nginx 是能够匹配/django
的,并且会自动给你补齐最末尾的/
总结
当location 带/ 结尾时,就类似于精确匹配,必须是/django
或/django/
结尾才会匹配
(3)location 不带 /,url 带/
- nginx 配置
location /django {
proxy_pass http://127.0.0.1:8000/;
}
- 用户访问
/django/api/login
时,nginx将请求转发到了django程序,但是django响应的还是404,当时你会发现请求的地址不再是以django开头,而是把/django
替换从了/
,所以就变成了//api/login
,不明白的可以仔细看看请求的地址/django => / + /api/login ===> //api/login
- 用户访问
/djangoxxx/api/login
,nginx匹配成功,但是django程序接收的地址也却是/xxx/api/login
,说明/djangoxxx
被nginx 替换成了/xxx
- 若url 以
/yy/
结尾时,用户请求/djangoxxx/api/login
,则最终请求的为/yy/xxx/api/login
location /django {
proxy_pass http://127.0.0.1:8000/yy/;
}
总结
若url 带 / 时,当location 匹配成功,会将匹配的内容替换为 /
,若url 以url 带/yy/
结尾,则location匹配的内容将会被替换成/yy/
在加上剩余部分。
(4)location 带 /,url 也带 /
location /django/ {
proxy_pass http://127.0.0.1:8000/;
}
- 结果前面的测试,我们能够猜测出来,若用户以
/django/api/login
发起请求,nginx会将/django/
替换成/
,最终的请求为/api/login
,此时django程序api接口成功请求成功。
- 若用户请求
/djangoxxx/api/login
,会匹配失败。
总结
location 带 / ,就类似于精确匹配,url 带 /
则会见匹配到的内容替换为 /
整体简单概述一下:
- 若
location
结尾不带 /
,表示模糊匹配,只要location
开头能匹配上,后面的内容是否有多余都会匹配成功。 - 若
location
结尾带 /
, 表示只能匹配location
结尾指定内容,匹配内容结尾可以不包含/ - 若
url
结尾不带 /
,则会把location
匹配的内容追加到后面 - 若
url
结尾带 /
,则会将location
匹配的内容替换为url /
后的内容。
实际项目开发中,location 往往是以/结尾的。url 也往往是以/ 结尾,这样能够有效保障精确的匹配api,并后端服务能够正确的处理。
例如
location /admin/api/ {
proxy_pass http://127.0.0.1:8000/api/;
}
将匹配
/admin/api/
替换成/api/
开头,
这样请求http://www.rion.top/admin/api/
,实际转发后为http://127.0.0.1:8000/api/
;
此时对用户来说他是无感知的,但是访问时已经切换到另一个后端服务了。
四、负载均衡
4.1 概述
在早期,用户访问量不大的时候,用户每次请求,nginx将服务器响应的数据返回给用户,当用户访问越来越多时,服后端务器开始处理不过来,此时我们可以配置多台服务器,当A服务器负载较高时,nginx将请求转发到B服务器来响应用户,这样就能保障用户访问时的快速响应。
例如,晚高峰乘坐地铁的时候,入站口经常会有地铁工作人员大喇叭“请走 B 口,B 口人少车空…”,这个工作人员的作用就是负载均衡。
一般来说,都需要将动态资源和静态资源分开,由于 Nginx 的高并发和静态资源缓存等特性,经常将静态资源部署在 Nginx 上。如果请求的是静态资源,直接到静态资源目录获取资源,如果是动态资源的请求,则利用反向代理的原理,把请求转发给对应后台应用去处理,从而实现动静分离。
使用前后端分离后,可以很大程度提升静态资源的访问速度,即使动态服务不可用,静态资源的访问也不会受到影响。
4.2 实现负载均衡
- 当请求过来时,nginx 判断每台服务器的负载情况来决定请求转发到那台服务器上。
upstream flask {
server 127.0.0.1:5000 weight=2; # weight : 权重,表示3次请求有2次请求会发送到 5000 端口的服务器
server 127.0.0.1:5001 weight=1;
}
server {
listen 8080;
server_name www.rion.top;
location / {
proxy_pass http://flask;
}
}
当服务器性能不一致时,权重就需要调整设置了。默认都是1,表示依次轮询。
参数介绍:
down:表示当前的server暂时不参与负载
weight:默认为1.weight越大,负载的权重就越大。
backup: 其它所有的非backup机器down或者忙的时候,请求backup机器。
- 我们启动两个flask程序来处理用户请求,当请求三次时,会发现有2次请求在5000端口的服务器 上,1次请求在5001端口的服务器上
注意:
前面我们提到需要注意location 和 url 是否带 / 的问题,在这里也是同样的道理。
- location 不带 / ,则请求
/flask/api/login
到flask程序时,nginx会在url后面拼接上/flask
upstream flask {
server 127.0.0.1:5000 weight=2;
server 127.0.0.1:5001 weight=1;
}
server {
listen 8080;
server_name www.rion.top;
location /flask {
proxy_pass http://flask;
}
}
- location 带 / ,url 带 / 时的配置。请求
/flask/api/login
时
upstream flask {
server 127.0.0.1:5000 weight=2;
server 127.0.0.1:5001 weight=1;
}
server {
listen 8080;
server_name www.rion.top;
location /flask/ {
proxy_pass http://flask/; # 注意,url的 / 是加在这里,并不是加在upstream 中
}
}
其余两种情况,朋友们可以自己去测试了。
nginx 的基本使用就介绍到这里了,有任何错误欢迎大家指出~
参考链接: https://www.nginx.org.cn/article/detail/545