任务流边界
生成任务不是一次请求
文本、图像、视频生成有个共同点:结果不一定马上返回。
- 请求可能排队。
- 可能执行很久。
- 可能中途失败。
- 可能先输出一部分内容。
如果设计成同步接口,客户端会一直等。网络断开,任务状态说不清。用户刷新页面,不知道任务还在不在。
更合适的模型是任务制。
创建任务 -> 返回 task_id |
多了一层,但把长任务从 HTTP 请求里解放出来。请求只负责创建任务,任务自己走完生命周期。
轮询负责状态
轮询回答一个问题:任务现在怎么样了?
状态字段要少而稳定。四个就够:pending、processing、succeeded、failed。有过期机制再加 expired。
状态越多,前端分支越多,用户越难懂。
{ |
轮询接口不要承担内容流职责。它可以返回最终结果或失败原因,但不要每次带上不断增长的中间文本。
那会让接口变重,缓存、重试、分页都变复杂。
状态查询要轻,内容传输要专。
前端每 1-3 秒查一次状态,足够支撑大多数生成任务。用户离开再回来,也能用 task_id 恢复进度。
SSE 负责内容
SSE 回答另一个问题:这次生成正在输出什么?
它不证明任务是否存在,也不维护完整状态。它只是一条内容流。
服务端持续发 text_delta,结束发 done,失败发 error。
event: text_delta |
关键点:中间 text_delta 可以不落库,最终 done 必须完整。
存储层只保存稳定结果,不保存大量临时碎片。客户端断线后,重新查状态和最终输出;要实时体验,再重新订阅流。
这样数据库存最终结果,SSE 管实时传输,两者不互相绑死。
flowchart LR
A[创建任务] --> B[轮询状态]
A --> C[订阅 SSE]
B -->|pending/processing| B
B -->|succeeded| D[获取最终结果]
C -->|text_delta| E[实时输出]
C -->|done| D边界清楚,系统才轻
轮询和 SSE 常被拿来比较,好像只能二选一。实际项目里,它们是两种工具。
- 轮询:可靠、简单、容易恢复,适合任务状态。
- SSE:实时、轻量、单向推送,适合生成内容。
混在一起,接口越来越重。拆开,系统反而好维护。
异步任务的设计重点不是追求高级协议,而是让每条链路只做一件事:创建任务、查询状态、订阅内容、获取结果。
边界越清楚,失败时越好排查。