Nginx那么神,我想去看看,走起~
Nginx 是什么?
Nginx (engine x
) 是一款由俄罗斯的程序设计师Igor Sysoev所开发的 高性能 Web和 反向代理 服务器,也是一个通用的TCP/UDP代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。
它现在几乎是众多大型网站必备的技术,大多数情况下,对于前端的我们来说,都无需自己去配置它,但是对于了解他在应用程序中所发挥的作用及担任的角色来说,理解如何解决这些问题则是非常必要的。
Nginx有什么优势?
- 高并发。使用epoll和kqueue模型来处理连接,支持2-5万高并发,比Apache的Select模型更有优势。
- 占用资源少。FASTCGI 接口,在3万并发连接下,开启10个Nginx进程共消耗约150MB内存...。
- 支持热部署。能够在不间断服务的情况下,对软件版本进行升级
- 支持Rewrite重写。能够根据域名、URL的不同,将http请求分到不同的后端服务器群组。
- 更稳定的服务。如果某台Web服务器宕机了,可以将请求转发到其他服务器上不会影响前端的访问。
- 利用nginx可以管理连接数。
- 节省带宽。支持gzip压缩资源
- 配置文件非常简单。
这里提供一个各种处理连接模型的性能对比,横轴标识连接句柄,纵轴表示处理连接的时长(毫秒),可以看出epoll和kqueue在高并发下的优势。
......
Nginx常用来做什么?
打开官网,可以看到Nginx的功能有很多,对于前端开发者来说,通常掌握 HTTP功能模块即可,如您对其他功能也感兴趣可以到官网上继续学习。常涉及的如下内容:
- 解决跨域(反向代理)
- 响应过滤
- 压缩资源(gzip)
- 负载均衡
- 动静分离
- Web缓存
# 正向代理与反向代理
Nginx是一台反向代理服务器,那什么是“反向代理”?
代理 就是委托、代办。在服务器和客户端之间架设的一层服务器,它负责接收客户端请求,处理完想要做的事情后,再转发给真正的服务器。不论是正向代理还是反向代理,最终实现的都是该效果,只不过是代理的对象不一样罢了。
怎么区分正向和反向?这是一个既定的称呼。我们认为请求通过客户端发起,最终被服务端接受并处理,这么一个流向被认为是正向的流转。当代理服务器代理了客户端,充当了请求的发起方,我们称之为正向代理;相反,代理服务器代理了服务端,充当了请求接收方,隐藏了服务端,我们称之为反向代理。
正向代理
用户知道目标服务器地址,但由于网络限制等原因(如跨域),无法直接访问。这时候需要先连接代理服务器,然后再由代理服务器访问目标服务器。这种代理方式,对服务端隐藏了真正的客户端,服务端并不知道请求是来自于代理的访问还是来自于真正的客户端访问。
最常见的正向代理案例就是,Webpack 的 proxyTable,如下
在这里,http://jsonplaceholder.typicode.com
正是正向代理中的代理服务器,它负责收集真正客户端发起的请求并进行转发,如此一来,在服务器端收到的请求看起来均为由它发起的,从而达到了因此客户端的目的。
反向代理
反向代理代理的是服务器,对于客户端是无感知的,甚至客户端无需做任何处理。客户端只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,再反馈给客户端。
例如我们给10086打客服电话,我们只需要知道10086这个代理号码,拨通后,由哪一位客服姐姐给你服务,就是由反向代理服务器为你选择。而这个客服姐姐叫什么名字,座机号码是什么,你是不知道的。
再比如,我们访问百度网站,我们输入www.baidu.com
后,就能得到响应,但百度的服务器肯定不止一台,那是哪一台在为我们做请求处理?我们也是不知道的。
解决跨域
对于XHR类型的请求,浏览器同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。用于隔离潜在恶意文件的重要安全机制。当存在协议,域名,访问端口三者其中之一不一致时即发生跨域。
Nginx是如何解决跨域的呢?假设
(1)前端server域名:front.server.com
(2)后端server域名:end.server.com
这种条件下,前端发起xhr请求,因为域名不一致一定会出现跨域。为解决这个问题,只需要启动一个nginx服务器,将 server_name
设置为和前端一样的域名front.server.com
,然后设置相应的location
用以拦截前端需要跨域的请求,最后将请求代理回后端真正的服务end.server.com
即可。配置如下:
server {
listen 80;
server_name front.server.com;
location / {
proxy_pass end.server.com
}
}
这样,由前端服务器front.server.com
发起的接口请求,被代理服务器以front.server.com
的身份接收,保持协议与端口一致,就不会出现跨域。而代理服务器在收到请求后,再进行转发,此时已经没有浏览器参与,也就没有所谓的跨域问题,从而完成了请求的正常流转。
你可能听着还是不太明白,具体是怎么工作的?接着往下看
# Nginx 文件配置结构
Nginx 一个重要的功能就是反向代理,它帮助接收来自客户端的请求,执行请求转发,解决跨域,负载均衡等。他的基本配置结构如下:
用代码表示,即:
... #main全局块
events { #events块
...
}
http #http块
{
... #http全局块
server #server块
{
... #server全局块
location [PATTERN] #location块
{
...
}
location [PATTERN]
{
...
}
}
server
{
...
}
... #http全局块
}
-
main
:全局配置。配置影响nginx全局的指令。一般有运行nginx服务器的用户组,nginx进程pid存放路径,日志存放路径,配置文件引入,允许生成worker process数等; -
events
:配置影响nginx服务器或与用户的网络连接。有每个进程的最大连接数,选取哪种事件驱动模型处理连接请求,是否允许同时接受多个网路连接,开启多个网络连接序列化等。 -
http
:可以嵌套多个server,配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置。如文件引入,mime-type定义,日志自定义,是否使用sendfile传输文件,连接超时时间,单连接请求数等。 -
server
:配置虚拟主机的相关参数,一个http中可以有多个server -
location
:配置请求的路由,以及各种页面的处理情况,如静态资源,正则路径匹配等。
# 响应过滤
Nginx的响应过滤主需要使用 location
关键字进行匹配。匹配规则如下:
符号 | 说明 |
---|---|
= | 对URI 做精确匹配 |
~ | 对URI 做正则匹配,区分大小写 |
~* | 对URI做正则匹配,不区分大小写 |
^~ | 对URI做半部分左匹配检查,不区分字符大小写 |
不带符号 | 匹配起始于此URI的所有URL |
匹配的优先级为:=、^~、~/~*、不带符号
(1)直接根据URL名称过滤,
location / {
root /data/apps/myproject-web;
index index.html;
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://localhost:60001;
}
proxy_set_header
用来设置请求头,以便后端获取。跟两个参数:名称 值
。名称是一个可自定义的字符串,但常用的都有约定俗成的专用名词。本例中的Host
,X-Real-IP
都是约定名
- Host:如果设置为
$host
,则表示原始请求的host。如果客户端的请求header
中没有HOST字段,那么$host
表示nginx代理服务器的地址。 - X-Real-IP:设置客户端的真实IP,使用
$remote_addr
获取 - X-Forwarded-For:记录请求在网络链路中被转发的路径。设置为
$proxy_add_x_forwarded_for
指的是返回所有转发节点的IP地址。如:一个请求经过两次nginx转发,那么第二个nginx接收到请求后,在设置该请求头参数时,就会带上第一个nginx的IP。
proxy_pass
:设置请求转发的代理地址,这是Nginx非常重要的功能
若访问http://nginx_server/api/index.html
,则设置
(1)location /api/ { proxy_pass http://localhost:60001 }
代理到http://localhost:60001/api/index.html
上
(2)location /api/ { proxy_pass http://localhost:60001/ }
代理到http://localhost:60001/index.html
上
当遇到跨域问题且客户端无法支持CORS时,最好的办法就是让Nginx来做代理。在前端所在的服务器的Nginx上设置一个路由,然后使用proxy
去请求另一个域名下的资源。如果前后端部署在同一台机器上,则更简单,直接使用127.0.0.1
或localhost
加后端服务端口即可,例:
location /api {
proxy_pass http://localhost:60001
}
这样,当客户端请求/api
这个路径下的资源时,服务器就会帮我们去localhost
的60001
端口获取资源,完成代理解决跨域问题。
proxy_path
在转发时会将$uri
带过去,所以如果这个/api
是我们手动加的,转发时需要去掉,则可以使用rewrite
来实现
location /api/ {
rewrite ^/api/(.*)/$1 brek;
proxy_pass http://localhost:60001;
}
rewrite
的作用是修改 $uri
,但有个副作用即重新匹配 location
。由于proxy_pass
的处理阶段比 location
晚,所以需要 break 掉,以防止rewrite
进入下一次location
匹配而丢失proxy_pass
。$1
表示第一个括号内匹配的正则参数。
(2)通过状态码进行过滤,使用error_page
前端关于页面丢失有两种错误:
一种是访问的路由不存在,可在路由表最后一行配置不匹配路由的统一404页面:
{ path: '*', redirect: '/404', hidden: true }
二是请求访问的资源不存在,此时,通过nginx来设置统一的404页面更为合适:
error_page 500 501 502 503 504 506 /50x.html;
location = /50x.html {
root /root/static/50x.html;
}
error_page 404 /40x.html;
location = /40x.html {
root /root/static/40x.html
}
如果需要设置自定义的响应码,则需要在code前增加等号如 error_page 401 404 =700 /40x.html
(3)根据既定条件过滤,如请求类型
if ($request_method !~ ^(GET|POST|HEAD)$ ) {
return 403
}
$request_method
是Nginx配置的一个全局变量,表示请求资源的方式。在location中常用的全局变量还有
-
$request_method
:请求资源的方式(GET|POST|PUT|DELETE等) -
$request_filename
:请求的资源文件名称。常用作在location内部配置add_header Cache-Control "no-cache"
,表示某一类如以.html
结尾的文件,在资源未发生变更时取缓存文件。 -
$host
:主机头,也就是域名 -
$args
:请求中的参数,问好后面部分。如www.abc.com/index.html?a=1&b=2
,就是a=1&b=2
-
$uri
:请求文档中不包含指令的URI,如www.abc.com/index.html?a=1&b=2
,就是index.html
,不包含参数部分。 -
$ducument_uri
:和$uri
一致。 -
$request_uri
:请求的链接,包括$ducument_uri
文档路径和$args
参数串。为/index.html?a=1&b=2
。 -
$remote_addr
:客户端的公网IP -
$server_addr
:服务器的公网IP -
$http_cookie
:客户端cookie信息 -
$scheme
:请求的协议。如ftp,http,https -
http_user_agent
:客户端浏览器的UA标识 -
remote_user
:客户端有认证的用户名 -
content_type
:http请求信息里的Content-Type
-
$referer
:客户端请求是的referer,即请求是通过哪个链接跳过来的。可以用作防盗链,如下:
location ~ .*\.(jpg|gif|png)$ {
valid_referers none blocked 119.2x.1x3.218 #支持正则匹配;
if ($invalid_referer) {
return 403;
}
root /nginxtest/images;
}
# 配置压缩(gzip)
GZIP是规定的三种标准HTTP压缩格式之一。目前绝大多数的网站都在使用GZIP传输 HTML、CSS、JavaScript 等资源文件。
对于文本文件,gzip 的效果非常明显,开启后传输所需流量大约会降至 1/4 ~ 1/3。
但并不是每个浏览器都支持gzip,我们可以通过请求头中的Accept-Encoding
来了解浏览器是否支持gzip压缩。
当明确浏览器支持gzip压缩后,就允许服务器返回gzip的文件,一般真正服务器很少会返回gzip文件,我们就可以通过Nginx来统一设置压缩。可以通过浏览器的
response
信息来观察是否已经正确开启
gzip的配置
gzip配置在http块内部的全局变量中,针对所有的server块生效
-
gzip
:设置为on
为开启压缩功能 -
gzip_comp_level
:压缩级别,默认为1,取值1-9 -
gzip_types
:要采用gzip压缩的文件类型(MIME类型),默认值:text/html(默认不压缩js/css)
# 负载均衡
负载均衡是Nginx另一个非常重要的功能之一,它用来帮助我们将众多的客户端请求合理的分配到各个服务器上,以达到服务端资源充分利用和更少的请求时间。不仅如此,当其中一台服务器发生宕机时,可以将后续的请求分配给另外两台上,保证了服务的可用性。
如上图示例,三个窗口就是需要负载的三个服务器,在Nginx中,使用
upstream
来配置,假设我们将负载均衡名称叫做banlanceServer
,则
upstream balanceServer {
server 10.231.5.100:60001;
server 10.231.5.101:60001;
server 10.231.5.102:60001;
}
那么,在server
块中location
节点的proxy_pass
即可配置为
location / {
root html;
index index.html index.html;
proxy_pass http://banlanceServer;
}
负载均衡策略选择
(1)轮询策略
默认情况下会采用的策略,将所有客户端请求轮询分配给服务端。这种策略是可以正常工作的,但是如果其中某一台服务器压力太大,出现延迟,会影响所有分配在这台服务器下的用户。而轮询依旧会将请求分配给该服务器。
(2)最小连接数策略
将请求优先分配给压力较小的服务器,它可以平衡每个队列的长度,并避免向压力大的服务器添加更多的请求。
upstream balanceServer {
least_conn;
server 10.1.22.33:12345;
server 10.1.22.34:12345;
server 10.1.22.35:12345;
}
(3)最快响应时间策略
依赖于NGINX Plus,优先分配给响应时间最短的服务器。配置办法为将以上least_conn
改成fair
(4)客户端IP绑定
来自同一个ip的请求永远只分配一台服务器,有效解决了动态网页存在的session共享问题。配置办法为将以上least_conn
改成ip_hash
(*)upstream其他状态值
除了如上常规配置外,upstream还能为每一台服务器设置状态值,如下:
-
down
:当前的server临时不参与负载 -
weight
:承受的负载配比,被轮询几率。默认为1,越大负载越高 -
max_fails
:允许请求失败的最大次数,默认为1。超过时返回proxy_next_upstream
模块定义的错误。 -
fail_timeout
:max_fails
之后,暂停的时间。 -
backup
:其他全部的非backup机器down或者忙的时候,请求backup机器。俗称:备用机。
是不是感觉配置起来很简单,但其实这些功能都有Nginx的Http模块为你实现了,如果你有兴趣打开Nginx的源码,可以看到各个模块源码文档如下
# 静态资源服务
静态资源服务又叫动静分离,为了减少不必要的请求以减少资源浪费,请求延时等
location ~* \.(htm|html|js|css|png|gif|jpg|jpeg|eto|svg|ttf|woff|woff2)$ {
root /apps/data/static/;
autoindex on;
access_log off;
expires 10h; # 设置过期时间为10小时
}
当匹配到上述后缀名文件后,直接去apps/data/static/
目录下取,不向后台发请求。
# 配置https服务
要配置https服务,需要有如下流程:
(1)申请证书,便宜ssl官网可以申请三个月免费SSL证书
(2)申请完成,下载nginx版本的证书到本地,一个crt证书文件,一个key密钥文件。
(3)配置nginx如下:
server {
listen 443 ssl; # 443是https的默认端口。80为http的默认端口
sever_name www.domain.com; # 配置域名
ssl_sertificate 证书绝对路径;
ssl_certificate_key 密钥绝对路径;
# locaton / {
# proxy_pass http://10.213.5.100;
# }
}
给一个案例如下图:
如果用户输入
http://域名
,默认80端口,nginx监听到80端口被访问,匹配到域名www.deram.com
,将服务代理到http://192.168.3.10:8080
上,服务器访问页面资源。
如果用户输入
https://二级域名
,默认端口为443,nginx监听到443端口被监听,配到相应的域名,进行证书验证,将服务代理到指定服务器。
# Nginx其他配置描述
(1)全局配置
常用的全局配置如下:
-
user
:指定Nginx Worker进程运行用户以及用户组,默认使用user nobody nobody
-
worker_processes
:指定允许生成的进程数,默认为1。每个Nginx进程平均耗费10M~12M内存。建议指定和CPU的数量一致即可。 -
error_log
:定义全局错误日志文件。日志输出级别有debug、info、notice、warn、error、crit可供选择,其中,debug输出日志最为最详细,而crit输出日志最少。 -
pid
:指定进程pid文件的位置
(2)event
配置选取的驱动模型和进程最大连接数,如下:
-
accept_mutex
:防止惊群现象。即当收到一个请求时,所有的nginx进程都被唤醒,但只有一个会获得分配。 -
use
:Nginx支持的工作模式有select、poll、kqueue、epoll、rtsig和/dev/poll。Apache就是用的select。 -
worker_connections
:每一个worker进程能并发处理(发起)的最大连接数(包含与客户端或后端被代理服务器间等所有连接数)。nginx作为反向代理服务器,计算公式最大连接数 = worker_processes * worker_connections/4
(3)http
http内部也有针对所有server的全局配置,本文就以下案例进行解读,更多好用的配置参见
https://www.cnblogs.com/yanshicheng/p/12673897.html
Nginx会把每个用户访问过程的 日志 信息记录到指定的日志文件里,供网站管理员分析用户浏览行为,问题定位等。和日志相关的配置主要有如下两个:
-
access_log
:配置用户访问日志记录文件。 -
log_format
:自定义记录日志的格式,想让nginx帮你记录日志的内容模板。语法格式:log_format
;
(1)$remote_addr
记录访问网站的客户端地址
(2)$remote_user
远程客户端用户名
(3)$time_local
记录访问时间与时区
(4)$request
用户的http请求起始含信息
(5)$status
http状态码,记录请求的状态,如:200, 301, 404, 500等
(6)$body_bytes_send
服务器发送给客户端的响应body字节数
(7)$http_referer
记录此次请求是从哪个访问链接过来的。
(8)$http_user_agent
记录客户端的UA信息,如浏览器品牌,版本等
(9)$http_x_forwarded_for
当前端有代理服务器时,设置web节点记录客户端地址的配置,此参数需要相关代理也进行此项设置。 -
sendfile
:指定nginx是否调用sendfile函数
来输出文件,sendfile
是一种高效的(zero copy)传输方式,减少用户空间到内核空间的上下文切换,普通应用设为on
即可。IO重负载的可设置off -
tcp_nopush
:仅在sendfile
为on
时生效。设置tcp_nopush
为on
时(默认为off
),表示调用tcp_cork
方法,这时应用程序接收到一个数据包后,不需要马上传送出去,等到数据包最大时再一次性传输出去,这样有利于解决网络堵塞。 -
tcp_nodelay
:tcp_nodelay
的效果和tcp_nopush
是相反的,这两者不应该同时设置。当tcp_nodelay
设置为on
时,表示应用程序接收到一个数据包后就立刻发出该数据,以达到较快的响应速度。 -
sendfile_max_chunk
:每个进程每次调用传输数量不能大于设定的值,默认为0,即不设上限 -
keepalive_timeout
:设置连接超时时间,默认为75s,可以在http,server,location中配置。 -
types_hash_max_size
:设置每个存储MIME type与文件扩展散列桶占用的内存大小,越大检索速度越快,同时也越消耗内存 -
client_max_body_size
:设置NGINX能处理的最大请求主体大小。 如果请求大于指定的大小,则NGINX发回HTTP 413(Request Entity too large)错误。如果服务器处理大文件上传,则该指令则很有用。
......
结束语
Nginx的功能非常强大,也有很多复杂的用法,本文主要介绍常用的配置和知识点,欢迎指导。