1、常见负载均衡的方式(概念普及)
【1】用户手动选择
例如我们玩游戏,服务器会显示当前服务器的状态是拥挤、繁忙、还是空闲,然后用户根据自己实际需要,选择自己想去的服务器。
如果服务器人太多(达到上限),则触发排队,比如猪场主推的一些游戏,在刚上线的时候通常都特别火,要排很久,还有戏称阴兵的存在。
这个适合用户和用户之间需要交互的情况(且跨服务器之间无法交互,或交互比较麻烦)。
【2】DNS解析控制
需要知道一个前提,你访问同一个域名,第一次可能是1xx.1xx.1xx.1,第二次可能是1xx.1xx.1xx.2(即不同的ip)。
但由于这两个都是网站的服务器,所以不管访问哪个都一样。
在我们访问域名的时候,DNS解析服务器,会将你的域名转为IP,因此我们具体访问哪一个IP上的服务器,是根据DNS解析服务器来决定的。
以阿里云为例,我们可以配置云解析。
具体来说,我们可以设置 被解析为 1xx.1xx.1xx.1 和 1xx.1xx.1xx.2 ,甚至更多。
效果是当访问 时,用户可能跳转到 1xx.1xx.1xx.1 和 1xx.1xx.1xx.2 或者其他你设置的 ip 上。
缺点:
假如 1xx.1xx.1xx.1 服务器挂了,所以被导向这个 IP 地址的流量,都不会得到相应;不是真负载均衡,特别在各个服务器高压力的情况下,部分服务器可能先达到 100% 负载,然后出现问题。
【3】涉及到网络七层协议的负载均衡
分为第四层(传输层)和第七层(网络层)两种。
第四层的负载均衡简单来说,当你访问到一个 IP 地址(外部)时,他会被映射到内部多个预先保存的 IP 地址(内部)中的一个。
第七层的负载均衡,是根据HTTP请求头的内容,来指向不同的服务器。
硬件实现,通常是通过交换机。软件实现,就各有所长了。
例如 Nginx 就是典型的通过软件的第七层来实现负载均衡。
【4】混合形式
比如:
根据访问者的地区,找不同地区的机房(例如来自长三角的流量找上海的机房)根据访问者的网络,找不同运营商的机房(电信的找电信机房)进了机房后,根据机子的负载不同,找负载低的服务器
2、Nginx反向代理的基本配置
配置很简单,核心属性是proxy_pass
,可以直接指向域名
解释:
当访问 时,触发这里的反向代理配置(当然,需要先配置好host,让这个域名指向nginx服务器的IP),然后会重定向到 proxy_pass 配置的IP地址。
即:【访问nginx服务器】–反向代理–>【103.94.185.215:3000】
server {listen 80;# 监听的域名是 ,即用户访问 时,触发这个配置server_name ;location / {# 访问根域名,跳转到 http://103.94.185.215:3000/ 这个IP,这个IP是我自己的网站proxy_pass http://103.94.185.215:3000/;}}
3、反向代理到多个服务器
以上只能反向代理到单个服务器,nginx也支持配置反向代理到多个服务器。
通过借用 upstream 属性,存储一个服务器列表,具体写法如下:
# 配置服务器列表upstream my_server_list{server 185.186.147.210;server 103.94.185.215:3000;}# 配置 serverserver {listen 80;# 监听的域名是 ,即用户访问 时,触发这个配置server_name ;location / {# 反向代理到 my_server_list 池中proxy_pass http://my_server_list;}}
注意:
upstream 和 server 的配置是同一级的;每一个 server 一行,只写ip即使用默认端口80,也可以写上端口;这里是最基本的配置,将在下面不断增加更复杂的内容;
4、属性详解
4.1 proxy_set_header
位置:
http, server, location
格式:
proxy_set_header field value;# 默认值proxy_set_header Host $proxy_host;proxy_set_header Connection close;
说明:
这个属性用于配置nginx服务器传给后端服务器的请求的 request 的 header 属性;具体来说,使用反向代理时,流量的流向形式是:【客户端】->【nginx服务器】->【后端服务器】;以host属性为例,默认情况下: 当访问 时,【客户端】->【nginx服务器】这一步,request 的 header 为 ;而【nginx服务器】->【后端服务器】这一步的header为proxy_pass
属性中,http://
属性后的字符串的值。例如proxy_pass http://127.0.0.1:3000;
,则 host 的值为127.0.0.1:3000
;如果是proxy_pass http://my_server_list;
,那么传给后端服务器的header里 host 字段值为my_server_list
; 如果想让 Host 值等于用户访问的域名,则需要这么设置proxy_set_header Host $host;
也可以使用$http_host
来替代$host
,这个不同之处在于,当访问的不是 80/443 端口时,$http_host
会带上端口值(示例::81
) 几个属性值的区别,可以参考这个链接 关于nginx中host, server_name, http_host的区别 你也可以通过这个字段,来添加你想自定义的 header 属性对,或者覆写已有的字段值;注意:field 里的大写字母,在后端拿到的header里,大写字母都已经被转为小写字母了。我们之前在【07、日志】里面,通过http_x_forwarded_for
来拿取重定向之前,用户真实的 IP 地址,因此我们通常在做反向代理的时候,应该通过这个属性,将用户真实 IP 地址添加进去。写法如下:proxy_set_header X-Forwarded-For $remote_addr;
如果自定义header的话,示例配置如下:
server {listen 80;# 监听的域名是 ,即用户访问 时,触发这个配置server_name ;location / {# 反向代理到 my_server_list 池中proxy_pass http://my_server_list;# 在发送到后端服务器的 request 里的 header 里添加属性头 Abc ,值为 abcdproxy_set_header Abc abcd;}}
后端服务器打个日志:
console.log('header-abc-', req.headers['abc']);
输出结果:
header-abc- abcd
4.2 upstream
upstream 在配置负载均衡时,提供了一个服务器池。每个服务器以server 关键字作为开头,配置一行。
upstream的配置和server配置平级。
server的配置很简单,都用下面这个:
server {listen 80;server_name ;location / {proxy_pass http://my_server_list/;}}
【默认配置】
轮询:在默认情况下,upstream按照轮询的方式实现负载,条件是时间;
upstream my_server_list{server 185.186.147.210;server 103.94.185.215:3000;}
【server】
语法:
server name [参数]
server:关键字,不变;name:域名、IP地址、端口号(不能只有端口号)、UNIX Socket;
【权重:weight】
写在 server 行中,默认值为1,值越大,负载权重越大。
当在 server 的服务器地址(例如IP)后面添加 weight 属性时,说明按权重的方式实现负载均衡。
weight 是通过数值的不同实现概率不同的,以下为例,理论上上面服务器被轮训到的概率是下面服务器的1/4;
upstream my_server_list{server 185.186.147.210 weight=1;server 103.94.185.215:3000 weight=4;}
【ip_hash】
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
简单来说,你第一次访问被分配到A服务器,那么后面永远都是A服务器来对你进行处理了。适合需要保存用户登录状态(session)的场景(但又做不到session共享的情况)。
这种情况缺点是不能保证每个服务器负载是相同的。
写法:
upstream my_server_list{ip_hash;server 185.186.147.210;server 103.94.185.215:3000;}
【fair:第三方插件】
需要安装第三方插件,效果是:按后端服务器的响应时间来分配请求,响应时间短的优先分配。
写法类似 ip_hash,安装方法谷歌搜索:【nginx安装fair】,下同。
【url_hash:第三方插件】
需要安装第三方插件,效果是:按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。
写法类似 ip_hash
【down】
写在 server 行中,表示该服务器暂时不参加负载。
如以下示例中,你永远只能访问到第一个服务器。
upstream my_server_list{server 185.186.147.210 down;server 103.94.185.215:3000;}
之所以写 down,而不是直接注释掉。是因为 nginx 在做负载均衡时,是根据已有服务器做处理的。
例如4个server时的 ip_hash 和3个服务器的 ip_hash,在ip不变的情况下,命中的服务器是不一样的。
4个服务器一个后面写down,那还是四个服务器,而不是三个服务器。而注释掉,就真的只有3个服务器了。
【max_fails】
写在 server 行中,表示允许该服务器请求失败的次数,默认值为1,超过最大次数时,将被proxy_next_upstream
这一行的配置所处理。
注意,这个过程不会被用户所感知,会被nginx所自动排除。
【fail_timeout】
写在 server 行中,表示在 max_fails 次失败后,该 server 暂停的时间(即在之后一段时间里,不会被nginx放在负载均衡池中),默认值是10s
(即10秒)。
如果将这个值设置为fail_timeout=0
,则表示如果遇见偶尔的错误情况,并不会将这个 server 排除在之后的请求(或者暂停一段时间不去请求这个服务器)。
示例写法如下(第一个服务器允许3次失败,失败后移出5秒)
upstream my_server_list{server 185.186.147.210 max_fails=3 fail_timeout=5s;server 103.94.185.215:3000;}
【backup】
写在 server 行中,表示在其他服务器都挂了(无一存活)的情况下,请求这个服务器。
默认情况下不会被请求,只有一台挂了的话,也不会被请求。
如下示例:
upstream my_server_list{server 185.186.147.210 max_fails=3 fail_timeout=5s;server 127.0.0.1:3000;server 103.94.185.215:3000 backup;}
若是前2个只有一个挂的情况下,也不会访问第三个服务器的。只有前2个都挂的情况下,才会访问。
适合在服务器都挂的情况下,给出一个静态的错误提示页面(静态页面是很难挂的)。
4.3、upstream 的相关变量
$upstream_addr
:处理请求的 upstream 服务器地址;$upstream_status
:upstream 服务器的应答状态;$upstream_response_time
:upstream服务器的响应时间(秒),多个响应时间以逗号和冒号分割;$upstream_http_$HEADER
:任意的HTTP协议头信息。注意,这个并不是指客户端请求的HTTP协议头里的信息。貌似是需要额外配置的。这些东西可以直接打到日志中;如果某个服务器错误(比如服务器挂了),重定向信息也会被写到同一行里;示例:
1、日志格式
'[$request] | $upstream_addr | $upstream_status | $upstream_response_time s'
2、条件
服务器为:127.0.0.1:3000
,访问链接/
(本机host已经指向 nginx 服务器),忽略资源带来的日志(每个资源请求也会写入日志),日志结果为:
3、upstream只有一个服务器
[GET / HTTP/1.1] | 127.0.0.1:3000 | 200 | 0.010 s
4、upstream有两个服务器,但是一个服务器挂了(会触发nginx导向另外一个服务器)
[GET / HTTP/1.1] | 185.186.147.210:80, 127.0.0.1:3000 | 502, 200 | 0.287, 0.009 s
说明:
先访问到第一个服务器 185.186.147.210:80,但是因为服务器挂了,报502错误,访问时间是 0.287s;于是访问另外一个服务器 127.0.0.1:3000,服务器ok,状态码是200,访问时间是 0.009s;
5、以 URL 做区分实现负载均衡
常见场景:
有一个根域名,然后已开发了
/a
等多个服务已在线上正常运行。
现需要开发一个新业务,和之前的业务是独立的(即不需要和之前的页面交互),因为解耦原则,因此定该新业务的域名为/b
。
由于上新业务,当然最好是上新服务器(服务器组)咯。这样避免新业务down了影响老业务,也方便管理和开发。
因此,我们要实现以下效果:
访问/a
,由x.x.x.1
服务器(服务器组)处理;访问/b
,由x.x.x.2
服务器(服务器组)处理;x.x.x.2
服务器在处理 url 的时候,不考虑/b
。例如,当访问/a/abc
的时候,服务器只匹配/abc
,这样做的原因是好处很多,略;
实现方法:
1、先配置两组服务器的服务器池:
x.x.x.1
服务器(服务器组,记为my_server_list_a),如下配置。(这里的IP地址是可用的,指向我自己的服务器,下同)
upstream my_server_list_a{server 185.186.147.210;}
x.x.x.2
服务器(服务器组,记为my_server_list_b),如下配置。
upstream my_server_list_b{server 103.94.185.215:3000;}
2、根据 url 来配置 server:
server {listen 80;# 监听的域名是 ,即用户访问 时,触发这个配置server_name ;# 访问 /a 时触发这个逻辑location /a/ {# 反向代理到 my_server_list_a 池中,记得最后一定要加斜杠 /proxy_pass http://my_server_list_a/;# proxy_set_header X-Forwarded-For $remote_addr;}# 访问 /b 时触发这个逻辑location /b/ {# 反向代理到 my_server_list_b 池中proxy_pass http://my_server_list_b/;# proxy_set_header X-Forwarded-For $remote_addr;}}
3、done,搞定了。
4、唯一需要注意的是,前端页面在写路径时(例如css文件style.css
位于服务器 b 的根路径下),不能写绝对路径/style.css
,而是要写相对路径./style.css
才可以。
5、类似的情况有:【各种资源文件】、【异步请求】等,都需要写相对路径。
6、解决方案:将静态资源和页面解耦,页面放在服务器下,而静态资源放在CDN下,在页面中写成绝对路径。当 URL 匹配时,后端服务器只返回符合要求的静态页面,静态页面自己去找路径符合的静态资源。(我在阿里时,就是这么实现的)
6、双机高可用
原因:
一个nginx服务器作为入口,如果nginx服务器挂了,后面服务将全部不可用。
解决办法:
一台服务器不够,那就上两台。但是两台nginx服务器怎么运作呢?
6.1、方法一:一主一备份
原理解释:
两个服务器,分别称为主服务器和备份服务器,两个在同一个网关下(必须);使用虚拟IP技术,两台服务器都指向这一个虚拟IP;初始情况下,主服务器生效(虚拟IP指向主服务器),流量全部走主服务器;当主服务器挂了(注意,是服务器挂了,而不是nginx服务挂了),备份服务器自动顶上去(即此时虚拟IP指向备份服务器);效果:主服务器即使挂了,备份服务器也能在几秒钟后接管;这种虚拟IP从主服务器指向备份服务器的过程,叫做【IP漂移】;
实现方法:
首先你要有一台主服务器,然后配置虚拟IP。
虚拟IP的实现方法,是让一台服务器指向多个IP,参考我之前写的那篇文章:05、一个nginx服务器为多个ip服务.md
假如主服务器的 IP 是192.168.1.11
,备份服务器的 IP 是192.168.1.12
。
配置虚拟IP,让主服务器和备份服务器的虚拟 IP ,都指向192.168.1.100
(先配主服务器的)。
注:制造备份服务器的简单解决办法,是先配置好主服务器,然后复制一份创建备份服务器(比如虚拟机,可以直接克隆一份)
此时访问192.168.1.100
,会自动访问主服务器。
此时手动挂掉主服务器(比如直接关机),然后再访问192.168.1.100
,有几秒无响应,过后会自动由备份服务器接管。
6.2、方法二:利用 Keepalived 双机同存
方法一中,入口只有一个虚拟IP,他只会指向其中一个nginx服务器,除非这个服务器挂了,或者手动漂移到另外一个服务器,否则虚拟IP不会指向另外一个服务器;而方法二,利用 Keepalived,可以自动切换虚拟IP指向的服务器,当服务器down了,或者nginx服务挂了,也会自动自动切换;实现方法我给一个参考链接吧:/p/da26df4f7d60
7、更多nginx内容,请查看我的github
/qq20004604/nginx-demo
如果方便,麻烦给一个star,谢谢