Nginx反向代理-1-实践简单的负载均衡
特别申明:内容出自《跟老男孩学Linux:Web集群实战》
文章目录
Nginx负载均衡集群介绍
1.反向代理与负载均衡概念简介
严格地说,Nginx仅仅是作为Nginx Proxy反向代理使用的,因为这个反向代理功能表现的效果是负载均衡集群的效果,所以本文称之为Nginx负载均衡。那么,反向代理和负载均衡有什么区别呢?
普通负载均衡软件,例如大名鼎鼎的LVS,其实现的功能只是对请求数据包的转发(也可能会改写数据包)、传递,其中DR模式明显的特征是从负载均衡下面的节点服务器来看,接收到的请求还是来自访问负载均衡器的客户端的真实用户,而反向代理就不一样了,反向代理接收访问用户的请求后,会代理用户重新发起请求代理下的节点服务器,最后把数据返回给客户端用户,在节点服务器看来,访问的节点服务器的客户端用户就是反向代理服务器了,而非真实的网站访问用户。
一句话,LVS等的负载均衡是转发用户请求的数据包,而Nginx反向代理是接收用户的请求然后重新发起请求去请求其后面的节点。
2.实现Nginx负载均衡的组件说明
实现Nginx负载均衡的组件主要有两个。
Nginx http 功能模块 | 模块说明 |
---|---|
ngx_http_proxy_module | proxy代理模块,用户把请求后抛给服务器节点或upstream服务池 |
ngx_http_upstream_module | 负载均衡模块,可以实现网站的负载均衡功能及节点的健康检查 |
快速实践Nginx负载均衡环境准备
所有用户的请求统一发送到Nginx负载均衡器,然后由负载均衡器根据调度算法来请求web01和web02。
1.软硬件准备
1.硬件准备
准备4台VM虚拟机(有物理服务器更佳),两台做负载均衡,两台做RS。
HOSTNAME | IP | 说明 |
---|---|---|
lb01 | 192.168.55.7 | Nginx主负载均衡器 |
lb02 | 192.168.55.8 | Nginx辅负载均衡器 |
web01 | 192.168.55.9 | web01服务器 |
web02 | 192.168.55.10 | web02服务器 |
2.软件准备
系统:CentOS 7
软件:Nginx-1.13.1
2.安装Nginx软件
Nginx安装目录:/application/nginx
3.配置用于测试的Web服务
Nginx web01和Nginx web02的配置如下:
[aaa@qq.com conf]# cat /application/nginx/conf/nginx.conf
worker_processes 1;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main;
error_log logs/error.log;
sendfile off;
keepalive_timeout 65;
server {
listen 80;
server_name bbs.etiantian.org;
root html/bbs;
access_log logs/access_bbs.log main;
error_log logs/error_bbs.log;
location / {
index index.html index.htm;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
server {
listen 80;
server_name www.etiantian.org;
root html/www;
access_log logs/access_www.log main;
error_log logs/error_www.log;
location / {
index index.html index.htm;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
}
配置完成后检查语法,并启动Nginx服务。
[aaa@qq.com conf]# mkdir /application/nginx/html/{www,bbs}
[aaa@qq.com conf]# /application/nginx/sbin/nginx -t
nginx: the configuration file /home/sy/local/nginx1.13.1/conf/nginx.conf syntax is ok
nginx: configuration file /home/sy/local/nginx1.13.1/conf/nginx.conf test is successful
[aaa@qq.com conf]# /application/nginx/sbin/nginx
[aaa@qq.com conf]# netstat -lntup|grep nginx
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 5697/nginx: master
然后填充测试文件数据,如下:
[aaa@qq.com conf]# mkdir /application/nginx/html/{www,bbs} -p
[aaa@qq.com conf]# for dir in www bbs; do echo "`ifconfig eth1|grep -o "192.168.55.[109]."` $dir" > /application/nginx/html/$dir/index.html; done
[aaa@qq.com conf]# for dir in www bbs; do cat /application/nginx/html/$dir/index.html; done
192.168.55.9 www
192.168.55.9 bbs
以上命令需要在web01上操作。
[aaa@qq.com conf]# mkdir /application/nginx/html/{www,bbs} -p
[aaa@qq.com conf]# for dir in www bbs; do echo "`ifconfig eth1|grep -o "192.168.55.[109]."` $dir" > /application/nginx/html/$dir/index.html; done
[aaa@qq.com conf]# for dir in www bbs; do cat /application/nginx/html/$dir/index.html; done
192.168.55.10 www
192.168.55.10 bbs
以上命令需要在web02上操作。
提示:以上配置在两台Web服务器上的操作命令时一样的。
配置解析web01的IP和主机后,用curl简单测试web01,如下:
[aaa@qq.com conf]# tail -2 /etc/hosts
192.168.55.9 www.etiantian.org
192.168.55.9 bbs.etiantian.org
[aaa@qq.com conf]# curl www.etiantian.org
192.168.55.9 www
[aaa@qq.com conf]# curl bbs.etiantian.org
192.168.55.9 bbs
配置解析web02的IP和主机后,用curl简单测试web02,如下:
[aaa@qq.com conf]# tail -2 /etc/hosts
192.168.55.10 www.etiantian.org
192.168.55.10 bbs.etiantian.org
[aaa@qq.com conf]# curl www.etiantian.org
192.168.55.10 www
[aaa@qq.com conf]# curl bbs.etiantian.org
192.168.55.10 bbs
4.实现一个简单的负载均衡
本小节将在Nginx lb01服务器节点操作。
HOSTNAME | IP | 说明 |
---|---|---|
lb01 | 192.168.55.7 | Nginx主负载均衡器 |
反向代理多虚拟主机节点服务器,需要在Nginx代理WWW服务虚拟主机配置里增加proxy_set_head Host $host;
。就是当反向代理向后重新发起请求时,要携带主机头信息,以明确告诉节点服务器要找哪个虚拟主机。如果反向代理不携带主机头信息,则Web节点服务器接收到请求后发现没有主机头信息,就把节点服务器的第一个虚拟主机发给了反向代理。
下面进行一个简单的Nginx负载均衡配置,代理www.etiantian.org服务,节点为web01和web02。
/application/nginx/conf/nginx.conf配置文件内容如下:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile off;
keepalive_timeout 65;
upstream www_server_pools{
server 192.168.55.9:80 weight=1;
server 192.168.55.10:80 weight=1;
}
server {
listen 80;
server_name www.etiantian.org;
location / {
proxy_pass http://www_server_pools;
include proxy.conf;
}
}
}
/application/nginx/conf/proxy.conf内容如下:
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_connect_timeout 60;
proxy_send_timeout 60;
proxy_read_timeout 60;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
说明:
-
upstream www_server_pools{ #<==这里定义Web服务器池,包含了9,10两个Web节点
-
server { #<==这里定义代理的负载均衡域名虚拟主机
检查语法并启动。
检查负载均衡测试结果。Linux作为客户端的测试结果如下:
[aaa@qq.com conf]# tail -2 /etc/hosts
192.168.55.7 www.etiantian.org #<==负载均衡器的IP
192.168.55.7 bbs.etiantian.org #<==负载均衡器的IP
[aaa@qq.com conf]# curl www.etiantian.org
192.168.55.10 www
[aaa@qq.com conf]# curl www.etiantian.org
192.168.55.9 www
[aaa@qq.com conf]# curl bbs.etiantian.org
192.168.55.10 bbs
[aaa@qq.com conf]# curl bbs.etiantian.org
192.168.55.9 bbs
经过反向代理后的节点服务器记录用户IP
完成了反向代理WWW服务后,自然很开心,但是,不久后你用其他客户端作为客户端测试时,就会发现一个问题,节点服务器对应的WWW虚拟主机的访问日志的第一个字段记录的并不是客户端的IP,而是反向代理服务器的IP,最后一个字段也是“-”!
例如:使用Windows客户端计算机(IP为192.168.55.1)访问已经解析好代理IP的www.etiantian.org后,去节点服务器WWW服务的日志查看,会发现如下日志:
[aaa@qq.com logs]# tail -2 /application/nginx/logs/access_www.log
192.168.55.7 - - [01/Aug/2019:19:55:25 +0800] "GET / HTTP/1.0" 200 18 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" "-"
192.168.55.7 - - [01/Aug/2019:20:02:05 +0800] "GET / HTTP/1.0" 200 18 "-" "curl/7.29.0" "-"
proxy_set_header X-Forwarded-For $remote_addr;
#<==这是反向代理时,节点服务器获取用户真实IP的必要功能配置。在代理向后端服务器发送的http请求头中加入X-Forwarded-For字段信息,用户后端服务器程序、日志等接收记录真实用户的IP,而不是代理服务器的IP。
应该显示为
[aaa@qq.com logs]# tail -2 /application/nginx/logs/access_www.log
192.168.55.7 - - [01/Aug/2019:22:47:00 +0800] "GET / HTTP/1.0" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36" "192.168.55.1"
192.168.55.7 - - [01/Aug/2019:22:49:14 +0800] "GET / HTTP/1.0" 200 18 "-" "curl/7.29.0" "192.168.55.7"
web01的/application/nginx/conf/nginx.conf文件的log_format部分,里面的"$http_x_forwarded_for"
参数,如果希望在第一行显示,可以替换第一行的$remote_addr变量。
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
PHP程序获取用户IP
代理服务器配置了proxy_set_header X-Forwarded-For $remote_addr;
后,节点服务器PHP程序的$_SERVER全局变量信息如下:
Windows浏览器访问:http://www.etiantian.org/index.php
Array
(
[USER] => sy
[HOME] => /home/sy
[HTTP_ACCEPT_LANGUAGE] => zh-CN,zh;q=0.9,en;q=0.8
[HTTP_ACCEPT_ENCODING] => gzip, deflate
[HTTP_ACCEPT] => text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
[HTTP_USER_AGENT] => Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36
[HTTP_UPGRADE_INSECURE_REQUESTS] => 1
[HTTP_CACHE_CONTROL] => no-cache
[HTTP_PRAGMA] => no-cache
[HTTP_CONNECTION] => close
[HTTP_X_FORWARDED_FOR] => 192.168.55.1 #用户IP
[HTTP_HOST] => www.etiantian.org
[REDIRECT_STATUS] => 200
[SERVER_NAME] => www.etiantian.org
[SERVER_PORT] => 80
[SERVER_ADDR] => 192.168.55.9 #节点服务器IP
[REMOTE_PORT] => 35918
[REMOTE_ADDR] => 192.168.55.7 #代理服务器IP
[SERVER_SOFTWARE] => nginx/1.13.1
[GATEWAY_INTERFACE] => CGI/1.1
[REQUEST_SCHEME] => http
[SERVER_PROTOCOL] => HTTP/1.0
[DOCUMENT_ROOT] => /home/sy/local/nginx1.13.1/html/www
[DOCUMENT_URI] => /index.php
[REQUEST_URI] => /index.php
[SCRIPT_NAME] => /index.php
[CONTENT_LENGTH] =>
[CONTENT_TYPE] =>
[REQUEST_METHOD] => GET
[QUERY_STRING] =>
[SCRIPT_FILENAME] => /home/sy/local/nginx1.13.1/html/www/index.php
[FCGI_ROLE] => RESPONDER
[PHP_SELF] => /index.php
[REQUEST_TIME_FLOAT] => 1564674231.4154
[REQUEST_TIME] => 1564674231
)
相关参数说明
Nginx反向代理重要参数 | 解释说明 |
---|---|
proxy_pass http://server_pools; | 通过proxy_pass功能把用户的请求转向到反向代理定义的upstream服务器池 |
proxy_set_header Host $host; | 在代理向后端服务器发送的http请求头中加入host字段信息,用于当后端服务器配置有多个虚拟主机时,可以识别代理的是哪个虚拟主机。这是节点服务器多虚拟主机时的配置 |
proxy_set_header X-Forwarded-For $remotre_addr; | 在代理向后端服务器发送的http请求头中加入X-Forwarded-For字段信息,用于向后端服务器程序、日志等接收记录真实用户的IP,而不是代理服务器的IP。 这是反向代理时,节点服务器获取用户真实IP的必要功能配置 |
client_body_buffer_size | 用于指定客户端请求主体缓冲区大小,此处如了解http的请求包的原理就好理解了 |
proxy_connect_timeout | 表示反向代理与后端节点服务器连接的超时时间,即发起握手等候响应的超时时间 |
proxy_send_timeout | 表示代理后端服务器的数据回传时间,即在规定时间之内后端服务器必须传完所有的数据,否则,Nginx将断开这个连接 |
proxy_read_timeout | 设置Nginx从代理的后端服务器获取信息的时间,表示连接建立成功之后,Nginx等待后端服务器的响应时间,其实是Nginx已经进入后端的排队之中等候处理的时间 |
proxy_buffer_size | 设置缓冲区大小,默认该缓冲区大小等于指令proxy_buffers设置的大小 |
proxy_buffer | 设置缓冲区的数量和大小。Nginx从代理服务的后端服务器获取的响应信息,会放置到缓冲区 |
proxy_busy_buffers_size | 用于设置系统很忙时可以使用的proxy_buffers大小,官方推荐的大小为proxy_buffers*2 |
proxy_temp_file_write_size | 指定proxy缓存临时文件的大小 |
upstream模块相关说明
upstream模块的内容应放于nginx.conf配置的http{}标签内,其默认调度节点算法是wrr(weighted round-robin,即权重轮询)。
upstream模块内参数 | 参数说明 |
---|---|
server 192.168.55.9:80 | 负载均衡后面的RS配置,可以是IP或域名,如果端口不写,默认是80端口。高并发场景下,IP可换成域名,通过DNS做负载均衡 |
weight=1 | 代表服务器权重,默认值是1。权重数字越大表示接受的请求比例越大 |
max_fails=1 | Nginx尝试连接后端主机失败的次数,这个数值是配合proxy_next_upstream、fastcgi_next_upstream和memcached_next_upstream这三个参数来使用的,当Nginx接收后端服务器返回的这三个参数定义的状态码时,会将这个请求转发给正常工作的后端服务器,例如404/502/503。max_fails的默认值是1;企业场景下建议2~3次,根据业务需求去配置 |
backup | 热备配置(RS节点的高可用),当前面**的RS都失败后会自动启用热备RS。这标志着这个服务器作为备份服务器,若主服务器全部宕机了,就会向它转发请求; 注意,当负载调度算法为ip_has时,后端服务器在负载均衡调度中的状态不能是weight和backup |
fail_fimeout=10s | 在max_fails定义的失败次数后,距离下次检查的间隔时间,默认是10s;如果max_fails是5,它就检查5次,如果5次都是502。那么,它就会根据fail_timeout的值,等待10s再去检查,还是只检查一次,如果连续502,在不重新加载Nginx配置的情况下,每隔10s都会只检测一次。常规业务2~3次比较合理,可根据业务需求去配置 |
down | 这标志着服务器永远不可用,这个参数可配合ip_hash使用 |
upstream模块调度算法
调度算法一般分为两类,第一类为静态调度算法,即负载均衡器根据自身设定的规则进行分配,不需要考虑后端节点服务器的情况,例如:rr、wrr、ip_hash等都属于静态调度算法。
第二类为动态调度算法,即负载均衡器会根据后端节点的当前状态来决定是否分发请求,例如:连接数少的优先获得请求,响应时间短的优先获得请求。例如:least_conn、fair等都属于动态调度算法。
常见的调度算法。
(1)rr轮询(默认调度算法,静态调度算法)
按客户端请求顺序把客户端的请求逐一分配到不同的后端节点服务器,这相当于LVS中的rr算法,如果后端节点服务器宕机(默认情况下Nginx只检测80端口),宕机的服务器会被自动从节点服务器池中剔除,以使客户端的用户访问不受影响。新的请求会分配给正常的服务器。
(2)wrr(权重轮询,静态调度算法)
在rr轮询算法的基础上加上权重,即为权重轮询算法,当使用该算法时,权重和用户访问成正比,权重值越大,被转发的请求也就越多。可以根据服务器的配置和性能指定权重值大小,有效解决新旧服务器性能不均带来的请求分配问题。
假设希望在有30个请求到达前端时,其中20个请求交给192.168.55.10处理,剩余10个请求交给192.168.55.9处理,就可做如下配置:
upstream www_server_pools{
server 192.168.55.9:80 weight=1;
server 192.168.55.10:80 weight=2;
}
(3)ip_hash(静态调度算法)
每个请求按客户端IP的hash结果分配,当新的请求到达时,先将其客户端IP通过哈希算法哈希出一个值,在随后的客户端请求中,客户IP的哈希值只要相同,就会被分配至同一台服务器,该调度算法可以解决动态网页的session共享问题,但有时会导致请求分配不均,即无法保证1:1的负载均衡,因为在国内大多数公司都是NAT上网模式,多个客户端会对应一个外部IP,所以,这些客户端都会被分配到同一节点服务器,从而导致请求分配不均。LVS负载均衡的-p参数、Keepalived配置里的persistence_timeout 50参数都类似这个Nginx里的ip_hash参数,其功能都可以解决动态网页的session共享问题。
同样也来看一个示例,如下:
upstream www_server_pools {
ip_hash;
server 192.168.1.2:80;
server 192.168.1.3:8080;
}
upstream backend {
ip_hash;
server backend1.example.com;
server backend2.example.com;
server backend3.example.com down;
server backend4.example.com;
}
注意: 当负载调度算法为ip_hash时,后端服务器在负载均衡调度中的状态不能有weight和backup,即使有也不会生效。
(4)fair(动态调度算法)
此算法会根据后端节点服务器的响应时间来分配请求,响应时间短的优先分配。这是更加智能的调度算法。此种算法可以依据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间来分配请求,响应时间短的优先分配。Nginx本身不支持fair调度算法,如果需要使用这种调度算法,必须下载Nginx的相关模块upstream_fair。
示例如下:
upstream www_server_pools {
server 192.168.1.2;
server 192.168.1.3;
fair;
}
(5)least_conn
least_conn算法会根据后端节点的连接数来决定分配情况,哪个机器连接数少就分发。
除了上面介绍的这些算法外,还有一些第三方调度算法,例如:url_hash、一致性hash算法等,介绍如下。
(6)url_hash算法
与ip_hash类似,这里是根据访问URL的hash结果来分配请求的,让每个URL定向到同一个后端服务器,后端服务器为缓存服务器时效果显著。在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method使用的是hash算法。
url_hash按访问URL的hash结果来分配请求,使每个URL定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率命中率。Nginx本身是不支持url_hash的,如果需要使用这种调度算法,必须安装Nginx的hash模块软件包。
url_hash(web缓存节点)和ip_hash(会话保持)类似。示例配置如下:
upstream www_server_pools {
server squid1:3128;
server squid2:3128;
hash $request_uri;
hash_method crc32;
}
(7)一致性hash算法
一致性hash算法一般用于代理后端业务为缓存服务(如Squid、Memcached)的场景,通过将用户请求的URI或者指定字符串进行计算,然后调度到后端的服务器上,此后任何用户查找同一个URI或者指定字符串都会被调度到这一台服务器上,因此后端的每个节点缓存的内容都是不同的,一致性hash算法可以解决后端某个或几个节点宕机后,缓存的数据动荡最小,一致性hash算法知识比较复杂,这里仅仅给出配置示例:
http {
upstream test {
consistent_hash $request_uri;
server 127.0.0.1:9001 id=1001 weight=3;
server 127.0.0.1:9002 id=1002 weight=10;
server 127.0.0.1:9003 id=1003 weight=20;
}
}
虽然Nginx本身不支持一致性hash算法,但Nginx的分支Tengine支持。详细可见 http://tengine.taobao.org/document_cn/http_upstream_consistent_hash_cn.html
参考文献
[1] 老男孩. 跟老男孩学Linux:Web集群实战[M]. 机械工业出版社,2016-03-01。
[2] [DB|OL]. http://nginx.org/en/docs/http/ngx_http_proxy_module.html