Master-Manager-Worker 进程职责划分
解读
国内高并发 PHP 项目普遍采用 “多进程 + 多路复用” 的常驻服务架构(Swoole、Swow、Workerman)。Master-Manager-Worker 三层模型是官方推荐、面试官高频追问的进程组织方式。题目表面问“谁干什么”,实质考察:
- 能否把“进程”与“连接”“请求”“业务”三者解耦;
- 是否理解信号、进程间通信、reload 零中断、异常隔离、CPU 亲和等生产级细节;
- 能否结合 PHP 的 Zend VM 生命周期解释为何 Worker 里不能随意 die、global 变量为什么“貌似”能共享却必须谨慎。
一句话:让面试官确信你写过 10w+ QPS 的线上服务,而不是只跑过 demo。
知识点
- Linux 进程、线程、协程区别与 PHP 的 Zend 线程安全(ZTS/NTS)
- Swoole 进程模型:Master 事件循环、Manager 进程监控、Worker 业务进程、TaskWorker 异步任务进程
- 进程间通信:UnixSocket、消息队列、共享内存 Table、Channel
- 信号处理:SIGTERM/SIGINT 优雅退出、SIGUSR1/SIGUSR2 重载、SIGCHLD 回收
- reload 的两种模式:only Worker(代码热更)与 full reload(Master 也重启)
- 惊群、CPU 亲和、 backlog、max_request 内存回收机制
- PHP 超全局变量、Opcache、CLI 模式下的持久连接池生命周期
答案
-
Master 进程
职责:- 监听服务器端口(listen fd),创建 Main-Reactor,使用 epoll/kqueue 做事件分发;
- 接受新连接,把已建立连接的 fd 通过 UnixSocket 发给对应 Worker,自身不处理任何业务逻辑;
- 捕获 SIGTERM、SIGUSR1 等信号,向 Manager 转发“重载/退出”指令,实现零中断热更新;
- 维护全局共享内存(如 Swoole\Table、Atomic),但对 PHP 层不可见,避免业务代码污染。
注意:Master 里不会加载 Composer 自动加载器,也不初始化任何 PHP 类,一旦 Crash 整组服务全挂,必须被 systemd/supervisor 重启。
-
Manager 进程
职责:- 作为 Master 的子进程、Worker 的父进程,专职“进程生命周期管理”;
- 根据 onWorkerStart 之前配置的数量 fork Worker/TaskWorker,并挂在 wait() 上,实时回收异常退出的子进程,防止僵尸;
- 处理 reload 信号:向所有 Worker 发送 SIGTERM,待其 exit(0) 后重新 fork,实现代码热更;若 Master 也重启,则 Manager 随 Master 一起重建;
- 提供心跳检测通道,定时通过 UnixSocket 向 Master 上报“Worker 退出过快”等异常,方便监控系统报警。
注意:Manager 里可以运行少量 PHP 代码(如 onManagerStart 回调),但官方建议只做进程管理,不连数据库、不加载业务,防止把致命错误带到 Master 树。
-
Worker 进程
职责:- 真正运行 PHP 代码,每个进程拥有独立的 Zend VM、Opcache、自动加载器、连接池;
- 通过 ReactorThread 拿到已就绪的 fd,读取 HTTP/TCP/WebSocket 数据,调用 onRequest/onReceive 等回调;
- 处理完请求后把响应写回内核缓冲区,继续事件循环,实现单进程多路复用;
- 每完成 max_request 次请求或内存占用超过 max_memory 时主动 exit(0),由 Manager 重新 fork,释放内存泄漏;
- 支持协程调度,可在高 I/O 场景下用 Co::sleep、Co::gethostbyname 等函数实现异步,但注意不能调用 exit/die,否则进程直接销毁。
注意:Worker 内禁止再 fork,禁止 pcntl_exec,禁止长时阻塞;数据库连接必须走连接池或短连,否则 reload 时会触发 MySQL server has gone away。
一句话总结:Master 管连接,Manager 管生死,Worker 管业务;三层隔离,故障不扩散,热更不中断。
拓展思考
-
如果线上出现“Worker 不断重启但错误日志为空”,如何定位?
答:先查 Manager 日志是否收到 SIGCHLD;再用 strace -p 跟踪 Worker,看是否死在 futex 或 include 阶段;确认 Opcache 是否启用 validate_timestamps=0 导致语法错误被吞;最后检查 systemd 的 OOMKill 记录,排除内存超限被内核杀。 -
当 max_request 设置过大时,为何会出现“内存持续增长却不见释放”?
答:PHP 的 Zend 内存管理器(ZendMM)只在请求边界回收 small heap,持久数组、静态变量、Swoole\Table 引用、循环引用不会随请求结束而 free;此时应降低 max_request 或把大对象改存到外部缓存(Redis),并开启 opcache.validate_timestamps=0 防止重复编译。 -
如何做到“灰度 reload”只重启部分 Worker?
答:利用 Swoole\Process::kill($worker_pid, SIGUSR1) 单独发信号,或把 Worker 编号写入共享内存,Manager 根据标记位选择性重启;配合 Nginx 权重摘除,可实现用户无感灰度。 -
在 Kubernetes 中,上述模型是否需要调整?
答:K8s 本身通过 Pod 级别做弹性与自愈,单 Pod 内仍保留 Master-Manager-Worker 模型,但把 Worker 数调小(1-2 核 2-4 Worker),利用 HPA 横向扩容 Pod 而非进程,既兼容 PHP 状态less,又避免单节点进程数爆炸。