laravel怎么实现队列任务执行进度百分比计算_laravel基于总量动态估算【教程】

为什么 dispatch() 后拿不到实时进度

因为 Laravel 队列本身不记录任务执行中的中间状态,dispatch() 只是把任务推入队列,后续由 worker 拉取并执行——整个过程默认无“进度”概念。所谓“百分比”,必须靠你自己在任务内部主动上报,再配合外部存储(如 Redis、DB)做状态快照。

常见错误现象:Job::dispatch()->onQueue(‘default’) 后立刻查数据库或缓存,发现没数据;或者用 job->progress 这种不存在的属性直接报错。

进度不是 Laravel 内置字段,$this->progress 会报 Undefined property不要依赖队列驱动(如 database、redis)自动记录执行阶段——它们只管投递和完成,不管“做到哪了”如果任务分多步但每步耗时不均(比如前 90% 是 IO 等待,后 10% 是 CPU 密集),按步骤数硬算百分比会严重失真

用 Redis + incrby 实现轻量级进度更新

Redis 的原子操作适合高频写入且无需事务保障的场景,比 DB 写入快一个数量级,也避免并发覆盖问题。关键不是存“百分比”,而是存“已完成单位数”,再由前端/接口按总量换算。

假设你有个导出 Excel 的任务,总行数已知为 $totalRows:

public function handle(){ $jobId = $this->job->getJobId(); // 或用 uniqid() + 任务参数哈希 $progressKey = "job:progress:{$jobId}";<pre class="brush:php;toolbar:false;">Redis::set($progressKey . ‘:total’, $this->totalRows);foreach ($this->rows as $index => $row) { // 处理单行… $done = $index + 1; Redis::incrby($progressKey . ‘:done’, 1); // 原子自增 // 每 10 行同步一次,避免 Redis 请求爆炸 if ($done % 10 === 0 || $done === $this->totalRows) { $percent = round(($done / $this->totalRows) * 100); Redis::setex($progressKey . ‘:percent’, 3600, $percent); }}

}

别用 SET 覆盖 :done,要用 INCRBY,否则并发时会丢进度不要在每次循环都写 :percent,Redis 频繁写入会拖慢任务,用定时采样更稳jobId 必须全局唯一,不能只用 getJobId()(它在重试时会变),建议拼接任务类名+参数 hash

如何让前端安全读取进度而不轮询爆 Redis

前端直接 GET /api/job/progress?job_id=xxx 没问题,但得控制频率和缓存策略。重点不是“怎么查”,而是“查不到时怎么兜底”。

后端接口示例(Laravel Controller):

public function progress(Request $request){ $jobId = $request->input(‘job_id’); $key = "job:progress:{$jobId}";<pre class="brush:php;toolbar:false;">$done = (int) Redis::get($key . ‘:done’) ?: 0;$total = (int) Redis::get($key . ‘:total’) ?: 0;// 如果 total 为 0,说明任务还没初始化好,返回 0 或 null 更合理if ($total === 0) { return response()->json([‘percent’ => 0, ‘status’ => ‘pending’]);}$percent = $done >= $total ? 100 : round(($done / $total) * 100);return response()->json([ ‘percent’ => $percent, ‘done’ => $done, ‘total’ => $total, ‘status’ => $done >= $total ? ‘completed’ : ‘running’]);

}

前端轮询间隔至少 1.5 秒起,短于 1 秒容易触发 Redis QPS 限流(尤其共享环境)如果 :total 查不到,别猜、别默认设 100,直接返回 pending,否则前端会显示“100% 但实际卡住”别在响应里塞 Redis key 名,更不要暴露 job:progress:xxx 这种结构,防止被恶意探测

重试、失败、超时场景下进度怎么不“回滚”也不“卡死”

队列任务重试时,handle() 会重新执行,但你不能让进度从 0 开始——否则用户看到“30% → 0% → 10%”就懵了。核心原则:进度只增不减,且失败后要标记状态,而非清空。

在 failed() 方法里,只设 status: failed,不要删 :done 或 :percent,让用户知道“卡在哪儿了”重试时先检查 :done 是否已存在,若存在则跳过已处理部分(需你在逻辑里支持断点续传,比如加 where id > $lastDoneId)给进度 key 加过期时间(SETEX),但别设太短(比如 60 秒),否则任务跑 20 分钟,中途 Redis 清掉 key 就彻底丢失进度超时(timeout 配置)后 worker 强制 kill 进程,此时 Redis 写入可能中断,所以务必用 INCRBY + 定期采样,而不是依赖最后一步写入

最麻烦的其实是“总量动态变化”——比如边查库边导出,总数一开始根本不知道。这种就得改方案:先扫一遍计数,或用流式估算(如每万条上报一次,再按比例外推),不能硬套固定百分比模型。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。