concurrent.limit控制协程内并发执行数,非进程或连接数;它限定同一时刻handle()中并行任务数,需依I/O占比、CPU密集度及下游响应时间合理设置,过高致内存挤占与协程饥饿,过低则资源浪费。

concurrent.limit 控制的是协程内并发执行数,不是进程或连接数
很多人看到 concurrent.limit 就以为是“最多开几个进程”或“最多接多少请求”,其实它只管一件事:同一时刻,最多有几个任务在你的 handle() 方法里跑着。它不控制队列积压速度、不控制 TCP 连接数、也不影响 Redis 连接池大小。
设太高会挤占内存、拖慢 IPC、触发协程饥饿;设太低又浪费资源。真实吞吐不取决于这个值本身,而取决于 I/O 等待占比、CPU 密集度、以及下游服务响应时间。
-
concurrent.limit = 1:适合强依赖顺序、写共享资源(如单文件日志)、或调用非线程安全 SDK 的场景 -
concurrent.limit = 4~8:通用推荐区间,兼顾响应延迟与资源利用率,适用于微信消息推送、短信发送等中等耗时任务 -
concurrent.limit = 16+:仅当任务极轻量(如纯内存计算)且确认无锁竞争时才考虑,否则极易引发协程调度失衡
AMQP/AsyncQueue 配置里 limit 必须配合 processes 拆通道使用
全局统一设 concurrent.limit = 10 是高危操作——一个报表生成任务卡住 30 秒,整个队列就堵死,微信消息延迟直接上分钟。Hyperf 不会自动按优先级分流,必须手动建独立通道。
正确做法是为不同业务类型定义专属队列配置,每个通道有自己的 processes 和 concurrent.limit:
- 微信消息通道:
processes => 4+concurrent.limit => 25,保证高实时性 - 报表生成通道:
processes => 1+concurrent.limit => 3,防止单任务吃光 CPU - 所有通道的
pool必须指向redis_cluster,不能复用默认 Redis 连接池,否则单机 Redis 连接数瞬间打满
自定义异步任务里要用 Channel 做信号量,别只靠注解
@RateLimit 或 AMQP 的 concurrent.limit 对自定义 Command 类无效。如果你写了批量处理命令(比如定时同步用户数据),并发控制得自己加。
最轻量可靠的方式是用 Swoole Channel 模拟信号量:
$sem = new \Swoole\Coroutine\Channel(5);
// 每次进任务前申请
$sem->push(1);
try {
// 执行实际逻辑
} finally {
// 一定要 pop,否则信号量漏掉
$sem->pop();
}
注意:不要用 sleep() 或阻塞式 IO 替代 Channel,协程里 sleep 会挂起整个协程栈;也不要忘记 finally,否则一旦异常退出,信号量就永远卡住。
concurrent.limit 设错的典型错误现象
你改了配置但没生效?常见原因不是代码写错,而是被更高层机制覆盖或误读日志:
- 报错
Too many open files:这不是concurrent.limit的问题,是系统文件句柄没配够,得查/proc/PID/limits看硬限制是否真到了65536 - 队列消费变慢但 CPU 很低:可能是下游接口超时未设
read_write_timeout,导致协程卡在等待,而不是被limit拦住 - 限流注解不生效:检查 Redis 配置里有没有
\Redis::OPT_SERIALIZER => \Redis::SERIALIZER_PHP,这个会让 RateLimit 的令牌桶解包失败
真正难调的从来不是数字本身,而是搞清这个数字到底约束在哪一层——是协程调度器?是 Redis 连接池?还是下游 HTTP 客户端的并发连接上限?先定位瓶颈再动 concurrent.limit,不然只是把水从一个桶舀到另一个桶。


















