Webman进程不能直接用cgroups管理,因其fork机制导致子进程脱离cgroup且无法预知PID;应通过Docker(--memory、--cpus等)或裸机下cgexec/systemd --scope包裹启动命令来限制整体资源,并在PHP代码层添加内存与超时熔断。

Webman进程不能直接用cgroups管理
Webman本身是基于Workerman的常驻内存PHP服务,它的子进程模型与systemd或手动cgroups管理存在天然冲突。如果你试图用echo $PID > /sys/fs/cgroup/memory/test/tasks把Webman主进程或worker进程塞进cgroup,会立刻遇到两个问题:一是进程可能被OOM Killer杀掉(尤其在内存限制过严时),二是Workerman的多进程fork机制会让子进程脱离原cgroup,导致限制失效。
更关键的是,Webman启动后会持续fork新worker,而cgroups的tasks文件只接受当前存在的PID——你无法预知后续worker的PID,也无法批量写入。所以别对Webman进程本身做cgroups硬限。
- 真正该限制的是Webman容器或宿主机运行环境,不是PHP进程个体
- 若用Docker部署,必须通过
--memory、--cpus等参数在容器启动时注入cgroups规则 - 裸机部署时,应限制整个Webman服务的父进程组(如用
systemd --scope或cgexec包裹php start.php start)
用Docker部署时必须设置--memory和--cpus
Docker是目前最可靠、最易落地的Webman资源限制方式。它底层就是靠cgroups实现的,且自动处理了进程继承、子进程捕获、OOM行为控制等细节。不设资源上限等于放任PHP进程吃光主机内存。
例如启动一个带硬限制的Webman容器:
docker run -d \ --name webman-app \ --memory=512m \ --memory-swap=768m \ --cpus=1.2 \ --pids-limit=200 \ -p 8787:8787 \ your-webman-image:latest
注意几个关键点:
-
--memory=512m对应cgroups的memory.limit_in_bytes,超限后进程会被OOM Killer终结 -
--memory-swap=768m表示总内存+swap上限,差值(256m)就是可用swap空间 -
--cpus=1.2等价于cpu.cfs_quota_us=120000+cpu.cfs_period_us=100000,避免CPU争抢 -
--pids-limit=200防fork炸弹,对应cgroups的pids.max,比单纯限制内存更防崩溃
裸机部署需用cgexec或systemd scope包装start.php
如果不用Docker,又必须在Linux裸机上跑Webman,唯一可行的方式是把整个启动命令包裹进cgroup上下文。直接操作/sys/fs/cgroup/xxx/tasks不可靠,要用cgexec或systemd --scope这类工具确保所有子进程自动归属。
比如限制Webman整体最多用300MB内存、0.8核CPU:
# 先创建cgroup(需root) sudo mkdir -p /sys/fs/cgroup/memory/webman sudo mkdir -p /sys/fs/cgroup/cpu/webman <p>echo 314572800 | sudo tee /sys/fs/cgroup/memory/webman/memory.limit_in_bytes echo 80000 | sudo tee /sys/fs/cgroup/cpu/webman/cpu.cfs_quota_us echo 100000 | sudo tee /sys/fs/cgroup/cpu/webman/cpu.cfs_period_us
然后用cgexec启动(不要用php start.php start裸跑):
cgexec -g memory:webman,cpu:webman php start.php start -d
这样所有由Webman fork出的worker进程都会自动进入这两个cgroup,无需手动追踪PID。
- 必须用
-d后台模式,否则cgexec退出会导致整个服务中断 - 如果系统没装cgroup-tools,先
sudo apt install cgroup-bin(Debian/Ubuntu)或yum install libcgroup-tools(CentOS/RHEL) - 别在
config/process.php里额外开子进程——它们不会自动继承cgroup,得单独cgexec包装
Webman插件级资源熔断要靠代码层控制
cgroups管的是整个进程或容器的资源边界,但Webman插件可能在单次请求中就耗尽CPU或内存(比如图片缩放、大文件解析)。这时cgroups来不及响应,必须在PHP代码里加熔断。
参考官方插件实践,在插件handle()开头加资源检查:
$mem = memory_get_usage(true);
if ($mem > 100 * 1024 * 1024) { // 超100MB立即拒绝
throw new \Exception('Memory limit exceeded');
}
$startTime = microtime(true);
// ... 执行业务逻辑
if (microtime(true) - $startTime > 3) { // 超3秒强制中断
throw new \Exception('Timeout');
}重点不是“能不能限制”,而是“在哪一层限制”:
- cgroups负责兜底,防止失控进程拖垮整台机器
- PHP代码负责细粒度控制,比如单次请求的内存/时间上限
- Webman的
plugin/webman/redis-queue消费者也必须加同样逻辑,否则队列积压会掩盖真实瓶颈
最容易被忽略的是:cgroups限制生效需要内核支持(确认cat /proc/cgroups里memory和cpu已启用),且Docker或systemd版本太老时--memory可能被静默忽略——务必用docker stats或systemd-cgtop实时验证是否真生效。


















