为什么我的异步jQuery Ajax调用会阻塞其他轮询请求?
问题根因
你的问题核心是PHP会话独占锁导致的:PHP默认使用文件存储会话时,会对同一个客户端的会话文件加独占锁,同一时间只能处理一个来自同一会话的请求。你触发导入请求后,该请求会一直持有会话锁直到处理完成,后续所有同会话的loadprogress请求都会被阻塞排队,直到导入结束锁释放才会依次执行,所以会出现进度请求全部卡到导入完成后才返回的现象。
解决方案
1. 导入接口提前释放会话锁(成本最低的快速修复方案)
你只需要在导入接口的业务逻辑执行前,完成权限校验、参数验证等需要用到会话的操作后,立即释放会话锁即可,示例代码如下:
use Illuminate\Support\Facades\Cache; public function import(Request $request) { // 先完成所有需要用到会话的操作:权限校验、参数校验等 abort_if(!auth()->check(), 403); $validated = $request->validate([ // 你的参数校验规则 ]); // 校验完成后立即释放会话锁,不影响后续进度请求 session()->save(); // 也可以用原生PHP函数:session_write_close(); // 后续执行导入业务逻辑 $totalRecords = count($excelRecords); foreach ($excelRecords as $index => $record) { // 处理单条交易记录 $this->processSingleRecord($record); // 进度写入缓存,关联当前用户ID避免不同用户进度冲突 Cache::put('import_progress_'.auth()->id(), ceil(($index + 1) / $totalRecords * 100), 600); } // 导入完成后清理进度缓存 Cache::forget('import_progress_'.auth()->id()); return response()->json(['status' => 'success']); }
对应的进度查询接口示例:
public function loadProgress() { abort_if(!auth()->check(), 403); return response()->json([ 'progress' => Cache::get('import_progress_'.auth()->id(), 0) ]); }
2. 改用队列异步处理导入任务(更推荐的生产级方案)
目前你的导入逻辑直接跑在Web请求中,总耗时约80秒,很容易触发PHP、Web服务器的超时限制导致导入中断,更合理的方案是把导入逻辑丢到异步队列执行:
- 前端触发导入请求后,后端生成唯一任务ID,立即将导入任务推送到队列,返回任务ID给前端
- 导入任务执行过程中,将进度写入缓存(如Redis),关联对应任务ID
- 前端轮询时携带任务ID调用
loadprogress接口,从缓存中读取对应进度返回
这种方案完全规避了会话锁问题,也避免了Web请求超时的风险,适合长期使用。
前端优化建议
当前轮询间隔设置为100ms过于频繁,对服务器压力较大,调整为1000ms即可满足进度展示的实时性需求,只需要修改JS代码中的setTimeout(loadProgress, 1000)即可。
内容的提问来源于stack exchange,提问作者Peter F.
相关产品推荐
相关产品推荐

