文章目录
- 前言
- [1 选购清单](#1 选购清单)
-
- [1.1 阿里云产品](#1.1 阿里云产品)
- [1.2 服务器网络规划](#1.2 服务器网络规划)
- [1.3 后续调整](#1.3 后续调整)
- [2 项目架构](#2 项目架构)
- [3 服务器基础环境检测和环境配置](#3 服务器基础环境检测和环境配置)
-
- [3.1 环境检测](#3.1 环境检测)
- [3.2 SSH配置](#3.2 SSH配置)
- [3.3 安全组规则](#3.3 安全组规则)
- [3.4 流量入口服务器防火墙规则](#3.4 流量入口服务器防火墙规则)
- [3.5 fail2ban配置](#3.5 fail2ban配置)
- [3.6 时间源](#3.6 时间源)
- [4 项目部署](#4 项目部署)
-
- [4.1 MySQL集群部署](#4.1 MySQL集群部署)
- [4.2 Redis集群部署](#4.2 Redis集群部署)
- [4.3 高可用部署](#4.3 高可用部署)
- [4.4 高性能部署](#4.4 高性能部署)
- [4.5 Discuz! X5部署](#4.5 Discuz! X5部署)
-
- [4.5.1 Discuz! X5安装遇到问题](#4.5.1 Discuz! X5安装遇到问题)
- [4.5.2 重新安装discuz](#4.5.2 重新安装discuz)
- [5 测试](#5 测试)
-
- [5.1 MHA测试](#5.1 MHA测试)
- [5.2 浏览器访问检测](#5.2 浏览器访问检测)
- [5.3 Redis测试](#5.3 Redis测试)
- [5.4 ab压测](#5.4 ab压测)
前言
1 选购清单
1.1 阿里云产品
| 产品 | 规格建议 | 数量 | 用途 |
|---|---|---|---|
| ECS实例 | 2核2G,rockylinux_9_4_x64_20G | 5台 | 业务服务器 |
| 弹性公网IP(EIP) | 按使用量计费(入1MB出2MB) | 1个 | 绑定弹性网卡 |
| 域名 | 看个人喜欢 | 1个 | 公网域名访问 |
| 云解析DNS | 免费版 | 1个 | 域名解析 |
| SSL证书 | 免费版DV证书 | 1个 | HTTPS加密 |
| CDN | 免费 | 1个 | 隐藏公网IP、加速静态资源 |
🔭费用:
CDN新用户免费30天,不过不建议使用,CDN+HTTPS抵用卷扣费超快,单用HTTPS抵用卷扣费也蛮快的。
阿里云的云工开物活动300抵用卷(学生专属)-->用于扣ECS等除按量计费产品。
实付:一台包年ECS(99)加四台按量付费ECS+域名(8)=107元
1.2 服务器网络规划
| 服务器 | 公网IP | 私网IP | 可用区 | 角色 |
|---|---|---|---|---|
| Server1 | 嘘 | 172.23.0.100 | 可用区A | 边缘网关主 |
| Server2 | 172.23.0.10 | 可用区A | 边缘网关备 | |
| Server3 | 172.23.0.20 | 可用区B | 应用节点A | |
| Server4 | 172.23.0.30 | 可用区B | 应用节点B | |
| Server5 | 172.23.0.40 | 可用区C | 数据层 |
1.3 后续调整
- 99包年云服务器的固定公网IP 升级成弹性公网EIP--->绑定到弹性网卡eth1上--->充当前端集群VIP(注:要把默认静态路由从eth0上改到eth1上)
- 手动创建阿里云虚拟VIP(HAVIP)给后端MySQL集群充当VIP(也要给当前master网卡配HAVIP的静态路由,不然私网集群是连接不达HAVIP的)
2 项目架构
2.1 整体架构

缓存流程:
否
是
缓存命中(99%请求)
缓存未命中(1%请求)
用户发起请求
Nginx 前端层
检测到登录用户 Cookie
(sid/saltkey 等)
检查 Nginx+Memcached 一级缓存
(缓存 key:uri+参数)
转发请求到 PHP-FPM 后端
直接返回缓存的完整页面响应
PHP 处理动态逻辑
读取/写入 Redis Session 二级缓存
(key:W8iX_2132_* 前缀)
必要时查询 MySQL 数据库
(如用户数据/帖子内容)
PHP 生成动态页面响应
Nginx 将响应回写到 Memcached 一级缓存
(设置过期时间 60s)
Nginx 向用户返回最终响应
项目说明
🔭项目目的
ECS云服务器:一堆真实的裸金属服务器集群集中在一起,为我们提供计算功能的一片"区域"就叫做云服务器,所有的都是虚拟出来的,不是真实摸得着的。
此项目不使用阿里云的负载均衡托管服务SLB、ALB、NLB(一键部署的),主要用于实践学习解决裸金属服务器的知识。
🔭说说阿里云的负载均衡技术:SLB(传统型负载均衡)、应用型负载均衡(ALB)、网络型负载均衡(NLB)
1 应用型负载均衡 (ALB)
这是目前最推荐的七层(HTTP/HTTPS)负载均衡方案,性能远超传统SLB。
- 核心优势 :超强弹性 。它基于NFV虚拟化平台,能根据流量自动伸缩,单实例最高支持100万QPS,非常适合应对秒杀、直播等突发流量场景。
- 适用场景:Web网站、移动App、微服务API网关、音视频直播(支持QUIC协议)。
2 网络型负载均衡 (NLB)
这是专门为**四层(TCP/UDP)**大流量设计的"性能怪兽"。
- 核心优势 :超高并发 。单实例最高支持1亿并发连接,且具备极低的网络延迟,非常适合物联网(IoT)、游戏、金融交易等对连接数要求极高的场景。
- 适用场景:物联网设备接入、游戏服务器、金融高频交易、混合云(支持挂载云下IDC服务器)。
3 传统型负载均衡 (CLB)
也就是提到的SLB,属于"经典款",目前主要适用于对性能要求不高的传统业务或存量系统迁移。
- 特点:基于物理机架构,性能上限相对较低(单实例约5万QPS),且需要手动选择规格,弹性能力不如ALB和NLB。
4 选择
- 做网站/App后端 :首选 ALB(性能最好,自动弹性)。
- 做游戏/物联网 :首选 NLB(并发最高,延迟最低)。
- 做数据库代理/简单转发 :可选 CLB(成本可能更低)。
❓MHA和mysql router+MGR怎么选?说明:这两个架构都是实现高可用的。
1 性能实现:
- MHA的性能实现 :主要靠读写分离。主库写、从库读,分摊压力 ;可配合半同步复制减少延迟 ;需要优化MySQL自身(如索引、Buffer Pool)。
- MGR的性能实现 :
- 单主模式:主库写、从库读,提供强一致性读(参数控制)。
- 多主模式 :所有节点均可写,消除单点瓶颈,提升并发 。但应用需解决冲突,且对网络要求极高 。
2 性能瓶颈与代价:
- MHA的主要瓶颈 :复制延迟影响读一致性 ;主库仍是写入单点;切换时可能丢数据且短暂不可用 。
- MGR的主要代价 :强一致性带来写放大 ,性能低于异步复制 ;对网络极其敏感 (延迟>50ms性能下降30%);多主下冲突检测 导致高并发写入性能下降 ;大事务可能阻塞集群 。
3 选择:
- 选 MHA :如果追求极致的写性能 ,能容忍秒级延迟和数据丢失风险,且主要是读写分离场景。架构简单,适合传统企业应用 。
- 选 MGR :如果数据一致性 是第一位(如金融、交易),或需要多主写入 提升并发,并且具备稳定低延迟网络。适合对一致性要求极高的核心系统 。
技术栈
keepalived+nginx高可用技术、redis集群技术、mysql集群技术、mysql-mha技术、nginx+memcache+srcache技术、php+redis session存储技术。
- nginx-1.28.1
- php-8.3.30
- mysql-8.3.0
- redis-6.2.20
- memcached-1.6.9
- masterha_manager-0.57
- keepalived-2.2.8
- discuz! X5
nginx模块
echo-nginx-module-0.64、memc-nginx-module-0.20、srcache-nginx-module-0.33
php支持discuz5的需要有的模块
| 模块 | 版本信息 | 备注 |
|---|---|---|
| redis | 6.0.2 | PECL扩展 |
| memcache | 8.2 | PECL扩展 |
| mysqli | mysqlnd 8.3.30 | 使用 MySQL Native Driver |
| pdo_mysql | mysqlnd 8.3.30 | 使用 MySQL Native Driver |
| gd | bundled (2.1.0 compatible) | 捆绑库,libPNG 1.6.37 |
| mbstring | libmbfl 1.3.2, oniguruma 6.9.6 | 依赖库版本 |
| zip | 1.22.3 (Libzip 1.10.1) | PECL/bundled 混合 |
| xml | libxml2 2.9.13 | 依赖系统库版本 |
| json | 无 | PHP 核心,版本 = PHP 8.3.30 |
| ctype | 无 | PHP 核心,版本 = PHP 8.3.30 |
| Zend OPcache | 用 zend opcache 查看 |
Zend 模块,版本 = PHP 8.3.30 |
2.2 流量入口架构
单Nginx 入口+多 PHP 后端
Server 1: 作为唯一对外入口(绑定EIP)部署Nginx层高速缓存。
Server 3、4: 不部署Nginx,专注于运行 MySQL (通过 HAVIP 提供高可用)、部署 PHP-FPM连接Redis 。
Server 5: 部署二级缓存Redis。
3 服务器基础环境检测和环境配置
3.1 环境检测
bash
# 实验环境监测
[root@server1 ~]# cat /proc/cmdline
BOOT_IMAGE=(hd0,gpt3)/boot/vmlinuz-5.14.0-427.42.1.el9_4.x86_64 root=UUID=c6377ee7-5b32-40a6-b5a8-ff9d8b68deb2 ro rhgb quiet selinux=0 net.ifnames=0 console=tty0 console=ttyS0,115200n8 crashkernel=0M-1G:0M,1G-4G:160M,4G-8G:192M,8G-128G:256M,128G-:512M crash_kexec_post_notifiers=1 nvme_core.io_timeout=4294967295 nvme_core.admin_timeout=4294967295 iommu=pt
# selinux=0 net.ifnames=0 --->selinux关着的,网卡命名是有规则的
# 服务器系统防火墙检测
[root@server1 ~]# systemctl status firewalld.service
○ firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; preset: enabled)
Active: inactive (dead)
Docs: man:firewalld(1)
[root@server1 ~]# rpm -q iptables
package iptables is not installed
3.2 SSH配置
bash
# 创建普通用户(看个人需要)
[root@server1 ~]# useradd -m xixi
[root@server1 ~]# passwd xixi
[root@server1 ~]# usermod -aG wheel xixi # 加入sudo组
# ssh配置
[root@server1 ~]# vim /etc/ssh/sshd_config
22 Port 新端口号
42 PermitRootLogin without-password
[root@server1 ~]# systemctl restart sshd
# 密钥登录,拒绝密码登入root,密钥采用Ed25519算法生成方式,避免服务器不支持旧的rsa算法。
# 生成本地windows密钥
# ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
# ssh-copy-id -i ~/.ssh/id_ed25519.pub root@服务器公网IP
# 测试密钥连接
# ssh -p 新端口号 -i id_windows_ed25519.pub root@服务器公网IP
# 成功连接就可以新增shell记录了
# 记得勾选mobaxtrem里面的SSH keepalive,这个设置会让MobaXterm定期向服务器发送一个极小的数据包,告诉服务器连接还在,别关掉,不然老是掉连接.
# 1 本地与服务器的密钥连接
[2026-03-11 15:44.02] ~
[Is XiaFeng Computer.IsXiaFengComputer] ⮞ cd ~/.ssh
[2026-03-11 15:44.09] ~/.ssh
[Is XiaFeng Computer.IsXiaFengComputer] ⮞ pwd
/home/mobaxterm/.ssh
[2026-03-11 15:44.11] ~/.ssh
[Is XiaFeng Computer.IsXiaFengComputer] ⮞ rm -rf *
[2026-03-11 15:44.39] ~/.ssh
[Is XiaFeng Computer.IsXiaFengComputer] ⮞ ssh-keygen -t ed25519 -f ~/.ssh/id_windows_ed25519
[2026-03-11 15:45.41] ~/.ssh
[Is XiaFeng Computer.IsXiaFengComputer] ⮞ ls
id_windows_ed25519 id_windows_ed25519.pub
# 测试密钥连接
# ssh -p 新端口号 -i id_windows_ed25519.pub root@服务器公网IP
[root@Router ~]# who
root pts/0 2026-03-11 15:19 (182.91.160.164)
root pts/1 2026-03-11 15:02 (182.91.160.164)
[root@Router ~]# pkill -9 -t pts/1
[root@Router ~]# who
root pts/0 2026-03-11 15:19 (182.91.160.164)
# 2 各服务器用户间免密连接
# 消除指纹yes用-o StrictHostKeyChecking=no
# 2.1 密钥生成
[root@server1 ~]# ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_root -C "all_root_password-free_login" -N ""
[root@server1 ~]# ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_mha -C "mha_specia" -N ""
# 各服务器账号相互免密
# 2.2 传公钥
# 传公钥到各root用户
[root@server1 ~]# for i in 172.23.0.{10,20,30,40};do
> ssh-copy-id -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519_root.pub root@$i
> done
# 传公钥给普通用户
[root@server1 ~]# for i in 172.23.0.{10,20,30,40};do
> ssh-copy-id -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519_root.pub mysql@$i
> done
# 2.3 传私钥
# root的密钥只传到root账号里面
[root@server1 ~]# for i in 172.23.0.{10,20,30,40};do
> scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ~/.ssh/id_ed25519_root root@$i:/root/.ssh/
> done
# mah的密钥传到普通用户和root用户里面
[root@server1 ~]# for i in 172.23.0.{10,20,30,40};do
> scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ~/.ssh/id_ed25519_mha root@$i:/root/.ssh/
> scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ~/.ssh/id_ed25519_mha mysql@$i:/home/xixi/.ssh/
> done
# 持久化配置,简化ssh免密连接,这样后面做项目很快(嫌乱的话每台server的每个用户家目录下的.ssh/config文件)
[root@server1 ~]# cat .ssh/config
Host server1
HostName 172.23.0.100
User root
Port server1新端口
IdentityFile ~/.ssh/id_ed25519_root
Host localhost
HostName 172.23.0.100
User root
Port server1新端口
IdentityFile ~/.ssh/id_ed25519_root
Host server2
HostName 172.23.0.10
User root
IdentityFile ~/.ssh/id_ed25519_root
Host server3
HostName 172.23.0.20
User root
IdentityFile ~/.ssh/id_ed25519_root
Host server4
HostName 172.23.0.30
User root
IdentityFile ~/.ssh/id_ed25519_root
Host server5
HostName 172.23.0.40
User root
IdentityFile ~/.ssh/id_ed25519_root
# MHA管理专用
Host server1-mha
HostName 172.23.0.100
User gxf
Port server1新端口
IdentityFile ~/.ssh/id_ed25519_mha
Host server2-mha
HostName 172.23.0.10
User gxf
IdentityFile ~/.ssh/id_ed25519_mha
Host server3-mha
HostName 172.23.0.20
User gxf
IdentityFile ~/.ssh/id_ed25519_mha
Host server4-mha
HostName 172.23.0.30
User gxf
IdentityFile ~/.ssh/id_ed25519_mha
Host server5-mha
HostName 172.23.0.40
User gxf
IdentityFile ~/.ssh/id_ed25519_mha
# 服务器私网域名解析
[root@server1 ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
172.23.0.100 server1
172.23.0.10 server2
172.23.0.20 server3
172.23.0.30 server4
172.23.0.40 server5
172.23.0.150 域名 www.域名
[root@server1 ~]# for i in {server2,server3,server4,server5};do
> scp /etc/hosts root@$i:/etc/hosts
> done
3.3 安全组规则
只开放WEB服务端口(80、443)和阿里云VNC需要的SSH端口(22)和自定义的SSH端口(本地ssh到服务器)
其实我想把阿里云交换机的源IP放行的22端口都给删了,但是怕服务器被我搞坏了,通过VNC不到服务器修复就惨了,我记得快照要收费,随时随地的自由拍快照也不行了,所以还是留着这个22端口吧。
3.4 流量入口服务器防火墙规则
bash
# 服务器防火墙设置
# 1.编辑公共区域的配置文件
[root@Router ~]# vim /etc/firewalld/zones/public.xml
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Public</short>
<description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
<service name="dhcpv6-client"/>
<service name="cockpit"/>
<port port="ssh新端口号" protocol="tcp"/>
<port port="80" protocol="tcp"/>
<port port="443" protocol="tcp"/>
<port port="123" protocol="udp"/>
<forward/> # udp123端口是NTP服务使用的端口,后续要配置时间同步,这台做时间源主
</zone>
[root@Router ~]# systemctl enable --now firewalld
Created symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service → /usr/lib/systemd/system/firewalld.service.
Created symlink /etc/systemd/system/multi-user.target.wants/firewalld.service → /usr/lib/systemd/system/firewalld.service.
[root@server1 ~]# firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0 eth1
sources:
services: cockpit dhcpv6-client
ports: 新ssh端口/tcp 80/tcp 443/tcp 123/udp
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
# 2.直接开启防火墙,
# 使用命令放行端口、服务如下:
# 因为之前改了ssh端口,所以会断掉当前mobaxtrem运程shell,在阿里云云服务管理控制台中通过VNC远程连接执行下面命令:
[root@Router ~]# firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: cockpit dhcpv6-client ssh
ports:
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
[root@Router ~]# firewall-cmd --permanent --remove-service=ssh
[root@Router ~]# firewall-cmd --permanent --add-port=ssh的新端口/tcp
[root@Router ~]# firewall-cmd --permanent --add-port=80/tcp
[root@Router ~]# firewall-cmd --permanent --add-port=443/tcp
[root@Router ~]# firewall-cmd --permanent --add-port=3306/tcp
[root@Router ~]# firewall-cmd --reload
# 3 MHA更改服务器配置--富规则
[root@server1 ~]# firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.23.0.10" port protocol="tcp" port="22" accept'
[root@server1 ~]# firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.23.0.20" port protocol="tcp" port="22" accept'
[root@server1 ~]# firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.23.0.30" port protocol="tcp" port="22" accept'
[root@server1 ~]# firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.23.0.40" port protocol="tcp" port="22" accept'
[root@server1 ~]# firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.23.0.10" port protocol="tcp" port="3306" accept'
[root@server1 ~]# firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.23.0.20" port protocol="tcp" port="3306" accept'
[root@server1 ~]# firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.23.0.40" port protocol="tcp" port="3306" accept'
[root@server1 ~]# firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="172.23.0.30" port protocol="tcp" port="3306" accept'
[root@server1 ~]# firewall-cmd --reload
[root@server1 ~]# firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0 eth1
sources:
services: cockpit dhcpv6-client
ports: 新ssh端口/tcp 80/tcp 443/tcp 123/udp
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
rule family="ipv4" source address="172.23.0.10" port port="3306" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.10" port port="22" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.40" port port="22" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.20" port port="3306" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.30" port port="3306" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.30" port port="22" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.40" port port="3306" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.20" port port="22" protocol="tcp" accept
[root@server1 ~]# firewall-cmd --list-rich-rules
rule family="ipv4" source address="172.23.0.10" port port="3306" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.10" port port="22" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.40" port port="22" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.20" port port="3306" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.30" port port="3306" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.30" port port="22" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.40" port port="3306" protocol="tcp" accept
rule family="ipv4" source address="172.23.0.20" port port="22" protocol="tcp" accept
3.5 fail2ban配置
fail2ban主要作用:当一个IP一直尝试使用密码连接,但是密码错误,fail2ban会直接拉黑这个IP,可以有效的阻止暴力破解密码.
还可以解封IP或监控其他服务(如Nginx、FTP)
bash
[root@Router ~]# dnf install -yq fail2ban fail2ban-systemd
[root@Router ~]# systemctl enable --now fail2ban
Created symlink /etc/systemd/system/multi-user.target.wants/fail2ban.service → /usr/lib/systemd/system/fail2ban.service.
[root@Router ~]# systemctl status fail2ban | grep -i active
Active: active (running) since Sat 2026-03-07 17:48:48 CST; 45s ago
[root@Router ~]# cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
[root@Router ~]# vim /etc/fail2ban/jail.local
274 [sshd]
275 enabled = true
276 port = ssh
277 filter = sshd
278 logpath = /var/log/secure
279 maxretry = 3
280 bantime = 1h
281 findtime = 10m
282
283 # To use more aggressive sshd modes set filter parameter "mode" in jail.local:
284 # normal (default), ddos, extra or aggressive (combines all).
285 # See "tests/files/logs/sshd" or "filter.d/sshd.conf" for usage example and details.
286 #mode = normal
287 #port = ssh
288 #logpath = %(sshd_log)s
289 backend = %(sshd_backend)s
# enabled = true: 开启对SSH的保护。
# logpath = /var/log/secure: 这是RHEL/CentOS系列系统记录SSH登录日志的路径,非常重要。
# maxretry = 3: 允许的最大失败次数。
# findtime = 10m: 在这个时间窗口内(例如10分钟)进行失败次数统计。
# bantime = 1h: 封禁IP的时长(可以写1h、3600秒或1d等)。
[root@Router ~]# fail2ban-client reload
OK
[root@Router ~]# fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd + _COMM=sshd-session
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
3.6 时间源
bash
# 主
[root@server1 ~]# vim /etc/chrony.conf
:8,19s /^/#/ # 留两个就好,不用这么多
6 server ntp.cloud.aliyuncs.com minpoll 4 maxpoll 10 iburst
7 server ntp.aliyun.com minpoll 4 maxpoll 10 iburst
36 allow 172.23.0.0/16
[root@server1 ~]# systemctl is-active chronyd
active
[root@server1 ~]# netstat -lntupa | grep 123
udp 0 0 0.0.0.0:123 0.0.0.0:* 7841/chronyd
[root@server1 ~]# systemctl restart chronyd
[root@server1 ~]# systemctl enable chronyd
# 从,其它server都要搞
[root@server1 ~]# ssh server2
[root@server2 ~]# nc -zuv 172.23.0.100 123 # 测试端口连通性
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Connected to 172.23.0.100:123.
Ncat: UDP packet sent successfully
Ncat: 1 bytes sent, 0 bytes received in 2.02 seconds.
[root@server2 ~]# vim /etc/chrony.conf
server 172.23.0.100 iburst
..................
[root@server2 ~]# chronyc tracking | grep -i system
System time : 0.000085531 seconds slow of NTP time# 两台主机间的偏差值,比NTP时间快0.000085
[root@server2 ~]# chronyc sources | grep ^\^
^* server1 2 6 377 8 +98us[ +413us] +/- 13ms
# scp传给其它server
[root@server2 ~]# for i in {server3,server4,server5};do scp /etc/chrony.conf root@$i:/etc/chrony.conf; done
chrony.conf 100% 1489 5.4MB/s 00:00
chrony.conf 100% 1489 5.2MB/s 00:00
chrony.conf 100% 1489 5.1MB/s 00:00
# 重启、开机自启动、查看是否同步成功,重启的话等几秒再看同步,需要时间同步
4 项目部署
两个思路
- 思路1:实力NB的人直接按照架构进行-->数据层部署(MySQL-MHA+Redis-集群+Redis哨兵集群)-->接入层部署(nginx+keepalive、nginx高速缓存架构、php+redis)-->应用层部署(PHP+discuz)
- 思路2:先实现高性能server1、3、5部署,再实现高可用直接scp目录过去改个文件所属用户和用户组就好。(简单高效、不容易乱)
4.1 MySQL集群部署
有点忙,有空再上传整理后的学习笔记。
4.2 Redis集群部署
有点忙,有空再上传整理后的学习笔记。
4.3 高可用部署
云服务器和裸金属服务器高可用部署有点差异
前端高可用:VIP-->弹性公网EIP绑定弹性网卡eth1充当VIP,故障时直接弹性网卡漂移
后端高可用:VIP-->直接为MHA创建HAVIP
生效新增VIP
bash
# 1 关闭反向路径过滤(rp_filter)所有-->server
[root@server1 ~]# tail -n4 /etc/sysctl.conf
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.eth0.rp_filter = 0
net.ipv4.conf.eth1.rp_filter = 0
[root@server1 ~]# sysctl -p
vm.swappiness = 0
kernel.sysrq = 1
net.ipv4.neigh.default.gc_stale_time = 120
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.default.arp_announce = 2
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.eth0.rp_filter = 0
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 1024
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.eth0.rp_filter = 0
net.ipv4.conf.eth1.rp_filter = 0
# 2 弹性网卡充当VIP连通性问题解决
# 创建自定义路由表
[root@server1 ~]# echo "100 eth1_table" >> /etc/iproute2/rt_tables
# 临时给eth1_table添加路由规则
[root@server1 ~]# ip route add 172.23.0.0/20 dev eth1 src 172.23.0.150 table eth1_table
[root@server1 ~]# ip route add default via 172.23.0.1 dev eth1 src 172.23.0.150 table eth1_table
# 添加策略路由规则(匹配源IP走对应表)
[root@server1 ~]# ip rule add from 172.23.0.150 table eth1_table
# 把路由策略写进启动脚本
[root@server1 ~]# tail -n3 /etc/rc.local
ip route add 172.23.0.0/20 dev eth1 src 172.23.0.150 table 100
ip route add default via 172.23.0.1 dev eth1 src 172.23.0.150 table 100
ip rule add from 172.23.0.150 table 100
[root@server1 ~]# chmod +x /etc/rc.d/rc.local
# 现在可用ping通172.23.0.150了,但是又出了给新问题,出公网优先使用eth0上的私网IP
# 把默认路由加到eth1弹性网卡上
[root@server1 ~]# ip route del default
[root@server1 ~]# ip route add default via 172.23.15.253 dev eth1
# 永久生效
# 在/etc/sysconfig/network-scripts/ifcfg-eth0文件上追加DEFROUTE=no就好,确保/etc/sysconfig/network-scripts/ifcfg-eth1文件中DEFROUTE=no,然后重启网络(systemctl restart NetworkManager)。但是不建议,可以在keepalived故障转移脚本中加上临时解决方法即可。
# 3 直接创建HAVIP充当VIP连通性问题解决
# 创建HAVIP绑定到ECS实例后连通性是还没好的,还要在ECS实例操作系统中将其添加为网卡的辅助私网IP,否则系统不会响应针对该IP的流量。
[root@server1 ~]# ip addr add 172.23.0.200/20 dev eth0
# 同样是临时生效的,直接写在MHA故障转移脚本就好。
RAM子用户权限
给故障转移脚本添加阿里云使用角色,使得弹性网卡和HAVIP可以正常漂移。
进入阿里云RAM控制台-->创建RAM访问子用户

创建用户后给它权限,如下:
- 管理ECS弹性网卡的权限
- 管理云服务器服务(ECS)的权限
- 使用ECS-Workbench的完整权限
- 管理ECS Extensions服务的权限
- 管理ECS云助手服务的权限
- 管理专有网络(VPC)的权限
配置CLI格式
要执行VIP漂移的主机都要配置
bash
# 执行配置命令
aliyun configure
# 按提示输入accessKeyId和accessKeySecret是创建RAM角色时看到的,因为只在创建那时出现一次,建议下载下来,DefaultRegionId是地域ID一般和实例在一个地域,看个人操作。
Configuring profile 'default' in 'AK' authenticate mode...
Access Key Id []: LTAI5txxxxxxxxxxxxxxxxx(AccessKey ID)
Access Key Secret []: 8FZxxxxxxxxxxxxxxxxxxxxxxxxxx(AccessKey Secret)
Default Region Id []: cn-beijing(地域ID)
# 配置完成后,验证是否生效
aliyun ecs DescribeNetworkInterfaces # 能查到弹性网卡信息即配置成功
[root@server1 ~]# cd CLI/
[root@server1 CLI]# curl -O https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-amd64.tgz
[root@server1 CLI]# tar -xzf aliyun-cli-linux-latest-amd64.tgz
[root@server1 CLI]# mv aliyun /usr/local/bin/
# 配置凭证(根据提示输入AccessKey ID/Secret,区域设为cn-beijing等)
aliyun configure
# 试了两次才得这个效果,不知道为什么,生效就得了。
Configure Done!!!
..............888888888888888888888 ........=8888888888888888888D=..............
...........88888888888888888888888 ..........D8888888888888888888888I...........
.........,8888888888888ZI: ...........................=Z88D8888888888D..........
.........+88888888 ..........................................88888888D..........
.........+88888888 .......Welcome to use Alibaba Cloud.......O8888888D..........
.........+88888888 ............. ************* ..............O8888888D..........
.........+88888888 .... Command Line Interface(Reloaded) ....O8888888D..........
.........+88888888...........................................88888888D..........
..........D888888888888DO+. ..........................?ND888888888888D..........
...........O8888888888888888888888...........D8888888888888888888888=...........
............ .:D8888888888888888888.........78888888888888888888O ..............
4.4 高性能部署
4.5 Discuz! X5部署
discuz3.5 本身部署时和我现在的技术环境有点bug,建议直接部署discuz5或者已经部署了3.5可以升级到5
其中之一bug如下图,改为一个又蹦出一个,着实烦了,直接升级到5了:

4.5.1 Discuz! X5安装遇到问题
直接按照现在环境部署时遇到的问题--->卡住了,不自动跳到安装完成页面了

浏览器直接访问index.php页面出现以下错误
discuz获取客户端IP失败

问题分析:
这个
(1054) Unknown column '0x' in 'where clause'错误,本质是 Discuz! 在获取客户端 IP 时失败,导致生成了不完整的 SQL 语句:
- 代码里
lowerip <= 0x AND upperip >= 0x中的0x本应是你的 IP 地址转成的十六进制值(比如0x7F000001),但因为 IP 获取失败,变量为空,直接拼接成了0x,被 MySQL 误判为列名。- 触发点在
source/class/class_ip.php的check_banned()函数,以及source/class/table/table_common_banned.php。具体来说,是 Discuz 在获取客户端 IP 并转换为十六进制时,在某些环境下(特别是 PHP 8.3)返回了空值或格式错误,导致拼出的 SQL 语句变成了 0x 后面没数字。
具体代码比较片段
php
// 总思路:给IP参数加 "全链路的合法性校验"
// 当$ip为空或格式错误时,ip_to_hex_str返回空字符串"",然后代码拼接成'0x'.$iphex就变成了'0x',导致 SQL语法错误。
// 1 checkaccess函数无空值校验,增加IP合法性校验
// 原生checkbanned函数(无校验)
public static function checkbanned($ip) {
global $_G;
if(array_key_exists('security', $_G['config']) && array_key_exists('useipban', $_G['config']['security']) && $_G['config']['security']['useipban'] == 0) {
return false;
}
// ... 后续逻辑
}
// 修改后check_banned函数(新增双重校验)
public function check_banned($time_to_check, $ip) {
// 新增第一层:IP空值+格式校验
if(empty($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) {
return false;
}
$iphex = ip::ip_to_hex_str($ip);
// 新增第二层:IP转16进制结果校验
if(empty($iphex)) {
return false;
}
$banned = true;
if($this->_allowmem) $banned = memory('zscore', 'index', $iphex, 0, $this->_pre_cache_key);
// ... 后续SQL逻辑
}
整个class_ip.php文件
bash
[root@server1 ~]# vim /data/www/discuz/source/class/class_ip.php
php
<?php
/**
* [Discuz!] (C)2001-2099 Discuz! Team
* This is NOT a freeware, use is subject to license terms
* https://license.discuz.vip
*/
if(!defined('IN_DISCUZ')) {
exit('Access Denied');
}
class ip {
function __construct() {
}
/*
* 将IPv6地址外面加方括号,用于显示
*/
public static function to_display($ip) {
if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
return '['.$ip.']';
}
return $ip;
}
/*
* 将各种显示格式的IPv6地址处理回标准IPv6格式
* [::1] -> ::1
* [::1]/16 -> ::1/16
*/
public static function to_ip($ip) {
if(strlen($ip) == 0) return $ip;
if(preg_match('/(.*?)\[((.*?:)+.*)\](.*)/', $ip, $m)) { // [xx:xx:xx]格式
if(filter_var($m[2], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$ip = $m[1].$m[2].$m[4];
}
}
return $ip;
}
/*
* 验证IP是否合法,支持v4和v6
*/
public static function validate_ip($ip) {
return filter_var($ip, FILTER_VALIDATE_IP) !== false;
}
/*
* 验证是否是合法的CIDR:
* - 包含 /
* - / 后面大于0
* - / 前面是合法的IP
* 返回值:
* - TRUE,表示是合法的CIDR,$new_str为处理过的CIDR(IP部分调用了to_ip)
* - FALSE, 不是合法的CIDR
*/
public static function validate_cidr($str, &$new_str) {
if(str_contains($str, '/')) {
[$newip, $mask] = explode('/', $str);
if($mask <= 0) {
return FALSE;
}
$newmask = intval($mask);
$newip = self::to_ip($newip);
if(!self::validate_ip($newip)) {
return FALSE;
}
if($newmask > 128 || ($newmask > 32 && !str_contains($newip, ':'))) {
return FALSE;
}
$new_str = $newip.'/'.$mask;
return TRUE;
}
return FALSE;
}
/*
* 给一个ipv4或v6的cidr,计算最小IP和最大IP
* 如果输入的是一个IP,那最大最小IP都等于其自身
* $as_hex = true
* 返回值为 二进制表达的字符串格式
* $as_hex = false
* 返回值可用inet_ntop轮换为IP字符串表达式
*/
public static function calc_cidr_range($str, $as_hex = false) {
if(self::validate_cidr($str, $str)) {
[$ip, $prefix] = explode('/', $str);
} elseif(filter_var($str, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$ip = $str;
$prefix = 32;
} elseif(filter_var($str, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$ip = $str;
$prefix = 128;
} else {
return FALSE;
}
$ip_bytes = unpack('C*', inet_pton($ip));
$total_bytes = count($ip_bytes);
$num_diff_bits = 8 * $total_bytes - $prefix;
if($num_diff_bits >= 0) {
$num_same_bytes = $prefix >> 3;
$same_bytes = array_slice($ip_bytes, 0, $num_same_bytes);
$diff_bytes_start = ($total_bytes === $num_same_bytes) ? [] : array_fill(0, $total_bytes - $num_same_bytes, 0);
$diff_bytes_end = ($total_bytes === $num_same_bytes) ? [] : array_fill(0, $total_bytes - $n um_same_bytes, 255);
$start_same_bits = $prefix % 8;
if($start_same_bits !== 0) {
$vary_byte = $ip_bytes[$num_same_bytes];
$diff_bytes_start[0] = $vary_byte & bindec(str_pad(str_repeat('1', $start_same_bits ), 8, '0', STR_PAD_RIGHT));
$diff_bytes_end[0] = $diff_bytes_start[0] + bindec(str_repeat('1', 8 - $start_same_ bits));
}
$start_array = array_merge($same_bytes, $diff_bytes_start);
$end_array = array_merge($same_bytes, $diff_bytes_end);
if($as_hex) {
if($total_bytes < 16) {
$start_array = array_merge(array_fill(0, 16 - $total_bytes, 0), $start_arra y);
$end_array = array_merge(array_fill(0, 16 - $total_bytes, 0), $end_array);
}
$start = unpack('H*hex', join(array_map('chr', $start_array)))['hex'];
$end = unpack('H*hex', join(array_map('chr', $end_array)))['hex'];
return [$start, $end];
} else {
$start = call_user_func_array('pack', array_merge(['C*'], $start_array));
$end = call_user_func_array('pack', array_merge(['C*'], $end_array));
return [$start, $end];
}
}
return FALSE;
}
/*
* 将一个IP地址转为16进制表达的字符串
*/
public static function ip_to_hex_str($ip) {
if(!self::validate_ip($ip)) {
return false;
}
$ip_bytes = unpack('C*', inet_pton($ip));
$total_bytes = count($ip_bytes);
if($total_bytes < 16) {
$ip_bytes = array_merge(array_fill(0, 16 - $total_bytes, 0), $ip_bytes);
}
return unpack('H*hex', join(array_map('chr', $ip_bytes)))['hex'];
}
/*
* 以下三个函数,检查$requestIp是否在$ip给出的cidr范围内
*/
public static function check_ip($requestIp, $ips) {
// 新增:检查请求IP是否为空或不合法,直接返回false
if(empty($requestIp) || !self::validate_ip($requestIp)) {
return false;
}
if(!\is_array($ips)) {
$ips = [$ips];
}
$method = substr_count($requestIp, ':') > 1 ? 'check_ip6' : 'check_ip4';
foreach($ips as $ip) {
if(self::$method($requestIp, $ip)) {
return true;
}
}
return false;
}
public static function check_ip6($requestIp, $ip) {
// 新增:检查IP参数合法性
if(empty($requestIp) || empty($ip) || !self::validate_ip($requestIp)) {
return false;
}
if(str_contains($ip, '/')) {
[$address, $netmask] = explode('/', $ip, 2);
if('0' === $netmask) {
return (bool)unpack('n*', @inet_pton($address));
}
if($netmask < 1 || $netmask > 128) {
return false;
}
} else {
$address = $ip;
$netmask = 128;
}
$bytesAddr = unpack('n*', @inet_pton($address));
$bytesTest = unpack('n*', @inet_pton($requestIp));
if(!$bytesAddr || !$bytesTest) {
return false;
}
for($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) {
$left = $netmask - 16 * ($i - 1);
$left = ($left <= 16) ? $left : 16;
$mask = ~(0xffff >> $left) & 0xffff;
if(($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) {
return false;
}
}
return true;
}
public static function check_ip4($requestIp, $ip) {
// 新增:检查IP参数合法性
if(empty($requestIp) || empty($ip) || !self::validate_ip($requestIp)) {
return false;
}
if(str_contains($ip, '/')) {
[$address, $netmask] = explode('/', $ip, 2);
if('0' === $netmask) {
return false;
}
if($netmask < 0 || $netmask > 32) {
return false;
}
} else {
$address = $ip;
$netmask = 32;
}
if(false === ip2long($address)) {
return false;
}
return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($addres s)), 0, $netmask);
}
/*
* 将IP转为位置,支持传入CIDR
*/
public static function convert($ip, $simple = false) {
$return = '';
require childfile('ip', 'global/core');
return $return;
}
public static function checkbanned($ip) {
global $_G;
// 核心修复:先检查IP是否为空或不合法,直接返回false,避免执行错误SQL
if(empty($ip) || !self::validate_ip($ip)) {
return false;
}
if(array_key_exists('security', $_G['config']) && array_key_exists('useipban', $_G['config']['secur ity']) && $_G['config']['security']['useipban'] == 0) {
return false;
}
if($_G['setting']['ipaccess'] && !self::checkaccess($ip, $_G['setting']['ipaccess'])) {
return true;
}
return table_common_banned::t()->check_banned(TIMESTAMP, $ip);
}
// 补充:修复checkaccess函数可能的空值问题
public static function checkaccess($ip, $accesslist) {
if(empty($ip) || empty($accesslist)) {
return false;
}
return preg_match('/^('.str_replace(["\r\n", ' '], ['|', ''], preg_quote($accesslist, '/')).')/', $ ip);
}
}
PHP8.3的严格类型检查问题

问题分析:
- 这个错误
implode(): Argument #1 ($array) must be of type array, string given是PHP 8.3 的严格类型检查导致的。- 在旧版本PHP中,implode() 函数允许参数顺序颠倒(先传字符串分隔符,再传数组),或者当第二个参数不是数组时不会报错。但在 PHP 8.3 中,这会导致致命错误。
$_G['setting']['domain']['app']在某些情况下(比如刚安装完还没配置域名,或者配置为空)可能是一个字符串或者空值,而不是数组。PHP 8.3 的 implode 函数严格要求第二个参数必须是数组。
具体代码修改片段:
bash
[root@server1 ~]# vim /data/www/discuz/source/function/function_core.php
:1467
php
// 当还没配置域名,或者配置为空时也强制转换成数组
// $havedomain = implode('', $_G['setting']['domain']['app']);
$havedomain = implode('', (array)$_G['setting']['domain']['app']);
4.5.2 重新安装discuz
由于没安装成功,只产生一些缓存和
config_global.php、config_ucenter.php文件,删掉这些文件后再刷新install页面就可以了,如果觉得不干净可以备份数据库后重建数据库。
手动清理产生缓存、配置文件
bash
[root@server3 ~]# cd /data/www/discuz
[root@server3 ~]# rm -rf data/template/*
[root@server3 ~]# rm -rf data/cache/*
[root@server3 ~]# rm -f /data/www/discuz/config/config_global.php
[root@server3 ~]# rm -f /data/www/discuz/config/config_ucenter.php
[root@server3 ~]# systemctl restart php-fpm.service
备份数据库
bash
[root@server3 ~]# mysqldump -uroot -p --default-character-set=utf8mb4 --single-transaction discuz > /data/backup/discuz_bak_for_update_discuz5_$(date +%Y%m%d).sql
重建数据库
sql
drop database if exists discuz;
create database discuz default character set utf8mb4 collate utf8mb4_unicode_ci;
show databases;
安装成功后
反正我的环境安装成功了。
虽然php代码应该放到后端服务器,但是这源码文件这么多,在往前端服务器传静态资源时总会少一些静态资源导致前端页面渲染失败,所以我建议直接把整个源码目录都传到前端服务器这样保证不会出现渲染失败情况。
bash
[root@server3 ~]# for i in {server2,server4};do
> scp -r /data/www/discuz root@$i:/data/www/
> ssh root@$i "chown -R nginx:nginx /data/www"
> done
[root@server3 ~]# for i in {server2,server4};do scp -r /data/www/discuz root@$i:/data/www/; ssh root@$i "chown -R nginx:nginx /data/www"; done
往新数据库导数据
bash
[root@server3 ~]# mysql -u root -p discuz < /data/backup/discuz_bak_for_update_discuz5_20260315.sql

5 测试
5.1 MHA测试
bash
# 复制状态
[root@server4 ~]# mysql -p -e "show replica status\G"
*************************** 1. row ***************************
Replica_IO_State: Waiting for source to send event
Source_Host: 172.23.0.20
Source_User: repl
Source_Port: 3306
Connect_Retry: 10
Source_Log_File: mysql-bin.000003
Read_Source_Log_Pos: 198
Relay_Log_File: relay-bin.000006
Relay_Log_Pos: 375
Relay_Source_Log_File: mysql-bin.000003
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
........................
# GTID
[root@server4 ~]# mysql -p -e "show global variables like '%gtid%'\G"
*************************** 1. row ***************************
Variable_name: binlog_gtid_simple_recovery
Value: ON
*************************** 2. row ***************************
Variable_name: enforce_gtid_consistency
Value: ON
*************************** 3. row ***************************
Variable_name: gtid_executed
Value: 56081ae8-1afa-11f1-ba99-00163e4b966a:1-13,
cee3396d-1b0b-11f1-8806-00163e4bd82f:1-3
*************************** 4. row ***************************
Variable_name: gtid_executed_compression_period
Value: 0
*************************** 5. row ***************************
Variable_name: gtid_mode
Value: ON
*************************** 6. row ***************************
Variable_name: gtid_owned
Value:
*************************** 7. row ***************************
Variable_name: gtid_purged
Value:
*************************** 8. row ***************************
Variable_name: session_track_gtids
Value: OFF
*************************** 1. row ***************************
File: mysql-bin.000004
Position: 238
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set: 56081ae8-1afa-11f1-ba99-00163e4b966a:1-13,
cee3396d-1b0b-11f1-8806-00163e4bd82f:1-3
# binlog
[root@server4 ~]# mysql -p -e "show binary logs;"
+------------------+-----------+-----------+
| Log_name | File_size | Encrypted |
+------------------+-----------+-----------+
| mysql-bin.000001 | 181 | No |
| mysql-bin.000002 | 849 | No |
| mysql-bin.000003 | 3630 | No |
| mysql-bin.000004 | 238 | No |
+------------------+-----------+-----------+
# MHA检测
[gxf@server2 ~]$ masterha_check_ssh --conf=/etc/mha/app1.cnf
Mon Mar 9 20:30:23 2026 - [info] All SSH connection tests passed successfully.
[gxf@server2 ~]$ masterha_check_repl --conf=/etc/mha/app1.cnf
MySQL Replication Health is OK.
# 在线切换
[root@server2 ~]# masterha_master_switch --conf=/etc/mha/app1.cnf --master_state=alive --new_master_host=172.23.0.40 --new_master_port=3306 --orig_master_is_new_slave --running_updates_limit=10000
..................
Mon Mar 9 23:58:07 2026 - [info] Switching master to 172.23.0.40(172.23.0.40:3306) completed successfully.
# 后台自动MHA检测
[root@server2 ~]# nohup masterha_manager --conf=/etc/mha/app1.cnf --remove_dead_master_conf --ignore_last_failover < /dev/null > /var/log/mha/manager.log 2>&1 &
[1] 96047
[root@server2 ~]# masterha_check_status --conf=/etc/mha/app1.cnf
app1 (pid:96047) is running(0:PING_OK), master:172.23.0.20
5.2 浏览器访问检测
bash
# 任意一台nginx服务器(Server1或Server2)
[root@server1 ~]# > /usr/local/nginx/logs/access.log
[root@server1 ~]# tail -n1 /usr/local/nginx/logs/access.log
[18/Mar/2026:19:39:55 +0800] "GET /misc.php?mod=mobile HTTP/2.0" 403 0 "-" "Mozilla/5.0 (compatible; AhrefsBot/7.0; +http://ahrefs.com/robot/)" "-" cache_status="BYPASS" rt=0.001
5.3 Redis测试
bash
# 没什么好测的,设置了优先级,只要master故障恢复了,redis哨兵会从新选择40为master
# 任意一台主机都可以
[root@server3 ~]# redis-cli -p 26379 sentinel master mymaster
1) "name"
2) "mymaster"
3) "ip"
4) "172.23.0.40"
5) "port"
6) "6379"
........................
[root@server3 ~]# redis-cli -h 172.23.0.40 KEYS "*session*"
1) "InjZmo_common_session_fetch_member_3rd_eval_sha"
2) "InjZmo_common_session_M3D3RZ"
3) "InjZmo_common_session_idx_invisible_0"
4) "InjZmo_common_session_idx_lastactivity"
5) "InjZmo_common_session_idx_fid_0"
6) "InjZmo_common_session_idx_ip_运营商公网IP"
7) "InjZmo_common_session_delete_by_session_eval_sha"
8) "InjZmo_common_session_idx_uid_group_1"
9) "InjZmo_common_session_idx_uid_1"
[root@server3 ~]# redis-cli -h 172.23.0.40 INFO keyspace
# Keyspace
db1:keys=76,expires=8,avg_ttl=2274133065
5.4 ab压测
bash
[root@server1 ~]# ab -n 20000 -c 100 -k -l https://172.23.0.150/forum.php This is ApacheBench, Version 2.3 <$Revision: 1913912 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 172.23.0.150 (be patient)
Completed 2000 requests
Completed 4000 requests
Completed 6000 requests
Completed 8000 requests
Completed 10000 requests
Completed 12000 requests
Completed 14000 requests
Completed 16000 requests
Completed 18000 requests
Completed 20000 requests
Finished 20000 requests
Server Software: nginx
Server Hostname: 172.23.0.150
Server Port: 443
SSL/TLS Protocol: TLSv1.3,TLS_AES_256_GCM_SHA384,2048,256
Server Temp Key: X25519 253 bits
Document Path: /forum.php
Document Length: Variable
Concurrency Level: 100
Time taken for tests: 2.677 seconds
Complete requests: 20000
Failed requests: 0
Keep-Alive requests: 19864
Total transferred: 268339320 bytes
HTML transferred: 264240000 bytes
Requests per second: 7471.67 [#/sec] (mean)
Time per request: 13.384 [ms] (mean)
Time per request: 0.134 [ms] (mean, across all concurrent requests)
Transfer rate: 97897.63 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 7.7 0 158
Processing: 0 13 10.0 11 151
Waiting: 0 13 9.9 11 151
Total: 0 13 15.1 11 180
Percentage of the requests served within a certain time (ms)
50% 11
66% 12
75% 12
80% 12
90% 15
95% 20
98% 45
99% 72
100% 180 (longest request)

