hyperf redis长连接被防火墙切断,主因是tcp keepalive和swoole空包心跳均无效,必须配置带payload的应用层心跳(如heartbeat=120)并协同max_idle_time(如270)与opt_read_timeout=-1,且需重启生效。

Hyperf 的 Redis 长连接被防火墙切断,不是因为没心跳,而是心跳太弱或节奏错位——TCP 层 tcp_keepalive 在云环境和多数企业防火墙前基本失效,Swoole 层的空包探测又常被 SLB 或 H3C 防火墙丢弃,必须靠应用层带 payload 的业务心跳帧来“刷存在感”。
为什么 tcp_keepalive 和 Swoole 默认心跳都扛不住防火墙
Linux 内核的 tcp_keepalive_time 默认是 7200 秒,远超防火墙常见超时(H3C F1005 是 240 秒,腾讯云 CLB 多为 90–300 秒);而 Swoole 的 heartbeat_idle_time 只对 WebSocket/HTTP2 生效,Redis 连接池完全不走这条路。更关键的是:纯 ACK 或无 payload 的心跳包,会被很多中间设备视为“无效流量”直接过滤——抓包会看到发出去了,但服务端根本收不到响应。
- Redis 连接池用的是标准 PHP
Redis扩展(非 Swoole 原生协程客户端),不参与 Swoole 的 heartbeat 机制 - Hyperf 的
pool.heartbeat默认值是false,即使配置写了'heartbeat' => true,也只在连接取出时单次 ping,不是后台持续探测 - 真正起作用的是连接池后台的
IdleConnectionChecker,它依赖heartbeat和max_idle_time协同触发
pool.heartbeat 必须设成数值,且要小于防火墙超时的 70%
设成 true 或 -1 都无效;必须填具体秒数(如 30),这个值决定后台线程多久发一次 PING 命令——它带真实 payload,能穿透绝大多数网关。
- 先实测防火墙真实超时:用
tcpdump -i any port 6379抓包,观察空闲后多久出现 RST;或查 H3C 日志,确认是否为 240 秒 - 若确认是 240 秒,
heartbeat最大设为168(240 × 0.7),推荐120或90,留出响应与网络抖动余量 - 不能设太小(如
5):每秒大量PING会吃光 Redis 的maxclients连接数,尤其在高并发场景下 - 不能设太大(如
300):比防火墙还慢,等于没设
max_idle_time 必须严格小于 Redis 的 timeout 配置
Redis 服务端的 timeout 参数(单位秒)才是连接被主动关闭的判决依据,它默认是 0(永不过期),但生产环境通常显式设为 300 或 60。Hyperf 连接池的 max_idle_time 必须比它小至少 30 秒,否则连接在池里“睡过头”,取出来就直接报 Connection has been closed。
- 查 Redis 实际配置:
redis-cli config get timeout,假设返回300 - 则
max_idle_time应设为270,heartbeat设为135(270 ÷ 2),形成“心跳探测 + 空闲兜底”双保险 - 如果 Redis 没开
timeout(即0),max_idle_time可设为600,但heartbeat仍需按防火墙节奏设(如120) -
min_connections别设0或1,冷启动时全靠这几点缓冲,建议 ≥5
别忽略 OPT_READ_TIMEOUT 和重连兜底
防火墙切断连接后,客户端可能卡在读操作上不动,而不是立刻报错。这时仅靠心跳不够,还得让底层驱动不等死。
- 在
config/autoload/redis.php的options里加:\Redis::OPT_READ_TIMEOUT => -1,禁用读超时(Pub/Sub 场景必需) - 心跳只能防“静默断连”,不能防“半开连接”:建议在订阅逻辑里手动加重连循环,用
try/catch捕获RedisException后重建订阅连接 - 如果用了 Twemproxy 或某些老 Redis Proxy,它们不支持
PING,此时heartbeat要设为-1,完全依赖max_idle_time主动释放
最易被忽略的一点:所有这些参数都只在连接池初始化时读取,改完配置必须重启 Hyperf 服务,热加载不生效;另外,heartbeat 值一旦设错,错误不会立即暴露,而是等连接空闲一段时间后才集中爆发——所以调参后务必用长时空闲+业务请求组合验证,不能只看启动日志。











