题目与场景
你负责 POST /exports 大型导出接口。请求会校验参数并启动任务,但导出可能耗时数分钟。面试官要求你在 200、201 和 202 之间选择,并说明客户端如何知道最终结果。
假设服务端能持久化导出记录并投递任务。客户端可能超时后重发请求。回答必须定义完整合同,不能只报出一个状态码。
面试官考察什么
- 能否区分“资源已创建”和“请求已接受、稍后处理”。
- 能否建模持久状态资源、终态和错误详情。
- 重试是否会创建两个导出任务,或者丢失原始响应。
- 数据库更新与队列投递在没有分布式事务时是否仍然可靠。
作答前的澄清问题
- POST 是否立即创建持久导出资源?如果是,
201 Created可以描述该资源;否则202可以确认任务已接受。 - 同一个逻辑请求是否允许重试?如果允许,应要求幂等键或调用方操作 ID。
- 客户端需要轮询、Webhook,还是两者都要?这会改变状态表示和通知合同。
- 导出文件的保留和授权规则是什么?任务完成不代表任何人都能下载结果。
30 秒回答框架
“只有在处理被延后且最终结果还没准备好时,我才返回 202 Accepted。我先创建持久任务记录,再返回状态 URI 和操作标识。客户端使用退避轮询,或接收经过认证的回调。幂等键把重试映射到同一个任务和响应。任务经过排队、运行、成功或失败等明确状态;worker 与 outbox 都可以重试,状态接口仍是事实来源。”
分步骤深入解答
1. 按资源生命周期选择状态码
200 OK 表示请求已完成并返回表示。201 Created 表示资源已创建且可被识别。202 Accepted 表示请求已接受,处理可能尚未开始或尚未完成;它不承诺最终成功。
如果导出记录同步创建,且它就是调用方要管理的资源,可以返回 201 和该资源。如果接口只是确认后台工作已接受,则用 202 加监控 URI 更清晰。选择应由可观察生命周期决定,不是因为系统内部用了队列。
2. 让响应可以执行
返回操作 ID、状态 URL,以及包含 state、时间戳和安全重试提示的表示。最小响应可以是:
HTTP/1.1 202 Accepted
Location: /exports/exp_123
Retry-After: 5
Content-Type: application/json
{"id":"exp_123","state":"queued","status_url":"/exports/exp_123"}每次读取状态资源都要做授权。queued 和 running 是非终态;succeeded 返回短期下载引用;failed 返回稳定错误码和修复提示,不能泄露堆栈。客户端还要能处理超过保留期后资源消失。
3. 让重试收敛
要求创建任务的操作携带 Idempotency-Key。保存相关请求的哈希、任务 ID 和响应状态。相同键配合相同请求时返回原结果;相同键配合不同请求时返回客户端错误。不能只依赖时间窗口,因为迟到的重试可能在窗口之后到达。
不同键仍然可以创建两个导出任务。幂等性防止一个逻辑操作重复创建,不代表 worker 能做到恰好执行一次。
4. 关闭数据库到队列的间隙
在一个数据库事务中同时写导出记录和 outbox 事件。relay 发布待处理 outbox,并在 broker 确认后标记已发送。崩溃可能导致事件重复发布,因此消费者以导出 ID 做幂等键。这保证已提交任务最终可被发现,但不声称数据库和 broker 具备原子提交。
worker 通过条件状态转换更新任务,例如 queued -> running -> succeeded|failed。过期重试不能把 succeeded 改回 running。指标应包括队列等待时长、运行时长、终态失败率和 outbox 延迟。
5. 定义轮询、回调和取消
状态接口提供 ETag 或版本号,让轮询使用条件请求。客户端根据服务器提示做指数退避,并在终态停止轮询。Webhook 是优化,不是唯一结果通道:投递可能失败,因此客户端必须能够读取状态资源进行对账。
取消使用独立命令,例如 POST /exports/exp_123/cancel。只允许可取消状态,并且取消本身也要幂等。任务已经 succeeded 后,迟到的取消不能回滚结果。
高质量示范回答
我会先确认这个调用是否同步创建了一个持久导出资源。如果是,可以返回 201 和资源记录。对于结果延后、响应只确认接受的操作,我返回 202,附带操作 ID 和需要授权的状态 URL。我要求幂等键,保存请求指纹和任务 ID,让重试得到同一表示。
事务同时写导出记录和 outbox 事件,relay 与幂等消费者按至少一次投递处理。状态机单调地从排队到运行,再到成功或失败。客户端用条件请求和退避轮询,Webhook 只是加速器。我会监控队列时长、outbox 延迟和终态错误,并明确保留、授权、下载链接过期和取消规则。202 只确认接受,不确认成功。
常见失误
- 错误表现:把
202当成任务一定成功 → 失败原因:语义允许处理失败或根本未开始 → 修正方法:暴露终态失败和保留策略。 - 错误表现:只返回
202,不给状态 URL → 失败原因:客户端无法发现状态,只能猜测 → 修正方法:返回受授权保护的状态资源和操作 ID。 - 错误表现:数据库提交后再直接发布队列消息 → 失败原因:崩溃可能留下无人处理的任务 → 修正方法:使用事务 outbox 和可重放 relay。
- 错误表现:认为队列能保证恰好执行一次 → 失败原因:投递重试和崩溃会造成重复 → 修正方法:消费者幂等,状态转换带条件。
- 错误表现:相同幂等键无条件返回成功 → 失败原因:它可能掩盖了请求已被修改 → 修正方法:比较请求指纹,冲突时拒绝。
追问与回答
这里应该改用 201 吗?
当同步调用创建持久导出资源并能通过 Location 识别它时返回 201。当有意义的结果被延后、响应只确认接受时返回 202。有些接口可以用 201 创建任务资源,同时把资源状态设为 pending;必须说明状态描述的是哪个资源。
如果客户端永远不轮询怎么办?
在约定保留期内持久保存状态,发送可选的受认证 Webhook,并允许客户端之后用操作 ID GET。回调失败不能删除唯一的状态路径。客户端几天后回来时,仍要执行下载链接过期和授权检查。
worker 可以两次更新同一个任务吗?
可以,投递通常是至少一次。使用唯一导出 ID、带条件的状态转换和幂等输出写入。重复的 succeeded 事件应当无害;终态回到 running 的转换应被拒绝并记录。