题干与适用场景
你要为多个应用设计一个可互操作的外部数据存储服务。客户端需要发现存储、请求读写权限、创建资源并订阅变更。请说明资源模型、HTTP 语义、认证、并发控制、通知和回滚策略。
W3C Linked Web Storage Protocol 1.0 目前是 Working Draft,目标是让应用以安全、有权限且可互操作的方式访问外部存储。规范要求通过 Link 关系发现存储和父资源,使用 linkset 管理元数据,并要求元数据更新与资源操作保持原子性。题目考察协议边界和失效处理,不要求把草案当作稳定标准。
面试官考察点
面试官会关注你是否把资源、权限、身份、元数据和通知拆成可验证的边界,能否正确处理 POST 重试、并发 PATCH、405/415、缓存和撤销。高质量回答还会说明 Working Draft 的不确定性,以及 OpenID Connect、SAML、自签名身份等认证套件应按部署场景选择。
回答前需要澄清的问题
- 数据所有者、应用和存储服务分别由谁运营,信任边界在哪里?
- 需要哪些操作:读取、创建、更新、删除、共享,还是只读订阅?
- 权限是按用户、代理、资源路径、动作还是时间窗口授予?
- 是否要求跨区域、离线访问、审计、撤销和最终一致通知?
- 客户端能否处理 Link 发现、ETag、405/415 和重试去重?
30 秒回答框架
“我会把存储、容器、数据资源、linkset、身份和访问授权分开建模。客户端先通过 Link 关系发现存储和父容器,再用声明的认证套件获取身份,提交带动作、主体、目标和约束的访问请求。创建使用 POST,但用幂等键或客户端去重避免重试重复;元数据 PATCH 必须有并发条件并与资源操作原子提交。服务用 ETag、能力头和结构化错误处理冲突,通知采用可重试的最终一致队列。每次灰度都保留授权撤销、旧权限版本和审计回滚。”
分步骤深入解答
1. 建立资源和发现模型
区分 storage、container、data resource 和 linkset。GET 或 HEAD 的响应通过 Link 关系暴露存储根、父容器、类型和 linkset;客户端不把 URL 路径格式当作协议契约。存储描述还应声明服务端能力和支持的媒体类型,让客户端在发送 PUT 或特定 PATCH 前先协商。
2. 把权限建模为可审计授权
授权记录至少包含 assignee、action、target、约束、签发者、有效期和撤销状态。存储服务校验调用方身份与授权版本,按最小权限执行 read、create、update 或 delete。认证套件可以按组织选择 OpenID Connect、SAML 或自签名身份,但身份验证与资源授权必须分层,避免把“登录成功”当作任意资源访问权。
3. 设计创建、更新和并发语义
规范示例中 POST 创建资源并由服务器分配最终 URI,返回 201 和 Location;POST 不是幂等操作,客户端重试必须使用唯一请求标识或去重表。linkset 的 PATCH 是主要元数据更新方式,服务端要用 ETag、If-Match 或等价版本检查防止丢失更新;不支持 PUT 时返回 405,不支持媒体类型时返回 415。
POST /alice/notes/ HTTP/2
Idempotency-Key: 7b3c...
Link: <https://www.w3.org/ns/lws#DataResource>; rel="type"
Content-Type: text/plain
meeting notes4. 保证资源与元数据一致
创建、容器成员关系和必需的服务端元数据必须作为一个原子事务提交;失败时不能留下已可见但没有父关系或 linkset 的孤儿资源。跨分片时使用事务日志或 outbox 记录状态,读接口按状态返回 pending、committed 或 failed,避免用通知到达顺序推断提交成功。
5. 设计通知和读写扩展
变更事件进入可重试的 outbox,再投递到订阅者 inbox;事件包含存储、资源、动作和版本,消费者用事件 ID 去重并可通过 GET 校验当前状态。通知丢失时依靠重放或定期对账恢复,通知重复时保持处理幂等。Prefer、Link 和媒体类型协商应允许客户端逐步采用能力,而不是假设所有服务器都支持同一 PATCH 或订阅方案。
6. 认证、撤销与灰度回滚
认证套件的密钥轮换、令牌受众、时钟偏差和撤销路径必须纳入设计。权限变更写入带版本的审计日志,撤销在授权检查和缓存失效后生效。灰度先选择低风险租户,观察 401/403、405/415、冲突率、重复创建、通知延迟和审计完整率;任何越权或数据孤儿立即停止并恢复旧授权策略。
高质量示范回答
我会把 storage、container、data resource、linkset、身份和授权分层。客户端通过 GET/HEAD 的 Link 关系发现存储根、父资源、类型和 linkset,先协商服务端能力再发送写操作。授权记录包含主体、动作、目标、约束、有效期和撤销状态,OpenID Connect、SAML 或自签名身份只负责证明身份,不能自动授予资源权限。POST 创建资源时返回 201 和 Location,因为 POST 非幂等,客户端必须带唯一请求标识或去重;linkset PATCH 使用 ETag/If-Match 防止并发丢失更新,不支持的方法或媒体类型返回 405/415。创建、成员关系和服务端元数据原子提交,事件通过 outbox 投递到可重试 inbox,消费者按事件 ID 去重并用 GET 对账。灰度观察 401/403、冲突、重复创建、通知延迟和审计完整率;发现越权、孤儿资源或无法回滚就停止,保留旧授权版本和审计记录。由于规范仍是 Working Draft,最终协议与测试矩阵需要版本化。
常见错误
- 把 URL 路径当作全部协议契约 → 发现和能力协商会失效 → 依赖 Link 关系、媒体类型和能力头。
- 无条件重试 POST → 可能创建重复资源 → 使用幂等键、去重记录和最终状态查询。
- 只校验登录不校验授权 → 身份成立不代表可读写目标资源 → 按主体、动作、目标和约束检查授权。
- 元数据和资源分开提交 → 可能产生孤儿或错误 linkset → 采用原子事务或可恢复 outbox。
- 假设所有服务器支持 PUT/PATCH → 客户端兼容性脆弱 → 先发现能力,正确处理 405/415。
追问及应对
为什么 POST 重试不能直接依赖 TCP 或 HTTP?
连接可能在服务端提交后断开,客户端无法知道结果;唯一请求标识和状态查询才能把重试与原始提交关联起来。
如何避免权限缓存导致撤销延迟?
授权带版本和短 TTL,撤销写入审计后主动失效相关缓存;关键写操作再次向授权服务确认版本。
通知丢失时怎样恢复?
事件先写 outbox,投递失败可重试;消费者保留游标并定期按资源版本对账,必要时从事件日志重放。
为什么需要 linkset 的原子更新?
资源已经成功但成员关系或类型元数据未更新,会让发现、权限和缓存看到不一致状态;原子提交能避免半完成资源公开。
Working Draft 阶段如何控制投入?
实现隔离适配层和版本化测试矩阵,只在低风险流量试验;保留旧协议和数据迁移路径,等规范与实现报告稳定后再扩大。