Hyperf连接池填VIP不可行,因其不感知主库状态、不自动重连或重试,VIP漂移后旧连接仍指向宕机节点,易引发复制延迟放大、主键冲突、连接卡死;须配合MySQL层健康检测、调优连接池参数(如max_idle_time≤30s)并实现写后读一致性兜底。

Hyperf 应用连接 MySQL 双主 + Keepalived VIP,不能只靠改 pool.host 为 VIP 就完事——连接池本身不感知主库状态,也不做故障重试或读写分离,盲目配 VIP 反而会放大复制延迟、主键冲突、连接卡死等风险。
Hyperf 连接池里填 192.168.1.100(VIP)为什么不行
Hyperf 的 MySQL 连接池(基于 Swoole 协程)在初始化时只做一次 DNS 解析和 TCP 连接建立,之后复用连接。一旦 Keepalived 把 VIP 漂到另一台机器,已建立的连接仍维持在旧节点(可能已宕机),新请求虽能连上 VIP,但:
- 旧连接不会自动断开或重连,
SELECT @@server_id可能返回已下线节点的 ID - 应用层无感知复制延迟,
Seconds_Behind_Master是几百时照样写入 - 双主自增步长(
auto_increment_increment)若未对齐,INSERT 同一主键直接报ERROR 1062 (23000): Duplicate entry - 连接池中空闲连接超时(
pool.max_idle_time)默认 60 秒,故障切换后这 60 秒内请求大概率失败
必须配合 MySQL 层的双主健康检测脚本
Keepalived 的 vrrp_script 必须调用真实检测逻辑,不能只用 nc -z 127.0.0.1 3306。Hyperf 不参与 VIP 切换决策,但它依赖的这个 VIP 背后是否真正可写,得由 MySQL 自身状态决定:
- 检测脚本需执行:
mysql -uhealth -p... -e "SELECT 1" | grep -q "1",且附加--connect-timeout=3 --execution-timeout=3 - 必须校验复制状态:
SHOW SLAVE STATUS\G中Slave_IO_Running: Yes和Slave_SQL_Running: Yes同时成立,且Seconds_Behind_Master < 5 - 脚本返回非 0 时,Keepalived 才触发降级;否则即使 MySQL 进程活着,但复制已断,VIP 也不能漂过去
Hyperf 连接池配置要适配双主语义
Hyperf 默认把 MySQL 当单点用,但双主本质是“两个可写端点”,连接池参数需抑制副作用:
-
pool.connect_timeout建议设为3.0:避免因 VIP 漂移中短暂不可达导致协程阻塞过久 -
pool.wait_timeout提高到5.0:防止连接池在切换窗口期因等待连接超时而抛异常 -
pool.max_idle_time严格设为30.0或更低:强制在 30 秒内回收空闲连接,让新连接有机会命中新 VIP 节点 - 禁用
options.ATTR_PERSISTENT(持久连接):Swoole 协程下持久连接与双主场景互斥,容易复用到已失效连接 - 不要依赖
pool.min_connections预热:预热连接可能全落在旧节点,反而加剧故障期错误
应用层必须做写后读一致性兜底
即便 VIP 切换正确、复制延迟控制在秒级,Hyperf 请求仍可能因路由错位读到旧数据。这不是连接池问题,而是最终一致性模型下的固有约束:
- 关键写操作(如创建订单)后,若紧接着查该记录,应显式指定
server_id或走强一致性读(如加SELECT ... FOR UPDATE) - 避免在事务中跨连接读写:Hyperf 连接池不保证同个协程内多次
db::select()复用同一连接,可能读到不同节点 - 业务日志中建议打上
@@server_id和@@hostname,出问题时能快速定位是哪台节点响应了请求
双主架构里最易被忽略的,不是 Keepalived 怎么配,也不是 Hyperf 连接池怎么调,而是「写入后立即读」这个动作本身,在 MySQL 复制机制下天然不保序——哪怕 VIP 漂得再快,也救不了没加读一致性策略的代码。


















