文章851
标签121
分类10

[踩坑] 记一次 Swoole 报错排查:`swReactor_write (ERRNO 1008): socket#58 output buffer overflow`

在最近的项目中,我们线上脚本服务跑了一段时间后,日志里频繁出现如下警告:

WARNING swReactor_write (ERRNO 1008): socket#58 output buffer overflow

起初看见这个报错有些懵,后来一番排查才确认根因:子进程给 worker 进程发送消息过大且过快,导致 Swoole 的输出缓冲区溢出。本文记录一下整个问题分析与解决过程。


1. 现象描述

  • 服务运行一段时间后,Swoole 日志中出现大量

    swReactor_write (ERRNO 1008): socket#XX output buffer overflow
  • 服务本身没有立刻崩溃,但某些业务消息丢失。
  • 报错定位在 子进程与 worker 进程通信 的场景。

2. 背景知识

Swoole 在底层基于 异步事件循环socket 通信。

  • 当服务端往某个 socket 写数据时,Swoole 会先写入 输出缓冲区
  • 如果客户端(或目标进程)没有及时消费,缓冲区就会堆积。
  • 一旦超过配置的阈值,就会报出 output buffer overflow 警告。

换句话说,这个错误其实就是:
“写的速度太快,读的速度太慢,导致缓冲区爆了。”


3. 我们的踩坑点

在项目里,我们用 Swoole fork 子进程 的方式做数据采集,采集完的数据通过 管道消息 发送给 worker 进程。

问题在于:

  • 子进程一次性推送的 数据包很大(可能几 MB 以上)。
  • 推送 频率也很高,基本没有限速控制。

结果就是 worker 进程来不及消费,缓冲区瞬间被撑爆,触发了报错。


4. 解决方案

针对这个问题,我们从两个角度入手:

(1)调整 Swoole 配置

server->set() 里增加配置,扩大缓冲区上限:

$server->set([
    'buffer_output_size' => 32 * 1024 * 1024,   // 默认 2M,这里调大到 32M
    'socket_buffer_size' => 128 * 1024 * 1024,  // 默认 8M,这里调大到 128M
]);

这样可以避免轻易触发溢出警告,适合大数据包场景。


(2)优化业务逻辑

更根本的办法是 控制写入速度拆分大数据

  • 分片发送
    将单个大数据包拆分成小块逐步推送,而不是一次性写入。
  • 写入返回值检查
    在调用 send/push 时判断返回值,如果 false,说明缓冲区满了,需要重试或丢弃。
  • 异步队列缓冲
    子进程先写入本地队列,由 worker 消费,避免直接“洪水式”灌数据。

5. 总结

这次问题让我再次体会到:
在高并发/大数据场景下,消息队列和流控机制一定要设计好,不能指望底层缓冲区“兜底”。

最终的经验:

  • 短期可以通过 增大 buffer 配置 解决。
  • 长期要通过 限速、拆分、队列 来避免缓冲区压力。

参考

【踩坑】connect() failed: Connection timed out in 偶发超时问题排查与解决

在日常运维和开发中,我们经常会用到 Redis 作为缓存或消息存储。但线上偶尔会出现这样一个报错:

[13-Sep-2025 15:59:16 Asia/Shanghai] PHP Warning:  
Redis::connect(): connect() failed: Connection timed out in /www/DB/RedisLibrary.php on line 31

这是 Redis 连接超时,在某些情况下会导致业务请求失败。本文记录一次排查过程以及优化方案。


一、问题现象

  • 错误提示:Redis::connect() failed: Connection timed out
  • 触发特征:偶尔发生,不是持续报错。
  • Redis 监控指标:

    connected_clients:1796
    blocked_clients:0
    used_cpu_sys:5712795
    used_cpu_user:4863491

    看起来连接数和 CPU 占用都不高。


二、初步怀疑方向

  1. Redis 崩溃/重启?
    → 否,监控显示 Redis 稳定运行,未重启。
  2. 客户端连接过多?
    → 当前连接数约 1796,远低于 maxclients 默认上限(10000)。
  3. CPU 或阻塞问题?
    blocked_clients=0,说明没有命令阻塞;CPU 占用也正常。

因此,问题并不是 Redis 整体挂掉,而是偶发连接超时


三、深入排查思路

1. 内存与 OOM

此前我们已经遇到过:

OOM command not allowed when used memory > 'maxmemory'

Redis 内存接近上限时,写入会卡顿甚至拒绝,导致客户端等待时间变长。
👉 检查 maxmemorymaxmemory-policy 配置,确保有合适的淘汰策略(如 allkeys-lru)。


2. 短连接开销

代码里使用的是:

$redis->connect($host, $port, 3.0);

这意味着每次请求都要新建 TCP 连接。当并发较高时,大量连接握手容易导致延迟。
👉 建议改为持久连接:

$redis->pconnect($host, $port, 5.0);

3. 系统参数限制

即使连接数不高,如果 Linux 内核参数不足,也可能在高峰期丢连接:

  • ulimit -n 文件描述符数太小(需 >= 65535)
  • net.core.somaxconn 太低(推荐 >= 1024)
  • net.ipv4.tcp_max_syn_backlog 太低(推荐 >= 2048)

👉 可以用以下命令查看:

ulimit -n
sysctl net.core.somaxconn
sysctl net.ipv4.tcp_max_syn_backlog

4. 网络抖动

如果 PHP 与 Redis 不在同一台机器,偶尔的网络延迟或丢包也会触发超时。
👉 可以用 pingmtr 长时间测试,观察网络是否有延迟尖刺。


四、解决方案

  1. Redis 配置优化

    CONFIG SET maxmemory 24gb
    CONFIG SET maxmemory-policy allkeys-lru

    保证不会因为 OOM 导致阻塞。

  2. 客户端优化

    • 使用 pconnect 代替 connect,减少 TCP 握手次数。
    • 设置更合理的超时时间,比如 5 秒
    • 增加重试机制,避免偶发超时导致请求失败。
  3. 系统层优化

    • 提高文件句柄数:ulimit -n 65535
    • 调整 TCP backlog 参数:

      sysctl -w net.core.somaxconn=1024
      sysctl -w net.ipv4.tcp_max_syn_backlog=2048
  4. 监控与预警

    • 监控 connected_clientsblocked_clientslatency
    • 配置 slowlog,捕捉耗时命令。

五、总结

这次问题的根源不是 Redis 本身崩溃,而是 偶发性的连接超时。常见诱因有:

  • 内存逼近上限导致卡顿
  • 短连接压力大
  • 系统参数不足
  • 网络延迟抖动

通过优化 Redis 配置、调整客户端连接方式,以及检查系统内核参数,可以有效减少这种问题的发生。

">