早期业务都是基于单体节点部署,前期访问流量不大,单体结构也可满足需求,但随着业务增长,流量也越来越大,最终单台服务器受到的访问压力也会逐步增高。时间一长,单台服务器性能无法跟上业务增长,就会造成线上频繁宕机的现象发生,最终导致系统瘫痪无法继续处理用户的请求。
那么就需要部署集群,引入负载均衡技术。
引入负载均衡技术的优点:
高可用: 当某个节点宕机后可以迅速将流量转移至其他节点。
高性能: 多台服务器共同对外提供服务,为整个系统提供了更高规模的吞吐。
拓展性: 当业务再次出现增长或萎靡时,可再加入/减少节点,灵活伸缩。
Nginx
是目前负载均衡技术中的主流方案,几乎绝大部分项目都会使用它,Nginx
是一个轻量级的高性能HTTP
反向代理服务器,同时它也是一个通用类型的代理服务器,支持绝大部分协议,如TCP、UDP、SMTP、HTTPS
等
Nginx
与Redis相同,都是基于多路复用模型构建出的产物,因此它与Redis
同样具备 「资源占用少、并发支持高」的特点,在理论上单节点的Nginx
同时支持5W
并发连接,而实际生产环境中,硬件基础到位再结合简单调优后确实能达到该数值。
Nginx
引入前后,客户端请求处理流程的对比:
原本客户端是直接请求目标服务器,由目标服务器直接完成请求处理工作;
加入Nginx
后,所有的请求会先经过Nginx
,再由其进行分发到具体的服务器处理,处理完成后再返回Nginx
,最后由Nginx
将最终的响应结果返回给客户端;
首先创建Nginx
的目录并进入:
mkdir /soft && mkdir /soft/nginx/
cd /soft/nginx/
下载Nginx
的安装包,可以通过FTP
工具上传离线环境包,也可通过wget
命令在线获取安装包:
wget https://nginx.org/download/nginx-1.21.6.tar.gz
或
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 -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel
通过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
在Windows/Mac
的浏览器中,直接输入刚刚配置的IP
地址访问Nginx
:
最终看到Nginx
欢迎界面,代表Nginx
安装完成。
首先通过SpringBoot+Freemarker
快速搭建一个WEB
项目:springboot-web-nginx,然后在该项目中,创建一个IndexNginxController.java
文件,逻辑如下:
@Controller
public class IndexNginxController {
@Value("${server.port}")
private String port;
@RequestMapping("/")
public ModelAndView index(){
ModelAndView model = new ModelAndView();
model.addObject("port", port);
model.setViewName("index");
return model;
}
}
在该Controller
类中,存在一个成员变量:port
,它的值即是从application.properties
配置文件中获取server.port
值。当出现访问/
资源的请求时,跳转前端index
页面,并将该值携带返回。
前端的index.ftl
文件代码如下:
Nginx演示页面
欢迎来到熊猫高级会所,我是竹子${port}号!
前提工作准备就绪后,再简单修改一下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;
}
}
因为配置了请求分发的权重,8080、8090
的权重比为2:1
,因此请求会根据权重比均摊到每台机器,也就是8080
一次、8090
两次、8080
一次......
客户端发出的请求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
会依次根据权重比分发请求。
动静分离应该是听的次数较多的性能优化方案,那先思考一个问题:为什么需要做动静分离呢?它带来的好处是什么?
例如淘宝,当浏览器输入www.taobao.com
访问淘宝首页时,打开开发者调试工具可以很明显的看到,首页加载会出现100+
的请求数,而正常项目开发时,静态资源一般会放入到resources/static/
目录下:
在项目上线部署时,这些静态资源会一起打成包,那此时思考一个问题:假设淘宝也是这样干的,那么首页加载时的请求最终会去到哪儿被处理?首页100+
的所有请求都会来到部署WEB
服务的机器处理,那则代表着一个客户端请求淘宝首页,就会对后端服务器造成100+
的并发请求。这对于后端服务器的压力是尤为巨大的。
大部分请求属于静态的,这些资源大概率情况下,长时间也不会出现变动,那为何还要让这些请求到后端再处理呢?能不能在此之前就提前处理掉?因此经过分析之后能够明确一点:做了动静分离之后,至少能够让后端服务减少一半以上的并发量。
先在部署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
效果、图片等依旧有效
其中static
目录下的nginx_style.css
文件已被移除,但效果依旧存在(绿色字体+蓝色大边框)
解读规则:location ~ .*\.(html|htm|gif|jpg|jpeg|bmp|png|ico|txt|js|css)
~
代表匹配时区分大小写
.*
代表任意字符都可以出现零次或多次,即资源名不限制
\.
代表匹配后缀分隔符.
(html|...|css)
代表匹配括号里所有静态资源类型
也可以将静态资源上传到文件服务器中,然后location
中配置一个新的upstream
指向。
建立在动静分离的基础之上,如果一个静态资源的Size
越小,那么自然传输速度会更快,同时也会更节省带宽,因此我们在部署项目时,也可以通过Nginx
对于静态资源实现压缩传输,一方面可以节省带宽资源,第二方面也可以加快响应速度并提升系统整体吞吐。
在Nginx
也提供了三个支持资源压缩的模块ngx_http_gzip_module、ngx_http_gzip_static_module、ngx_http_gunzip_module
,其中ngx_http_gzip_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;
}
在上述的压缩配置中,最后一个gzip_proxied
选项,可以根据系统的实际情况决定,总共存在多种选项:
off
:关闭Nginx
对后台服务器的响应结果进行压缩。
expired
:如果响应头中包含Expires
信息,则开启压缩。
no-cache
:如果响应头中包含Cache-Control:no-cache
信息,则开启压缩。
no-store
:如果响应头中包含Cache-Control:no-store
信息,则开启压缩。
private
:如果响应头中包含Cache-Control:private
信息,则开启压缩。
no_last_modified
:如果响应头中不包含Last-Modified
信息,则开启压缩。
no_etag
:如果响应头中不包含ETag
信息,则开启压缩。
auth
:如果响应头中包含Authorization
信息,则开启压缩。
any
:无条件对后端的响应结果开启压缩机制。
修改好了Nginx
的压缩配置后,可以在原本的index
页面中引入一个jquery-3.6.0.js
文件
接入Nginx
的项目一般请求流程为:“客户端→Nginx
→服务端”,在这个过程中存在两个连接:“客户端→Nginx
、Nginx
→服务端”,那么两个不同的连接速度不一致,就会影响用户的体验(比如浏览器的加载速度跟不上服务端的响应速度)。
在Nginx
也同样存在缓冲区的机制,主要目的就在于:用来解决两个连接之间速度不匹配造成的问题 ,有了缓冲后,Nginx
代理可暂存后端的响应,然后按需供给数据给客户端。先来看看一些关于缓冲区的配置项:
proxy_buffering
:是否启用缓冲机制,默认为on
关闭状态。
client_body_buffer_size
:设置缓冲客户端请求数据的内存大小。
proxy_buffers
:为每个请求/连接设置缓冲区的数量和大小,默认4 4k/8k
。
proxy_buffer_size
:设置用于存储响应头的缓冲区大小。
proxy_busy_buffers_size
:在后端数据没有完全接收完成时,Nginx
可以将busy
状态的缓冲返回给客户端,该参数用来设置busy
状态的buffer
具体有多大,默认为proxy_buffer_size*2
。
proxy_temp_path
:当内存缓冲区存满时,可以将数据临时存放到磁盘,该参数是设置存储缓冲数据的目录。
path
是临时目录的路径。
语法:proxy_temp_path path;
path是临时目录的路径
proxy_temp_file_write_size
:设置每次写数据到临时文件的大小限制。
proxy_max_temp_file_size
:设置临时的缓冲目录中允许存储的最大容量。
非缓冲参数项:
proxy_connect_timeout
:设置与后端服务器建立连接时的超时时间。
proxy_read_timeout
:设置从后端服务器读取响应数据的超时时间。
proxy_send_timeout
:设置向后端服务器传输请求数据的超时时间。
具体的nginx.conf
配置如下:
http{
proxy_connect_timeout 10;
proxy_read_timeout 120;
proxy_send_timeout 10;
proxy_buffering on;
client_body_buffer_size 512k;
proxy_buffers 4 64k;
proxy_buffer_size 16k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 128k;
proxy_temp_path /soft/nginx/temp_buffer;
}
使用缓冲也可以减少即时传输带来的带宽消耗。
对于整个系统而言,加入缓存带来的优势额外明显:
减少了再次向后端或文件服务器请求资源的带宽消耗。
降低了下游服务器的访问压力,提升系统整体吞吐。
缩短了响应时间,提升了加载速度,打开页面的速度更快。
在Nginx
中,如何配置代理缓存,缓存相关的配置项:
proxy_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size] [manager_files=number] [manager_sleep=time] [manager_threshold=time] [loader_files=number] [loader_sleep=time] [loader_threshold=time] [purger=on|off] [purger_files=number] [purger_sleep=time] [purger_threshold=time];
解释一下每个参数项的含义:
path
:缓存的路径地址。
levels
:缓存存储的层次结构,最多允许三层目录。
use_temp_path
:是否使用临时目录。
keys_zone
:指定一个共享内存空间来存储热点Key(1M可存储8000个Key)。
inactive
:设置缓存多长时间未被访问后删除(默认是十分钟)。
max_size
:允许缓存的最大存储空间,超出后会基于LRU算法移除缓存,Nginx会创建一个Cache manager的进程移除数据,也可以通过purge方式。
manager_files
:manager进程每次移除缓存文件数量的上限。
manager_sleep
:manager进程每次移除缓存文件的时间上限。
manager_threshold
:manager进程每次移除缓存后的间隔时间。
loader_files
:重启Nginx载入缓存时,每次加载的个数,默认100。
loader_sleep
:每次载入时,允许的最大时间上限,默认200ms。
loader_threshold
:一次载入后,停顿的时间间隔,默认50ms。
purger
:是否开启purge方式移除数据。
purger_files
:每次移除缓存文件时的数量。
purger_sleep
:每次移除时,允许消耗的最大时间。
purger_threshold
:每次移除完成后,停顿的间隔时间。
proxy_cache zone | off;
zone为内存区域的名称,即上面中keys_zone设置的名称。
proxy_cache_key string;
string为生成Key的规则,如$scheme$proxy_host$request_uri
。
proxy_cache_valid [code ...] time;
code为状态码,time为有效时间,可以根据状态码设置不同的缓存时间。
例如:proxy_cache_valid 200 302 30m;
proxy_cache_min_uses number;
number为次数,默认为1。
proxy_cache_use_stale error;
error为错误类型,可配置timeout|invalid_header|updating|http_500...
。
proxy_cache_lock on | off;
proxy_cache_lock_timeout time;
proxy_cache_methods method;
method为请求方法类型,如GET、HEAD等。
proxy_no_cache string...;
string为条件,例如$cookie_nocache $arg_nocache $arg_comment;
proxy_cache_bypass string...;
和上面proxy_no_cache
的配置方法类似。
MISS
:请求未命中缓存。
HIT
:请求命中缓存。
EXPIRED
:请求命中缓存但缓存已过期。
STALE
:请求命中了陈旧缓存。
REVALIDDATED
:Nginx验证陈旧缓存依然有效。
UPDATING
:命中的缓存内容陈旧,但正在更新缓存。
BYPASS
:响应结果是从原始服务器获取的。
配置一下Nginx
代理缓存:
http{
# 设置缓存的目录,并且内存中缓存区名为hot_cache,大小为128m,
# 三天未被访问过的缓存自动清楚,磁盘中缓存的最大容量为2GB。
proxy_cache_path /soft/nginx/cache levels=1:2 keys_zone=hot_cache:128m inactive=3d max_size=2g;
server{
location / {
# 使用名为nginx_cache的缓存空间
proxy_cache hot_cache;
# 对于200、206、304、301、302状态码的数据缓存1天
proxy_cache_valid 200 206 304 301 302 1d;
# 对于其他状态的数据缓存30分钟
proxy_cache_valid any 30m;
# 定义生成缓存键的规则(请求的url+参数作为key)
proxy_cache_key $host$uri$is_args$args;
# 资源至少被重复访问三次后再加入缓存
proxy_cache_min_uses 3;
# 出现重复请求时,只让一个去后端读数据,其他的从缓存中读取
proxy_cache_lock on;
# 上面的锁超时时间为3s,超过3s未获取数据,其他请求直接去后端
proxy_cache_lock_timeout 3s;
# 对于请求参数或cookie中声明了不缓存的数据,不再加入缓存
proxy_no_cache $cookie_nocache $arg_nocache $arg_comment;
# 在响应头中添加一个缓存是否命中的状态(便于调试)
add_header Cache-status $upstream_cache_status;
}
}
}