Master-Manager-Worker 进程职责划分

解读

国内高并发 PHP 项目普遍采用 “多进程 + 多路复用” 的常驻服务架构(Swoole、Swow、Workerman)。Master-Manager-Worker 三层模型是官方推荐、面试官高频追问的进程组织方式。题目表面问“谁干什么”,实质考察:

  1. 能否把“进程”与“连接”“请求”“业务”三者解耦;
  2. 是否理解信号、进程间通信、reload 零中断、异常隔离、CPU 亲和等生产级细节;
  3. 能否结合 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 模式下的持久连接池生命周期

答案

  1. Master 进程
    职责:

    • 监听服务器端口(listen fd),创建 Main-Reactor,使用 epoll/kqueue 做事件分发;
    • 接受新连接,把已建立连接的 fd 通过 UnixSocket 发给对应 Worker,自身不处理任何业务逻辑;
    • 捕获 SIGTERM、SIGUSR1 等信号,向 Manager 转发“重载/退出”指令,实现零中断热更新;
    • 维护全局共享内存(如 Swoole\Table、Atomic),但对 PHP 层不可见,避免业务代码污染。
      注意:Master 里不会加载 Composer 自动加载器,也不初始化任何 PHP 类,一旦 Crash 整组服务全挂,必须被 systemd/supervisor 重启。
  2. Manager 进程
    职责:

    • 作为 Master 的子进程、Worker 的父进程,专职“进程生命周期管理”;
    • 根据 onWorkerStart 之前配置的数量 fork Worker/TaskWorker,并挂在 wait() 上,实时回收异常退出的子进程,防止僵尸;
    • 处理 reload 信号:向所有 Worker 发送 SIGTERM,待其 exit(0) 后重新 fork,实现代码热更;若 Master 也重启,则 Manager 随 Master 一起重建;
    • 提供心跳检测通道,定时通过 UnixSocket 向 Master 上报“Worker 退出过快”等异常,方便监控系统报警。
      注意:Manager 里可以运行少量 PHP 代码(如 onManagerStart 回调),但官方建议只做进程管理,不连数据库、不加载业务,防止把致命错误带到 Master 树。
  3. 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 管业务;三层隔离,故障不扩散,热更不中断。

拓展思考

  1. 如果线上出现“Worker 不断重启但错误日志为空”,如何定位?
    答:先查 Manager 日志是否收到 SIGCHLD;再用 strace -p 跟踪 Worker,看是否死在 futex 或 include 阶段;确认 Opcache 是否启用 validate_timestamps=0 导致语法错误被吞;最后检查 systemd 的 OOMKill 记录,排除内存超限被内核杀。

  2. 当 max_request 设置过大时,为何会出现“内存持续增长却不见释放”?
    答:PHP 的 Zend 内存管理器(ZendMM)只在请求边界回收 small heap,持久数组、静态变量、Swoole\Table 引用、循环引用不会随请求结束而 free;此时应降低 max_request 或把大对象改存到外部缓存(Redis),并开启 opcache.validate_timestamps=0 防止重复编译。

  3. 如何做到“灰度 reload”只重启部分 Worker?
    答:利用 Swoole\Process::kill($worker_pid, SIGUSR1) 单独发信号,或把 Worker 编号写入共享内存,Manager 根据标记位选择性重启;配合 Nginx 权重摘除,可实现用户无感灰度。

  4. 在 Kubernetes 中,上述模型是否需要调整?
    答:K8s 本身通过 Pod 级别做弹性与自愈,单 Pod 内仍保留 Master-Manager-Worker 模型,但把 Worker 数调小(1-2 核 2-4 Worker),利用 HPA 横向扩容 Pod 而非进程,既兼容 PHP 状态less,又避免单节点进程数爆炸。