题干与适用场景
一个 B2B 产品要提供全量或增量数据导出。单次导出可能超过请求超时和单文件大小,客户端也可能在下载中断后重试。面试重点是如何定义导出边界、异步状态、检查点、文件可见性、权限和资源配额。
面试官考察什么
面试官会看你能否把长任务拆成可观察、可恢复的协议。Google Data Portability API 将导出作为独立能力并要求敏感范围审核;Zendesk 的增量导出用 cursor 或时间边界保存下一次起点,并明确避免最近一分钟的竞态;Oracle FHIR Bulk Export 通过 Content-Location 返回任务地址供客户端轮询。高质量回答还要说明快照一致性、删除语义和租户隔离。
回答前要澄清的问题
先确认导出资源、全量还是增量、最大记录数和保留时长;导出是否必须跨表一致、能否接受某个时间点的快照;结果是 JSONL、CSV 还是压缩分片;客户端是否需要取消和断点下载;谁能发起、谁能下载;每租户并发、带宽和存储预算是多少。不要把“导出完成”与“文件已经下载”混为一个状态。
30 秒回答框架
可以这样开场:“我把协议分成创建任务、查询状态、下载清单和取消四类操作。创建时固定授权范围与快照边界,返回任务 ID;后台按分片读取并持久化检查点,产出不可变文件清单。客户端轮询任务地址,完成后只拿到短期下载凭证。检查点和幂等键让 worker 重试不会重复计数,过期任务会删除文件并释放配额;增量导出使用 cursor 或封存的高水位,避免时间边界导致漏数。”
分步骤深入分析
第一步:创建任务并冻结导出边界
POST /exports 校验租户权限、资源范围、过滤条件和配额,生成不可猜的 exportId。任务记录授权快照、格式、压缩方式、创建时间、过期时间和 snapshotWatermark。数据库支持一致性快照时,在同一事务中读取水位;否则明确这是按资源分别截取的近似快照,不能声称跨表严格一致。
第二步:异步状态与幂等
创建请求接受 Idempotency-Key,相同租户和 key 返回同一个任务。GET /exports/{id} 返回 QUEUED、RUNNING、SUCCEEDED、FAILED、CANCELLED 或 EXPIRED,并给出已处理分片、预计范围和下一步动作。状态机只允许单向推进,取消与完成并发时用条件更新决定胜者,重试不能重新扣配额。
第三步:分片读取与持久化检查点
worker 按稳定主键或数据库提供的快照游标读取固定批次,将结果写入临时对象。每个分片保存 (exportId, partition, cursor, rowCount, checksum, objectKey);提交检查点和分片元数据要幂等。worker 崩溃后从最近已提交 cursor 重跑,重复分片使用版本号或对象条件写入覆盖,最终清单只引用一次。
第四步:全量与增量的边界语义
全量导出在 snapshotWatermark 之前读取,之后新增或更新的记录留给下一次增量。增量任务从上次保存的 cursor 或高水位开始,而不是简单使用当前时间减一秒。Zendesk 文档说明 cursor 可作为下一页和下一次导出的起点,并用安全延迟避免最近写入的竞态;设计中应把延迟、重复和补偿窗口写进协议。
第五步:完成、清单与下载
所有分片完成并校验后,任务原子地从 RUNNING 变为 SUCCEEDED,生成不可变 manifest(文件、大小、校验和、记录范围)。下载接口只返回短期、绑定租户和对象的凭证;凭证不授予任意对象访问权。下载失败可重新获取同一 manifest,不重新执行导出。文件在过期前保持只读,避免下载途中内容变化。
第六步:资源保护、取消与清理
创建和 worker 调度都检查租户并发、扫描行数、CPU、对象存储和出口带宽配额。大型任务进入队列并设全局并发上限,在线请求使用独立资源池。取消后停止新分片、标记任务并异步删除临时文件;EXPIRED 任务由可重试清理器回收,记录孤儿字节、失败原因和清理延迟。管理员可在审计日志中看到发起人、范围和下载事件。
高质量示范回答
“我会用 POST /exports 创建任务,校验租户权限与配额并保存授权范围、格式、过期时间和快照水位,支持幂等 key。响应只返回 exportId,客户端通过 GET /exports/{id} 轮询。worker 从快照游标按主键分批读取,每个分片持久化 cursor、行数和 checksum;重试从最近检查点继续,重复写入由 (exportId, partition, cursor) 条件保证幂等。全量使用固定水位,增量保存 cursor 或高水位并带安全延迟,避免漏数。所有分片完成后生成不可变 manifest,任务原子进入 SUCCEEDED,下载接口发短期、租户绑定的凭证。取消和过期会停止新工作、删除临时对象并释放配额;限速、资源池和审计保护在线流量。”
常见错误与改进
- 在 HTTP 请求中同步扫描全表: 改为任务队列和可轮询状态,避免超时占满连接。
- 用当前时间做增量起点: 保存 cursor 或封存高水位,并定义安全延迟与重复处理。
- 每次重试都生成新文件: 以分片检查点和 manifest 幂等,重试只补缺失部分。
- 下载链接长期有效: 凭证绑定租户、对象和过期时间,下载失败只重新签发同一文件。
追问及应对
导出期间有记录被删除怎么办?
先定义产品语义。快照导出只保证水位前存在的记录;增量流应携带删除墓碑或变更类型。清理墓碑前要满足消费者保留窗口,否则下游无法知道删除。
worker 在写完文件但提交检查点前崩溃怎么办?
重启时以数据库检查点为准重跑该批次。对象写入使用确定的分片版本或条件覆盖,manifest 只采纳已提交且校验通过的版本,因此不会把半成品暴露给下载者。
如何避免一个租户拖垮在线数据库?
为导出使用只读副本、快照或专用查询资源池,限制扫描并发和每秒行数;队列按租户公平调度,监控副本延迟、锁等待和出口带宽,超过阈值自动降速或暂停。
客户端需要从中断位置继续下载怎么办?
导出结果保持不可变并支持 HTTP Range 或分片下载。客户端保存 manifest、文件校验和已下载范围;重新获取凭证不会改变文件版本,也不会重新运行导出。