题干与适用场景
这道题考察后端 API、对象存储和并发控制。AWS 的条件请求支持在 PutObject、CompleteMultipartUpload 和 CopyObject 上设置前置条件:创建时可要求目标键不存在,更新时可要求 ETag 仍未改变。问题核心是把“检查”和“写入”放进存储服务的原子判断,而不是让客户端自己先 HEAD 再 PUT。
面试官考察点
强回答会先区分创建与更新,再选择 If-None-Match: * 或带当前 etag-value 的 If-Match,把条件失败映射为可重试或需重新合并的结果。还应覆盖 multipart 完成、弱 ETag 不适用、网络超时后的不确定状态和审计指标。只说“加锁”无法解释多客户端、跨进程和故障场景。
回答前需要澄清的问题
写入语义
是只允许第一个写入者创建不可变对象,还是允许基于已读版本更新?前者使用不存在条件,后者需要版本 ETag。
冲突处理
冲突时是返回 412 让客户端重新读取合并,还是放弃本次写入?若对象代表清单或注册表,通常不能静默覆盖。
上传路径
小对象走 PutObject,大对象是否使用 multipart?确认条件要放在最终完成请求,并定义上传中断、重试和清理规则。
30 秒回答框架
“我先确定是创建还是基于版本更新。创建用 If-None-Match: *,让 S3 原子拒绝已有键;更新先读取当前 ETag,再用 If-Match 提交,ETag 变化就返回 412,让客户端重新读取或合并。multipart 上传要在 CompleteMultipartUpload 再施加条件。超时不能直接重试未知状态,应先查询对象或用幂等键确认结果,并监控 412、孤儿分片和重试次数。”
分步骤深入解答
第一步:淘汰 check-then-act
“先 HEAD、再 PUT”存在竞态:两个客户端都读到不存在,随后都写入。把前置条件放进同一个写请求,才能让存储层在当前对象状态上判断并拒绝冲突。
第二步:选择条件
创建不可变对象时使用 If-None-Match: *;它只在没有当前表示时成功。更新已有对象时使用强比较的 If-Match,只有 ETag 与读取版本一致才写入;不匹配返回 412,保护较新的内容。
第三步:定义冲突协议
收到 412 后不要盲目覆盖。客户端重新 GET 当前对象,计算自己的变更能否合并;不能合并就提示人工解决或生成新键。重试应带退避和上限,避免热点键形成重试风暴。
第四步:覆盖 multipart 与复制
大对象分片上传期间,其他客户端可能先创建同名对象;最终 CompleteMultipartUpload 仍需条件检查。复制或替换流程同样要带 ETag 条件,清理失败的 multipart 由生命周期规则兜底。
第五步:处理超时和可观测性
请求超时不代表写入失败。先用 HEAD 或带条件的读取确认对象版本,再决定是否重试;对创建操作使用客户端命令 ID 记录意图。监控 412 比例、成功重试率、孤儿分片和对象版本漂移,验证冲突确实被拒绝而非覆盖。
高质量示范回答
我会把对象分成不可变数据文件和可更新注册表。数据文件创建用 If-None-Match: *,同名写入只有一个成功;注册表读取 ETag 后用 If-Match 更新,若返回 412 就重新读取并合并,而不是覆盖别人的版本。大文件在 CompleteMultipartUpload 再做条件检查,失败的上传由清理任务回收。网络超时后先查询对象和 ETag,再决定重试。这样 S3 负责原子冲突判断,客户端负责合并策略和幂等记录;监控 412、重试和孤儿分片来验证流程。
常见错误
- 错误表现: 先 HEAD 判断不存在,再 PUT。→ 失败原因: 两个客户端可同时通过检查。→ 修正方法: 在 PUT 上使用
If-None-Match: *。 - 错误表现: 更新时使用
If-None-Match。→ 失败原因: 它表达“必须不存在”,无法保护已读版本。→ 修正方法: 读取强 ETag 后使用If-Match。 - 错误表现: 412 后无条件重试原请求。→ 失败原因: 可能反复覆盖或制造重试风暴。→ 修正方法: 重新读取、合并或明确放弃,并限制退避重试。
- 错误表现: 只在首个分片请求设置条件。→ 失败原因: 最终完成时对象状态可能已变化。→ 修正方法: 在
CompleteMultipartUpload重新施加条件。
追问及应对
追问一:ETag 能当内容哈希吗?
不能一概而论。题目需要的是版本验证器;multipart 或加密场景下不要把 ETag 当作通用内容摘要,按服务文档选择校验字段。
追问二:412 与 409 如何区分?
以 API 文档定义为准:412 表示请求前置条件不满足,适合让客户端重新读取;其他冲突状态可能表示资源状态或操作语义冲突,不能仅凭状态码猜测。
追问三:客户端合并失败怎么办?
保留当前版本,生成冲突记录或新键,交给拥有业务上下文的人处理;不要为了成功率静默覆盖。
追问四:如何测试并发正确性?
让多个客户端同时创建和更新同一键,注入超时、重复完成 multipart 和清理失败,验证最多一个创建成功、旧版本不会覆盖新版本,并核对审计日志。