1、Nginx高可用集群模式
再介绍Nginx高可用之前,先介绍一下实现高可用的软件:keepalived,我们需要借助keepalived实现Nginx的高可用。
1.1 高可用keepalived
1.1.1 keepalived简介
Keepalived软件起初是专为LVS负载均衡软件设计的,用来管理并监控LVS集群系统中各个服务节点的状态,后来又加入了可以实现高可用的VRRP功能。因此,Keepalived除了能够管理LVS软件外,还可以作为其他服务(例如:Nginx、Haproxy、MySQL等)的高可用解决方案软件。
Keepalived软件主要是通过**VRRP**
协议实现高可用功能的。VRRP是Virtual Router RedundancyProtocol(虚拟路由器冗余协议)的缩写,VRRP出现的目的就是为了解决静态路由单点故障问题的,它能够保证当个别节点宕机时,整个网络可以不间断地运行。
keepalived是如何基于VRRP协议实现高可用的呢?
- 首先集群通过VRRP协议的竞选机制确定节点的主备身份,工作时,主节点优先获取所有的资源,备用节点处于等待状态;
- 在keepalived服务正常工作时,主Master节点会不断地向备节点通过多播的方式发送心跳消息,通知备节点自己还活着;
- 当主Master节点发生故障时就无法发送心跳消息,备节点也就无法继续检测到来自主Master节点的心跳了,此时备节点会调用自身的接管程序,接管主Master节点的资源及服务(切换时间最快小于1s,但切换的1s的时间内服务不可用?);
- 当主Master节点恢复时,备节点又会释放掉自己接管的主节点的资源及服务,恢复到备用身份。
生产环境使用Keepalived正常运行,共启动3个进程,一个是父进程,负责监控其子进程,一个是VRRP子进程,另外一个是Checkers子进程
两个子进程都被系统Watchlog看管,两个子进程各自负责自己的事,Healthcheck子进程检查各自服务器的健康状况
如果Healthcheck进程检查到Master上服务不可用了,就会通知本机上的VRRP子进程,让他删除通告,并且去掉虚拟IP,转换为BACKUP状态。
1.1.2 keepalived.conf
一个完整的keepalived配置文件可以包含三个部分:全局定义块、VRRP实例块(包含健康检查脚本配置)以及虚拟服务器定义块。其中全局定义块和虚拟服务器块是必须的,VRRP实例块是可选的,具体细节如下:
# 全局定义模块
! Configuration File for keepalived
global_defs {
notification_email {
acassen@firewall.loc
failover@firewall.loc
# 邮件报警,可以不设置,后期nagios统一监控
sysadmin@firewall.loc
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 192.168.200.1
smtp_connect_timeout
# router_id为负载均衡标识,在局域网内应该是唯一的。
router_id LVS_DEVEL
vrrp_skip_check_adv_addr
vrrp_strict # 严格遵守VRRP协议
vrrp_garp_interval
vrrp_gna_interval
}
# 健康检查脚本配置
vrrp_script chk_nginx {
# 心跳检测脚本路径
script "/etc/keepalived/check_nginx.sh"
# 脚本执行间隔,每2s检测一次
interval 2
# 脚本结果导致的优先级变更,检测失败(脚本返回非0)则优先级 -20
weight -20
# 检测连续3次失败才算确定是真失败。会用weight减少优先级(1-255之间)
fall 3
# 检测2次成功就算成功。但不修改优先级
rise 2
}
# VRRP实例定义块
vrrp_instance VI_1 {
# 表示状态是MASTER主机还是备用机BACKUP
state MASTER
# 该实例绑定的网卡名称
interface ens33
# 保证主备节点的这个配置项的值一致即可
virtual_router_id 51
# 权重,master权重一般高于backup,如果有多个,那就是选举,谁的权重高,谁就当选
priority 100
# 主备之间同步检查时间间隔,单位秒
advert_int 2
# 认证权限密码,防止非法节点进入
authentication {
auth_type PASS
auth_pass 1111
}
# 虚拟出来的ip,可以有多个(vip)
virtual_ipaddress {
192.168.1.108
}
}
#虚拟服务器定义块
#虚拟IP,来源与上面的虚拟IP地址,后面加空格加端口号
virtual_server 192.168.200.100 443 {
# 健康检查间隔,单位为秒
delay_loop 6
# 负载均衡调度算法,一般用wrr、rr、wlc
lb_algo rr
# 负载均衡转发规则,一般包括DR,NAT,TUN 3种
lb_kind NAT
# 会话保持时间,会话保持,就是把用户请求转发给同一个服务器,不然刚在1上提交完帐号密码,就跳转到另一台服务器2上了。
persistence_timeout 50
# 转发协议,有TCP和UDP两种,一般用TCP
protocol TCP
# 真实服务器,包括IP和端口号
real_server {
# 权重,数值越大,权重越高
weight 1
# 连接超时时间
connect_timeout 3
# 重连次数
nb_get_retry 3
delay_before_retry
}
}
上面繁杂的配置项中,有以下几个是我们这次试验要修改适配的:
- 全局块:
- router_id LVS_DEVEL:同一个keepalived集群中的节点上该配置相同,且在局域网应是全局唯一的;
- VRRP实例块:
- state MASTER:状态只有MASTER和BACKUP两种,表明当前keepalived节点是主节点还是备节点;
- interface ens33:绑定的网卡名称;
- virtual_router_id 51:负载均衡标识,局域网中相同virtual_router_id的keepalived节点组成了一套高可用集群,virtual_router_id在局域网内应该是唯一的;
- priority 100:优先级,Master的priority必须必Backup的priority高;
- virtual_ipaddress:keepalived集群对外暴露的虚Ip。
1.2 Nginx高可用试验
1.2.1 实现效果
如上图所示,一台节点上同时部署keepalived和Nginx,由这样的三台节点组成一个高可用集群,三台keepalived节点向客户端暴露一个共同的虚Ip;每一台Nginx都反向代理到三台Tomcat节点,Tomcat集群模拟后端服务集群。要达到的效果是:1.2.2 准备工作
(1)拉取centos镜像
(2)创建centos7容器docker pull centos:7
(3)进入容器centos1,并安装常用工具 ```bash docker exec -it centos1 bashdocker run -it -d --name centos1 centos:7
yum update yum install -y vim yum install -y wget yum install -y gcc-c++ yum install -y pcre pcre-devel yum install -y zlib zlib-devel yum install -y openssl openssl—devel yum install -y popt-devel yum install -y initscripts yum install -y net-tools
**(4)将容器centos1打包成镜像,以后可以使用该镜像生成比较完善的centos容器**
```bash
docker commit -a 'zj' -m 'centos7 with common tools' 5ed60794e0e4
其中5ed60794e0e4是容器centos7的ID。
(5)将打包好的镜像上传到阿里云镜像仓库中
# 登录阿里云镜像仓库
sudo docker login --username=158****4705 registry.cn-chengdu.aliyuncs.com
# 将生成好的centos镜像打标签
sudo docker tag 42840af0d2a4 registry.cn-chengdu.aliyuncs.com/docker_study_jerry/mycentos7:1.0
# 将打过标签的镜像上传到阿里云镜像仓库
sudo docker push registry.cn-chengdu.aliyuncs.com/docker_study_jerry/mycentos7:1.0
上传好的镜像在阿里云的镜像仓库中如下:
(6)删除之前创建的容器centos1,以上面创建的新镜像创建容器
docker rm -f centos1
# 容器内需要使用systemctl服务,需要加上/usr/sbin/init
docker run -it --name centos_temp -d --privileged 42840af0d2a4 /usr/sbin/init
docker exec -it centos_temp bash
(7)安装Nginx
# 使用yum安装nginx,需要包括Nginx的库
rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
# yum install 安装nginx
yum install -y nginx
# 启动nginx
systemctl start nginx.service
# 查看是否启动成功,出现nginx欢迎界面表示安装成功
curl localhost:80
Nginx启动成功如图所示:
(8)安装keepalived
# 下载keepalived
wget http://www.keepalived.org/software/keepalived-1.2.18.tar.gz
# 解压安装
tar -zxvf keepalived-1.2.18.tar.gz -C /usr/local/
# 下载插件openssl
yum install -y openssl openssl-devel
# 编译keepalivedcd
cd /usr/local/keepalived-1.2.18/ && ./configure --prefix=/usr/local/keepalived
# make
make && make install
(9)将keepalived安装成系统服务
mkdir /etc/keepalived
cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/
cp /usr/local/keepalived/etc/rc.d/init.d/keepalived /etc/init.d/
cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/
chkconfig keepalived on
(10)修改keepalived.conf文件
# 备份一下keepalived.conf
cp /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.backup
# 修改keepalived.conf,关键配置如下:
vrrp_script chk_nginx {
script "/etc/keepalived/nginx_check.sh"
interval 2
weight -20
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.200.16
}
}
我们将虚拟IP设置成192.168.200.16(后来发现需要设置成docker0一个网段的ip:172.17.0.100)
(11)添加keepalived心跳检测脚本
# 新建脚本
touch nginx_check.sh
# 添加以下内容
#!/bin/bash
A=`ps -C nginx –no-header |wc -l`
if [ $A -eq 0 ];then
/usr/local/nginx/sbin/nginx
sleep 2
if [ `ps -C nginx --no-header |wc -l` -eq 0 ];then
killall keepalived
fi
fi
# 给nginx_check.sh脚本赋予执行权限
chmod +x nginx_check.sh
(12)将keepalived设置成开机启动
systemctl enable keepalived.service
# 开启keepalived
systemctl start keepalived.service
如果启动失败,执行如下命令:
cd /usr/sbin/
rm -f keepalived
cp /usr/local/keepalived/sbin/keepalived /usr/sbin/
keepalived服务相关命令:
# 重新加载
systemctl daemon-reload
# 设置开机自动启动
systemctl enable keepalived.service
# 取消开机自动启动
systemctl disable keepalived.service
# 启动
systemctl start keepalived.service
# 停止
systemctl stop keepalived.service
# 查看服务状态
systemctl status keepalived.service
(13)验证虚拟IP是否成功,keepalived与nginx是否连通
curl 192.168.200.16
出现如下nginx欢迎页面,说明成功:
(14)将上面安装了nginx、keepalived以及一些基本工具的centos镜像再次打包成一个新的镜像,之后要用这个镜像生成主备节点的容器
# 生成镜像
docker commit -a 'zj' -m 'centos7 with common tools nginx keepalived' 9e7bc142e2f1
# 打tag
sudo docker tag 4af9e6bf2a5a registry.cn-chengdu.aliyuncs.com/docker_study_jerry/mycentos7:2.0
# push到阿里云镜像仓库
sudo docker push registry.cn-chengdu.aliyuncs.com/docker_study_jerry/mycentos7:2.0
(15)删除之前的容器centos_temp
docker rm -f 9e7bc142e2f1
1.2.3 热备试验修改配置文件
我们准备配置一个主节点centos(nginx + keepalived),2个从节点,试验过程我们验证以下几个点:
- 访问虚IP 192.168.200.16,请求会先转发给Master节点;
- 停止Master节点的centos容器,再次访问虚IP 192.168.200.16(后来发现需要设置成docker0一个网段的ip:172.17.0.100),请求会转发给Slave节点;
- 重启Master节点的centos容器,访问访问虚IP 192.168.200.16,请求会重新转发给Master节点。
(1)用上面创建好的nginx + keepalived镜像创建Master节点
docker run --privileged -tid --name centos_master --restart=always 4af9e6bf2a5a /usr/sbin/init
docker exec -it centos_master bash
(2)修改centos_master节点的nginx的欢迎页,并配置centos_master的keepalived.conf
vim /usr/share/nginx/html/index.html
vim /etc/keepalived/keepalived.conf
(3)重新加载keepalived
systemctl daemon-reload
systemctl restart keepalived.service
(4)创建两个slave节点的centos容器,修改nginx首页和keepalived.conf,并重新加载keepalived
1.2.4 验证工作
验证过程中开始时没有成功,排查了下原因,还需要做以下动作:
手动启动nginx进程
systemctl start nginx
如果nginx进程在,则输入`ps -ef | grep nginx`命令时结果如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1536187/1616169217277-5d9c61bf-7b32-4f9a-8a8e-13b11f9648c2.png#height=120&id=sEa5s&margin=%5Bobject%20Object%5D&name=image.png&originHeight=120&originWidth=1023&originalType=binary&size=14853&status=done&style=none&width=1023)
- 开始时在宿主机curl 192.168.200.16发现网不通,在宿主机输入`ip addr`,发现docker0网桥的ip为:172.17.0.1,如下:
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1536187/1616167654898-0411a7bf-9e09-402b-9744-36c95928719e.png#height=403&id=xXDt3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=403&originWidth=1022&originalType=binary&size=58658&status=done&style=none&width=1022)<br />由于上面设置的虚ip地址为192.168.200.16,与docker0网桥都不在一个网段,自然ping不通,因此我们要将虚IP设置成跟docker0网关的ip是一个网段的,这里我们将虚IP重新改成:172.17.0.100。
> 有关docker网络的基本介绍请参考:[https://www.yuque.com/docs/share/d9d0ff52-2917-45e2-b1c3-46adb6bb083f?#](https://www.yuque.com/docs/share/d9d0ff52-2917-45e2-b1c3-46adb6bb083f?#) 《狂神Docker学习笔记》,第9节。
**(1)验证宿主机curl虚Ip,请求是否能转给Master的centos节点**
```bash
# 在宿主机上curl
curl 172.17.0.100
结果成功请求到Master的centos节点,如下:
(2)停止Master的centos容器,验证请求是否能转发到从节点的centos容器
# 停止主节点的centos
docker stop centos_master
此时再在宿主机上curl虚ip,看到请求可以发送给备节点的centos上,如下图所示:
如果没有成功,检查centos容器里的nginx进程是否还在。
(3)重启Master的centos容器,验证请求是否能重新转发到Master节点上
# 重启Master容器
docker start centos_master
# 进入容器
docker exec -it centos_master bash
# 启动nginx进程
systemctl start nginx
结果是请求又重新转发到Master节点上,说明Master节点恢复后,keepalived会将从节点退出之前占有的主节点的资源,让主节点重新获取资源,结果如下图所示:
如果启动nginx进程出现一直卡着没反应的情况,输入以下命令:
systemctl status nginx.service
2、Nginx原理浅析
2.1 Nginx的进程模型
Nginx启动后,会有一个Master进程和多个Worker进程,Worker进程的数目是在nginx.conf配置中的全局块里的worker process配置项设置的,默认是1,查看Ninx相关进程,如下图所示:
Nginx的进程模型如下图所示:
2.1.1 Master进程
Master进程只有一个,Master进程充当整个进程组与用户的交互接口,请求先过Master进程,再到下面的Worker进程。Master进程不负责处理网络事件,而是交给下面的Worker进程来处理,Master进程来管理这些worker进程,Master进程有以下5个功能:
- 接收来自外界的请求;
- 向各Worker进程发送请求信号;
- 监控Worker进程的运行状态;
- 当Worker进程在异常情况下退出后,Master进程来重启新的Worker进程;
-
2.1.2 Worker进程
基本的网络事件(请求)是放在Worker进程中处理的,每个Worker进程之间是平等且独立的,一个请求只能在一个Worker进程中处理,一个Worker进程不能处理其他进程的请求。Worker进程的数目一般设置为服务器CPU核数一致。一个Worker进程可以处理多个请求,一个Worker进程可以处理的请求数目是在nginx.conf中的Event块的worker_connections配置项设置的,默认为512。
一个请求仅由一个Worker进程处理,采用这种方式的好处是? 节省锁带来的开销。独立的进程意味着不需要加锁,省掉了加锁带来的开销,同时在定位问题看日志也会方便;
- 高可用。采用独立的进程处理请求,可以让不同的Worker进程间互不影响,一个Worker进程因为异常退出后其他Worker进程还在工作,服务不会终止,且Master进程会很快重启新的Worker进程;
- 无需进程切换。一个请求仅有一个进程处理,不需要进行进程间的切换,也是节省了开销。
2.2 Nginx处理请求的流程
Nginx的IO使用的是epoll,epoll使用了I/O复用模型,与I/O阻塞模型比较,I/O复用模型的优势在于可以同时等待多个(而不只是一个)套接字描述符就绪(这就触碰到知识盲区了,没研究过Socket),Nginx的epoll工作流程如下:
- Master进程接收到信号(nginx -s reload)后,读取nginx.conf配置文件,建立需要listen的socket后,根据nginx.conf中的worker process配置fork出多个Worker进程,这样每个Worker进程都可以去accept这个socket;
- 当一个client链接到来时,所有accept的Worker进程都会收到通知,但只有一个Worker进程可以accept成功,其他的则会accept失败。Nginx提供了一把进程间的共享锁accept_mutex来保证同一时刻只有一个Worker进程在accept链接,从而解决了惊群问题;
- 当一个占有共享锁的Workeraccept这个链接后,就开始读取请求、解析请求、处理请求、获取数据并返回给客户端,最后断开连接,这样一个完整的请求就结束了。
2.3 IO多路复用 + 异步非阻塞
一个worker进程可以同时处理多个请求,每个worker进程只有一个主线程,而是采用异步非阻塞的方式来处理并发请求。比如同时有多个http request的时候,worker主线程与第一条request建议连接将其处理转发给下游fast cgi后,并不会挂起等待,而是立马处理下一条,可以理解轮询处理。与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换),更多的并发数,只是会占用更多的内存而已。因此nginx 是非常适合处理高并发请求的。