上个月给客户的云设计模块接 AI 生图,运营一次提交 200 张商品图需求,第一版是"来一个请求调一次生图接口",结果瞬间把 API 并发打满,大量请求超时,前端任务状态卡在"生成中"半小时不动。后来用消息队列做削峰、限制并发、加失败重试和状态回调,200 张图稳定在 12 分钟全部出完,失败自动补偿。这篇把任务队列的配置过程记下来。
一、先看清:批量生成为什么会崩
直接同步调用生图 API 有三个致命问题:
| 问题 | 原因 | 表现 |
|---|---|---|
| 并发打满 | 200请求同时打到API,超过QPS限制 | 大量429限流/超时 |
| 状态丢失 | 同步等待,HTTP连接断了任务就丢 | 任务永远卡在"生成中" |
| 无失败补偿 | 单张失败整个批次中断 | 要人工逐张重发 |
正确做法是异步化:请求先入队,worker 按受控并发消费,结果通过回调更新,失败进重试队列。
二、队列削峰:请求入队与状态机
提交任务时不同步生图,而是先写队列和任务表,立刻返回"已排队"。
任务状态机:
PENDING(排队)→ RUNNING(生成中)→ SUCCESS(成功)
↓
FAILED(失败)→ RETRYING(重试中)→ SUCCESS
↓超过重试次数
DEAD(死信,人工处理)
入队配置:
1. 每个生图任务生成唯一 task_id,落库记录
(prompt、参数、状态、重试次数、创建时间)
2. 任务消息投递到消息队列(如RabbitMQ/Kafka/Redis List)
3. 接口立即返回 task_id,前端轮询或等回调
4. 消息要持久化,防止服务重启丢任务
用 Redis 实现轻量队列时,用 List 结构:LPUSH 入队、BRPOP 阻塞消费;任务详情单独存 Hash,队列里只放 task_id,避免大消息体。
踩坑提醒:任务状态流转必须幂等。同一个 task_id 的状态更新要带前置状态判断(比如只有 RUNNING 才能变 SUCCESS),防止重试时重复扣额度或重复出图。
三、并发控制:令牌桶限制消费速率
API 有 QPS 和并发上限,worker 不能无限消费。用信号量 + 令牌桶把实际并发压在 API 限额内。
并发控制配置:
1. worker 数量:假设API并发上限=10,部署2个worker实例,
每个实例内部信号量=5,总并发=10,留20%余量按8配
2. 令牌桶:按 QPS 限制,比如每秒最多发3个请求,
桶容量=突发量,填充速率=稳定QPS
3. 消费流程:worker 取任务前先 acquire 令牌
(拿不到就等待),调用完释放
4. 收到 429 限流响应:指数退避(1s→2s→4s),
并把令牌填充速率临时下调
为什么要留余量:API 标称的 QPS 上限是理论值,网络抖动和其他业务也在占用,按限额的 80% 配置更稳,避免持续贴着上限被限流。
四、失败重试与死信:保证任务不丢
生图会因为内容审核不通过、API 抖动、超时等失败,要分级处理,不能无脑重试。
失败分级与重试:
1. 可重试错误(瞬时):超时、429限流、5xx服务错误
- 进延迟重试队列,指数退避,最多3次
- 延迟用 队列+延迟时间 或 死信TTL实现
2. 不可重试错误(确定):内容审核拒绝、参数非法、余额不足
- 直接置 FAILED,记录原因,不重试
3. 超过3次仍失败 → 死信队列,告警通知人工
4. 重试要保证幂等:用 task_id 做幂等键,
避免重复生成、重复计费
回调更新:生图 API 通常支持 webhook 回调,生成完成后回调你的接口更新任务状态和图片地址。回调接口也要幂等(同一 task_id 多次回调只生效一次),并提供主动轮询兜底——回调可能丢失,RUNNING 超过阈值时间(如3分钟)的任务主动查一次结果。
五、踩坑清单(这 5 个坑都踩过)
- 200请求同步打满API并发,大面积429,改成队列削峰+信号量控并发后稳定
- 任务只存内存,服务重启全丢,改成任务落库+消息持久化
- 失败无脑重试,内容审核拒绝的任务重试10次纯浪费,区分可重试/不可重试错误
- 回调丢失导致任务永远"生成中",加超时主动轮询兜底后不再有僵尸任务
- 重试没做幂等,一张图生成两遍扣两次额度,用 task_id 幂等键解决
这次批量生图是搭在乔拓云的云设计产品上的,设计稿和素材在它的后台管理,我主要负责消息队列削峰、并发令牌桶和失败重试补偿这三块,改完后200张图稳定12分钟出完。
复盘要点
- 批量异步任务先入队削峰,用状态机管理生命周期,接口立即返回
- 并发按API限额的80%配,信号量+令牌桶双控,遇429指数退避
- 失败分级重试+死信告警,回调和主动轮询双保险,全程幂等
以上是个人实践记录,各平台具体功能以官方实时信息为准。
开放问题:你们做批量AI任务时,队列选型更倾向轻量Redis还是专业MQ?量级上来后差别大吗?