Nginx构建高可用集群,实现负载均衡应对高并发
备注:为了这次测试,下了血本,配置了4台4核8G云服务器,Linux版本centos8,防火墙均为开启状态,SELinux均为permissive模式,以测试负载均衡,分别是服务器A 141.164.50.226(Korea-1),服务器B 141.164.34.223(Korea-2),服务器C 45.76.51.26(Japan-1),服务器D 202.182.107.126(Japan-2),以下简称ABCD。
关于SELinux的查看,可以直接使用getenforce查看,也可以使用/usr/sbin/sestatus -v查看详细信息,使用setenforce 0修改成宽容模式。
[]Enforcing[][]Permissive
本篇文章的优化仅针对网站访问量巨大的情况,例如访问日均IP超过10万,小型网站没有必要做这个优化。先来说下原理,我们知道传统的web访问,大概是下图这个样子,一台服务器接收所有的网络请求,而一次完整的网络请求大概有4个步骤,客户端发起请求,服务器接收请求,服务器处理请求(压力最大),服务器返回请求,这样有可能会出现以下几种问题,该服务器如果出现故障/请求压力过大出现的故障(单点故障);单台服务器资源有限;单台服务器处理能力有限(例如内存基本没用,但是数据库链接数过多卡死)
为了解决上面的问题,我们有两种方案,第一种是我们可以考虑部署一个备份服务器,在主机器宕机时进行切换,但是这种做法只能解决主机硬件故障导致的问题,如果是请求压力过大导致的故障,没法解决,因为主机扛不住巨量的请求,那么备机肯定也是扛不住的。第二种是部署多台服务器,使用DNS轮询解析去分发用户请求到不同的服务器,但是这也会有问题,因为DNS解析不是我们所能控制的,那么当某台服务器故障时,DNS解析到该台服务器的用户请求将无法得到处理。
其实我们还有第三种解决方案,那就是集群模式,即将多个物理机器组成一个逻辑计算机,实现负载均衡和容错。下图中,可以看到,当一个请求过来的时候,DNS先将域名请求解析到主服务器1,1将接受到的请求分发到3456等业务服务器进行处理请求,处理完成后返回响应,如果机器3故障,那么主分发器将其剔除,等待3修复完成后再加入处理队列,这是我们可控的;那么备机2的作用是什么呢?我们知道域名是解析到公网IP上的,我们可以通过VIP(Vrtual IP Address)的指向,来让公网IP指向到不同的分发器上,当1宕机时,VIP漂浮到2上,即可实现自动切换分发器的功能。
集群的组成要素:
VIP(Vrtual IP Address),虚拟IP
分发器:nginx
数据服务器:web服务器
需要用到的功能模块:
ngx_http_upstream_module:基于应用层(七层)分发模块
ngx_stream_core_module:基于传输层(四层)分发模块(1.9开始提供该功能)
Nginx集群原理:虚拟主机+反向代理+upstream分发模块
虚拟主机:负责接受和响应请求。
反向代理:带领用户去数据服务器拿数据。
upstream:告诉nginx去哪个数据服务器拿数据。
数据走向
1)虚拟主机接受用户请求
2)虚拟主机去找反向代理(问反向代理去哪拿数据)
3)反向代理让去找upstream
4)upstream告诉一个数据服务器IP
5)Nginx去找数据服务器,并发起用户的请求
6)数据服务器接受请求并处理请求
7)数据服务器响应请求给Nginx
8)Nginx响应请求给用户
接下来的一些说明,A-主分发器,B-备分发器,CD-数据服务器,4台服务器均为格式化原配置
[root@Korea1 ~]# uname -aLinux Korea1 4.18.0-240.1.1.el8_3.x86_64 #1 SMP Thu Nov 19 17:20:08 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
1.A、B、C、D安装nginx
useradd -r www -s /sbin/nologinwget http://nginx.org/download/nginx-1.18.0.tar.gz -P /srv/cd /srv/yum -y install gcc pcre-devel zlib zlib-devel make tartar -zxvf nginx-1.18.0.tar.gzcd nginx-1.18.0/./configure --prefix=/usr/local/nginxmake && make install
如果在安装nginx之后发现可能有某个内置模块未开启,该如何增加?
[]nginx version: nginx/1.18.0built by gcc 8.3.1 20191121 (Red Hat 8.3.1-5) (GCC)configure arguments: --prefix=/usr/local/nginx
现在假设我需要开启某个模块,重新配置、编译即可(不要安装)
cd /srv/nginx-1.18.0/./configure --prefix=/usr/local/nginx --user=www --group=www --with-streammake# 备份cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak# 拷贝至nginx的二进制目录下cp objs/nginx /usr/local/nginx/sbin/# 再次查看安装情况/usr/local/nginx/sbin/nginx -V
强调一下,安装elinks需要依赖powertools库,但centos的各版本不同,之前在介绍过该库的安装,当时用的是centos8.1版本,现在的版本是el8_3,所以方法又有点不一样,你可以使用如下命令查看CentOS具体版本号
cat /etc/redhat-release
首先查看自己的仓库有哪些,使用yum repolist或者dnf repolist没有什么差别,查看可用或不可用的仓库
#显示系统中可用和不可用的所有的DNF软件库dnf repolist all
是的你没看错,powertools改名了!!!所以原来的开启该库的方法需要更改下名字了。
同时开启C\D上的nginx,将首页命名为cc和dd,当然,在实际的生产环境中,CD服务器上的数据肯定是要求一样的,只不过这里为了方便测试,看出效果,故意设计成不一样的。
2.配置Nginx分发器(其实就是虚拟主机+反向代理+upstream)。
主分发器A的nginx.conf配置文件如下:
http {#必须定义在http段中upstream web{#Cserver 45.76.51.26;#Dserver 202.182.107.126 ;}#主分发器的虚拟主机server {listen 80;server_name localhost;location / {#反向代理proxy_pass http://web;}}}
重启nginx发现无法访问,状态码502,说明是服务器内部错误,那么应该是防火墙的问题,因为所有的服务器我都是开启了防火墙的,A反向代理CD,但是CD根本没有给予A的公网IP访问权限,所以在CD服务器上,我增加了A服务器IP的白名单(如果是国内服务器,还要放行安全组),未添加白名单之前
firewall-cmd --zone=trusted --list-all
添加白名单,重新加载防火墙之后
firewall-cmd --permanent --zone=trusted --add-source=141.164.50.226
再次尝试访问主分发器A
elinks http://141.164.50.226 --dump
访问的同一个链接,可以看到cc和dd交替出现,正是CD业务服务器的首页内容。
关于为什么不开放CD服务器的80端口,这么麻烦的加载白名单IP,我的考虑是如果在生产环境中,业务服务器肯定是不想让别人直接访问的,所以没有必要开放端口,只放行固定的IP访问就可以了。
另外,在配置了nginx分发器之后,也可以先使用elinks测试下A和CD的连通性(上面我是通过经验来判断防火墙的问题),如果提示No route to host,那么连通性肯定是有问题的。
3.nginx分发算法。Nginx的upstream分发模块大致有以下几种
轮询(默认)。每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
weight。指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
ip_hash。每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务,可以解决session的问题。用以处理动态网站。
fair(第三方模块,还有很多)。按后端服务器的响应时间来分配请求,响应时间短的优先分配。
--测试upstream分发算法weight,主分发器nginx.conf配置文件修改如下
upstream web{#Cserver 45.76.51.26 weight=3;#Dserver 202.182.107.126 weight=1;}
可以很清楚的看到,3cc1dd,正好是weight配置的权重。
--测试upstream分发算法ip_hash,主分发器nginx.conf配置文件修改如下
#必须定义在http段中upstream web{ip_hash;#Cserver 45.76.51.26 weight=1;#Dserver 202.182.107.126 weight=1;}
当第一个返回的是业务服务器C的数据,那么意味着之后所有该IP来源的请求都交由C服务器来处理。
Nginx业务服务器的状态表示:
down。表示当前的server暂时不参与负载。
weight。默认为1,weight越大,负载的权重就越大。
max_fails。允许请求失败的次数默认为1,当超过最大次数时,返回proxy_next_upstream模块定义的错误。
fail_timeout。失败超时时间,在连接Server时,如果在超时时间之内超过max_fails指定的失败次数,会认为在fail_timeout时间内Server不可用,默认为10s。
backup。其他所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻,注意,该参数无法与ip_hash分发算法同时使用。
服务器down状态测试如下:
#必须定义在http段中upstream web{ip_hash;#Cserver 45.76.51.26 weight=1 down;#Dserver 202.182.107.126 weight=1;}
服务器backup状态测试如下:
#必须定义在http段中upstream web{# ip_hash;#Cserver 45.76.51.26 weight=1 ;#Dserver 202.182.107.126 weight=1 backup;}
可以看见默认会转到C服务器进行处理,因为D服务器是backup,现在将C服务器的nginx杀掉,再次请求主分发器A,发现D开始接手请求,进行工作!
而当我启动C服务器的nginx之后,D又再次退居幕后,测试的图片太多了,就不贴了。
前面的分发方式都是基于一个集群分发的,而基于请求头分发一般都是用于多集群分发的。
4.基于请求头的分发。
基于host分发
基于开发语言分发
基于浏览器的分发
基于源IP分发
基于host分发必须得有域名,其适用于多集群分发。例如:一个公司有多个网站(域名),每个网站就是一个集群。假设我将B服务器当作客户端,那么修改其hosts文件如下(因为仅仅是测试):
vim /etc/hosts#将域名解析到主分发器A的公网IP141.164.50.226 www.cc.com141.164.50.226 www.dd.com
这里需要注意的是,域名是解析到主分发器A的公网IP,而不是C/D的IP,如果直接解析到C/D,客户端B就直接通过IP去访问CD了,那么主分发器A就没有任何意义了!当做了以上解析之后,客户端B通过域名去访问的host就变成了cc.com和dd.com,尽管访问的目标IP是一样的(主分发器A的公网IP)。
配置主分发器A的nginx.conf文件如下:
http {upstream webc{#Cserver 45.76.51.26 weight=1;......}upstream webd{#Dserver 202.182.107.126 weight=1;......}server {listen 80;server_name www.cc.com;location / {#反向代理proxy_pass http://webc;}}server {listen 80;server_name www.dd.com;location / {#反向代理proxy_pass http://webd;}}
测试结果如下图
基于开发语言分发。主要应用于网站的开发语言比较混杂的情况,例如php,html等。在服务器C上安装apache和php环境,并启动Apache
yum install httpd phpsystemctl start httpd.servicekillall nginxsystemctl start httpd.serviceelinks 45.76.51.26 --dump
安装成功,写一个php首页方便测试
echo "<?php phpinfo()?>" > /var/www/html/index.php
配置主分发器的nginx.conf文件如下:
http {upstream php{server 45.76.51.26 weight=1;}upstream html{server 202.182.107.126 weight=1;}#一个虚拟主机即可server {listen 80;server_name 141.164.50.226;location ~* \.php$ {proxy_pass http://php;}location ~* \.html$ {proxy_pass http://html;}}}
测试如下:
基于浏览器的分发。主要是应用于通过user-agent判断是手机还是电脑用户,当然,现在很多网站都做了响应式了。
配置主分发器的nginx.conf文件如下:
http {upstream elinks{server 45.76.51.26 weight=1; # cc}upstream chrome{server 202.182.107.126 weight=1; # dd}server {listen 80;server_name 141.164.50.226;location /{if ($http_user_agent ~* Elinks){proxy_pass http://elinks;}if ($http_user_agent ~* chrome){proxy_pass http://chrome;}}}}
理论上,通过elinks访问统一返回cc,而通过浏览器只会返回dd,测试如下:
基于源IP分发。应用于不同地理位置的访问页面不同,例如淘宝,五八同城等。
配置主分发器的nginx.conf文件如下:
http {upstream hb.server{server 45.76.51.26 weight=1;}upstream hn.server{server 202.182.107.126 weight=1;}upstream default.server{#Bserver 141.164.34.223 weight=1;}#nginx内置的geo模块geo $geo{default default;#假设定义了CD的IP位置hb;hn;}server {listen 80;server_name 141.164.50.226;location /{proxy_pass http://$geo.server$request_uri;}}}
还是先推测一下结果,理论上,我在C服务器上访问141.164.50.226应当返回cc,而在D服务器上访问应当返回dd,在非CD服务器上返回的应该是default的内容。测试如下:
一切如推测所示。
5.如何构建高可用集群?在上面的例子中,我们已经解决了业务服务器的问题,但是还有一个问题,就是主备分发器的切换没有解决,如果主分发器发生故障,自动切换到备分发器,那就很高可用了。这个方法的实现就需要依赖虚拟IP了。
Keepalived实现虚拟IP的自动切换。百度百科对其描述如下:Keepalived的作用是检测服务器的状态,如果有一台web服务器宕机,或工作出现故障,Keepalived将检测到,并将有故障的服务器从系统中剔除,同时使用其他服务器代替该服务器的工作,当服务器工作正常后Keepalived自动将服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的服务器。
在分发器AB上安装Keepalived,源码编译步骤如下:
wget https://www.keepalived.org/software/keepalived-2.2.1.tar.gztar -zxvf keepalived-2.2.1.tar.gzcd keepalived-2.2.1/yum install openssl-devel libnl3-devel -y./configure --prefix=/usr/local/keepalivedmake && make install/usr/local/keepalived/sbin/keepalived -v
关于安装Keepalived可能的一些报错
yum install openssl-devel# OpenSSL is not properly installed on your system.yum install libnl3-devel# WARNING - this build will not support IPVS with IPv6. Please install libnl/libnl-3 dev libraries to support IPv6 with IPVS.
如果是自己编译的Keepalived,建议把配置文件keepalived.conf写在/etc/keepalived/目录下(keepalived文件夹自己创建),可以方便的使用其二进制程序检测我们的配置文件有无问题(类似nginx -t),具体的更多信息,可以在/var/log/messages中查看。
在AB上配置nginx分发器,分发规则为轮询模式。AB服务器上的nginx.conf的配置文件一样,并测试OK,如下:
http {upstream web{#Cserver 45.76.51.26 weight=1;#Dserver 202.182.107.126 weight=1;}server {listen 80;server_name localhost;location / {proxy_pass http://web;}}}
配置Keepalived.conf,截取修改部分,如下
global_defs {##定义故障通知邮箱notification_email {[email protected][email protected][email protected]}##发件人地址notification_email_from [email protected]##邮件服务器地址smtp_server 192.168.200.1##联系邮件服务器的超时时长smtp_connect_timeout 30#标识信息,一个名字而已;router_id LVS_DEVELvrrp_skip_check_adv_addrvrrp_strict}#定义一个脚本vrrp_script check_nginx {#脚本路径,这个脚本将用于监测nginx的运行script "/usr/local/keepalived/etc/keepalived/nginx_pid.sh"#探针每两秒运行一次脚本interval 2#允许的失败次数fall 1}#一个实例就是一个集群#定义了一个实例叫做VI_1vrrp_instance VI_1 {#主 MASTERstate MASTER#ifconfig查看到的网卡名 虚拟IP绑定的端口#interface eth0interface ens3#发送多播包的地址,如果不设置,默认使用绑定的网卡的IP#mcast_src_ip 141.164.50.226##让master 和backup在同一个虚拟路由里,id 号必须相同;virtual_router_id 51#优先级priority 100#多久发一次组播advert_int 1#组播只有确认过密码才能被收取authentication {auth_type PASSauth_pass 1111}#调用脚本track_script {check_nginx}virtual_ipaddress {192.168.200.16}}
创建nginx_pid.sh脚本如下,脚本自己可以先运行下,看下是否会报错
# 是否存在nginxnginx_pid=`ps -C nginx --no-header |wc -l`if [ $nginx_pid -eq 0 ];then# 尝试重启nginx/usr/local/nginx/sbin/nginx# nginx启动不了sleep 1if [ `ps -C nginx --no-header |wc -l` -eq 0 ];then# keepalived自我毁灭,那么将不会再发组播killall keepalivedfifi
需要注意windows下的格式和linux是有区别的,可能你使用sh命令死活无法执行这个程序,但是内容没问题,那就有可能是格式有问题
转换windows格式至Linux格式,成功运行脚本
[root@Korea1 keepalived]# dos2unix nginx_pid.shdos2unix: converting file nginx_pid.sh to Unix format...[root@Korea1 keepalived]# sh nginx_pid.sh
那么如何确定我的主分发器上的Keepalived是否运行成功,在运行之前,使用如下命令查看
ip addr show
在启动Keepalived之后,再次使用相同的命令
说明启动成功,或者使用如下命令,查看Keepalived是否开始向224.0.0.18发送讯号,如下,可以看到virtual_router_id 51等参数正是我们预先定义好的。
# ifconfig查看网卡ens3tcpdump -nn -vvv -i ens3 vrrp
现在Keepalived已经正常启动了,但是如何测试其重启nginx的功能是否正常?kill掉nginx,观察其是否会重启,例如,下图,没有重启,说明sh脚本是有问题的,原因是没有给脚本执行权限。
尝试给其执行权限,在我没有启动nginx时,可以看见nginx自动启动了,如下图(PID进程号不是一样也足以证明是重启的nginx进程),也可以再次kill掉进行测试,发现始终会重启(前面定义的2秒运行一次脚本)
现在我们可以尝试使用虚拟IP 192.168.200.16来进行访问,测试通讯是否正常。
同步配置备份服务器B的脚本(需要注意的是,使用B服务器编译的原始keepalived.conf来改),基本没什么变化,注意id保持一致,然后给予脚本权限,优先级要低于主机:
vrrp_instance VI_1 {state BACKUP##让master 和backup在同一个虚拟路由里,id 号必须相同;virtual_router_id 51#优先级低于主机priority 80}
开始进行测试,将两台主备服务器的nginx都清除掉,再重启。
网上的例子基本以同一网段(网卡类的知识了解的还比较少,有时间再了解)为教程,感觉并没有实际作用(个人见解)。测试发现目前公网无法通过Keepalived的虚拟IP连接,现在还没解决这个问题,等后续查下资料再来补充文章说明吧。
--20210124补充说明
多分发器的实现,依赖于虚拟IP的多播提醒,目前国内的云服务器商似乎并不支持浮动IP,其中,阿里云和腾讯云推出的havip(高可用虚拟 IP),均在内测中,“灰度优化中,切换的时延在10s左右”,实在太过鸡肋。
文章参考:
https://www.jianshu.com/p/a8ade44960dchttps://www.reddit.com/r/CentOS/comments/jd7x3d/how_to_enable_powertools_in_centos_stream/https://teddysun.com/566.htmlhttp://www.ttlsa.com/nginx/using-nginx-geo-method/https://www.redhat.com/sysadmin/keepalived-basicshttps://www.keepalived.org/manpage.htmlhttps://centos.pkgs.org/7/centos-x86_64/libnl3-devel-3.2.28-4.el7.x86_64.rpm.htmlhttps://www.jianshu.com/p/a6b5ab36292ahttps://blog.csdn.net/mofiu/article/details/76644012https://www.jianshu.com/p/7baea461dc2ehttps://blog.csdn.net/weixin_42724467/article/details/89147214https://cloud.tencent.com/document/product/215/36691?from=information.detail.%E9%AB%98%E5%8F%AF%E7%94%A8%E8%99%9A%E6%8B%9Fip
