vlambda博客
学习文章列表

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修改成宽容模式。

[root@Korea1 ~]# getenforceEnforcing[root@Korea1 ~]# setenforce 0[root@Korea1 ~]# getenforcePermissive


    本篇文章的优化仅针对网站访问量巨大的情况,例如访问日均IP超过10万,小型网站没有必要做这个优化。先来说下原理,我们知道传统的web访问,大概是下图这个样子,一台服务器接收所有的网络请求,而一次完整的网络请求大概有4个步骤,客户端发起请求,服务器接收请求,服务器处理请求(压力最大),服务器返回请求,这样有可能会出现以下几种问题,该服务器如果出现故障/请求压力过大出现的故障(单点故障);单台服务器资源有限;单台服务器处理能力有限(例如内存基本没用,但是数据库链接数过多卡死)

    为了解决上面的问题,我们有两种方案,第一种是我们可以考虑部署一个备份服务器,在主机器宕机时进行切换,但是这种做法只能解决主机硬件故障导致的问题,如果是请求压力过大导致的故障,没法解决,因为主机扛不住巨量的请求,那么备机肯定也是扛不住的。第二种是部署多台服务器,使用DNS轮询解析去分发用户请求到不同的服务器,但是这也会有问题,因为DNS解析不是我们所能控制的,那么当某台服务器故障时,DNS解析到该台服务器的用户请求将无法得到处理。



    其实我们还有第三种解决方案,那就是集群模式,即将多个物理机器组成一个逻辑计算机,实现负载均衡和容错。下图中,可以看到,当一个请求过来的时候,DNS先将域名请求解析到主服务器1,1将接受到的请求分发到3456等业务服务器进行处理请求,处理完成后返回响应,如果机器3故障,那么主分发器将其剔除,等待3修复完成后再加入处理队列,这是我们可控的;那么备机2的作用是什么呢?我们知道域名是解析到公网IP上的,我们可以通过VIP(Vrtual IP Address)的指向,来让公网IP指向到不同的分发器上,当1宕机时,VIP漂浮到2上,即可实现自动切换分发器的功能。

Nginx构建高可用集群,实现负载均衡应对高并发

    集群的组成要素:

  • 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

Nginx构建高可用集群,实现负载均衡应对高并发

Nginx构建高可用集群,实现负载均衡应对高并发

    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.gz cd nginx-1.18.0/./configure --prefix=/usr/local/nginxmake && make install

    如果在安装nginx之后发现可能有某个内置模块未开启,该如何增加?

# 大写的V可以查看安装命令[root@Japan2 nginx-1.18.0]# /usr/local/nginx/sbin/nginx -Vnginx 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

Nginx构建高可用集群,实现负载均衡应对高并发

    是的你没看错,powertools改名了!!!所以原来的开启该库的方法需要更改下名字了。

    同时开启C\D上的nginx,将首页命名为cc和dd,当然,在实际的生产环境中,CD服务器上的数据肯定是要求一样的,只不过这里为了方便测试,看出效果,故意设计成不一样的。

Nginx构建高可用集群,实现负载均衡应对高并发

Nginx构建高可用集群,实现负载均衡应对高并发


    2.配置Nginx分发器(其实就是拟主机+反向代理+upstream)。

主分发器A的nginx.conf配置文件如下:

http { #必须定义在http段中 upstream web{ #C server 45.76.51.26; #D server 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

Nginx构建高可用集群,实现负载均衡应对高并发

添加白名单,重新加载防火墙之后

firewall-cmd --permanent --zone=trusted --add-source=141.164.50.226

Nginx构建高可用集群,实现负载均衡应对高并发

再次尝试访问主分发器A

elinks http://141.164.50.226 --dump

Nginx构建高可用集群,实现负载均衡应对高并发

访问的同一个链接,可以看到cc和dd交替出现,正是CD业务服务器的首页内容。

    关于为什么不开放CD服务器的80端口,这么麻烦的加载白名单IP,我的考虑是如果在生产环境中,业务服务器肯定是不想让别人直接访问的,所以没有必要开放端口,只放行固定的IP访问就可以了。

    另外,在配置了nginx分发器之后,也可以先使用elinks测试下A和CD的连通性(上面我是通过经验来判断防火墙的问题),如果提示No route to host,那么连通性肯定是有问题的。

Nginx构建高可用集群,实现负载均衡应对高并发


    3.nginx分发算法。Nginx的upstream分发模块大致有以下几种

  • 轮询(默认)。每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。

  • weight。指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。

  • ip_hash。每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务,可以解决session的问题。用以处理动态网站。

  • fair(第三方模块,还有很多)。按后端服务器的响应时间来分配请求,响应时间短的优先分配。

--测试upstream分发算法weight,主分发器nginx.conf配置文件修改如下


upstream web{ #C server 45.76.51.26 weight=3; #D server 202.182.107.126 weight=1;}

Nginx构建高可用集群,实现负载均衡应对高并发

    可以很清楚的看到,3cc1dd,正好是weight配置的权重。

--测试upstream分发算法ip_hash,主分发器nginx.conf配置文件修改如下

#必须定义在http段中upstream web{ ip_hash; #C server 45.76.51.26 weight=1; #D server 202.182.107.126 weight=1;}

Nginx构建高可用集群,实现负载均衡应对高并发

当第一个返回的是业务服务器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; #C server 45.76.51.26 weight=1 down; #D server 202.182.107.126 weight=1;}

Nginx构建高可用集群,实现负载均衡应对高并发


服务器backup状态测试如下:

#必须定义在http段中upstream web{ # ip_hash; #C server 45.76.51.26 weight=1 ; #D server 202.182.107.126 weight=1 backup;}

Nginx构建高可用集群,实现负载均衡应对高并发

可以看见默认会转到C服务器进行处理,因为D服务器是backup,现在将C服务器的nginx杀掉,再次请求主分发器A,发现D开始接手请求,进行工作!

Nginx构建高可用集群,实现负载均衡应对高并发

Nginx构建高可用集群,实现负载均衡应对高并发

而当我启动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.comdd.com,尽管访问的目标IP是一样的(主分发器A的公网IP)。

Nginx构建高可用集群,实现负载均衡应对高并发

    配置主分发器A的nginx.conf文件如下:

http { upstream webc{ #C server 45.76.51.26 weight=1; ...... } upstream webd{ #D server 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; } }

测试结果如下图

Nginx构建高可用集群,实现负载均衡应对高并发


    基于开发语言分发。主要应用于网站的开发语言比较混杂的情况,例如php,html等。在服务器C上安装apache和php环境,并启动Apache

yum install httpd phpsystemctl start httpd.servicekillall nginxsystemctl start httpd.serviceelinks 45.76.51.26 --dump

Nginx构建高可用集群,实现负载均衡应对高并发

    安装成功,写一个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; } }}

测试如下:

Nginx构建高可用集群,实现负载均衡应对高并发

Nginx构建高可用集群,实现负载均衡应对高并发


    基于浏览器的分发。主要是应用于通过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,测试如下:

Nginx构建高可用集群,实现负载均衡应对高并发

Nginx构建高可用集群,实现负载均衡应对高并发


    基于源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{#B server 141.164.34.223 weight=1; } #nginx内置的geo模块 geo $geo{ default default; #假设定义了CD的IP位置 45.76.51.26 hb; 202.182.107.126 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的内容。测试如下:

Nginx构建高可用集群,实现负载均衡应对高并发

Nginx构建高可用集群,实现负载均衡应对高并发

Nginx构建高可用集群,实现负载均衡应对高并发

一切如推测所示。


    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

Nginx构建高可用集群,实现负载均衡应对高并发

关于安装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{ #C server 45.76.51.26 weight=1; #D server 202.182.107.126 weight=1;  } server { listen 80; server_name localhost; location / { proxy_pass http://web; } }}

Nginx构建高可用集群,实现负载均衡应对高并发

    配置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_DEVEL vrrp_skip_check_adv_addr vrrp_strict}#定义一个脚本vrrp_script check_nginx {#脚本路径,这个脚本将用于监测nginx的运行script "/usr/local/keepalived/etc/keepalived/nginx_pid.sh"#探针每两秒运行一次脚本interval 2#允许的失败次数fall 1}#一个实例就是一个集群#定义了一个实例叫做VI_1vrrp_instance VI_1 { #主 MASTER state MASTER #ifconfig查看到的网卡名 虚拟IP绑定的端口 #interface eth0  interface ens3 #发送多播包的地址,如果不设置,默认使用绑定的网卡的IP #mcast_src_ip 141.164.50.226 ##让master 和backup在同一个虚拟路由里,id 号必须相同; virtual_router_id 51 #优先级 priority 100  #多久发一次组播 advert_int 1 #组播只有确认过密码才能被收取 authentication { auth_type PASS auth_pass 1111 } #调用脚本 track_script { check_nginx } virtual_ipaddress {        192.168.200.16 }}

    创建nginx_pid.sh脚本如下,脚本自己可以先运行下,看下是否会报错

#!/bin/bash# 是否存在nginxnginx_pid=`ps -C nginx --no-header |wc -l`if [ $nginx_pid -eq 0 ];then# 尝试重启nginx /usr/local/nginx/sbin/nginx # nginx启动不了 sleep 1 if [ `ps -C nginx --no-header |wc -l` -eq 0 ];then      # keepalived自我毁灭,那么将不会再发组播 killall keepalived fifi

需要注意windows下的格式和linux是有区别的,可能你使用sh命令死活无法执行这个程序,但是内容没问题,那就有可能是格式有问题

Nginx构建高可用集群,实现负载均衡应对高并发

转换windows格式至Linux格式,成功运行脚本

[root@Korea1 keepalived]# dos2unix nginx_pid.sh dos2unix: converting file nginx_pid.sh to Unix format...[root@Korea1 keepalived]# sh nginx_pid.sh

    那么如何确定我的主分发器上的Keepalived是否运行成功,在运行之前,使用如下命令查看

ip addr show

Nginx构建高可用集群,实现负载均衡应对高并发

在启动Keepalived之后,再次使用相同的命令

Nginx构建高可用集群,实现负载均衡应对高并发

说明启动成功,或者使用如下命令,查看Keepalived是否开始向224.0.0.18发送讯号,如下,可以看到virtual_router_id 51等参数正是我们预先定义好的。

# ifconfig查看网卡ens3tcpdump -nn -vvv -i ens3 vrrp

Nginx构建高可用集群,实现负载均衡应对高并发

    现在Keepalived已经正常启动了,但是如何测试其重启nginx的功能是否正常?kill掉nginx,观察其是否会重启,例如,下图,没有重启,说明sh脚本是有问题的,原因是没有给脚本执行权限。

Nginx构建高可用集群,实现负载均衡应对高并发

尝试给其执行权限,在我没有启动nginx时,可以看见nginx自动启动了,如下图(PID进程号不是一样也足以证明是重启的nginx进程),也可以再次kill掉进行测试,发现始终会重启(前面定义的2秒运行一次脚本)

Nginx构建高可用集群,实现负载均衡应对高并发

    现在我们可以尝试使用虚拟IP 192.168.200.16来进行访问,测试通讯是否正常。

Nginx构建高可用集群,实现负载均衡应对高并发

    同步配置备份服务器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左右”,实在太过鸡肋。


文章参考:

#虚拟IPhttps://www.jianshu.com/p/a8ade44960dc# centos安装powertools库问题https://www.reddit.com/r/CentOS/comments/jd7x3d/how_to_enable_powertools_in_centos_stream/# 防火墙如何加载白名单IPhttps://teddysun.com/566.html# nginx的地理位置判断模块http://www.ttlsa.com/nginx/using-nginx-geo-method/# keepalivedhttps://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/76644012# shell脚本格式转换https://www.jianshu.com/p/7baea461dc2e# 内外网的关系https://blog.csdn.net/weixin_42724467/article/details/89147214# 腾讯云虚拟IPhttps://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