Nginx: 动静分离、压缩、缓存、黑白名单、跨域、高可用......
单机部署的问题
-
- 单体结构的部署方式无法承受日益增长的业务流量。
-
- 当后端节点宕机后,整个系统会陷入瘫痪,导致整个项目不可用。
负载均衡技术可带来的收益
-
- 系统的高可用:当某个节点宕机后可以迅速将流量转移到其它节点。
-
- 系统的高性能:多台服务器共同对外提供服务,为整个系统提供了更高规模的吞吐。
-
- 系统的拓展性:当业务再次出现增长或萎靡时,可再加入/减少节点,灵活伸缩。
负载均衡方案
-
- 硬件层面:比较常用的硬件负载器:A10、F5等。
-
- 软件层面:Nginx等。
一、Nginx概念深入浅出
Nginx是目前负载均衡技术中的主流方案,几乎绝大部分项目都会使用它,Nginx是一个轻量级的高性能HTTP反向代理服务器,同时它也是一个通用类型的代理服务器,支持绝大部分协议,如TCP、UDP、SMTP、HTTPS等。
Nginx与Redis相同,都是基于多路复用模型构建出的产物,因此同样具备资源占用少、并发支持高的特点,在理论上单节点的Nginx可同时支持5W并发连接,而实际生产环境中,硬件基础到位再结合简单调优后确实能达到该数值。下图是引入Nginx前后,客户端请求处理流程的对比:
原本客户端是直接请求目标服务器,由目标服务器直接完成请求处理工作,但加入Nginx后,所有的请求会先经过Nginx,再由Nginx进行分发到具体的服务器处理,服务器处理完成后再返回Nginx,最后由Nginx将最终的响应结果返回给客户端。
下面进行环境搭建、动静分离、资源压缩、缓存配置、IP黑名单、高可用保障等的介绍。
二、Nginx环境搭建
-
- 首先创建Nginx的目录并进入
mkdir /soft && mkdir /soft/nginx cd /soft/nginx
-
- 下载Nginx的安装包,可以通过
FTP
工具上传离线环境包,也可以通过wget
命令在线获取安装包
- 下载Nginx的安装包,可以通过
wget https://nginx.org/download/nginx-1.21.6.tar.gz
如果没有wget
命令可以通过yum命令安装
yum -y install wget
-
- 解压
Nginx
的压缩包
- 解压
tar -xvzf nginx-1.21.6.tar.gz
-
- 下载并安装
Nginx
所需的依赖库和包
- 下载并安装
yum install --downloadonly --downloaddir=/soft/nginx/ gcc-c++ yum install --downloadonly --downloaddir=/soft/nginx/ pcre pcre-devel4 yum install --downloadonly --downloaddir=/soft/nginx/ zlib zlib-devel yum install --downloadonly --downloaddir=/soft/nginx/ openssl openssl-devel
也可以通过yum
命令一键下载(推荐上面那种方式)
yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel
执行完成后通过ls
可以看到很多依赖
通过rpm
命令依次将依赖包一个个构建,或者通过以下命令一键安装所有依赖包
rpm -ivh --nodeps *.rpm
-
- 进入解压后的
Nginx
目录,执行Nginx
的配置脚本,为后续的安装提前配置好环境,默认位于/usr/local/nginx
目录下(可自定义目录)
- 进入解压后的
cd nginx-1.21.6 ./configure --prefix=/soft/nginx/
-
- 编译并安装
Nginx
- 编译并安装
make && make install
-
- 最后回到前面的
/soft/nginx/
目录,输入ls
即可看见安装nginx
完成后生成的文件。
- 最后回到前面的
-
- 修改安装后生成的
conf
目录下的nginx.conf
配置文件:
- 修改安装后生成的
vi conf/nginx.conf 修改端口号:listen 80; 修改IP地址:server_name 你当前机器的本地IP(线上配置域名);
-
- 指定配置文件并启动
Nginx
- 指定配置文件并启动
sbin/nginx -c conf/nginx.conf ps aux | grep nginx
Nginx
的其它操作命令
sbin/nginx -t -c conf/nginx.conf # 检测配置文件是否正常 sbin/nginx -s reload -c conf/nginx.conf # 修改配置后平滑重启 sbin/nginx -s quit # 优雅关闭Nginx,会在执行完当前的任务后再退出 sbin/nginx -s stop # 强制终止Nginx,不管当前是否有任务在执行
-
- 开发80端口,并更新防火墙
firewall-cmd --zone=public --add-port=80/tcp --permanent firewall-cmd --reload firewall-cmd --zone=public --list-ports
-
- 在浏览器中访问
步骤8
中配置的ip地址,无需输入端口号
最终看到如上的Nginx欢迎界面,代表Nginx安装完成。
- 在浏览器中访问
三、Nginx反向代理-负载均衡
首先通过SpringBoot+Freemarker快速搭建一个WEB项目:springboot-web-nginx,然后在该项目中,创建一个IndexNginxController.java文件:
public class IndexNginxController {
@Value("${server.port}")
private String port;
@RequestMapping("/")
public ModelAndView index() {
ModelAndView mv = new ModelAndView();
mv.addObject("port", port);
mv.setViewName("index");
return mv;
}
}
在该Controller类中,存在一个成员变量:port,它的值是从application.properties配置文件中获取的server.port值。当出现访问/
资源的请求时,展示index页面,并在页面中展示该成员变量的值。
前端的index.ftl
文件代码:
<html>
<head>
<title>Nginx演示页面</title>
<link href="nginx_style.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div style="border: 2px solid red;margin: auto;width: 800px;text-align: center">
<div id="nginx_title">
<h1>欢迎来到熊猫高级会所,我是竹子${port}号!</h1>
</div>
</div>
</body>
</html>
接下来修改一下Nginx
的配置文件nginx.conf
的配置
upstream nginx_boot {
# 30s内发送两次心跳检测包,未回复就代表该机器宕机,请求分发权重比为1:2
server 192.168.0.000:8080 weight=100 max_fails=2 fail_timeout=30s;
server 192.168.0.000:8090 weight=200 max_fails=2 fail_timeout=30s;
# 上面的ip需要配置成你部署Web服务的机器ip
}
server {
location / {
root html;
# 配置一下index的地址,最后加上index.ftl
index index.html index.htm index.jsp index.ftl
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 请求交给名为nginx_boot的upstream上
proxy_pass http://nginx_boot;
}
}
至此,所有的前提工作准备就绪,然后启动Nginx以及两个Web服务,Web服务的端口分别是8080和8090。
最终的效果如下图所展示:
https://mmbiz.qpic.cn/mmbiz_gif/7VqDzaTV5hhDrPNkSohviaXor0hSpe6QUFH46c1J9UgDsSGg0rLABRFt9Do580ayEIJ7tVXuF1ia963FsI3GvkUg/640?wx_fmt=gif&tp=wxpic&wxfrom=5&wx_lazy=1
因为配置了请求分发的权重,8080、8090的权重比为2:1,因此请求会根据权重比均摊到每台机器,也就是8080一次、8090两次、8080一次、8090两次(循环往复)。
3.1
Nginx
请求分发原理
客户端发出的请求192.168.12.129最终会变为:http://192.168.12.129:80/,然后再向目标ip发起请求,流程如下:
- 由于
Nginx
监听了192.168.12.129的80端口,所以最终该请求会找到Nginx
进程。Nginx
首先会根据配置的location
规则进行匹配,根据客户端的请求路径/
,会定位到location/{}
规则。- 然后根据该
location
中配置的proxy_pass
会再找到名为nginx_boot
的upstream
。- 最后根据
upstream
中的配置信息,将请求转发到WEB服务器进行处理,由于配置了多个WEB服务,且配置了权重比,因此Nginx
会根据权重比依次分发请求。
四、Nginx动静分离
当浏览器输入www.taobao.com访问淘宝首页时,打开开发者调试工具可以很明显的看到,首页加载会出现100+的请求数,而对于前后端不分离的项目,在进行开发时,静态资源一般会放入到resources/static/
目录下。项目上线部署时,这些静态资源会一起打成包,此时,对于静态资源的访问就会对服务器造成很大压力。这些资源大概率情况下,长时间也不会出现变动,那为何还要让这些请求到后端再处理呢?能不能在此之前就提前处理掉?当然OK,因此经过分析之后能够明确一点:做了动静分离之后,至少能够让后端服务减少一半以上的并发量。 到此时大家应该明白了动静分离能够带来的性能收益究竟有多大。
接下来使用
Nginx
实现动静分离
- 先在
Nginx
的安装目录下创建一个目录static_resources。mkdir static_resources
- 将项目中所有的静态资源全部拷贝到该目录下,然后将项目中的静态资源全部移除后重新打包。
- 在
nginx.conf
文件中增加一条location
匹配规则location ~ .*\.(html|htm|gif|jpg|jpeg|bmp|png|ico|txt|js|css) { root /soft/nginx/static_resources; expires 7d; }
然后照常启动Nginx
和移除了静态资源的Web
服务,你会发现原本的样式、js效果、图片等依旧有效。
location规则解释如下:
~
代表匹配时区分大小写
.*
代表任意字符都可以出现零次或多次,即资源名不限制
\.
代表匹配后缀分隔符.
(html|htm|gif|jpg|jpeg|bmp|png|ico|txt|js|css)
代表匹配括号里所有的静态资源类型
综上所述,该配置表示匹配以html|htm|gif|jpg|jpeg|bmp|png|ico|txt|js|css为后缀的所有资源请求
当然也可以将静态资源上传到文件服务器中,然后在location
中配置一个新的upstream
指向。
五、Nginx资源压缩
建立在动静分离的基础之上,如果一个静态资源的size越小,那么传输速度就会更快,同时也会节省宽带,因此在部署项目时,也可以通过Nginx
对于静态资源实现压缩传输,一方面可以节省带宽资源,第二方面也可以加快响应速度并提升系统整体吞吐。
Nginx
也提供了三个支持资源压缩的模块:ngx_http_gzip_module
、ngx_http_gzip_static_module
、ngx_http_gunzip_module
,其中ngx_http_gzip_static_module
属于内置模块,代表着可以直接使用该模块下的一些压缩指令,后续的资源压缩操作都基于该模块,先来看看压缩配置的一些参数/指令:
了解了Nginx
中的基本配置压缩后,接下来在Nginx
中简单配置一下(根据项目实际情况调整即可):
http {
# 开启压缩机制
gzip on;
# 指定会被压缩的文件类型
gzip_types text/plain application/javascript text/css application/xml text/javascript image/jpeg image/gif image/png;
# 设置压缩级别,越高资源消耗越大,压缩效果也越好
gzip_comp_level 5;
# 在头部中添加Vary: Accept-Encoding(建议开启)
gzip_vary on;
# 处理压缩请求的缓冲区数量和大小
gzip_buffers 16 8k;
# 对于不支持压缩功能的客户端请求不开启压缩机制
gzip_disable "MSIE [1-6]\."; # 低版本的IE浏览器不支持压缩
# 设置压缩响应所支持的HTTP最低版本
gzip_http_version 1.1;
# 设置触发压缩的最小阈值
gzip_min_length 2k;
# 关闭对后端服务器的响应结果进行压缩
gzip_proxied off;
}
六、Nginx缓冲区
接入Nginx
的项目一般请求流程为:“客户端–>Nginx
–>服务端”,在这个过程中存在两个连接:“客户端–>Nginx
”、“Nginx
–>服务端”,那么两个不同的连接速度不一致,就会影响用户的体验(比如浏览器的加载速度跟不上服务端的响应速度)。其实也就类似电脑的内存跟不上CPU的速度,所以对于用户造成的体验感极差,因此在CPU设计时都会加入三级高速缓冲区,用于缓解CPU和内存速度不一致的矛盾。在Nginx
也同样存在缓冲区的机制,主要目的就在于用来解决连接之间速度不匹配造成的问题
,有了缓冲后,Nginx
代理可暂存后端的响应,然后按需供给数据给客户端。先来看看一些关于缓冲区的配置项:
七、Nginx缓存机制
对于性能优化而言,缓存是一种能够大幅度提升性能的方案,因此几乎可以在各处都能看见缓存,如客户端缓存、代理缓存、服务器缓存等等,Nginx
的缓存则属于代理缓存的一种。对于整个系统而言,加入缓存带来的优势格外明显:
-
- 减少了再次向后端或文件服务器请求资源的宽带消耗。
-
- 降低了下游服务器的访问压力,提升系统整体吞吐。
-
- 缩短了响应时间,提升了加载速度,打开页面的速度更快。
那么在Nginx
中,该如何配置代理缓存呢?以下是缓存相关的配置项:
proxy_cache_path
: 代理缓存的路径。path
:
八、Nginx实现IP黑白名单
有时候往往有些需求,可能某些接口只能开放给对应的合作商或者购买/接入API
的合作伙伴,那么此时就需要实现类似于IP白名单
的功能。而有时候有些恶意攻击或爬虫程序,被识别到后需要禁止其再次访问网站,因此也需要实现IP黑名单
。有了Ngxin
后这些功能无需交由后端实现,可直接在Nginx
中处理。
Nginx
做黑白名单机制,主要是通过allow、deny
配置项来实现:
allow xxx.xxx.xxx.xxx; # 允许指定的IP访问,可以用于实现白名单
deny xxx.xxx.xxx.xxx; # 禁止指定的IP访问,可以用于实现黑名单
server {
listen 80;
server_name example.com;
location / {
# 允许特定的 IP 或 IP 段访问
allow 192.168.1.10;
allow 10.0.0.0/8; # 允许整个 10.0.0.0/8 网段
allow 172.16.0.0/12; # 允许整个 172.16.0.0/12 网段
deny all; # 拒绝其他所有未明确允许的 IP 访问
}
}
要同时屏蔽/开放多个IP访问时,如果所有IP全部写在nginx.conf
文件中定然是不合适的,这种方式比较冗余,那么可以新建两个文件【BlocksIP.conf、WhiteIP.conf
】:
黑名单:BlocksIP.conf
deny 192.177.12.222; # 屏蔽192.177.12.222访问
deny 192.177.44.201; # 屏蔽192.177.44.201访问
deny 127.0.0.0/8; # 屏蔽127.0.0.0到127.255.255.255网段中的所有IP访问
白名单:WhiteIP.conf
allow 192.177.12.222; # 允许192.177.12.222访问
allow 192.177.44.201; # 允许192.177.44.201访问
allow 127.45.0.0/16; # 允许127.45.0.0到127.45.255.255网段中的所有IP访问
deny all; # 除上述IP外,其它IP全部禁止访问
然后将这两个文件在nginx.conf
中配置即可
http {
# 屏蔽该文件中的所有IP
include /soft/nginx/IP/BlocksIP.conf;
server {
location xxx {
# 某一系列接口只开放给白名单中的IP
include /soft/nginx/IP/WhiteIP.conf
}
}
}
如果要整站屏蔽/开放就在http
中配置,如果只需要域名屏蔽/开放就在server
中配置,如果只需要针对于某一系列接口屏蔽/开放就在location
中配置。
以上只是最简单的IP黑/白名单实现方式,同时也可以通过
ngx_http_geo_module
第三方库去实现(这种方式可以按地区、国家进行屏蔽,并且提供了IP库)。
九、Nginx跨域配置
跨域问题在之前的单体架构开发中,是比较少见的问题,除非是需要接入第三方SDK时,才需要处理此问题。但随着现在前后端分离、分布式架构的流行,跨域问题也成为了每个开发人员必须要懂得解决的一个问题。
跨域问题产生的原因
产生跨域问题的主要原因就在于同源策略
,为了保证用户信息安全,防止恶意网站窃取数据,同源策略
是必须的,否则cookie
可以共享。有http
协议是无状态的,通常会借助cookie
来实现有状态的信息记录,例如用户的身份/密码等,因此一旦cookie
被共享,那么就会导致用户的身份信息被盗取。
同源策略
主要指三点相同,协议+域名+端口相同的两个请求,则可以被看做是同源的,但如果其中任意一点存在不同,则代表两个请求是不同源的,同源策略
会限制不用源之间的资源交互。
Nginx解决跨域问题
在nginx.conf
文件中添加如下配置即可解决跨域问题:
location / {
# 允许跨域的请求,可以自定义变量$http_origin, *表示所有
add_header 'Access-Control-Allow-Origin' *;
# 允许携带cookie请求
add_header 'Access-Control-Allow-Credentials' 'true';
# 允许跨域请求的方法: GET,POST,OPTIONS,PUT
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT';
# 允许请求时携带的头部信息,*表示所有
add_header 'Access-Control-Allow-Headers' *;
# 允许发送按段获取资源的请求
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
# 一定要有,否则Post请求无法进行跨域
# 在发送Post跨域请求前,会以Options方式发送预检请求,服务器接受时才会正式请求
if($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain'; charset=utf-8;
add_header 'Content-Length' 0;
# 对于Options方式的请求返回204,表示接受跨域请求
return 204;
}
}
添加完成后,重启nginx
,跨域请求即可生效。
但如果后端是采用分布式架构开发的,有时候RPC调用也需要解决跨域问题,不然也同样会出现无法跨域请求的异常,因此可以在后端项目中,通过继承
HandlerInterceptorAdapter
类、实现WebMvcConfigurer
接口、添加@CrossOrgin
注解的方式实现接口之间的跨域配置。
十、Nginx防盗链设计
何谓盗链
盗链即是指外部网站引入当前网站的资源对外展示,来举个简单的例子:
有壁纸网站
X
站、Y
站,X
站是一点点去购买版权、签约作者的方式,从而积累了海量的壁纸素材,但Y
站由于资金等各方面的原因,就直接通过<img src="X站/xxx.jpg" />
这种方式照搬了X站的所有壁纸资源,继而提供给用户下载。
那么如果我们自己是这个X站的Boss,心中必然不爽,那么此时又该如何屏蔽这类问题呢?那么接下来要叙说的防盗链 登场了!
Nginx
的防盗链机制实现,与请求头部中的Referer
字段有关,该字段主要描述了当前请求是从哪发出的,那么在Nginx
中就可获取该值,然后判断是否为本站的资源引用请求,如果不是则不允许访问。Nginx
中存在一个配置项为valid_referers
,可以满足前面的需求,语法如下:
valid_referers none | blocked | server_names | string ...;
none
: 表示接受没有Referer
字段的HTTP
请求访问。blocked
: 表示允许http://
或https://
以外的请求访问。server_names
: 资源的白订单,这里可以指定允许访问的域名。string
: 可自定义字符串,支持通配符、正则表达式写法。
十一、Nginx大文件传输配置
在某些业务场景中需要传输一些大文件,但大文件传输时往往都会出现一些Bug
,比如文件超出限制、文件传输过程中请求超时等,那么此时就可以在Nginx
稍微做一些配置,先来了解一些关于大文件传输时可能会用的配置项: