PHP脚本兼容Cron Job与AJAX调用:AJAX触发时页面冻结
解决AJAX调用PHP长脚本导致Web应用冻结的方案
问题核心
你的PHP脚本通过sleep()逐个处理记录,导致脚本执行时间过长;同时PHP默认的会话锁机制会阻塞其他页面请求,最终让整个Web应用看起来冻结。Cron模式正常是因为它不涉及前端会话的阻塞问题。
解决方案
1. 立即释放会话锁(快速修复)
PHP在开启Session后会独占session文件锁,直到脚本结束才释放。如果AJAX请求长时间占用锁,其他页面请求会被阻塞。在脚本完成Session相关操作后,主动关闭Session:
// 完成Session读取/写入后立即关闭 session_write_close(); // 后续的数据库查询、curl请求、sleep操作都不会再占用Session锁
2. 改用异步任务队列(根本解决)
长任务不适合通过AJAX同步执行,应该把任务丢进队列,让后台Cron或Worker进程处理,前端只负责提交任务和查询状态:
- 后端修改:新增一个仅负责任务入队的接口
public function queueDataTask() : array { // 1. 验证请求合法性 // 2. 将任务参数写入队列(比如数据库表task_queue) $taskId = $database->insertTask([ 'type' => $_POST['type'], 'params' => $_POST['params'], 'status' => 'pending' ]); // 3. 立即返回响应,不需要等待任务完成 return [ 'success' => true, 'task_id' => $taskId, 'msg' => '任务已提交,后台正在处理' ]; }
- 前端修改:提交任务后通过轮询获取处理状态
function apiRequestHandler(type, params) { $.ajax({ type: 'POST', url: '/queue-task', // 新的入队接口 data: { type, params }, dataType: 'json', success: (data) => { if (data.success) { // 启动轮询检查任务状态 const statusTimer = setInterval(() => { $.get(`/task-status?id=${data.task_id}`, (statusData) => { if (statusData.status === 'completed') { alert('任务处理完成'); clearInterval(statusTimer); } else if (statusData.status === 'failed') { alert('任务处理失败'); clearInterval(statusTimer); } }); }, 5000); // 每5秒查询一次 } } }); }
- 后台处理:复用现有Cron Job,每次执行时从
task_queue中取出待处理任务,用curl_multi_init()批量处理。
3. 优化同步脚本(临时过渡方案)
如果暂时无法改队列,把逐个处理+sleep改成并发请求,大幅缩短脚本执行时间:
public function getAndHandleData() : array { $data = $database->getData(); $mh = curl_multi_init(); $curlHandles = []; // 批量创建curl句柄 foreach ($data as $item) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $item['service_url']); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $item['request_data']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $curlHandles[] = $ch; curl_multi_add_handle($mh, $ch); } // 执行并发请求 $running = null; do { curl_multi_exec($mh, $running); curl_multi_select($mh); } while ($running > 0); // 处理所有响应 $results = []; foreach ($curlHandles as $ch) { $response = curl_multi_getcontent($ch); $results[] = $this->processResponse($response); curl_multi_remove_handle($mh, $ch); curl_close($ch); } curl_multi_close($mh); return $results; }
这种方式不需要sleep(),所有请求并行处理,脚本执行时间等于单个请求的最长耗时,而非累加时间。
内容的提问来源于stack exchange,提问作者GiLA3
相关产品推荐
相关产品推荐

