逐日AI

面试题库

共 328 题,当前筛选 1 题。

30 天从前端工程师到 Agent 工程师

D19 跨服务 Agent 集成:用户级 JWT 铸造、JWKS 验签、inject/memory/usage 三类接口、幂等 externalId

  • 跨服务调用的幂等键该怎么设计?由谁生成、存在哪、重复了返回什么?How do you design an idempotency key for cross-service calls — who generates it, where does it live, and what do you return on a repeat?
    国内高频海外高频进阶#idempotency#distributed-systems#api-design

    分析过程 · 先想清楚再作答

    1. 这题的区分度全在实现细节上。概念谁都会说,能不能答对「谁生成、存在哪、返回什么」这三个具体问题,直接暴露你有没有真做过。
    2. 先立一条铁律:**幂等的最终裁判必须是数据库的唯一约束**,不是应用层的「先查一下有没有」。先查后插在单进程测试里永远是对的,一上多实例就出双份——两个副本同时查、同时发现没有、同时插入,这个时间窗压测时窄到复现不出来,上线后每天出几条脏数据。
    3. 再答「谁生成」:由**调用方**生成,因为只有它知道重试的那两次是同一件事;但键必须由事件内容决定,不能是每次重试重新生成的随机 UUID——那等于没有幂等。这条和 D8 的用户消息幂等是同一条判据。
    4. 跨服务比同服务多一个坑,这是本题最有价值的一点:**调用方给的 id 不能直接当键用**。两个不同的调用方各自造出 evt-1 是迟早的事,撞车之后的表现不是报错,而是后来那个用户静默收不到消息——他的事件被当成重复丢掉了,日志里干干净净。所以落库前要加命名空间,用「签发方 + 用户 id + 事件 id」三段拼,而且三段都取自验签后的令牌,伪造不了。
    5. 「返回什么」也是个坑:重复送达要返回 200 并附上第一次的结果,不要返回 409。重复不是错误,是分布式系统的常态;回 409 会让调用方的重试逻辑把它当失败处理,越重试越乱。
    6. 可以预期的追问:这张表会不会无限涨?答「会,所以要有保留期」——按业务能接受的重放窗口设一个 TTL(比如 7 天)定期清理,同时说明清理之后超期的重复请求会被当成新事件,这是一个明确的、可接受的取舍,不是漏洞。

    How to reason about it · think before answering

    1. This question separates people entirely on implementation detail. Anyone can define idempotency; answering who generates the key, where it lives, and what a repeat returns shows whether you have actually built one.
    2. Start with the rule: the final arbiter must be a database uniqueness constraint, not an application-level check-then-insert. Check-then-insert always passes single-process tests and produces duplicates the moment you run two replicas — both check, both find nothing, both insert. The window is too narrow to reproduce under load testing and wide enough to produce dirty rows daily in production.
    3. Who generates it: the caller, because only the caller knows that two retries are the same event. But the key must be derived from the event itself, never a fresh random UUID per retry — that is idempotency in name only. Same criterion as the user-message case from day 8.
    4. Cross-service adds one trap worth the most points: never use the caller's raw id as the key. Two different callers will eventually both produce evt-1, and the failure is not an error — the second user silently receives nothing, because their event is treated as a duplicate and the logs look clean. Namespace it: issuer plus user id plus event id, all three taken from the verified token so none of them can be forged.
    5. What to return also matters: a repeat gets 200 with the original result, not 409. Repeats are normal in distributed systems; a 409 makes the caller's retry logic treat it as a failure and the situation compounds.
    6. Expect: does this table grow forever? Yes, so give it a retention window — a TTL matching the replay window the business tolerates, say seven days, with periodic cleanup. Say plainly that a duplicate arriving after cleanup is treated as new; that is a stated trade-off, not a hole.

    答题要点

    • 最终裁判是数据库唯一约束加 on conflict do nothing,先查后插在多实例下必然出双份
    • 键由调用方生成,但必须由事件内容决定,随机 UUID 等于没有幂等
    • 调用方给的 id 不能直接当键:加命名空间(签发方 + 用户 id + 事件 id),三段都取自验签后的令牌
    • 撞车的后果不是报错而是另一个用户静默收不到消息,日志里看不出异常
    • 重复送达返回 200 加第一次的结果,不要返回 409,否则调用方会当失败继续重试
    • 幂等表要设保留期,超期后的重复会被当成新事件,这是明确取舍不是漏洞

    Key points

    • The arbiter is a unique constraint plus on conflict do nothing; check-then-insert duplicates as soon as you run two replicas
    • The caller generates the key, but it must be derived from the event — a fresh UUID per retry is not idempotency
    • Never use the caller's raw id: namespace it with issuer plus user id plus event id, all taken from the verified token
    • A collision does not raise an error; it silently drops another user's event and leaves clean logs
    • Return 200 with the original result on a repeat, never 409, or the caller's retry logic treats success as failure
    • Give the table a retention window and state that post-cleanup repeats count as new events — a stated trade-off, not a hole