Nginx负载均衡
负载均衡用于从"upstream"模块定义的后端服务器列表中选取一台服务器接受用户的请求,一个最基本的upstream模块是这样的,模块内的server是服务器列表:
cpp
#动态服务器组
upstream dynamicserver {
server 172.16.44.47:9001; #tomcat 1
server 172.16.44.47:9002; #tomcat 2
server 172.16.44.47:9003; #tomcat 3
server 172.16.44.47:9004; #tomcat 4
}
在upstream模块配置完成后,要让指定的访问反向代理到服务器列表
cpp
#其他页面反向代理到tomcat容器
location ~.*$ {
index index.jsp index.html;
proxy_pass http://dynamicserver;
}
这就是最基本的负载均衡实例,但这不足以满足实际需求;目前Nginx服务器的upstream模块支持6种方式的分配。
完整配置文件如下
cs
upstream dynamicserver {
server 192.168.64.1:9001; #tomcat 1
server 192.168.64.1:9002; #tomcat 2
server 192.168.64.1:9003; #tomcat 3
server 192.168.64.1:9004; #tomcat 4
}
server {
server_name www.itcast.com;
default_type text/html;
charset utf-8;
location ~ .*$ {
index index.jsp index.html;
proxy_pass http://dynamicserver;
}
}
常用参数
参数 | 描述 |
---|---|
server | 反向服务地址 加端口 |
weight | 权重 |
fail_timeout | 与max_fails结合使用。 |
max_fails | 设置在fail_timeout参数设置的时间内最大失败次数,如果在这个时间内,所有针对该服务器的请求都失败了,那么认为该服务器会被认为是停机了 |
max_conns | 允许最大连接数 |
fail_time | 服务器会被认为停机的时间长度,默认为10s |
backup | 标记该服务器为备用服务器,当主服务器停止时,请求会被发送到它这里。 |
down | 标记服务器永久停机了 |
slow_start | 当节点恢复,不立即加入 |
负载均衡策略
在这里,只详细说明Nginx自带的负载均衡策略。
负载策略 | 描述 |
---|---|
轮询 | 默认方式 |
weight | 权重方式 |
ip_hash | 依据ip分配方式 |
least_conn | 最少连接方式 |
fair(第三方) | 响应时间方式 |
url_hash(第三方) | 依据URL分配方式 |
轮询
最基本的配置方法,上面的例子就是轮询的方式,它是upstream模块默认的负载均衡默认策略,每个请求会按时间顺序逐一分配到不同的后端服务器。
cs
#动态服务器组
upstream dynamicserver {
server 192.168.64.1:9001; #tomcat 1
server 192.168.64.1:9002; #tomcat 2
server 192.168.64.1:9003; #tomcat 3
server 192.168.64.1:9004; #tomcat 4
}
注意
- 在轮询中,如果服务器down掉了,会自动剔除该服务器。
- 缺省配置就是轮询策略。
- 此策略适合服务器配置相当,无状态且短平快的服务使用。
配置示例
cs
upstream dynamicserver {
server 192.168.64.1:9001; #tomcat 1
server 192.168.64.1:9002; #tomcat 2
server 192.168.64.1:9003; #tomcat 3
server 192.168.64.1:9004; #tomcat 4
}
server {
server_name www.test.com;
default_type text/html;
charset utf-8;
location ~ .*$ {
index index.jsp index.html;
proxy_pass http://dynamicserver;
}
}
weight
权重方式,在轮询策略的基础上指定轮询的几率
cpp
#动态服务器组
upstream dynamicserver {
server 192.168.64.1:9001 weight=2; #tomcat 1
server 192.168.64.1:9002; #tomcat 2
server 192.168.64.1:9003; #tomcat 3
server 192.168.64.1:9004; #tomcat 4
}
weight参数用于指定轮询几率,weight的默认值为1,;weight的数值与访问比率成正比,比如Tomcat 7.0被访问的几率为其他服务器的两倍。
注意
- 权重越高分配到需要处理的请求越多。
- 此策略可以与least_conn和ip_hash结合使用。
- 此策略比较适合服务器的硬件配置差别比较大的情况。
ip_hash
指定负载均衡器按照基于客户端IP的分配方式,这个方法确保了相同的客户端的请求一直发送到相同的服务器,以保证session会话,这样每个访客都固定访问一个后端服务器,可以解决session不能跨服务器的问题
cpp
upstream dynamicserver {
ip_hash; #保证每个访客固定访问一个后端服务器
server 192.168.64.1:9001 weight=2; #tomcat 1
server 192.168.64.1:9002; #tomcat 2
server 192.168.64.1:9003; #tomcat 3
server 192.168.64.1:9004; #tomcat 4
}
注意
- 在nginx版本1.3.1之前,不能在ip_hash中使用权重(weight)。
- ip_hash不能与backup同时使用
- 此策略适合有状态服务,比如session。
- 当有服务器需要剔除,必须手动down掉。
least_conn
把请求转发给连接数较少的后端服务器,轮询算法是把请求平均的转发给各个后端,使它们的负载大致相同;但是,有些请求占用的时间很长,会导致其所在的后端负载较高,这种情况下,least_conn这种方式就可以达到更好的负载均衡效果。
cpp
upstream dynamicserver {
least_conn; #把请求转发给连接数较少的后端服务器
server 192.168.64.1:9001 weight=2; #tomcat 1
server 192.168.64.1:9002; #tomcat 2
server 192.168.64.1:9003; #tomcat 3
server 192.168.64.1:9004; #tomcat 4
}
- 此负载均衡策略适合请求处理时间长短不一造成服务器过载的情况。
重试策略
现在对外服务的网站,很少只使用一个服务节点,而是部署多台服务器,上层通过一定机制保证容错和负载均衡。
基础失败重试
为了方便理解,使用了以下配置进行分析(proxy_next_upstream
没有特殊配置)
cpp
upstream dynamicserver {
server 192.168.64.1:9001 fail_timeout=60s max_fails=3; #Server A
server 192.168.64.1:9002 fail_timeout=60s max_fails=3; #Server B
}
配置说明
max_fails=3 fail_timeout=60s
代表在60
秒内请求某一应用失败3
次,认为该应用宕机,后等待60
秒,这期间内不会再把新请求发送到宕机应用,而是直接发到正常的那一台,时间到后再有请求进来继续尝试连接宕机应用且仅尝试1
次,如果还是失败,则继续等待60
秒...以此循环,直到恢复
模拟异常
模拟后端异常的方式是直接将对应服务关闭,造成 connect refused 的情况,对应
error
错误。
在最初始阶段,所有服务器都正常,请求会按照轮询方式依次转发给 AB 两个 Server 处理。假设这时 A 节点服务崩溃,端口不通,则会出现这种情况:
- 请求 1 转到 A 异常,再重试到 B 正常处理,A fails +1
- 请求 2 转到 B 正常处理
- 请求 3 转到 A 异常,再重试到 B 正常处理,A fails +1 达到 max_fails 将被屏蔽 60s
- 屏蔽 A 的期间请求都只转给 B 处理,直到屏蔽到期后将 A 恢复重新加入存活列表,再按照这个逻辑执行
如果在 A 的屏蔽期还没结束时,B 节点的服务也崩溃,端口不通,则会出现:
-
请求 1 转到 B 异常,此时所有线上节点异常,会出现:
- AB 节点一次性恢复,都重新加入存活列表
- 请求转到 A 处理异常,再转到 B 处理异常
- 触发 no live upstreams 报错,返回 502 错误
- 所有节点再次一次性恢复,加入存活列表
-
请求 2 依次经过 AB 均无法正常处理, 触发
no live upstreams
报错,返回 502 错误
错误重试
有时候我们系统出现500
等异常的情况下,希望nginx能够到其他的服务器进行重试,我们可以配置那些错误码才进行重试。在nginx的配置文件中,proxy_next_upstream项定义了什么情况下进行重试,官网文档中给出的说明如下
cpp
Syntax: proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | off ...;
Default: proxy_next_upstream error timeout;
Context: http, server, location
默认情况下,当请求服务器发生错误或超时时,会尝试到下一台服务器,还有一些其他的配置项如下:
错误状态 | 描述 |
---|---|
error | 与服务器建立连接,向其传递请求或读取响应头时发生错误; |
timeout | 在与服务器建立连接,向其传递请求或读取响应头时发生超时; |
invalid_header | 服务器返回空的或无效的响应; |
http_500 | 服务器返回代码为500的响应; |
http_502 | 服务器返回代码为502的响应; |
http_503 | 服务器返回代码为503的响应; |
http_504 | 服务器返回代码504的响应; |
http_403 | 服务器返回代码为403的响应; |
http_404 | 服务器返回代码为404的响应; |
http_429 | 服务器返回代码为429的响应(1.11.13); |
non_idempotent | 通常,请求与 非幂等 方法(POST,LOCK,PATCH)不传递到请求是否已被发送到上游服务器(1.9.13)的下一个服务器; 启用此选项显式允许重试此类请求; |
off | 禁用将请求传递给下一个服务器。 |
配置说明
这里面我们配置了500
等错误的时候会进行重试
cpp
upstream dynamicserver {
server 192.168.64.1:9001 fail_timeout=60s max_fails=3; #tomcat 1
server 192.168.64.1:9002 fail_timeout=60s max_fails=3; #tomcat 2
}
server {
server_name www.itcast.com;
default_type text/html;
charset utf-8;
location ~ .*$ {
index index.jsp index.html;
proxy_pass http://dynamicserver;
#下一节点重试的错误状态
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
}
}
模拟异常
在正常的情况下如果
500
错误会直接出现异常页面,现在我们加入了以上500
重试策略,重试错误的流程和上面流程是一样的
backup 服务器
Nginx 支持设置备用节点,当所有线上节点都异常时启用备用节点,同时备用节点也会影响到失败重试的逻辑,因此单独列出来介绍。
backup 处理逻辑
upstream 的配置中,可以通过
backup
指令来定义备用服务器,其含义如下
- 正常情况下,请求不会转到到 backup 服务器,包括失败重试的场景
- 当所有正常节点全部不可用时,backup 服务器生效,开始处理请求
- 一旦有正常节点恢复,就使用已经恢复的正常节点
- backup 服务器生效期间,不会存在所有正常节点一次性恢复的逻辑
- 如果全部 backup 服务器也异常,则会将所有节点一次性恢复,加入存活列表
- 如果全部节点(包括 backup)都异常了,则 Nginx 返回 502 错误
配置说明
cpp
upstream dynamicserver {
server 192.168.64.1:9001 fail_timeout=60s max_fails=3; #Service A
server 192.168.64.1:9002 fail_timeout=60s max_fails=3; #Server B
server 192.168.64.1:9003 backup; #backup
}
server {
server_name www.itcast.com;
default_type text/html;
charset utf-8;
location ~ .*$ {
index index.jsp index.html;
proxy_pass http://dynamicserver;
#下一节点重试的错误状态
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
}
}
在最初始阶段,所有服务器都正常,请求会按照轮询方式依次转发给 AB 两个节点处理。当只有 A 异常的情况下,与上文没有 backup 服务器场景处理方式一致,这里就不重复介绍了。
假设在 A 的屏蔽期还没结束时,B 节点的服务也崩溃,端口不通,则会出现:
-
请求 1 转到 B 处理,异常,此时所有线上节点异常,会出现:
- AB 节点一次性恢复,都重新加入存活列表
- 请求转到 A 处理异常,再重试到 B 处理异常,两者 fails 都 +1
- 因 AB 都异常,启用 backup 节点正常处理,并且 AB 节点一次性恢复,加入存活列表
-
请求 2 再依次经过 A、B 节点异常,转到 backup 处理,两者 fails 都达到 max_fails:
- AB 节点都将会被屏蔽 60s,并且不会一次性恢复
- backup 节点正式生效,接下来所有请求直接转到 backup 处理
- 直到 AB 节点的屏蔽到期后,重新加入存活列表
假设 AB 的屏蔽期都还没结束时,C 节点的服务也崩溃,端口不通,则会出现
-
请求 1 转到 C 异常,此时所有节点(包括 backup)都异常,会出现:
- ABC 三个节点一次性恢复,加入存活列表
- 请求转到 A 处理异常,重试到 B 处理异常,最后重试到 C 处理异常
- 触发
no live upstreams
报错,返回 502 错误 - 所有节点再次一次性恢复,加入存活列表
-
请求 2 依次经过 AB 节点异常,重试到 C 异常,最终结果如上个步骤,返回 502 错误
限制重试方式
默认配置是没有做重试机制进行限制的,也就是会尽可能去重试直至失败,Nginx 提供了以下两个参数来控制重试次数以及重试超时时间
proxy_next_upstream_tries
:设置重试次数,默认0
表示无限制,该参数包含所有请求 upstream server 的次数,包括第一次后之后所有重试之和;proxy_next_upstream_timeout
:设置重试最大超时时间,默认0
表示不限制,该参数指的是第一次连接时间加上后续重试连接时间,不包含连接上节点之后的处理时间
cpp
upstream dynamicserver {
server 192.168.64.1:9001 fail_timeout=60s max_fails=3; #Server A
server 192.168.64.1:9002 fail_timeout=60s max_fails=3; #Server B
}
server {
server_name www.itcast.com;
default_type text/html;
charset utf-8;
location ~ .*$ {
index index.jsp index.html;
proxy_pass http://dynamicserver;
# 表示重试超时时间是3s
proxy_connect_timeout 3s;
#表示在 6 秒内允许重试 3 次,只要超过其中任意一个设置,Nginx 会结束重试并返回客户端响应
proxy_next_upstream_timeout 6s;
proxy_next_upstream_tries 3;
}
}
常用案例
代理静态文件
cpp
server {
listen 10086;
server_name www.test.com;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_redirect off;
}
location /data/ {
alias '/usr/local/data/';
#这里是重点,就是代理这个文件夹
expires 7d;
}
}
访问 http://localhost:10086/data/下面的资源就是访问/usr/local/data文件夹的资源
反向代理
cpp
server {
listen 80;
server_name www.itcast.com;;
location / {
proxy_pass http://127.0.0.1:8080;
index index.html index.htm .jsp;
}
}
跨域配置
cpp
server {
listen 80;
server_name www.itcast.com;
if ( $host ~ (.*).itcast.com){
set $domain $1;##记录二级域名值
}
#是否允许请求带有验证信息
add_header Access-Control-Allow-Credentials true;
#允许跨域访问的域名,可以是一个域的列表,也可以是通配符*
add_header Access-Control-Allow-Origin *;
#允许脚本访问的返回头
add_header Access-Control-Allow-Headers 'x-requested-with,content-type,Cache-Control,Pragma,Date,x-timestamp';
#允许使用的请求方法,以逗号隔开
add_header Access-Control-Allow-Methods 'POST,GET,OPTIONS,PUT,DELETE';
#允许自定义的头部,以逗号隔开,大小写不敏感
add_header Access-Control-Expose-Headers 'WWW-Authenticate,Server-Authorization';
#P3P支持跨域cookie操作
add_header P3P 'policyref="/w3c/p3p.xml", CP="NOI DSP PSAa OUR BUS IND ONL UNI COM NAV INT LOC"';
if ($request_method = 'OPTIONS') {##OPTIONS类的请求,是跨域先验请求
return 204;##204代表ok
}
}
防盗链
通过Referer实现防盗链比较基础,仅可以简单实现方式资源被盗用,构造Referer的请求很容易实现
场景:由于图片链接可以跨域访问,所以图片链接往往被其他网站盗用,从而增加服务器负担;
解决方案:nginx可以通过valid_referers配置进行防盗链配置
valid_referers 指令
指定合法的来源'referer', 他决定了内置变量$invalid_referer的值,如果referer头部包含在这个合法网址里面,这个变量被设置为0,否则设置为1. 需要注意的是:这里并不区分大小写的.
- 语法: valid_referers none | blocked | server_names | string ...;
- 配置段: server, location
配置说明
- none : 允许没有http_refer的请求访问资源;
- blocked : 允许不是http://开头的,不带协议的请求访问资源;
- 192.168.0.1 : 只允许指定ip来的请求访问资源;
- *.google.com:允许
*.google.com
的域名请求访问资源
配置代码
cpp
# 需要防盗的后缀
location ~* \.(jpg|jpeg|png|gif|bmp|swf|rar|zip|doc|xls|pdf|gz|bz2|mp3|mp4|flv)$
#设置过期时间
expires 30d;
# valid_referers 就是白名单的意思
# 支持域名或ip
# 允许ip 192.168.0.1 的请求
# 允许域名 *.google.com 所有子域名
valid_referers none blocked 192.168.0.1 *.google.com;
if ($invalid_referer) {
# return 403;
# 盗链返回的图片,替换盗链网站所有盗链的图片
rewrite ^/ https://site.com/403.jpg;
}
root /usr/share/nginx/img;
}