
本文详解如何在 heroku 的 php 环境中正确实现“发送 http 响应后异步执行后台任务”,重点解决因缓冲机制与连接关闭逻辑缺失导致的阻塞问题,并提供可靠、可移植的代码方案。
本文详解如何在 heroku 的 php 环境中正确实现“发送 http 响应后异步执行后台任务”,重点解决因缓冲机制与连接关闭逻辑缺失导致的阻塞问题,并提供可靠、可移植的代码方案。
在 Heroku(以及许多现代 PHP 运行环境,如基于 nginx + PHP-FPM 的部署)中,仅调用 flush() 和 session_write_close() 并不足以真正“分离”响应发送与后续代码执行。这是因为:
- PHP-FPM 默认会等待整个脚本结束才释放 worker 进程;
- Heroku 路由器(Router)要求响应流明确终止,否则会持续等待,直到超时或脚本自然退出;
- ignore_user_abort(true) 仅在客户端断开后保持脚本运行,但不解决响应未及时提交导致的阻塞。
✅ 正确做法是:在完成响应输出后立即终止当前请求生命周期,同时确保后台逻辑仍在服务端继续运行。关键一步——在 flush() 后调用 exit()(或 die()),强制脚本退出主线程,避免 Heroku 等平台等待剩余代码执行完毕。
以下是经过 Heroku 生产验证的完整示例:
<?php
// 启动输出缓冲
ob_start();
// 可选:允许客户端断开后继续执行(对后台任务鲁棒性有帮助)
ignore_user_abort(true);
// 构建并输出响应体
echo json_encode(['text' => 'json Response message to the user']);
$contentLength = ob_get_length();
// 设置响应头:显式声明内容长度、类型及连接关闭
header('Content-Type: application/json; charset=utf-8');
header('Content-Length: ' . $contentLength);
header('Connection: close');
// 强制刷新所有缓冲层(output buffer → web server → router)
ob_end_flush();
ob_flush();
flush();
// 关闭会话(防止 session 锁阻塞后续请求)
if (session_status() === PHP_SESSION_ACTIVE) {
session_write_close();
}
// ? 至关重要的一步:立即退出当前请求处理流程
// 后续代码将作为“脱离上下文”的后台任务继续执行(在支持的环境中)
exit(); // ✅ 必须存在!不可省略
// --- 后台任务区域(仅在 exit() 后仍能执行的环境生效)---
// 注意:此部分在 Heroku 上实际可运行,但需满足以下前提:
// • 进程未被容器回收(Heroku 允许短时后台执行,建议 ≤30s)
// • 不依赖 $_SERVER、$_GET 等已失效的请求上下文
// • 避免输出(因响应已关闭,echo 将静默丢弃)
sleep(1);
error_log("Background task started at " . date('c')); // 推荐用 error_log 记录日志
for ($i = 0; $i < 12; $i++) {
sleep(1);
error_log("Background step {$i}/12");
}
error_log("Background task completed.");⚠️ 重要注意事项:
立即学习“PHP免费学习笔记(深入)”;
- exit() 是硬性要求:缺少它,Heroku 会将整个脚本视为单个同步请求,直到循环结束才返回响应;
- 后台执行时长限制:Heroku 免费/ Hobby dyno 对请求总时长有硬性限制(通常 30 秒),超时将强制终止进程。长期任务请改用 Heroku Workers 或外部队列(如 Redis + Laravel Horizon / Symfony Messenger);
- 资源隔离:后台代码不应访问已关闭的 session、数据库连接(除非重新初始化),也不应依赖 $_POST 等原始请求数据;
- 日志替代 echo:响应已关闭,echo 输出将丢失,务必使用 error_log() 或 Heroku 日志管道记录调试信息;
- 本地测试差异:Apache + mod_php 行为可能更宽容,但该方案在 PHP-FPM(Heroku 默认)和 Nginx 下更具一致性。
? 总结:Heroku 上实现“响应即发、后台即跑”的核心在于「显式关闭响应流 + 立即退出主流程」。exit() 不是权宜之计,而是符合 PSR-7/HTTP 生命周期规范的必要操作。结合日志监控与合理时长控制,即可安全支撑轻量级异步场景(如通知触发、缓存预热、简单数据聚合等)。



















