LVS+Nginx+Keepalived+Tomcat+MySQL高可用负载均衡集群

实验环境机器信息总览

所有机器规划在同一个内网网段 192.168.10.0/24,虚拟IP(VIP) 设定为 192.168.10.100,作为用户访问的统一入口。

服务器角色

主机名

IP地址

虚拟IP(VIP)

部署组件

网卡模式/备注

LVS 主调度器

lvs-master

192.168.10.10

192.168.10.100

LVS, Keepalived (MASTER)

必须与Web服务器在同一二层网络(同网段)

LVS 备调度器

lvs-backup

192.168.10.11

192.168.10.100

LVS, Keepalived (BACKUP)

必须与Web服务器在同一二层网络(同网段)

Web 服务器 1

web-1

192.168.10.21

192.168.10.100(仅用于回环接口)

Nginx, Tomcat

必须配置抑制ARP响应,防止IP冲突

Web 服务器 2

web-2

192.168.10.22

192.168.10.100(仅用于回环接口)

Nginx, Tomcat

必须配置抑制ARP响应,防止IP冲突

MySQL 主库

mysql-master

192.168.10.31

无

MySQL (Master)

开启二进制日志,用于主从复制

MySQL 从库

mysql-slave

192.168.10.32

无

MySQL (Slave)

配置为只读,从主库同步数据

整体操作流程概览

整个部署过程分为 6个阶段,按顺序执行:

 Step 1: 基础环境准备(所有6台机器)
     ↓
 Step 2: MySQL主从复制(mysql-master + mysql-slave)
     ↓
 Step 3: Tomcat集群部署 + Session共享配置(web-1 + web-2)
     ↓
 Step 4: Nginx反向代理部署(web-1 + web-2)
     ↓
 Step 5: LVS + Keepalived 部署(lvs-master + lvs-backup)
     ↓
 Step 6: 整体功能验证

基础环境准备

 # 1.关闭防火墙(所有6台机器)
 sudo ufw disable
 sudo systemctl stop ufw
 sudo systemctl disable ufw
 ​
 # 2.更新系统包(所有6台机器)
 sudo apt update && sudo apt upgrade -y
 ​
 # 3.安装常用工具(所有6台机器)
 sudo apt install -y vim curl wget net-tools iputils-ping

MySQL主从复制(mysql-master + mysql-slave)

 # 安装MySQL(两台数据库机器) mysql-master 和 mysql-slave 上分别执行
 sudo apt install mysql-server -y
 sudo systemctl start mysql
 sudo systemctl enable mysql

配置MySQL主库(mysql-master)

 # 编辑配置文件
 vim /etc/mysql/mysql.conf.d/mysqld.cnf
 ​
 # 在 [mysqld] 段落中添加或修改
 [mysqld]
 # ... 其他配置保持不变 ...
 # 取消注释并修改以下三项:
 server-id = 1
 log_bin = /var/log/mysql/mysql-bin.log
 binlog_do_db = testdb          # 要同步的数据库名,可按需修改
 ​
 # 确保 bind-address 改为 0.0.0.0 允许远程连接(主库必须)
 bind-address = 0.0.0.0
 ​
 # 重启MySQL
 sudo systemctl restart mysql
 ​
 # 登录MySQL并创建复制账号
 sudo mysql
 ​
 # 创建指令(记下输出中的 File 和 Position 值,配置从库时需要用到)
 CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'Repl@123456';
 GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
 FLUSH PRIVILEGES;
 ​
 # 查看当前的二进制日志位置信息
 SHOW MASTER STATUS;
 ​
 # 显示内容
 +------------------+----------+--------------+------------------+-------------------+
 | File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
 +------------------+----------+--------------+------------------+-------------------+
 | mysql-bin.000001 |     827  | testdb       |                  |                   |
 +------------------+----------+--------------+------------------+-------------------+

配置MySQL从库(mysql-slave)

 # 编辑配置文件
 vim /etc/mysql/mysql.conf.d/mysqld.cnf
 ​
 # 在 [mysqld] 段落中添加或修改
 [mysqld]
 server-id = 2       # 修改
 read_only = 1       # 添加
 ​
 # 重启MySQL
 sudo systemctl restart mysql
 ​
 # 登录MySQL
 sudo mysql
 ​
 # 配置主从关系
 CHANGE MASTER TO
   MASTER_HOST='192.168.10.31',
   MASTER_USER='repl',
   MASTER_PASSWORD='Repl@123456',
   MASTER_LOG_FILE='mysql-bin.000001',   -- 替换为上面记下的实际值
   MASTER_LOG_POS=827;                   -- 替换为上面记下的实际值
   
 # 启动复制
 START SLAVE;
 ​
 # 查看状态
 SHOW SLAVE STATUS\G;

Tomcat集群部署(web-1 + web-2)

 # web-1 和 web-2 上分别执行
 sudo apt install openjdk-11-jdk -y
 java -version
 ​
 # 下载并安装Tomcat(web-1 和 web-2 上分别执行)
 # 1. 进入 /opt 目录
 cd /opt
 sudo wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.120/bin/apache-tomcat-9.0.120.tar.gz
 sudo tar -xzf apache-tomcat-9.0.120.tar.gz
 sudo mv apache-tomcat-9.0.120 tomcat
 sudo chmod +x /opt/tomcat/bin/*.sh
 /opt/tomcat/bin/startup.sh
 ​
 # 配置Tomcat Session共享
 vim /opt/tomcat/conf/server.xml
 ​
 # 在 <Engine name="Catalina" defaultHost="localhost"> 标签内部(143h)
 # 添加以下 <Cluster> 配置
   <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
            channelSendOptions="6">
     <Manager className="org.apache.catalina.ha.session.DeltaManager"
              expireSessionsOnShutdown="false"
              notifyListenersOnReplication="true"/>
     <Channel className="org.apache.catalina.tribes.group.GroupChannel">
       <Membership className="org.apache.catalina.tribes.membership.McastService"
                   address="228.0.0.4"
                   port="45564"
                   frequency="500"
                   dropTime="3000"/>
       <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                 address="192.168.10.21"   # Web1 真实IP;Web2 改成 192.168.10.22
                 port="4000"
                 autoBind="100"
                 selectorTimeout="5000"
                 maxThreads="6"/>
       <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
         <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
       </Sender>
       <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
       <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
     </Channel>
     <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/>
     <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
     <!-- 注意:FarmWarDeployer 需放在 <Host> 下,放 <Cluster> 里会报 SEVERE,已移除 -->
     <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
   </Cluster>
 ​
 # 为应用启用Session复制支持
 vim /opt/tomcat/webapps/ROOT/WEB-INF/web.xml
 ​
 # 添加以下内容(大概在21行左右)
 <display-name>Welcome to Tomcat</display-name>
   <distributable/>      # 添加一行这个内容即可
   <description>
 ​
 # 启动Tomcat web-1 和 web-2 上分别执行(可能存在端口占用问题记得查看)
 # ⚠️ 重启 Tomcat 的正确方式(直接再跑 startup.sh 会因旧实例占端口报 BindException):
 #   先 ./shutdown.sh -> sleep 3 -> 再 ./startup.sh
 #   若 shutdown 停不掉:pkill -9 -f tomcat 后重新 ./startup.sh
 cd /opt/tomcat/bin
 ./startup.sh
 ​
 # 查看启动日志确认集群成员加入成功
 tail -f /opt/tomcat/logs/catalina.out

Nginx 配置步骤(web-1 和 web-2 都要操作)

 # 安装 Nginx
 sudo apt install nginx -y
 sudo systemctl start nginx
 sudo systemctl enable nginx
 ​
 # 配置 Nginx 反向代理
 vim /etc/nginx/sites-available/default
 ​
 # 替换为以下内容
 upstream tomcat_server {
     server 127.0.0.1:8080;
 }
 ​
 server {
     listen 80;
     server_name _;
 ​
     # 静态资源(由 Nginx 直接返回)
     location ~* \.(jpg|jpeg|png|gif|ico|css|js|html|htm)$ {
         root /opt/tomcat/webapps/ROOT;
         expires 7d;
     }
 ​
     # 动态请求(转发给本机 Tomcat)
     location / {
         proxy_pass http://tomcat_server;
         proxy_set_header Host $host;
         proxy_set_header X-Real-IP $remote_addr;
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
         proxy_set_header X-Forwarded-Proto $scheme;
     }
 }
 ​
 # 测试并重启 Nginx
 sudo nginx -t
 sudo systemctl restart nginx

配置 LVS + Keepalived

 # 在 LVS 调度器上安装软件(在 lvs-master 和 lvs-backup 上分别执行)
 sudo apt update
 sudo apt install ipvsadm keepalived -y
 ​
 # 创建 keepalived.conf
 sudo vim /etc/keepalived/keepalived.conf
 ​
 # 粘贴以下配置(lvs-master)
 ! Configuration File for keepalived
 ​
 global_defs {
     router_id LVS_MASTER
 }
 ​
 vrrp_instance VI_1 {
     state MASTER
     interface ens33
     virtual_router_id 51
     priority 100
     advert_int 1
     authentication {
         auth_type PASS
         auth_pass 1111
     }
     virtual_ipaddress {
         192.168.10.100/24 dev ens33
     }
 }
 ​
 virtual_server 192.168.10.100 80 {
     delay_loop 6
     lb_algo wrr
     lb_kind DR
     persistence_timeout 50
     # 备注:50=会话粘滞(同一客户端50秒内固定到同一台RS,属正常现象,不会轮询)
     # 想看严格轮询改成0:sed -i "s/persistence_timeout 50/persistence_timeout 0/" /etc/keepalived/keepalived.conf 后重启 keepalived
     protocol TCP
 ​
     real_server 192.168.10.21 80 {
         weight 1
         TCP_CHECK {
             connect_port 80
             connect_timeout 3
             nb_get_retry 3
             delay_before_retry 3
         }
     }
 ​
     real_server 192.168.10.22 80 {
         weight 1
         TCP_CHECK {
             connect_port 80
             connect_timeout 3
             nb_get_retry 3
             delay_before_retry 3
         }
     }
 }
 ​
 # 粘贴以下配置(lvs-backup)
 ! Configuration File for keepalived
 ​
 global_defs {
     router_id LVS_BACKUP
 }
 ​
 vrrp_instance VI_1 {
     state BACKUP
     interface ens33
     virtual_router_id 51
     priority 90
     advert_int 1
     authentication {
         auth_type PASS
         auth_pass 1111
     }
     virtual_ipaddress {
         192.168.10.100/24 dev ens33
     }
 }
 ​
 virtual_server 192.168.10.100 80 {
     delay_loop 6
     lb_algo wrr
     lb_kind DR
     persistence_timeout 50
     # 备注:50=会话粘滞(同一客户端50秒内固定到同一台RS,属正常现象,不会轮询)
     # 想看严格轮询改成0:sed -i "s/persistence_timeout 50/persistence_timeout 0/" /etc/keepalived/keepalived.conf 后重启 keepalived
     protocol TCP
 ​
     real_server 192.168.10.21 80 {
         weight 1
         TCP_CHECK {
             connect_port 80
             connect_timeout 3
             nb_get_retry 3
             delay_before_retry 3
         }
     }
 ​
     real_server 192.168.10.22 80 {
         weight 1
         TCP_CHECK {
             connect_port 80
             connect_timeout 3
             nb_get_retry 3
             delay_before_retry 3
         }
     }
 }
 ​
 # 配置 Web 服务器的 ARP 抑制 (在 web-1 和 web-2 上分别执行)且必须在启动 LVS 之前完成
 # 将 VIP 配置到回环接口的子接口
 sudo ip addr add 192.168.10.100/32 dev lo
 ​
 # 配置 ARP 抑制(临时生效)
 sudo sysctl -w net.ipv4.conf.lo.arp_ignore=1
 sudo sysctl -w net.ipv4.conf.lo.arp_announce=2
 sudo sysctl -w net.ipv4.conf.all.arp_ignore=1
 sudo sysctl -w net.ipv4.conf.all.arp_announce=2
 ​
 # 配置永久生效
 echo "net.ipv4.conf.lo.arp_ignore=1" | sudo tee -a /etc/sysctl.conf
 echo "net.ipv4.conf.lo.arp_announce=2" | sudo tee -a /etc/sysctl.conf
 echo "net.ipv4.conf.all.arp_ignore=1" | sudo tee -a /etc/sysctl.conf
 echo "net.ipv4.conf.all.arp_announce=2" | sudo tee -a /etc/sysctl.conf
 # DR 模式必须关闭 rp_filter(否则回环 VIP 的包被丢弃)
 echo "net.ipv4.conf.all.rp_filter=0" | sudo tee -a /etc/sysctl.conf
 echo "net.ipv4.conf.ens33.rp_filter=0" | sudo tee -a /etc/sysctl.conf
 sudo sysctl -p
 ​
 # 验证 VIP 配置
 ip addr show lo | grep 192.168.10.100
 ​
 # 启动 LVS + Keepalived
 # 在 lvs-master 上执行 先查看实际网卡名
 ip a
 ​
 # 启动 Keepalived
 sudo systemctl start keepalived
 sudo systemctl enable keepalived
 ​
 # 开启 IP 转发(LVS 必须,否则收包后无法转发)
 echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
 sudo sysctl -p
 # 查看 VIP 是否在主节点出现(替换 ens33 为实际网卡名)
 ip addr show ens33 | grep 192.168.10.100
 ​
 # 查看 LVS 转发规则
 sudo ipvsadm -Ln

整体功能验证

⚠️ 重要:DR 模式下,不要在 LVS 本机访问自己的 VIP(curl http://192.168.10.100 在 LVS 上会超时/SYN_RECV,这是 DR 模式的已知怪癖,不是故障)。验证必须从外部客户端(如 MySQL 主/从、或任意一台没有 VIP 的机器)访问。

① 从外部客户端访问 VIP

 # 在 MySQL 主库(192.168.10.31)或从库(192.168.10.32)上执行
 # 注意:不能在 LVS 本机测,不能在 Web 服务器上测(Web 的 lo 有 VIP 会打到本地)
 curl http://192.168.10.100        # 应返回 Tomcat 页面
 curl http://192.168.10.100        # 多次访问验证轮询

② 查看 LVS 分发情况(LVS 主上)

 sudo ipvsadm -Ln --stats   # 看两台 Web 的 Conns 是否都有增长
 sudo ipvsadm -Lnc          # 看连接分发到 .21 / .22

③ MySQL 主从同步验证(MySQL 从库上)

 sudo mysql -e "SHOW SLAVE STATUS\G" | grep -E "Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master"
 # 必须:Slave_IO_Running: Yes、Slave_SQL_Running: Yes、Seconds_Behind_Master: 0

④ keepalived 主备切换验证

 # 1. 停掉主节点 keepalived(模拟故障)
 sudo systemctl stop keepalived          # 在 lvs-master 上
 # 2. 备节点应自动接管 VIP
 ip addr show ens33 | grep 192.168.10.100   # 在 lvs-backup 上查看
 # 3. 外部客户端继续访问,应无感知
 curl http://192.168.10.100                 # 在 MySQL 主库上
 # 4. 恢复主节点,VIP 自动漂移回来(priority 100 > 90)
 sudo systemctl start keepalived         # 在 lvs-master 上

⑤ Nginx 静态资源权限检查

 # 若页面能打开但 CSS/图标 404,日志报 Permission denied,执行:
 sudo chmod -R o+r /opt/tomcat/webapps
 # 或
 sudo chown -R www-data:www-data /opt/tomcat/webapps/ROOT

常见问题排查(实测踩坑)

问题

原因

解决

LVS 本机 curl 自己 VIP 超时(SYN_RECV)

DR 模式本机访问 VIP 的已知怪癖

用外部客户端测试,不是故障

VIP 完全不通(所有客户端)

LVS 未开启 ip_forward

echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && sysctl -p

请求转发后 Web 不回包

Web 的 ens33 rp_filter 不是 0

sysctl -w net.ipv4.conf.ens33.rp_filter=0(并持久化)

Web 页面能开但 CSS/图标 404

nginx 用户读不了 tomcat 静态文件

chmod -R o+r /opt/tomcat/webapps

轮询看起来不均衡

wrr persistent 50 会话粘滞(同一客户端 50 秒固定一台)

不需要粘滞就去掉 persistence_timeout 50

主从不同步

从库未启动复制 / 账号错 / 防火墙

检查 SHOW SLAVE STATUS 的 IO/SQL 是否双 Yes

Tomcat 重启报 BindException(Address already in use)

旧 Tomcat 实例还占着 8080/8005

先 ./shutdown.sh,停不掉就 pkill -9 -f tomcat,再 ./startup.sh

Tomcat 日志刷 FarmWarDeployer SEVERE

<Deployer> 误放进 <Cluster>(应在 <Host> 下)

删除或移到 <Host> 下

Session 复制到不了另一台 Web

Receiver address="auto" 解析成 127.0.1.1

改为真实 IP:address="192.168.10.21"

关键注意事项

  1. 验证 VIP 一定要用外部客户端,不要在 LVS 本机或 Web 服务器上测(都有 VIP)

  2. LVS 必须 ip_forward=1,否则收包后无法转发(OutPkts=0)

  3. Web 必须 rp_filter=0(all 和 ens33 都要),否则 DR 转发包被丢弃

  4. Web 必须 ARP 抑制(arp_ignore=1 / arp_announce=2),且要持久化

  5. Nginx 静态资源目录权限要给 nginx 用户读权限

  6. keepalived 主备都 systemctl enable keepalived 开机自启

  7. wrr persistent 50 会粘滞会话,生产看需求保留或去掉

  8. MySQL 主库必须开 binlog + 建 repl 账号;从库 read_only=1