udp并发上不去主因是默认dispatch_mode=2导致包分配不均,应改用dispatch_mode=1轮询或mode=5配合高reactor_num;worker_num宜设为cpu核数至2倍,避免超48引发锁竞争;需同步调优内核udp缓冲参数。

UDP服务器扛不住并发包,不是代码写错了,而是默认配置没动过——worker_num、dispatch_mode、reactor_num 这几个参数不调,4核机器跑出2000 QPS 就算不错了。
为什么 UDP 并发上不去?关键在 dispatch_mode 和 reactor 分发逻辑
UDP 没连接状态,所有数据包都靠 Packet 事件回调处理,但 Swoole 默认的 dispatch_mode = 2(按 IP+PORT 哈希)在客户端复用端口或 NAT 环境下容易打到同一个 worker,造成单核瓶颈。真实压测中常看到一个 worker CPU 100%,其他 idle。
解决方法:
-
dispatch_mode改成1(轮询),让每个包均匀落到不同 worker,适合纯转发或简单处理场景 - 若需按客户端做会话级状态(比如限速、鉴权),改用
dispatch_mode = 5(IP 哈希),但必须搭配reactor_num调高,否则 reactor 线程成了瓶颈 -
reactor_num建议设为 CPU 核心数,避免单个 reactor 线程吞不下所有网卡中断包
worker_num 不是越多越好,得看业务类型
UDP 包处理路径极短,如果只是 echo 或简单字符串转换,worker_num 设太高反而增加进程调度和内存开销;但如果要查 Redis、写日志、调协程 MySQL,则需要足够 worker 来“等 I/O”。
实操建议:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 纯内存处理(如日志打点、指标聚合):
worker_num = swoole_cpu_num()即可 - 带协程 I/O(如
Swoole\Coroutine\Redis):worker_num = swoole_cpu_num() * 2,留出调度余量 - 千万别设
worker_num > 64,Swoole 的 UDP 分发在高 worker 数下有锁竞争,实测 32~48 是多数场景甜点区
sendto() 性能卡点:别在回调里做耗时操作
sendto() 本身很快,但如果你在 Packet 回调里做了 file_put_contents()、json_decode() 大包、或同步 curl,整个 reactor 就会被拖慢——UDP 包会堆积在内核 socket buffer,触发丢包。
必须规避的操作:
- 阻塞式文件写入:改用
Swoole\Coroutine\WriteFile或投递到 task 进程 - 大 payload 解析:先用
substr()或unpack()快速提取 header,再协程里处理 body - 直接
echo调试:生产环境关掉所有echo/var_dump,它们会刷 stdout 缓冲,锁住 worker
真实压测下容易被忽略的系统层限制
PHP 层调优做完,QPS 还卡在 3w 上下?大概率是 Linux 内核参数没跟上:
-
net.core.rmem_max和net.core.wmem_max至少调到262144,防止 recv/send buffer 溢出丢包 -
net.ipv4.udp_mem三元组建议设为"65536 131072 262144",避免 UDP 内存自动回收误杀 - 用
ss -u -n观察Recv-Q是否持续 > 0,这是内核 buffer 拥塞的直接证据
UDP 高并发真正的复杂点不在 PHP 代码,而在「谁在分发、谁在处理、谁在等」这三层之间的时间对齐——dispatch_mode 决定分发公平性,worker_num 决定处理吞吐,而内核参数决定了底层有没有给足缓冲空间。漏掉任意一层,压测曲线都会突然断崖。










