012.Nginx负载均衡补充
Nginx负载均衡算法
| 调度算法 | 概述 |
|---|---|
| 轮询 | 按照时间顺序逐一分配到不同的后端服务器(默认) |
| weight | 加权轮询,weight值越大,分配到的访问几率越高(默认是1) |
| ip_hash | 每个请求按访问IP的hash结果分配,使来自统一IP的请求固定访问一个后端服务器,此后该IP的请求固定到后端某一台服务器进行响应 |
| url_hash | 按照访问URL的hash结果来分配请求,使每个URL定向到同一个后端服务器,此后用户访问该URL时都是固定后端服务器提供响应 |
| least_conn | 最少链接数,那个机器链接数少就分配给哪台机器 |
示例
# 1.加权轮询示例
upstream node.example.com {
server 172.16.1.7:80 weight=5;
server 172.16.1.8:80 weight=1;
}
# 2.ip_hash示例
upstream node.example.com {
ip_hash;
server 172.16.1.7:80;
server 172.16.1.8:80;
}
加权轮询web01:web02比重是5:1,代表web01响应5次请求后web02才会响应一次请求;ip_hash不能与加权轮询一起使用,ip_hash将用户来源IP与后端web节点绑定存放到代理节点的内存中,若该web节点故障宕机,代理节点会将用户请求临时转发到其他web节点为用户提供响应,但只要该web节点恢复,用户访问请求又会被代理转发到该web节点;ip_hash能够解决会话登录的问题,但是会造成负载不均衡,导致某一台web节点的负载过大
会话登录问题:某些网站会通过session id来记录用户的登录状态,客户端浏览器的cookie保存了session id,浏览器每次向服务端发起请求时报文头会自动添加cookie信息,服务端会查询用户的cookie作为key去存储中找对应的value(session id),如果找到了对应的 session id,说明用户处于登录状态,有相应的权限
如果没有找到对应的session id,服务器会为用户重新分配一个session id,并需要用户重新登录。而在轮询负载场景下,用户的session id通过代理轮询访问后端的web节点,这会导致用户的session id一直与后端web节点的session id不一致,用户将一直处于未登录状态
一般情况下用户session id与服务端session id不一致时,这表示要么是用户把浏览器关掉了,要么session超时(没有请求服务器超过20分钟)被服务器清除了,则服务器会给用户分配一个新的session id,用户需要重新登录并把这个新的session id保存在cookie中
Nginx负载均衡后端状态
| 状态 | 概述 |
|---|---|
| down | 当前server暂不参与负载均衡 |
| backup | 预留的WEB服务器 |
| max_fails | 允许请求失败的次数 |
| fail_timeout | 经过max_fails失败后,服务暂停时间 |
| max_conns | 限制最大的接收连接数 |
down参数一般用于web节点的停机维护;配置有backup参数的节点,只有在其他所有web节点都不提供服务后,才会临时使用backup节点,只要任意web节点恢复正常,backup节点不再参与负载;max_fails与fail_timeout联用,常用于web节点的健康检查,直接测试看不出什么效果
示例
# 1.web01节点停止参与负载
upstream node.example.com {
server 172.16.1.7:80 down;
server 172.16.1.8:80;
}
# 2.web01节点作为备用节点
upstream node.example.com {
server 172.16.1.7:80 backup;
server 172.16.1.8:80;
}
# 3.max_fails与fail_timeout
upstream node.example.com {
server 172.16.1.7:80 max_fails=2 fail_timeout=10s; # 连接失败超过2次时
server 172.16.1.8:80 max_fails=2 fail_timeout=10s; # 暂停连接该主机10秒
}
Nginx负载均衡健康检查
Nginx官方提供的模块中没有对负载均衡后端节点的健康检查模块,但可以使用第三方模块nginx_upstream_check_module来检测后方服务的健康状态;(upstream_check_module项目地址)[https://github.com/yaoweibin/nginx_upstream_check_module]
安装这个健康检查模块需要对nginx进行编译,对于添加的模块(也就是--add-module选项),建议集中存放到一起便于管理;对于要复制某台web节点的nginx编译参数的场景,如果存在添加的模块目录,也需要一并拷贝到目的主机后再编译
# 1.安装依赖包
[root@lb01 ~]# yum install -y install -y gcc gcc-c++ openssl-devel pcre-devel patch
# 2.下载软件安装包
[root@lb01 ~]# wget http://nginx.org/download/nginx-1.22.1.tar.gz
[root@lb01 ~]# wget https://github.com/yaoweibin/nginx_upstream_check_module/archive/refs/heads/master.zip
# 3.解压软件包
[root@lb01 ~]# tar -xzf nginx-1.22.1.tar.gz
[root@lb01 ~]# unzip nginx_upstream_check_module-master.zip
# 4.为nginx打补丁,按照nginx的版本选择补丁保本,p1表示在nginx目录,p0表示不在nginx目录
[root@lb01 ~]# cd nginx-1.22.1/
[root@lb01 nginx-1.22.1]# patch -p1 < ../nginx_upstream_check_module-master/check_1.20.1+.patch
# 5.编译nginx
[root@lb01 nginx-1.22.1]# nginx -V # 复制nginx原有的编译参数,在原有的编译参数上添加--add-module=第三方模块的路径即可
[root@lb01 nginx-1.22.1]# ./configure ... --add-module=/root/nginx_upstream_check_module-master/
[root@lb01 nginx-1.22.1]# make && make install
# 6.配置健康检查模块
upstream node.example.com {
server 172.16.1.7:80 max_fails=2 fail_timeout=10s;
server 172.16.1.8:80 max_fails=2 fail_timeout=10s;
check interval=3000 rise=2 fall=3 timeout=1000 type=tcp;
# interval:检测间隔时间,单位为毫秒
# rsie:请求2次正常,标记后端web节点状态为up
# fall:请求3次失败,标记后端web节点状态为down
# timeout:请求间隔时间,单位为毫秒
# type:类型为TCP
}
server {
listen 80;
server_name node.example.com;
charset utf-8,gbk;
location /upstream_check { # 调用健康检查
check_status;
}
}
这个第三方健康检查模块与stub_status模块相似,它不需要编写站点页面,定义参数之后直接调用即可;一般健康检查页面仅提供给内网查看,为保障其安全性,建议配置访问控制模块与身份认证模块
会话登录问题
会话登录问题的痛点就在于用户的session id信息保存在web节点本地,这样用户通过负载访问web集群时,可能会造成持续处于未登录状态。为了解决这个问题产生了三种方式:ip_hash、session复制、session共享,ip_hash此前已经有提到它的弊端,session复制也就是实时监控文件变化,最常用的方式时session共享
而session共享又分为多种方式:nfs共享、通过程序写入redis数据库、通过程序写入mysql数据库,其中最常见的是使用redis数据库,它作为内存数据库读写速率比mysql更快;同一域名下的网站cookie是相同的,所以无论多少服务器、无论请求分配到哪个节点,同一用户的cookie是不变的,这意味着cookie对应的session也是唯一的。所以只要保证多台web节点访问同一个session共享服务器(memcache、redis、mysql、file)即可
为了模拟出会话登录问题场景,需要安装phpmyadmin,phpmyadmin将session id写在web节点本地存储
# 1.下载软件包
[root@web01 ~]# wget https://files.phpmyadmin.net/phpMyAdmin/5.2.1/phpMyAdmin-5.2.1-all-languages.zip
[root@web01 ~]# unzip phpMyAdmin-5.2.1-all-languages.zip
[root@web01 ~]# mv phpMyAdmin-5.2.1-all-languages myadmin
[root@web01 ~]# mv myadmin/ /usr/local/nginx/html/
# 2.修改phpmyadmin配置文件
[root@web01 ~]# cd cd /usr/local/nginx
[root@web01 nginx]# cp html/myadmin/config.sample.inc.php html/myadmin/config.inc.php
[root@web01 nginx]# vim html/myadmin/config.inc.php
$cfg['Servers'][$i]['host'] = '172.16.1.51'; # 只需要修改数据库地址
# 3.修改nginx配置文件
[root@web01 nginx]# vim conf.d/phpmyadmin.example.com.conf
server {
listen 80;
server_name phpmyadmin.example.com;
charset utf-8,gbk;
root //usr/local/nginx/html/myadmin/;
location / {
index index.php index.html;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
[root@web01 nginx]# systemctl restart nginx
# 4.配置授权
[root@web01 nginx]# chown -R www.www /usr/local/nginx/
# 5.将web02与web01节点的配置同步
[root@web01 nginx]# scp -rp /usr/local/nginx/html/myadmin/ root@172.16.1.8:/usr/local/nginx/html/
[root@web01 nginx]# scp -rp /usr/local/nginx/conf.d/phpmyadmin.example.com.conf root@172.16.1.8:/usr/local/nginx/conf.d/
[root@web02 ~]# systemctl restart nginx
[root@web02 ~]# chown -R www.www /usr/local/nginx
# 6.接入负载均衡
[root@lb01 ~]# vim /etc/nginx/conf.d/proxy_myadmin.conf
upstream phpmyadmin.example.com {
server 172.16.1.7:80;
server 172.16.1.8:80;
}
server {
listen 80;
server_name phpmyadmin.example.com;
charset utf-8,gbk;
location / {
proxy_pass http://phpmyadmin.example.com;
include proxy_params;
}
}
[root@lb01 ~]# systemctl restart nginx
在浏览器中登录phpmyadmin,账户密码是连接远程数据的账户密码,在浏览器中打开调试模式,选择Application -> Storage -> Cookies -> http://phpmyadmin.example.com,可以看到Name栏下面有个phpMyAdmin数据,这个数据对应的Value就是浏览器保存的session id,如果直接在浏览器中更改这个session id,刷新网页后就需要重新登录了
两个web节点接入负载均衡之后phpmyadmin就再也登录不上了,输入账户密码登录时会跳转到另外一台web节点上,导致客户端与服务端的session id永远都不一致
使用redis解决会话登录问题
- db01安装redis
# 1.db01节点安装redis
[root@db01 ~]# yum install -y redis
# 2.修改redis配置
bind 127.0.0.1 172.16.1.51 # 默认redis仅监听在本地127段,添加监听172段
# 3.启动redis
[root@db01 ~]# systemctl enable redis
[root@db01 ~]# systemctl start redis
- web01节点安装redis客户端
# 1.php补充编译redis模块
[root@web01 ~]# wget https://github.com/phpredis/phpredis/archive/refs/tags/5.3.7.tar.gz
[root@web01 ~]# tar -xzf 5.3.7.tar.gz
[root@web01 ~]# cd phpredis-5.3.7/
[root@web01 phpredis-5.3.7]# /usr/local/php/bin/phpize
[root@web01 phpredis-5.3.7]# ./configure --with-php-config=/usr/local/php/bin/php-config --enable-redis
[root@web01 phpredis-5.3.7]# make && make install
# 2.修改php.ini配置文件
[root@web01 phpredis-5.3.7]# vim /usr/local/php/lib/php.ini
extension=redis.so # 添加此行,开启redis支持
session.save_handler = redis # 默认存放file本地文件
session.save_path = "tcp://172.16.1.51:6379"
session.auto_start = 1 # 启用redis缓存
# 3.修改www.conf配置文件
[root@web01 phpredis-5.3.7]# vim /usr/local/php/etc/php-fpm.d/www.conf
;php_value[session.save_handler] = files
;php_value[session.save_path] = /var/lib/php/session/
# 4.重启php-fpm
[root@web01 phpredis-5.3.7]# systemctl restart php-fpm
[root@web01 phpredis-5.3.7]# /usr/local/php/bin/php -m # 查看php已加载的所有模块
php手册没有提供Redis的安装和使用,也没有提供相关的扩展模块,需要到Redis官网下载php扩展phpredis;使用php集成redis时,修改www>.conf文件,如果php是通过编译安装的,那么www.conf文件无需修改,yum安装的php需要进行修正
- 同步web02节点
# 1.web01同步
[root@web01 ~]# rsync -avzP /usr/local/php/ root@172.16.1.8:/usr/local/php/
[root@web02 ~]# systemctl restart php-fpm # web02重启php-fpm
# 2.redis查看数据
[root@db01 ~]# redis-cli
127.0.0.1:6379> keys * # 查看session id,web节点有2个,session id应该也有两个
Nginx四层负载均衡
四层负载均衡基于传输层协议包封装,前文使用到的负载均衡配置都是七层的应用层,它组装在四层基础上。比较常见的四层负载均衡的使用场景,是四层配合七层负载使用,四层负载能够保证七层负载的高可用性,例如nginx就无法保证自身的服务高可用,需要依赖LVS或KeepAlive来做高可用;或者针对TCP协议的负载,部分TCP请求(MySQL、ssh)只需要用到四层的端口转发,也适用四层负载
在用户访问请求量非常大的情况下,建议使用四层+七层负载结构,四层负载由于针对传输层的IP:PORT进行匹配,它不会完全将报文解封装,而是解封装到四层,确认目标IP是自身后,就会将数据包抛给七层负载,由此四层负载能够承受的用户连接更多,响应更快。用户数据包到七层负载后完全被解封装,再由七层负载执行域名匹配、URL规则控制等高级功能

四层负载解决了七层负载的高可用问题,但四层负载本身作为单节点去使用也会存在单点故障的风险,所以在四层负载之间还需要keepalive实现四层高可用,keepalive也可以直接在七层负载上实现高可用;keepalive、LVS等高可用技术只能在硬件主机上实施,在云主机上无法使用,公有云有自己的负载均衡产品,例如阿里云的SLB,其本身就支持四层负载均衡
在负载均衡的使用中,只要提到TCP负载,那就是指的四层负载,反之HTTP负载,就是指的七层负载
Nginx四层负载实践
- 准备lb02节点
# 1.新建lb02虚拟机,修改主机名/IP
# 2.安装nginx
[root@lb02 ~]# scp root@172.16.1.5:/etc/yum.repos.d/nginx.repo /etc/yum.repos.d/
[root@lb02 ~]# yum repolist
[root@lb02 ~]# yum install nginx -y
# 3.同步lb01节点配置
[root@lb02 ~]# scp -rp root@172.16.1.5:/etc/nginx/* /etc/nginx/
[root@lb02 ~]# nginx -t # 因缺少第三方健康检查模块报错
# 4.同步第三方检查模块
[root@lb02 ~]# scp -rp root@172.16.1.5:/root/nginx_upstream_check_module-master ./
[root@lb02 ~]# scp -rp root@172.16.1.5:/root/nginx-1.22.1 ./
[root@lb02 ~]# yum install -y install -y gcc gcc-c++ prce-devel openssl-devel pcre-devel patch
[root@lb02 ~]# cd nginx-1.22.1/
[root@lb02 nginx-1.22.1]# ./configure ... --add-module=/root/nginx_upstream_check_module-master/
[root@lb02 nginx-1.22.1]# make && make install
[root@lb02 nginx-1.22.1]# systemctl restart nginx
lb02节点同步第三方健康检查模块时不需要在执行一次打补丁,因为nginx的配置文件都是直接从lb01节点拷贝过来的,但yum安装的nginx默认编译模块里没有包含第三方模块,所以只需要重新编译一次,依赖包仍需正常安装
由于wecenter和wordpress都是通过程序代码将session id写入mysql的,所以web节点中的php.ini配置文件中session.auto_start = 0需要修改,否则访问站点可能会出错。在高版本的LNMP+wordpress似乎不用修改php.ini配置,也没有看到出错
- 四层+七层场景
四层负载均衡依靠stream模块,而且stream模块特性,它作为四层负载无法配置在http内核模块层,必须配置在http上层;官方stream配置示例
# 1.新建lb4-01虚拟机,配置主机名/IP
# 2.安装nginx
[root@lb4-01 ~]# scp root@172.16.1.5:/etc/yum.repos.d/nginx.repo /etc/yum.repos.d/
[root@lb4-01 ~]# yum install -y nginx # yum安装默认携带stream模块
# 3.配置stream
[root@lb4-01 ~]# vim /etc/nginx/nginx.conf
...
stream {
upstream lb.example.com {
server 172.16.1.5:80; # 七层负载均衡的算法和状态参数同样能够用在四层负载中
server 172.16.1.6:80;
}
server {
listen 80;
proxy_connect_timeout 3s; # 连接超时时间
proxy_timeout 3s; # 响应超时时间
proxy_pass lb.example.com;
}
}
...
[root@lb4-01 ~]# mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak
[root@lb4-01 ~]# systemctl restart nginx
四层负载均衡配置完成后可以任意关闭一台七层进行高可用测试
stream要求配置在http上层,可以选择直接配置在主配置文件nginx.conf中,或者在http层上使用include再包含其他目录中的配置文件;default.conf必须注释或删除,因为它默认包含了http层的配置,即便nginx检查语法没有问题,启动nginx时也会因为既配置了四层负载、又存在http配置文件,双方抢占80端口导致服务无法正常启动
- 四层端口转发场景
stream {
upstream 22 {
server 172.16.1.7:22;
}
upstream 3306 {
server 172.16.1.51:3306;
}
server {
listen 2222;
proxy_connect_timeout 3s;
proxy_timeout 30s; # ssh超过30秒没有任何动作会自动掉线
proxy_pass 22;
}
server {
listen 3333;
proxy_connect_timeout 3s;
proxy_timeout 3s;
proxy_pass 3306;
}
}
- 解决四层负载没有错误日志的问题
默认情况下nginx的access.log记录的都是http访问日志,log_format日志格式也是在http层定义和调用的,所以在四层负载上无论是访问成功或失败,access.log都没有任何记录;四层日志需要在stream块中定义log_format日志格式并调用。log_format日志变量格式
stream {
log_format proxy '$remote_addr $remote_port - [$time_local] $status $protocol'
'"$upstream_addr" "$upstream_bytes_sent" "$upstream_connect_time"';
access_log /var/log/nginx/proxy.log proxy;
...
}
转发与代理
四层负载只做数据包的转发,用户的请求到四层负载之后,四层负载只会修改用户请求的目标IP,然后就将用户请求抛给后端设备处理;七层负载(代理)会根据用户请求的资源,自身再想内部web集群发起请求,获取资源后返回给客户端

浙公网安备 33010602011771号