一个典型的 PHP 站点,生产环境通常要摆四样东西:nginx 负责静态文件和反向代理,php-fpm 跑 PHP,Node.js 扛 WebSocket,Redis 做 fpm 和 Node 之间的消息中转。再配上 supervisor 保活、Docker 部署——六个进程、三种语言、两个运行时。这套组合拳打了十几年,没人觉得哪儿不对。但 Qbix Server 的作者给出了一个反直觉的方案:所有这些,用一条命令 `php qbixserver.php` 就能全部替代,甚至还能顺带做到 nginx、php-fpm、Node、Redis 都做不到的事——让同一套 PHP 应用在手机之间通过蓝牙和 WiFi 离线同步数据。
核心的秘密在于一个操作系统机制:COW fork(写时复制)。传统 PHP-FPM 的做法是每个请求由一个常驻 worker 处理,worker 之间共享地址空间,一个请求的静态变量、全局状态、甚至内存里的密钥都可能泄漏给下一个请求——这是所有持久化进程服务器的通病,Node、Go、Java、Python WSGI 全都有。而传统 CGI 模式每次请求都新建进程,隔离安全了,但每个进程要 30-60MB 内存、几毫秒启动时间,根本扛不住高并发。Qbix Server 的解法是:先让父进程把整个应用框架(Laravel、WordPress 都行)完整加载进内存,然后每个请求到达时用 `pcntl_fork()` 复制出一个子进程。内核把父进程的内存页标记为只读共享,子进程只有真正写入时才复制那份页。于是每个 worker 的实际内存成本只有约 120KB,启动时间微秒级,而且子进程处理完请求就 `exit()`,操作系统直接回收整个地址空间——请求间的状态泄漏在机制上就不可能发生,不需要像 Swoole 那样靠 rewrite 代码来规避,也不需要像 FrankenPHP 那样审计所有全局变量。
这个设计带来的数字很直观:同样 1GB 内存,php-fpm 只能开大约 24 个 worker,Qbix Server 能跑约 5000 个;同样 I/O 负载下吞吐量从 78 req/s 提升到 1060 req/s(基准测试方法都在仓库的 BENCHMARKS.md 里)。静态文件吞吐确实比 nginx 慢,差不多只有后者的 55-73%,但别忘了它是个纯 PHP 解释器在跑 HTTP 服务,能摸到 C 语言性能的六成半,作者自己也承认这是 PHP 的天花板。但应用场景大多不是纯静态文件,而是 PHP 业务逻辑——nginx 加 fpm 处理一次请求要 0.1ms 静态文件 + 30ms PHP 框架引导 + 5ms 实际工作,Qbix 因为早已在父进程里预加载好框架,引导耗时是 0,总耗时 5ms 左右,整整快了七倍。
更妙的是它对现有生态的兼容性。Qbix Server 内置 13 个主流框架的 preset(Laravel、Symfony、WordPress、Drupal、Magento 等),你的 `.htaccess` 原样就能用,前端控制器路由自动识别。它通过源码转换在 include 时拦截了 27 个 PHP 函数(header、session_start、setcookie、ini_set 等),让这些函数在持久化 worker 模式下也能正确重置;每个请求之间,所有静态属性从一个快照恢复只需 0.03ms。也就是说,你把 `php qbixserver.php --root=public --preset=laravel --port=8080` 这么一跑,Laravel 应用就起来了,不用改任何代码。切换成本几乎为零,这比 Swoole 强制你改写成协程风格要友好得多。
最惊艳的是 2.0 版本带来的离线能力。同一套 PHP 应用,打包成单文件二进制,可以跑在 iOS 和 Android 上。手机之间通过 BLE(低功耗蓝牙)和 WiFi 发现彼此,ECDH 密钥交换建立加密会话,消息能经过多跳网格式路由转发,数据同步用 Bloom filter(一种高效判断集合成员的数据结构)先比对差异,再传缺失记录;数据集超过一万条时自动切换成 prolly tree(一种内容寻址的确定性树,相同子树直接跳过),只传输真正变化的部分。这意味着一个教室里的几十台手机,跑同一个 PHP 应用,在完全没互联网、没中心服务器的环境下也能互相通信、同步数据库。听起来像科幻,但作者贴出了完整的协议文档和移动端打包流程,App Store 上已经有 BitChat 这类产品验证过这种模式。
另一个让人觉得作者是认真在做产品的地方,是他提供的组件级缓存机制。传统缓存要么整页缓存、一旦数据动就全丢,要么只能靠 Redis 自己拼。Qbix Server 支持 PHP 脚本用 `header('X-Cache-Tree: ...')` 声明页面里各个组件(比如 feed、sidebar、members)的内容哈希,再用 `X-Cache-Deps` 声明每个组件依赖的数据键。当某个依赖键失效时,只需要发一个 `X-Cache-Invalidate` 头,服务器内部会维护一棵 Merkle 树,精确找到哪些页面上的哪个组件该重新渲染,其他部分直接走内存缓存。这套机制完全靠标准 `header()` 函数实现,不需要任何 SDK。还有 `X-Accel-Redirect` 支持,PHP 校验权限后告诉服务器直接流式发送文件,私有文件不再靠不可猜测的 URL 来伪装安全。
对于有安全洁癖的团队,COW fork 的隔离模型还带来了一个额外红利:可以放心跑未经审计的第三方插件。WordPress 生态里不少主题和插件代码质量堪忧,传统 fpm 下这些代码的全局变量可能会泄漏到下个请求,变成漏洞。Qbix 架构下,每个请求的死进程保证了内核级隔离,一个请求里的 bug 再大也影响不了别人。作者甚至提了一个 RFC 建议把 `switch_global_context()` 加进 PHP 核心,现在先在用户态实现了等效方案。
如果你正在被 php-fpm 的内存瓶颈、Node 和 Redis 的运维复杂度、或者 Swoole/FrankenPHP 的代码兼容性折腾,这个项目值得你花一小时跑个 demo。直接 `git clone https://github.com/Qbix/webserver`,然后 `php qbixserver.php` 就能起服务,examples 目录里有 todo、chat、协作文档等六个示例应用。需要注意的坑是:Windows 生产环境目前不支持 fork-per-request 隔离,官方建议生产跑 Linux/macOS;长跑脚本(迁移、导入)应该用 `--workers=1` 或直接 CLI 执行;另外如果代码里用了不常见的 PECL 扩展且扩展维护了 C 层状态,也可能无法在请求间正确重置。但绝大多数标准扩展(PDO、curl、mbstring)都没问题。它不像 Swoole 那样要求重写业务代码,也不像 FrankenPHP 那样依赖 Go 工具链,一个纯 PHP 文件 + 一条命令,就能把六件套换成单进程,还顺手获得了移动端离线同步的超能力——这种思路本身就值得每个后端工程师看看。
内容与图片版权归原作者所有 · 原文: https://github.com/Qbix/webserver/tree/main