收到新请求时终止Apache中重型PHP请求的解决方法
嘿,这个场景我之前帮朋友处理过,咱们一步步拆解解决。首先你提到用户在等待重型AJAX响应时没法操作,很大概率是PHP会话锁在搞鬼——默认PHP用文件存储session时,同一个会话的多个请求会因为session文件被锁住而排队,第一个请求没结束,后面的请求都得等着。先把这个基础问题搞定,再处理终止旧请求的核心需求。
第一步:解决会话阻塞问题
要让用户能发起新请求,首先得在重型AJAX请求里尽早释放session锁。只要你不再需要读写session,就立刻调用session_write_close()释放锁,这样其他请求就能正常处理了。
示例代码:
// 启动会话,读取必要的用户信息 session_start(); $userId = $_SESSION['user_id']; // 不需要再操作session了,立刻释放锁 session_write_close(); // 这里开始执行重型任务:复杂计算、批量数据库查询等 heavyProcessingTask();
第二步:实现新请求终止旧请求
要做到这一点,我们需要跟踪用户当前正在运行的重型请求进程(或请求标识),新请求过来时触发终止逻辑。这里分两种场景适配不同的Apache运行模式:
场景1:Apache用Prefork MPM(每个请求对应独立进程)
这种模式下,每个请求由单独的Apache子进程处理,我们可以通过进程ID(PID)来跟踪和终止旧请求。
重型请求的代码逻辑:
session_start(); $userId = $_SESSION['user_id']; session_write_close(); // 重新打开session,记录当前进程ID session_start(['read_and_close' => false]); $currentPid = getmypid(); // 获取当前PHP进程的PID $_SESSION['active_heavy_pid'] = $currentPid; session_write_close(); // 执行重型任务,同时定期检查是否需要终止 $shouldStop = false; for ($i = 0; $i < 1000; $i++) { // 模拟重型任务的分步执行 usleep(10000); // 暂停10ms // 检查终止信号:通过session读取是否需要终止当前请求 session_start(['read_and_close' => true]); if (isset($_SESSION['terminate_pid']) && $_SESSION['terminate_pid'] === $currentPid) { $shouldStop = true; $_SESSION['terminate_pid'] = null; // 清除信号 } session_write_close(); if ($shouldStop) { // 优雅终止任务,避免资源泄漏 exit("Request terminated by user action"); } } // 任务完成后,清除session里的PID记录 session_start(); $_SESSION['active_heavy_pid'] = null; session_write_close(); echo "Heavy task completed successfully";
新请求的处理逻辑(比如用户点击其他链接的后端代码):
session_start(); // 检查是否有活跃的重型请求PID if (isset($_SESSION['active_heavy_pid']) && $_SESSION['active_heavy_pid'] !== null) { $oldPid = $_SESSION['active_heavy_pid']; // 优先用优雅终止:设置session信号让旧请求自行停止 $_SESSION['terminate_pid'] = $oldPid; // 如果是Linux/Unix环境,也可以直接发送终止信号(需要posix扩展) if (function_exists('posix_kill')) { posix_kill($oldPid, SIGTERM); // SIGTERM是优雅终止,SIGKILL是强制杀死 } // 清除旧PID记录 $_SESSION['active_heavy_pid'] = null; } // 处理当前请求的业务逻辑 handleUserCurrentRequest(); session_write_close();
场景2:Apache用Worker/Event MPM(多线程模式)
这种模式下,多个请求共享同一个进程的不同线程,getmypid()返回的是父进程ID,没法区分线程。这时我们可以给每个重型请求分配唯一标识,通过session跟踪这个标识:
重型请求的代码逻辑:
session_start(); $userId = $_SESSION['user_id']; session_write_close(); // 生成唯一请求标识 $requestId = uniqid('heavy_', true); session_start(['read_and_close' => false]); $_SESSION['active_heavy_request_id'] = $requestId; session_write_close(); // 执行重型任务,定期检查标识是否有效 $shouldStop = false; for ($i = 0; $i < 1000; $i++) { usleep(10000); session_start(['read_and_close' => true]); if ($_SESSION['active_heavy_request_id'] !== $requestId) { $shouldStop = true; } session_write_close(); if ($shouldStop) { exit("Request terminated by new user action"); } } // 任务完成后清除标识 session_start(); $_SESSION['active_heavy_request_id'] = null; session_write_close(); echo "Task done!";
新请求的处理逻辑:
session_start(); // 直接覆盖旧的请求标识,让旧任务检测到后自行终止 $_SESSION['active_heavy_request_id'] = null; // 处理当前请求 handleUserCurrentRequest(); session_write_close();
关键注意事项
- 权限问题:Linux下Apache子进程需要有发送信号的权限(同用户进程默认可以互相发送信号);Windows下可以用
exec("taskkill /PID $oldPid /F")替代posix_kill(),但要注意权限和安全性。 - 优雅终止优先:强制杀死进程可能导致资源泄漏(比如未关闭的数据库连接),优先用session信号让任务自行停止。
- session配置:确保session存储方式不会成为瓶颈,比如用Redis替代文件存储,性能和并发支持更好。
内容的提问来源于stack exchange,提问作者Masoud Nazari
相关产品推荐
相关产品推荐

