发布于2026-07-09 阅读(0)
扫一扫,手机访问
在队列任务中应使用DB::table()->upsert()持久化进度到数据库,依赖$this->job->uuid()作为唯一键,避免模型事件、事务干扰及构造函数操作;前端轮询需查库并加索引,缓存仅作可选加速。

先说几个核心判断:队列任务进度的持久化,本质上是个“防丢失、防冲突、防回滚”的工程问题。很多团队一上手就想着用缓存糊弄,结果生产环境一挂,进度全丢,用户那边进度条卡在半截,体验直接崩盘。真正靠谱的做法,是把数据库当成主存储,并设计好写操作的容错路径。
直接在任务类里用 Eloquent 更新记录是最直觉的做法——但得避开几个暗坑。Lara vel 队列默认不开启 DB 事务,可要是在 handle() 里手动包了一层 DB::transaction,进度更新就可能跟着回滚,那叫一个冤枉。
几个关键点:
dispatch() 时最好传入唯一 $jobId,或者直接拿 $this->job->uuid() 做标识。多个相同任务并行时,这能避免互相覆盖。__construct() 里查或改模型——构造阶段模型状态压根不可靠,而且有些驱动(比如 Redis)会序列化对象,Eloquent 模型一序列化就容易出幺蛾子。DB::table('job_progress')->upsert() 替代先查后 sa ve,并发写冲突问题靠它规避。字段至少要有 job_uuid、progress、updated_at 三个,多一个没坏处。InteractsWithQueue 怎么配合进度更新这个 trait 提供了 $this->job 实例,能拿到 uuid() 和当前尝试次数——天然就是进度锚点。但注意,它只在 handle() 执行时才完整可用,failed() 或构造函数里依赖它,多半要翻车。
$this->job->uuid() 是最稳的 key,比自定义 ID 更可靠,尤其在用了 Horizon 的情况下。DB::table(...)->where('job_uuid', $this->job->uuid())->update(...),别等全做完再一笔写。进度更新的核心是“及时”,不是“一次性”。failed() 方法里别重置进度为 0——用户可能点了重试,这时该保留上次成功进度,而不是一棍子打死。Redis 缓存确实快,但队列失败重试、机器重启、TTL 过期,任何一次意外都能让进度凭空消失。生产环境要持久化,就得落库,缓存只适合做临时高频读——比如给前端轮询加速。
Cache::put('progress_'.$uuid, $p, 3600) 当主存储:没事务保证,也没历史可查,真出问题了连恢复路径都没有。Cache::forever('progress_'.$uuid, $p),但读的时候必须 fallback 到 DB 查询。缓存只能锦上添花,不能雪中送炭。/api/progress/{uuid} 接口要注意什么这个接口本质是查数据库,不是查内存或缓存,所以必须加索引。不然并发一高,全表扫描能把队列消费拖成蜗牛。
job_progress.job_uuid 加唯一索引或普通索引,避免全表扫描。这是基本功,但常见问题往往出在基本功上。{"progress": 65, "status": "processing"},别塞模型关系或额外字段。接口越重,轮询越慢,用户越焦虑。说到这里,其实真正难的不是存百分比,而是保证“写进度”和“任务执行流”在各种失败场景下不脱节。比如任务卡住、超时 kill、Horizon 手动停止,这些时候进度值是否还可信,得靠 uuid + 时间戳 + 状态字段组合判断,单靠一个数字撑不住。从数据来看,这才是整个方案的工程关键所在。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8